diff --git a/.github/workflows/cargo-audit.yml b/.github/workflows/cargo-audit.yml index 3197150b1..1934dcaeb 100644 --- a/.github/workflows/cargo-audit.yml +++ b/.github/workflows/cargo-audit.yml @@ -23,20 +23,11 @@ jobs: sudo apt-get update && sudo apt-get install -y clang curl libssl-dev llvm libudev-dev protobuf-compiler - - name: Install Rust Stable - uses: actions-rs/toolchain@v1.0.6 - with: - toolchain: stable - components: rustfmt, clippy - profile: minimal - - - name: Utilize Shared Rust Cache - uses: Swatinem/rust-cache@v2.2.1 - with: - key: ubuntu-latest-${{ env.RUST_BIN_DIR }} - - name: Install cargo-audit - run: cargo install --version 0.20.1 cargo-audit + run: cargo install --version 0.20.1 --force cargo-audit + + - name: Display cargo-audit --version + run: cargo audit --version - name: cargo audit run: cargo audit --ignore RUSTSEC-2024-0336 # rustls issue; wait for upstream to resolve this diff --git a/.github/workflows/check-devnet.yml b/.github/workflows/check-devnet.yml index 8f04d78cf..c7f39082e 100644 --- a/.github/workflows/check-devnet.yml +++ b/.github/workflows/check-devnet.yml @@ -20,9 +20,6 @@ jobs: sudo apt-get install -y curl clang curl libssl-dev llvm \ libudev-dev protobuf-compiler - - name: Set up Rust Toolchain - run: curl https://sh.rustup.rs -sSf | sh -s -- -y - - name: Install substrate-spec-version run: cargo install substrate-spec-version diff --git a/.github/workflows/check-finney.yml b/.github/workflows/check-finney.yml index 947b9a902..292bae7ee 100644 --- a/.github/workflows/check-finney.yml +++ b/.github/workflows/check-finney.yml @@ -20,9 +20,6 @@ jobs: sudo apt-get install -y curl clang curl libssl-dev llvm \ libudev-dev protobuf-compiler - - name: Set up Rust Toolchain - run: curl https://sh.rustup.rs -sSf | sh -s -- -y - - name: Install substrate-spec-version run: cargo install substrate-spec-version diff --git a/.github/workflows/check-rust.yml b/.github/workflows/check-rust.yml index 80d543163..3c9fc759b 100644 --- a/.github/workflows/check-rust.yml +++ b/.github/workflows/check-rust.yml @@ -43,9 +43,7 @@ jobs: env: RELEASE_NAME: development # RUSTFLAGS: -A warnings - RUSTV: ${{ matrix.rust-branch }} RUST_BACKTRACE: full - RUST_BIN_DIR: target/${{ matrix.rust-target }} SKIP_WASM_BUILD: 1 TARGET: ${{ matrix.rust-target }} steps: @@ -55,10 +53,10 @@ jobs: - name: Install dependencies run: sudo apt-get update && sudo apt-get install -y build-essential - - name: Install Rust ${{ matrix.rust-branch }} + - name: Install Rust Nightly uses: actions-rs/toolchain@v1.0.6 with: - toolchain: ${{ matrix.rust-branch }} + toolchain: nightly components: rustfmt profile: minimal @@ -84,11 +82,10 @@ jobs: env: RELEASE_NAME: development # RUSTFLAGS: -A warnings - RUSTV: ${{ matrix.rust-branch }} RUST_BACKTRACE: full - RUST_BIN_DIR: target/${{ matrix.rust-target }} SKIP_WASM_BUILD: 1 TARGET: ${{ matrix.rust-target }} + RUST_BIN_DIR: target/${{ matrix.rust-target }} steps: - name: Check-out repository under $GITHUB_WORKSPACE uses: actions/checkout@v4 @@ -98,13 +95,6 @@ jobs: sudo apt-get update && sudo apt-get install -y clang curl libssl-dev llvm libudev-dev protobuf-compiler - - name: Install Rust ${{ matrix.rust-branch }} - uses: actions-rs/toolchain@v1.0.6 - with: - toolchain: ${{ matrix.rust-branch }} - components: rustfmt, clippy - profile: minimal - - name: Utilize Shared Rust Cache uses: Swatinem/rust-cache@v2.2.1 with: @@ -128,12 +118,11 @@ jobs: # - macos-latest env: RELEASE_NAME: development - RUSTV: ${{ matrix.rust-branch }} RUSTFLAGS: -D warnings RUST_BACKTRACE: full - RUST_BIN_DIR: target/${{ matrix.rust-target }} SKIP_WASM_BUILD: 1 TARGET: ${{ matrix.rust-target }} + RUST_BIN_DIR: target/${{ matrix.rust-target }} steps: - name: Check-out repository under $GITHUB_WORKSPACE uses: actions/checkout@v4 @@ -166,8 +155,6 @@ jobs: runs-on: SubtensorCI strategy: matrix: - rust-branch: - - stable rust-target: - x86_64-unknown-linux-gnu # - x86_64-apple-darwin @@ -180,7 +167,6 @@ jobs: env: RELEASE_NAME: development # RUSTFLAGS: -A warnings - RUSTV: ${{ matrix.rust-branch }} RUST_BACKTRACE: full RUST_BIN_DIR: target/${{ matrix.rust-target }} SKIP_WASM_BUILD: 1 @@ -194,13 +180,6 @@ jobs: sudo apt-get update && sudo apt-get install -y clang curl libssl-dev llvm libudev-dev protobuf-compiler - - name: Install Rust ${{ matrix.rust-branch }} - uses: actions-rs/toolchain@v1.0.6 - with: - toolchain: ${{ matrix.rust-branch }} - components: rustfmt, clippy - profile: minimal - - name: Utilize Shared Rust Cache uses: Swatinem/rust-cache@v2.2.1 with: @@ -215,8 +194,6 @@ jobs: runs-on: SubtensorCI strategy: matrix: - rust-branch: - - stable rust-target: - x86_64-unknown-linux-gnu # - x86_64-apple-darwin @@ -229,7 +206,6 @@ jobs: env: RELEASE_NAME: development # RUSTFLAGS: -A warnings - RUSTV: ${{ matrix.rust-branch }} RUST_BACKTRACE: full RUST_BIN_DIR: target/${{ matrix.rust-target }} SKIP_WASM_BUILD: 1 @@ -243,13 +219,6 @@ jobs: sudo apt-get update && sudo apt-get install -y clang curl libssl-dev llvm libudev-dev protobuf-compiler - - name: Install Rust ${{ matrix.rust-branch }} - uses: actions-rs/toolchain@v1.0.6 - with: - toolchain: ${{ matrix.rust-branch }} - components: rustfmt, clippy - profile: minimal - - name: Utilize Rust shared cached uses: Swatinem/rust-cache@v2.2.1 with: @@ -278,7 +247,6 @@ jobs: env: RELEASE_NAME: development # RUSTFLAGS: -A warnings - RUSTV: ${{ matrix.rust-branch }} RUST_BACKTRACE: full RUST_BIN_DIR: target/${{ matrix.rust-target }} SKIP_WASM_BUILD: 1 @@ -292,13 +260,6 @@ jobs: sudo apt-get update && sudo apt-get install -y clang curl libssl-dev llvm libudev-dev protobuf-compiler - - name: Install Rust ${{ matrix.rust-branch }} - uses: actions-rs/toolchain@v1.0.6 - with: - toolchain: ${{ matrix.rust-branch }} - components: rustfmt, clippy - profile: minimal - - name: Utilize Rust shared cached uses: Swatinem/rust-cache@v2.2.1 with: @@ -322,12 +283,6 @@ jobs: runs-on: SubtensorCI steps: - - name: Install stable Rust - uses: actions-rs/toolchain@v1 - with: - profile: minimal - toolchain: stable - - name: Install Zepter run: cargo install --locked -q zepter && zepter --version diff --git a/.github/workflows/check-testnet.yml b/.github/workflows/check-testnet.yml index a869129ab..03c7e8f8a 100644 --- a/.github/workflows/check-testnet.yml +++ b/.github/workflows/check-testnet.yml @@ -20,9 +20,6 @@ jobs: sudo apt-get install -y curl clang curl libssl-dev llvm \ libudev-dev protobuf-compiler - - name: Set up Rust Toolchain - run: curl https://sh.rustup.rs -sSf | sh -s -- -y - - name: Install substrate-spec-version run: cargo install substrate-spec-version diff --git a/.github/workflows/try-runtime.yml b/.github/workflows/try-runtime.yml index 174e6db37..a86482804 100644 --- a/.github/workflows/try-runtime.yml +++ b/.github/workflows/try-runtime.yml @@ -14,7 +14,7 @@ jobs: runs-on: SubtensorCI steps: - name: Checkout sources - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Run Try Runtime Checks uses: "paritytech/try-runtime-gha@v0.1.0" @@ -29,7 +29,7 @@ jobs: runs-on: SubtensorCI steps: - name: Checkout sources - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Run Try Runtime Checks uses: "paritytech/try-runtime-gha@v0.1.0" diff --git a/build.rs b/build.rs index 7261a28e1..1abd7456b 100644 --- a/build.rs +++ b/build.rs @@ -60,6 +60,7 @@ fn main() { }; track_lint(ForbidAsPrimitiveConversion::lint(&parsed_file)); + track_lint(ForbidKeysRemoveCall::lint(&parsed_file)); track_lint(RequireFreezeStruct::lint(&parsed_file)); track_lint(RequireExplicitPalletIndex::lint(&parsed_file)); }); diff --git a/node/src/benchmarking.rs b/node/src/benchmarking.rs index a97550071..cee1cb4ac 100644 --- a/node/src/benchmarking.rs +++ b/node/src/benchmarking.rs @@ -2,7 +2,7 @@ //! //! Should only be used for benchmarking as it may break in other contexts. -use crate::service::Client; +use crate::client::FullClient; use node_subtensor_runtime as runtime; use node_subtensor_runtime::check_nonce; @@ -21,12 +21,12 @@ use std::{sync::Arc, time::Duration}; // // Note: Should only be used for benchmarking. pub struct RemarkBuilder { - client: Arc, + client: Arc, } impl RemarkBuilder { // Creates a new [`Self`] from the given client. - pub fn new(client: Arc) -> Self { + pub fn new(client: Arc) -> Self { Self { client } } } @@ -58,14 +58,14 @@ impl frame_benchmarking_cli::ExtrinsicBuilder for RemarkBuilder { // // Note: Should only be used for benchmarking. pub struct TransferKeepAliveBuilder { - client: Arc, + client: Arc, dest: AccountId, value: Balance, } impl TransferKeepAliveBuilder { // Creates a new [`Self`] from the given client. - pub fn new(client: Arc, dest: AccountId, value: Balance) -> Self { + pub fn new(client: Arc, dest: AccountId, value: Balance) -> Self { Self { client, dest, @@ -105,7 +105,7 @@ impl frame_benchmarking_cli::ExtrinsicBuilder for TransferKeepAliveBuilder { // // Note: Should only be used for benchmarking. pub fn create_benchmark_extrinsic( - client: &Client, + client: &FullClient, sender: sp_core::sr25519::Pair, call: runtime::RuntimeCall, nonce: u32, diff --git a/node/src/chain_spec/localnet.rs b/node/src/chain_spec/localnet.rs index 1595c75ae..06ff5b755 100644 --- a/node/src/chain_spec/localnet.rs +++ b/node/src/chain_spec/localnet.rs @@ -3,7 +3,7 @@ use super::*; -pub fn localnet_config() -> Result { +pub fn localnet_config(single_authority: bool) -> Result { let wasm_binary = WASM_BINARY.ok_or_else(|| "Development wasm not available".to_string())?; // Give front-ends necessary data to present to users @@ -32,11 +32,15 @@ pub fn localnet_config() -> Result { .with_genesis_config_patch(localnet_genesis( // Initial PoA authorities (Validators) // aura | grandpa - vec![ - // Keys for debug - authority_keys_from_seed("Alice"), - authority_keys_from_seed("Bob"), - ], + if single_authority { + // single authority allows you to run the network using a single node + vec![authority_keys_from_seed("Alice")] + } else { + vec![ + authority_keys_from_seed("Alice"), + authority_keys_from_seed("Bob"), + ] + }, // Pre-funded accounts true, )) diff --git a/node/src/client.rs b/node/src/client.rs index c7196b5a9..dd7dfda45 100644 --- a/node/src/client.rs +++ b/node/src/client.rs @@ -1,76 +1,16 @@ -use scale_codec::Codec; -// Substrate +use node_subtensor_runtime::{opaque::Block, RuntimeApi}; use sc_executor::WasmExecutor; -use sp_runtime::traits::{Block as BlockT, MaybeDisplay}; - -use crate::ethereum::EthCompatRuntimeApiCollection; /// Full backend. -pub type FullBackend = sc_service::TFullBackend; +pub type FullBackend = sc_service::TFullBackend; /// Full client. -pub type FullClient = sc_service::TFullClient>; - -/// A set of APIs that every runtime must implement. -pub trait BaseRuntimeApiCollection: - sp_api::ApiExt - + sp_api::Metadata - + sp_block_builder::BlockBuilder - + sp_offchain::OffchainWorkerApi - + sp_session::SessionKeys - + sp_transaction_pool::runtime_api::TaggedTransactionQueue -{ -} - -impl BaseRuntimeApiCollection for Api -where - Block: BlockT, - Api: sp_api::ApiExt - + sp_api::Metadata - + sp_block_builder::BlockBuilder - + sp_offchain::OffchainWorkerApi - + sp_session::SessionKeys - + sp_transaction_pool::runtime_api::TaggedTransactionQueue, -{ -} - -/// A set of APIs that Subtensor runtime must implement. -pub trait RuntimeApiCollection< - Block: BlockT, - AuraId: Codec, - AccountId: Codec, - Nonce: Codec, - Balance: Codec + MaybeDisplay, ->: - BaseRuntimeApiCollection - + EthCompatRuntimeApiCollection - + sp_consensus_aura::AuraApi - + sp_consensus_grandpa::GrandpaApi - + frame_system_rpc_runtime_api::AccountNonceApi - + pallet_transaction_payment_rpc_runtime_api::TransactionPaymentApi - + subtensor_custom_rpc_runtime_api::DelegateInfoRuntimeApi - + subtensor_custom_rpc_runtime_api::NeuronInfoRuntimeApi - + subtensor_custom_rpc_runtime_api::SubnetInfoRuntimeApi - + subtensor_custom_rpc_runtime_api::SubnetRegistrationRuntimeApi -{ -} - -impl - RuntimeApiCollection for Api -where - Block: BlockT, - AuraId: Codec, - AccountId: Codec, - Nonce: Codec, - Balance: Codec + MaybeDisplay, - Api: BaseRuntimeApiCollection - + EthCompatRuntimeApiCollection - + sp_consensus_aura::AuraApi - + sp_consensus_grandpa::GrandpaApi - + frame_system_rpc_runtime_api::AccountNonceApi - + pallet_transaction_payment_rpc_runtime_api::TransactionPaymentApi - + subtensor_custom_rpc_runtime_api::DelegateInfoRuntimeApi - + subtensor_custom_rpc_runtime_api::NeuronInfoRuntimeApi - + subtensor_custom_rpc_runtime_api::SubnetInfoRuntimeApi - + subtensor_custom_rpc_runtime_api::SubnetRegistrationRuntimeApi, -{ -} +pub type FullClient = sc_service::TFullClient>; +/// Always enable runtime benchmark host functions, the genesis state +/// was built with them so we're stuck with them forever. +/// +/// They're just a noop, never actually get used if the runtime was not compiled with +/// `runtime-benchmarks`. +type HostFunctions = ( + sp_io::SubstrateHostFunctions, + frame_benchmarking::benchmarking::HostFunctions, +); diff --git a/node/src/command.rs b/node/src/command.rs index a5a92a377..7b6d6982b 100644 --- a/node/src/command.rs +++ b/node/src/command.rs @@ -41,7 +41,8 @@ impl SubstrateCli for Cli { fn load_spec(&self, id: &str) -> Result, String> { Ok(match id { - "local" => Box::new(chain_spec::localnet::localnet_config()?), + "dev" => Box::new(chain_spec::localnet::localnet_config(true)?), + "local" => Box::new(chain_spec::localnet::localnet_config(false)?), "finney" => Box::new(chain_spec::finney::finney_mainnet_config()?), "devnet" => Box::new(chain_spec::devnet::devnet_config()?), "" | "test_finney" => Box::new(chain_spec::testnet::finney_testnet_config()?), diff --git a/node/src/ethereum.rs b/node/src/ethereum.rs index 337013a56..073635387 100644 --- a/node/src/ethereum.rs +++ b/node/src/ethereum.rs @@ -1,30 +1,23 @@ -use crate::rpc::EthDeps; +pub use fc_consensus::FrontierBlockImport; use fc_rpc::{ pending::AuraConsensusDataProvider, Debug, DebugApiServer, Eth, EthApiServer, EthConfig, EthDevSigner, EthFilter, EthFilterApiServer, EthPubSub, EthPubSubApiServer, EthSigner, EthTask, Net, NetApiServer, Web3, Web3ApiServer, }; -use fp_rpc::{ConvertTransaction, ConvertTransactionRuntimeApi, EthereumRuntimeRPCApi}; +pub use fc_rpc_core::types::{FeeHistoryCache, FeeHistoryCacheLimit, FilterPool}; +/// Frontier DB backend type. +pub use fc_storage::{StorageOverride, StorageOverrideHandler}; +use fp_rpc::ConvertTransaction; use futures::future; use futures::StreamExt; use jsonrpsee::RpcModule; -use sc_client_api::{ - backend::{Backend, StorageProvider}, - client::BlockchainEvents, - AuxStore, UsageProvider, -}; -use sc_executor::HostFunctions; +use node_subtensor_runtime::opaque::Block; +use sc_client_api::client::BlockchainEvents; use sc_network_sync::SyncingService; use sc_rpc::SubscriptionTaskExecutor; use sc_service::{error::Error as ServiceError, Configuration, TaskManager}; use sc_transaction_pool::ChainApi; use sc_transaction_pool_api::TransactionPool; -use sp_api::{CallApiAt, ConstructRuntimeApi, ProvideRuntimeApi}; -use sp_block_builder::BlockBuilder as BlockBuilderApi; -use sp_blockchain::{Error as BlockChainError, HeaderBackend, HeaderMetadata}; -use sp_consensus_aura::sr25519::AuthorityId as AuraId; -use sp_consensus_aura::AuraApi; -use sp_core::H256; use sp_inherents::CreateInherentDataProviders; use sp_runtime::traits::Block as BlockT; use std::path::PathBuf; @@ -34,14 +27,10 @@ use std::{ sync::{Arc, Mutex}, }; -pub use fc_consensus::FrontierBlockImport; -pub use fc_rpc_core::types::{FeeHistoryCache, FeeHistoryCacheLimit, FilterPool}; -/// Frontier DB backend type. -pub use fc_storage::{StorageOverride, StorageOverrideHandler}; - use crate::client::{FullBackend, FullClient}; +use crate::rpc::EthDeps; -pub type FrontierBackend = fc_db::Backend; +pub type FrontierBackend = fc_db::Backend; /// Avalailable frontier backend types. #[derive(Debug, Copy, Clone, Default, clap::ValueEnum)] @@ -126,46 +115,23 @@ pub fn new_frontier_partial( }) } -/// A set of APIs that ethereum-compatible runtimes must implement. -pub trait EthCompatRuntimeApiCollection: - sp_api::ApiExt - + fp_rpc::ConvertTransactionRuntimeApi - + fp_rpc::EthereumRuntimeRPCApi -{ -} - -impl EthCompatRuntimeApiCollection for Api -where - Block: BlockT, - Api: sp_api::ApiExt - + fp_rpc::ConvertTransactionRuntimeApi - + fp_rpc::EthereumRuntimeRPCApi, -{ -} - #[allow(clippy::too_many_arguments)] -pub async fn spawn_frontier_tasks( +pub async fn spawn_frontier_tasks( task_manager: &TaskManager, - client: Arc>, - backend: Arc>, - frontier_backend: Arc>>, + client: Arc, + backend: Arc, + frontier_backend: Arc, filter_pool: Option, - storage_override: Arc>, + storage_override: Arc>, fee_history_cache: FeeHistoryCache, fee_history_cache_limit: FeeHistoryCacheLimit, - sync: Arc>, + sync: Arc>, pubsub_notification_sinks: Arc< fc_mapping_sync::EthereumBlockNotificationSinks< - fc_mapping_sync::EthereumBlockNotification, + fc_mapping_sync::EthereumBlockNotification, >, >, -) where - B: BlockT, - RA: ConstructRuntimeApi>, - RA: Send + Sync + 'static, - RA::RuntimeApi: EthCompatRuntimeApiCollection, - HF: HostFunctions + 'static, -{ +) { // Spawn main mapping sync worker background task. match &*frontier_backend { fc_db::Backend::KeyValue(b) => { @@ -180,7 +146,7 @@ pub async fn spawn_frontier_tasks( storage_override.clone(), b.clone(), 3, - 0u32.into(), + 0u32, fc_mapping_sync::SyncStrategy::Normal, sync, pubsub_notification_sinks, @@ -233,25 +199,16 @@ pub async fn spawn_frontier_tasks( ); } -fn extend_rpc_aet_api( +fn extend_rpc_aet_api( io: &mut RpcModule<()>, - deps: &EthDeps, + deps: &EthDeps, ) -> Result<(), Box> where - B: BlockT, - C: CallApiAt + ProvideRuntimeApi, - C::Api: AuraApi - + BlockBuilderApi - + ConvertTransactionRuntimeApi - + EthereumRuntimeRPCApi, - C: HeaderBackend + HeaderMetadata, - C: BlockchainEvents + AuxStore + UsageProvider + StorageProvider + 'static, - BE: Backend + 'static, - P: TransactionPool + 'static, - A: ChainApi + 'static, - CT: ConvertTransaction<::Extrinsic> + Send + Sync + Clone + 'static, - CIDP: CreateInherentDataProviders + Send + Clone + 'static, - EC: EthConfig, + P: TransactionPool + 'static, + A: ChainApi + 'static, + CT: ConvertTransaction<::Extrinsic> + Send + Sync + Clone + 'static, + CIDP: CreateInherentDataProviders + Send + Clone + 'static, + EC: EthConfig, { let mut signers = Vec::new(); if deps.enable_dev_signer { @@ -259,7 +216,7 @@ where } io.merge( - Eth::::new( + Eth::::new( deps.client.clone(), deps.pool.clone(), deps.graph.clone(), @@ -285,24 +242,15 @@ where Ok(()) } -fn extend_rpc_eth_filter( +fn extend_rpc_eth_filter( io: &mut RpcModule<()>, - deps: &EthDeps, + deps: &EthDeps, ) -> Result<(), Box> where - B: BlockT, - C: CallApiAt + ProvideRuntimeApi, - C::Api: AuraApi - + BlockBuilderApi - + ConvertTransactionRuntimeApi - + EthereumRuntimeRPCApi, - C: HeaderBackend + HeaderMetadata, - C: BlockchainEvents + AuxStore + UsageProvider + StorageProvider + 'static, - BE: Backend + 'static, - P: TransactionPool + 'static, - A: ChainApi + 'static, - CT: ConvertTransaction<::Extrinsic> + Send + Sync + Clone + 'static, - CIDP: CreateInherentDataProviders + Send + Clone + 'static, + P: TransactionPool + 'static, + A: ChainApi + 'static, + CT: ConvertTransaction<::Extrinsic> + Send + Sync + Clone + 'static, + CIDP: CreateInherentDataProviders + Send + Clone + 'static, { if let Some(filter_pool) = deps.filter_pool.clone() { io.merge( @@ -322,30 +270,21 @@ where } // Function for EthPubSub merge -fn extend_rpc_eth_pubsub( +fn extend_rpc_eth_pubsub( io: &mut RpcModule<()>, - deps: &EthDeps, + deps: &EthDeps, subscription_task_executor: SubscriptionTaskExecutor, pubsub_notification_sinks: Arc< fc_mapping_sync::EthereumBlockNotificationSinks< - fc_mapping_sync::EthereumBlockNotification, + fc_mapping_sync::EthereumBlockNotification, >, >, ) -> Result<(), Box> where - B: BlockT, - C: CallApiAt + ProvideRuntimeApi, - C::Api: AuraApi - + BlockBuilderApi - + ConvertTransactionRuntimeApi - + EthereumRuntimeRPCApi, - C: HeaderBackend + HeaderMetadata, - C: BlockchainEvents + AuxStore + UsageProvider + StorageProvider + 'static, - BE: Backend + 'static, - P: TransactionPool + 'static, - A: ChainApi + 'static, - CT: ConvertTransaction<::Extrinsic> + Send + Sync + 'static, - CIDP: CreateInherentDataProviders + Send + 'static, + P: TransactionPool + 'static, + A: ChainApi + 'static, + CT: ConvertTransaction<::Extrinsic> + Send + Sync + 'static, + CIDP: CreateInherentDataProviders + Send + 'static, { io.merge( EthPubSub::new( @@ -361,24 +300,15 @@ where Ok(()) } -fn extend_rpc_net( +fn extend_rpc_net( io: &mut RpcModule<()>, - deps: &EthDeps, + deps: &EthDeps, ) -> Result<(), Box> where - B: BlockT, - C: CallApiAt + ProvideRuntimeApi, - C::Api: AuraApi - + BlockBuilderApi - + ConvertTransactionRuntimeApi - + EthereumRuntimeRPCApi, - C: HeaderBackend + HeaderMetadata, - C: BlockchainEvents + AuxStore + UsageProvider + StorageProvider + 'static, - BE: Backend + 'static, - P: TransactionPool + 'static, - A: ChainApi + 'static, - CT: ConvertTransaction<::Extrinsic> + Send + Sync + 'static, - CIDP: CreateInherentDataProviders + Send + 'static, + P: TransactionPool + 'static, + A: ChainApi + 'static, + CT: ConvertTransaction<::Extrinsic> + Send + Sync + 'static, + CIDP: CreateInherentDataProviders + Send + 'static, { io.merge( Net::new( @@ -391,47 +321,29 @@ where Ok(()) } -fn extend_rpc_web3( +fn extend_rpc_web3( io: &mut RpcModule<()>, - deps: &EthDeps, + deps: &EthDeps, ) -> Result<(), Box> where - B: BlockT, - C: CallApiAt + ProvideRuntimeApi, - C::Api: AuraApi - + BlockBuilderApi - + ConvertTransactionRuntimeApi - + EthereumRuntimeRPCApi, - C: HeaderBackend + HeaderMetadata, - C: BlockchainEvents + AuxStore + UsageProvider + StorageProvider + 'static, - BE: Backend + 'static, - P: TransactionPool + 'static, - A: ChainApi + 'static, - CT: ConvertTransaction<::Extrinsic> + Send + Sync + 'static, - CIDP: CreateInherentDataProviders + Send + 'static, + P: TransactionPool + 'static, + A: ChainApi + 'static, + CT: ConvertTransaction<::Extrinsic> + Send + Sync + 'static, + CIDP: CreateInherentDataProviders + Send + 'static, { io.merge(Web3::new(deps.client.clone()).into_rpc())?; Ok(()) } -fn extend_rpc_debug( +fn extend_rpc_debug( io: &mut RpcModule<()>, - deps: &EthDeps, + deps: &EthDeps, ) -> Result<(), Box> where - B: BlockT, - C: CallApiAt + ProvideRuntimeApi, - C::Api: AuraApi - + BlockBuilderApi - + ConvertTransactionRuntimeApi - + EthereumRuntimeRPCApi, - C: HeaderBackend + HeaderMetadata, - C: BlockchainEvents + AuxStore + UsageProvider + StorageProvider + 'static, - BE: Backend + 'static, - P: TransactionPool + 'static, - A: ChainApi + 'static, - CT: ConvertTransaction<::Extrinsic> + Send + Sync + 'static, - CIDP: CreateInherentDataProviders + Send + 'static, + P: TransactionPool + 'static, + A: ChainApi + 'static, + CT: ConvertTransaction<::Extrinsic> + Send + Sync + 'static, + CIDP: CreateInherentDataProviders + Send + 'static, { io.merge( Debug::new( @@ -446,43 +358,34 @@ where } /// Extend RpcModule with Eth RPCs -pub fn create_eth( +pub fn create_eth( mut io: RpcModule<()>, - deps: EthDeps, + deps: EthDeps, subscription_task_executor: SubscriptionTaskExecutor, pubsub_notification_sinks: Arc< fc_mapping_sync::EthereumBlockNotificationSinks< - fc_mapping_sync::EthereumBlockNotification, + fc_mapping_sync::EthereumBlockNotification, >, >, ) -> Result, Box> where - B: BlockT, - C: CallApiAt + ProvideRuntimeApi, - C::Api: AuraApi - + BlockBuilderApi - + ConvertTransactionRuntimeApi - + EthereumRuntimeRPCApi, - C: HeaderBackend + HeaderMetadata, - C: BlockchainEvents + AuxStore + UsageProvider + StorageProvider + 'static, - BE: Backend + 'static, - P: TransactionPool + 'static, - A: ChainApi + 'static, - CT: ConvertTransaction<::Extrinsic> + Send + Sync + Clone + 'static, - CIDP: CreateInherentDataProviders + Send + Clone + 'static, - EC: EthConfig, + P: TransactionPool + 'static, + A: ChainApi + 'static, + CT: ConvertTransaction<::Extrinsic> + Send + Sync + Clone + 'static, + CIDP: CreateInherentDataProviders + Send + Clone + 'static, + EC: EthConfig, { - extend_rpc_aet_api::(&mut io, &deps)?; - extend_rpc_eth_filter::(&mut io, &deps)?; - extend_rpc_eth_pubsub::( + extend_rpc_aet_api::(&mut io, &deps)?; + extend_rpc_eth_filter::(&mut io, &deps)?; + extend_rpc_eth_pubsub::( &mut io, &deps, subscription_task_executor, pubsub_notification_sinks, )?; - extend_rpc_net::(&mut io, &deps)?; - extend_rpc_web3::(&mut io, &deps)?; - extend_rpc_debug::(&mut io, &deps)?; + extend_rpc_net::(&mut io, &deps)?; + extend_rpc_web3::(&mut io, &deps)?; + extend_rpc_debug::(&mut io, &deps)?; Ok(io) } diff --git a/node/src/rpc.rs b/node/src/rpc.rs index 4f2063215..3b4729c71 100644 --- a/node/src/rpc.rs +++ b/node/src/rpc.rs @@ -9,34 +9,31 @@ use std::{collections::BTreeMap, sync::Arc}; use futures::channel::mpsc; -use crate::{client::RuntimeApiCollection, ethereum::create_eth}; pub use fc_rpc::EthBlockDataCacheTask; pub use fc_rpc_core::types::{FeeHistoryCache, FeeHistoryCacheLimit, FilterPool}; use fc_storage::StorageOverride; use jsonrpsee::RpcModule; -use node_subtensor_runtime::{AccountId, Balance, Hash, Nonce}; -use sc_client_api::{ - backend::{Backend, StorageProvider}, - client::BlockchainEvents, - AuxStore, UsageProvider, -}; +use node_subtensor_runtime::opaque::Block; +use node_subtensor_runtime::Hash; use sc_consensus_manual_seal::EngineCommand; use sc_network::service::traits::NetworkService; use sc_network_sync::SyncingService; use sc_rpc::SubscriptionTaskExecutor; use sc_transaction_pool::{ChainApi, Pool}; use sc_transaction_pool_api::TransactionPool; -use sp_api::{CallApiAt, ProvideRuntimeApi}; -use sp_blockchain::{Error as BlockChainError, HeaderBackend, HeaderMetadata}; -use sp_consensus_aura::sr25519::AuthorityId as AuraId; use sp_core::H256; use sp_inherents::CreateInherentDataProviders; use sp_runtime::traits::Block as BlockT; +use crate::{ + client::{FullBackend, FullClient}, + ethereum::create_eth, +}; + /// Extra dependencies for Ethereum compatibility. -pub struct EthDeps { +pub struct EthDeps { /// The client instance to use. - pub client: Arc, + pub client: Arc, /// Transaction pool instance. pub pool: Arc

, /// Graph pool instance. @@ -50,13 +47,13 @@ pub struct EthDeps { /// Network service pub network: Arc, /// Chain syncing service - pub sync: Arc>, + pub sync: Arc>, /// Frontier Backend. - pub frontier_backend: Arc>, + pub frontier_backend: Arc>, /// Ethereum data access overrides. - pub storage_override: Arc>, + pub storage_override: Arc>, /// Cache for Ethereum block data. - pub block_data_cache: Arc>, + pub block_data_cache: Arc>, /// EthFilterApi pool. pub filter_pool: Option, /// Maximum number of logs in a query. @@ -75,52 +72,44 @@ pub struct EthDeps { } /// Default Eth RPC configuration -pub struct DefaultEthConfig(std::marker::PhantomData<(C, BE)>); +pub struct DefaultEthConfig; -impl fc_rpc::EthConfig for DefaultEthConfig -where - B: BlockT, - C: StorageProvider + Sync + Send + 'static, - BE: Backend + 'static, -{ +impl fc_rpc::EthConfig for DefaultEthConfig { type EstimateGasAdapter = (); - type RuntimeStorageOverride = - fc_rpc::frontier_backend_client::SystemAccountId20StorageOverride; + type RuntimeStorageOverride = fc_rpc::frontier_backend_client::SystemAccountId20StorageOverride< + Block, + FullClient, + FullBackend, + >; } /// Full client dependencies. -pub struct FullDeps { +pub struct FullDeps { /// The client instance to use. - pub client: Arc, + pub client: Arc, /// Transaction pool instance. pub pool: Arc

, /// Manual seal command sink pub command_sink: Option>>, /// Ethereum-compatibility specific dependencies. - pub eth: EthDeps, + pub eth: EthDeps, } /// Instantiate all full RPC extensions. -pub fn create_full( - deps: FullDeps, +pub fn create_full( + deps: FullDeps, subscription_task_executor: SubscriptionTaskExecutor, pubsub_notification_sinks: Arc< fc_mapping_sync::EthereumBlockNotificationSinks< - fc_mapping_sync::EthereumBlockNotification, + fc_mapping_sync::EthereumBlockNotification, >, >, ) -> Result, Box> where - B: BlockT, - C: CallApiAt + ProvideRuntimeApi, - C::Api: RuntimeApiCollection, - C: HeaderBackend + HeaderMetadata + 'static, - C: BlockchainEvents + AuxStore + UsageProvider + StorageProvider, - BE: Backend + 'static, - P: TransactionPool + 'static, - A: ChainApi + 'static, - CIDP: CreateInherentDataProviders + Send + Clone + 'static, - CT: fp_rpc::ConvertTransaction<::Extrinsic> + Send + Sync + Clone + 'static, + P: TransactionPool + 'static, + A: ChainApi + 'static, + CIDP: CreateInherentDataProviders + Send + Clone + 'static, + CT: fp_rpc::ConvertTransaction<::Extrinsic> + Send + Sync + Clone + 'static, { use pallet_transaction_payment_rpc::{TransactionPayment, TransactionPaymentApiServer}; use sc_consensus_manual_seal::rpc::{ManualSeal, ManualSealApiServer}; @@ -155,7 +144,7 @@ where } // Ethereum compatibility RPCs - let module = create_eth::<_, _, _, _, _, _, _, DefaultEthConfig>( + let module = create_eth::<_, _, _, _, DefaultEthConfig>( module, eth, subscription_task_executor, diff --git a/node/src/service.rs b/node/src/service.rs index 84a3a1255..22e167949 100644 --- a/node/src/service.rs +++ b/node/src/service.rs @@ -1,13 +1,7 @@ //! Service and ServiceFactory implementation. Specialized wrapper over substrate service. -use crate::cli::Sealing; -use crate::client::{FullBackend, FullClient}; -use crate::ethereum::{ - db_config_dir, new_frontier_partial, spawn_frontier_tasks, BackendType, EthConfiguration, - FrontierBackend, FrontierBlockImport, FrontierPartialComponents, StorageOverride, - StorageOverrideHandler, -}; use futures::{channel::mpsc, future, FutureExt}; +use node_subtensor_runtime::{opaque::Block, RuntimeApi, TransactionConverter}; use sc_client_api::{Backend as BackendT, BlockBackend}; use sc_consensus::{BasicQueue, BoxBlockImport}; use sc_consensus_grandpa::BlockNumberOps; @@ -24,30 +18,22 @@ use std::{cell::RefCell, path::Path}; use std::{sync::Arc, time::Duration}; use substrate_prometheus_endpoint::Registry; -// Runtime -use node_subtensor_runtime::{opaque::Block, RuntimeApi, TransactionConverter}; +use crate::cli::Sealing; +use crate::client::{FullBackend, FullClient}; +use crate::ethereum::{ + db_config_dir, new_frontier_partial, spawn_frontier_tasks, BackendType, EthConfiguration, + FrontierBackend, FrontierBlockImport, FrontierPartialComponents, StorageOverride, + StorageOverrideHandler, +}; /// The minimum period of blocks on which justifications will be /// imported and generated. const GRANDPA_JUSTIFICATION_PERIOD: u32 = 512; -/// Always enable runtime benchmark host functions, the genesis state -/// was built with them so we're stuck with them forever. -/// -/// They're just a noop, never actually get used if the runtime was not compiled with -/// `runtime-benchmarks`. -pub type HostFunctions = ( - sp_io::SubstrateHostFunctions, - frame_benchmarking::benchmarking::HostFunctions, -); - -pub type Backend = FullBackend; -pub type Client = FullClient; - -type FullSelectChain = sc_consensus::LongestChain, B>; -type GrandpaBlockImport = - sc_consensus_grandpa::GrandpaBlockImport, B, C, FullSelectChain>; -type GrandpaLinkHalf = sc_consensus_grandpa::LinkHalf>; +type FullSelectChain = sc_consensus::LongestChain; +type GrandpaBlockImport = + sc_consensus_grandpa::GrandpaBlockImport; +type GrandpaLinkHalf = sc_consensus_grandpa::LinkHalf; pub fn new_partial( config: &Configuration, @@ -55,34 +41,29 @@ pub fn new_partial( build_import_queue: BIQ, ) -> Result< PartialComponents< - Client, - FullBackend, - FullSelectChain, + FullClient, + FullBackend, + FullSelectChain, BasicQueue, - FullPool, + FullPool, ( Option, BoxBlockImport, - GrandpaLinkHalf, - FrontierBackend, + GrandpaLinkHalf, + FrontierBackend, Arc>, ), >, ServiceError, > where - // B: BlockT, - // RA: ConstructRuntimeApi, - // RA: Send + Sync + 'static, - // RA::RuntimeApi: RuntimeApiCollection, - // HF: HostFunctionsT + 'static, BIQ: FnOnce( - Arc, + Arc, &Configuration, &EthConfiguration, &TaskManager, Option, - GrandpaBlockImport, + GrandpaBlockImport, ) -> Result<(BasicQueue, BoxBlockImport), ServiceError>, { let telemetry = config @@ -188,20 +169,15 @@ where /// Build the import queue for the template runtime (aura + grandpa). pub fn build_aura_grandpa_import_queue( - client: Arc, + client: Arc, config: &Configuration, eth_config: &EthConfiguration, task_manager: &TaskManager, telemetry: Option, - grandpa_block_import: GrandpaBlockImport, + grandpa_block_import: GrandpaBlockImport, ) -> Result<(BasicQueue, BoxBlockImport), ServiceError> where - // B: BlockT, NumberFor: BlockNumberOps, - // RA: ConstructRuntimeApi>, - // RA: Send + Sync + 'static, - // RA::RuntimeApi: RuntimeApiCollection, - // HF: HostFunctionsT + 'static, { let frontier_block_import = FrontierBlockImport::new(grandpa_block_import.clone(), client.clone()); @@ -239,20 +215,13 @@ where /// Build the import queue for the template runtime (manual seal). pub fn build_manual_seal_import_queue( - client: Arc, + client: Arc, config: &Configuration, _eth_config: &EthConfiguration, task_manager: &TaskManager, _telemetry: Option, - _grandpa_block_import: GrandpaBlockImport, -) -> Result<(BasicQueue, BoxBlockImport), ServiceError> -// where - // B: BlockT, - // RA: ConstructRuntimeApi>, - // RA: Send + Sync + 'static, - // RA::RuntimeApi: RuntimeApiCollection, - // HF: HostFunctionsT + 'static, -{ + _grandpa_block_import: GrandpaBlockImport, +) -> Result<(BasicQueue, BoxBlockImport), ServiceError> { let frontier_block_import = FrontierBlockImport::new(client.clone(), client); Ok(( sc_consensus_manual_seal::import_queue( @@ -271,13 +240,7 @@ pub async fn new_full( sealing: Option, ) -> Result where - // B: BlockT, NumberFor: BlockNumberOps, - // ::Header: Unpin, - // RA: ConstructRuntimeApi>, - // RA: Send + Sync + 'static, - // RA::RuntimeApi: RuntimeApiCollection, - // HF: HostFunctionsT + 'static, NB: sc_network::NetworkBackend::Hash>, { let build_import_queue = if sealing.is_some() { @@ -649,11 +612,11 @@ pub fn new_chain_ops( eth_config: &EthConfiguration, ) -> Result< ( - Arc, - Arc, + Arc, + Arc, BasicQueue, TaskManager, - FrontierBackend, + FrontierBackend, ), ServiceError, > { @@ -673,9 +636,9 @@ pub fn new_chain_ops( fn run_manual_seal_authorship( eth_config: &EthConfiguration, sealing: Sealing, - client: Arc, - transaction_pool: Arc>, - select_chain: FullSelectChain, + client: Arc, + transaction_pool: Arc>, + select_chain: FullSelectChain, block_import: BoxBlockImport, task_manager: &TaskManager, prometheus_registry: Option<&Registry>, @@ -683,14 +646,7 @@ fn run_manual_seal_authorship( commands_stream: mpsc::Receiver< sc_consensus_manual_seal::rpc::EngineCommand<::Hash>, >, -) -> Result<(), ServiceError> -// where - // B: BlockT, - // RA: ConstructRuntimeApi>, - // RA: Send + Sync + 'static, - // RA::RuntimeApi: RuntimeApiCollection, - // HF: HostFunctionsT + 'static, -{ +) -> Result<(), ServiceError> { let proposer_factory = sc_basic_authorship::ProposerFactory::new( task_manager.spawn_handle(), client.clone(), diff --git a/pallets/subtensor/src/coinbase/run_coinbase.rs b/pallets/subtensor/src/coinbase/run_coinbase.rs index aa3fa17d2..5529fd129 100644 --- a/pallets/subtensor/src/coinbase/run_coinbase.rs +++ b/pallets/subtensor/src/coinbase/run_coinbase.rs @@ -199,59 +199,74 @@ impl Pallet { mining_emission: u64, ) { // --- 1. First, calculate the hotkey's share of the emission. - let take_proportion: I64F64 = I64F64::from_num(Self::get_childkey_take(hotkey, netuid)) - .saturating_div(I64F64::from_num(u16::MAX)); - let hotkey_take: u64 = take_proportion - .saturating_mul(I64F64::from_num(validating_emission)) - .to_num::(); - // NOTE: Only the validation emission should be split amongst parents. - - // --- 2. Compute the remaining emission after the hotkey's share is deducted. - let emission_minus_take: u64 = validating_emission.saturating_sub(hotkey_take); + let childkey_take_proportion: I96F32 = + I96F32::from_num(Self::get_childkey_take(hotkey, netuid)) + .saturating_div(I96F32::from_num(u16::MAX)); + let mut total_childkey_take: u64 = 0; - // --- 3. Track the remaining emission for accounting purposes. - let mut remaining_emission: u64 = emission_minus_take; + // --- 2. Track the remaining emission for accounting purposes. + let mut remaining_emission: u64 = validating_emission; - // --- 4. Calculate the total stake of the hotkey, adjusted by the stakes of parents and children. + // --- 3. Calculate the total stake of the hotkey, adjusted by the stakes of parents and children. // Parents contribute to the stake, while children reduce it. // If this value is zero, no distribution to anyone is necessary. let total_hotkey_stake: u64 = Self::get_stake_for_hotkey_on_subnet(hotkey, netuid); if total_hotkey_stake != 0 { - // --- 5. If the total stake is not zero, iterate over each parent to determine their contribution to the hotkey's stake, + // --- 4. If the total stake is not zero, iterate over each parent to determine their contribution to the hotkey's stake, // and calculate their share of the emission accordingly. for (proportion, parent) in Self::get_parents(hotkey, netuid) { - // --- 5.1 Retrieve the parent's stake. This is the raw stake value including nominators. + // --- 4.1 Retrieve the parent's stake. This is the raw stake value including nominators. let parent_stake: u64 = Self::get_total_stake_for_hotkey(&parent); - // --- 5.2 Calculate the portion of the hotkey's total stake contributed by this parent. + // --- 4.2 Calculate the portion of the hotkey's total stake contributed by this parent. // Then, determine the parent's share of the remaining emission. let stake_from_parent: I96F32 = I96F32::from_num(parent_stake).saturating_mul( I96F32::from_num(proportion).saturating_div(I96F32::from_num(u64::MAX)), ); let proportion_from_parent: I96F32 = stake_from_parent.saturating_div(I96F32::from_num(total_hotkey_stake)); - let parent_emission_take: u64 = proportion_from_parent - .saturating_mul(I96F32::from_num(emission_minus_take)) + let parent_emission: I96F32 = + proportion_from_parent.saturating_mul(I96F32::from_num(validating_emission)); + + // --- 4.3 Childkey take as part of parent emission + let child_emission_take: u64 = childkey_take_proportion + .saturating_mul(parent_emission) .to_num::(); + total_childkey_take = total_childkey_take.saturating_add(child_emission_take); + // NOTE: Only the validation emission should be split amongst parents. + + // --- 4.4 Compute the remaining parent emission after the childkey's share is deducted. + let parent_emission_take: u64 = parent_emission + .to_num::() + .saturating_sub(child_emission_take); - // --- 5.5. Accumulate emissions for the parent hotkey. + // --- 4.5. Accumulate emissions for the parent hotkey. PendingdHotkeyEmission::::mutate(parent, |parent_accumulated| { *parent_accumulated = parent_accumulated.saturating_add(parent_emission_take) }); - // --- 5.6. Subtract the parent's share from the remaining emission for this hotkey. - remaining_emission = remaining_emission.saturating_sub(parent_emission_take); + // --- 4.6. Subtract the parent's share from the remaining emission for this hotkey. + remaining_emission = remaining_emission + .saturating_sub(parent_emission_take) + .saturating_sub(child_emission_take); } } - // --- 6. Add the remaining emission plus the hotkey's initial take to the pending emission for this hotkey. + // --- 5. Add the remaining emission plus the hotkey's initial take to the pending emission for this hotkey. PendingdHotkeyEmission::::mutate(hotkey, |hotkey_pending| { *hotkey_pending = hotkey_pending.saturating_add( remaining_emission - .saturating_add(hotkey_take) + .saturating_add(total_childkey_take) .saturating_add(mining_emission), ) }); + + // --- 6. Update untouchable part of hotkey emission (that will not be distributed to nominators) + // This doesn't include remaining_emission, which should be distributed in drain_hotkey_emission + PendingdHotkeyEmissionUntouchable::::mutate(hotkey, |hotkey_pending| { + *hotkey_pending = + hotkey_pending.saturating_add(total_childkey_take.saturating_add(mining_emission)) + }); } //. --- 4. Drains the accumulated hotkey emission through to the nominators. The hotkey takes a proportion of the emission. @@ -270,8 +285,14 @@ impl Pallet { // --- 0. For accounting purposes record the total new added stake. let mut total_new_tao: u64 = 0; + // Get the untouchable part of pending hotkey emission, so that we don't distribute this part of + // PendingdHotkeyEmission to nominators + let untouchable_emission = PendingdHotkeyEmissionUntouchable::::get(hotkey); + let emission_to_distribute = emission.saturating_sub(untouchable_emission); + // --- 1.0 Drain the hotkey emission. PendingdHotkeyEmission::::insert(hotkey, 0); + PendingdHotkeyEmissionUntouchable::::insert(hotkey, 0); // --- 2 Update the block value to the current block number. LastHotkeyEmissionDrain::::insert(hotkey, block_number); @@ -280,13 +301,16 @@ impl Pallet { let total_hotkey_stake: u64 = Self::get_total_stake_for_hotkey(hotkey); // --- 4 Calculate the emission take for the hotkey. + // This is only the hotkey take. Childkey take was already deducted from validator emissions in + // accumulate_hotkey_emission and now it is included in untouchable_emission. let take_proportion: I64F64 = I64F64::from_num(Delegates::::get(hotkey)) .saturating_div(I64F64::from_num(u16::MAX)); - let hotkey_take: u64 = - (take_proportion.saturating_mul(I64F64::from_num(emission))).to_num::(); + let hotkey_take: u64 = (take_proportion + .saturating_mul(I64F64::from_num(emission_to_distribute))) + .to_num::(); - // --- 5 Compute the remaining emission after deducting the hotkey's take. - let emission_minus_take: u64 = emission.saturating_sub(hotkey_take); + // --- 5 Compute the remaining emission after deducting the hotkey's take and untouchable_emission. + let emission_minus_take: u64 = emission_to_distribute.saturating_sub(hotkey_take); // --- 6 Calculate the remaining emission after the hotkey's take. let mut remainder: u64 = emission_minus_take; @@ -327,8 +351,11 @@ impl Pallet { } } - // --- 13 Finally, add the stake to the hotkey itself, including its take and the remaining emission. - let hotkey_new_tao: u64 = hotkey_take.saturating_add(remainder); + // --- 13 Finally, add the stake to the hotkey itself, including its take, the remaining emission, and + // the untouchable_emission (part of pending hotkey emission that consists of mining emission and childkey take) + let hotkey_new_tao: u64 = hotkey_take + .saturating_add(remainder) + .saturating_add(untouchable_emission); Self::increase_stake_on_hotkey_account(hotkey, hotkey_new_tao); // --- 14 Reset the stake delta for the hotkey. diff --git a/pallets/subtensor/src/lib.rs b/pallets/subtensor/src/lib.rs index 958ef3480..1155076ae 100644 --- a/pallets/subtensor/src/lib.rs +++ b/pallets/subtensor/src/lib.rs @@ -796,6 +796,16 @@ pub mod pallet { DefaultAccumulatedEmission, >; #[pallet::storage] + /// Map ( hot ) --> emission | Part of accumulated hotkey emission that will not be distributed to nominators. + pub type PendingdHotkeyEmissionUntouchable = StorageMap< + _, + Blake2_128Concat, + T::AccountId, + u64, + ValueQuery, + DefaultAccumulatedEmission, + >; + #[pallet::storage] /// Map ( hot, cold ) --> stake: i128 | Stake added/removed since last emission drain. pub type StakeDeltaSinceLastEmissionDrain = StorageDoubleMap< _, diff --git a/pallets/subtensor/src/subnets/uids.rs b/pallets/subtensor/src/subnets/uids.rs index 11f59602d..61b6be70b 100644 --- a/pallets/subtensor/src/subnets/uids.rs +++ b/pallets/subtensor/src/subnets/uids.rs @@ -29,6 +29,7 @@ impl Pallet { // 2. Remove previous set memberships. Uids::::remove(netuid, old_hotkey.clone()); IsNetworkMember::::remove(old_hotkey.clone(), netuid); + #[allow(unknown_lints)] Keys::::remove(netuid, uid_to_replace); // 2a. Check if the uid is registered in any other subnetworks. diff --git a/pallets/subtensor/tests/children.rs b/pallets/subtensor/tests/children.rs index 0182888c0..644833345 100644 --- a/pallets/subtensor/tests/children.rs +++ b/pallets/subtensor/tests/children.rs @@ -3428,3 +3428,277 @@ fn test_set_weights_no_parent() { assert!(SubtensorModule::check_weights_min_stake(&hotkey, netuid)); }); } + +/// Test that drain_hotkey_emission sends childkey take fully to the childkey. +#[test] +fn test_childkey_take_drain() { + new_test_ext(1).execute_with(|| { + let coldkey = U256::from(1); + let parent = U256::from(2); + let child = U256::from(3); + let nominator = U256::from(4); + let netuid: u16 = 1; + let root_id: u16 = 0; + let subnet_tempo = 10; + let hotkey_tempo = 20; + let stake = 100_000_000_000; + let proportion: u64 = u64::MAX; + + // Add network, register hotkeys, and setup network parameters + add_network(root_id, subnet_tempo, 0); + add_network(netuid, subnet_tempo, 0); + register_ok_neuron(netuid, child, coldkey, 0); + register_ok_neuron(netuid, parent, coldkey, 1); + SubtensorModule::add_balance_to_coldkey_account( + &coldkey, + stake + ExistentialDeposit::get(), + ); + SubtensorModule::add_balance_to_coldkey_account( + &nominator, + stake + ExistentialDeposit::get(), + ); + SubtensorModule::set_hotkey_emission_tempo(hotkey_tempo); + SubtensorModule::set_weights_set_rate_limit(netuid, 0); + SubtensorModule::set_max_allowed_validators(netuid, 2); + step_block(subnet_tempo); + pallet_subtensor::SubnetOwnerCut::::set(0); + + // Set 20% childkey take + let max_take: u16 = 0xFFFF / 5; + SubtensorModule::set_max_childkey_take(max_take); + assert_ok!(SubtensorModule::set_childkey_take( + RuntimeOrigin::signed(coldkey), + child, + netuid, + max_take + )); + + // Set zero hotkey take for childkey + SubtensorModule::set_min_delegate_take(0); + assert_ok!(SubtensorModule::do_become_delegate( + RuntimeOrigin::signed(coldkey), + child, + 0 + )); + + // Set zero hotkey take for parent + assert_ok!(SubtensorModule::do_become_delegate( + RuntimeOrigin::signed(coldkey), + parent, + 0 + )); + + // Setup stakes: + // Stake from parent + // Stake from nominator to childkey + // Give 100% of parent stake to childkey + assert_ok!(SubtensorModule::add_stake( + RuntimeOrigin::signed(coldkey), + parent, + stake + )); + assert_ok!(SubtensorModule::add_stake( + RuntimeOrigin::signed(nominator), + child, + stake + )); + assert_ok!(SubtensorModule::do_set_children( + RuntimeOrigin::signed(coldkey), + parent, + netuid, + vec![(proportion, child)] + )); + // Make all stakes viable + pallet_subtensor::StakeDeltaSinceLastEmissionDrain::::set(parent, coldkey, -1); + pallet_subtensor::StakeDeltaSinceLastEmissionDrain::::set(child, nominator, -1); + + // Setup YUMA so that it creates emissions: + // Parent and child both set weights + // Parent and child register on root and + // Set root weights + pallet_subtensor::Weights::::insert(netuid, 0, vec![(0, 0xFFFF), (1, 0xFFFF)]); + pallet_subtensor::Weights::::insert(netuid, 1, vec![(0, 0xFFFF), (1, 0xFFFF)]); + assert_ok!(SubtensorModule::do_root_register( + RuntimeOrigin::signed(coldkey), + parent, + )); + assert_ok!(SubtensorModule::do_root_register( + RuntimeOrigin::signed(coldkey), + child, + )); + pallet_subtensor::Weights::::insert(root_id, 0, vec![(0, 0xFFFF), (1, 0xFFFF)]); + pallet_subtensor::Weights::::insert(root_id, 1, vec![(0, 0xFFFF), (1, 0xFFFF)]); + + // Run run_coinbase until PendingHotkeyEmission are populated + while pallet_subtensor::PendingdHotkeyEmission::::get(child) == 0 { + step_block(1); + } + + // Prevent further subnet epochs + pallet_subtensor::Tempo::::set(netuid, u16::MAX); + pallet_subtensor::Tempo::::set(root_id, u16::MAX); + + // Run run_coinbase until PendingHotkeyEmission is drained for both child and parent + step_block((hotkey_tempo * 2) as u16); + + // Verify how emission is split between keys + // - Child stake increased by its child key take only (20% * 50% = 10% of total emission) + // - Parent stake increased by 40% of total emission + // - Nominator stake increased by 50% of total emission + let child_emission = pallet_subtensor::Stake::::get(child, coldkey); + let parent_emission = pallet_subtensor::Stake::::get(parent, coldkey) - stake; + let nominator_emission = pallet_subtensor::Stake::::get(child, nominator) - stake; + let total_emission = child_emission + parent_emission + nominator_emission; + + assert!(is_within_tolerance( + child_emission, + total_emission / 10, + 500 + )); + assert!(is_within_tolerance( + parent_emission, + total_emission / 10 * 4, + 500 + )); + assert!(is_within_tolerance( + nominator_emission, + total_emission / 2, + 500 + )); + }); +} + +/// Test that drain_hotkey_emission sends childkey take fully to the childkey with validator take enabled. +#[test] +fn test_childkey_take_drain_validator_take() { + new_test_ext(1).execute_with(|| { + let coldkey = U256::from(1); + let parent = U256::from(2); + let child = U256::from(3); + let nominator = U256::from(4); + let netuid: u16 = 1; + let root_id: u16 = 0; + let subnet_tempo = 10; + let hotkey_tempo = 20; + let stake = 100_000_000_000; + let proportion: u64 = u64::MAX; + + // Add network, register hotkeys, and setup network parameters + add_network(root_id, subnet_tempo, 0); + add_network(netuid, subnet_tempo, 0); + register_ok_neuron(netuid, child, coldkey, 0); + register_ok_neuron(netuid, parent, coldkey, 1); + SubtensorModule::add_balance_to_coldkey_account( + &coldkey, + stake + ExistentialDeposit::get(), + ); + SubtensorModule::add_balance_to_coldkey_account( + &nominator, + stake + ExistentialDeposit::get(), + ); + SubtensorModule::set_hotkey_emission_tempo(hotkey_tempo); + SubtensorModule::set_weights_set_rate_limit(netuid, 0); + SubtensorModule::set_max_allowed_validators(netuid, 2); + step_block(subnet_tempo); + pallet_subtensor::SubnetOwnerCut::::set(0); + + // Set 20% childkey take + let max_take: u16 = 0xFFFF / 5; + SubtensorModule::set_max_childkey_take(max_take); + assert_ok!(SubtensorModule::set_childkey_take( + RuntimeOrigin::signed(coldkey), + child, + netuid, + max_take + )); + + // Set 20% hotkey take for childkey + SubtensorModule::set_max_delegate_take(max_take); + assert_ok!(SubtensorModule::do_become_delegate( + RuntimeOrigin::signed(coldkey), + child, + max_take + )); + + // Set 20% hotkey take for parent + assert_ok!(SubtensorModule::do_become_delegate( + RuntimeOrigin::signed(coldkey), + parent, + max_take + )); + + // Setup stakes: + // Stake from parent + // Stake from nominator to childkey + // Give 100% of parent stake to childkey + assert_ok!(SubtensorModule::add_stake( + RuntimeOrigin::signed(coldkey), + parent, + stake + )); + assert_ok!(SubtensorModule::add_stake( + RuntimeOrigin::signed(nominator), + child, + stake + )); + assert_ok!(SubtensorModule::do_set_children( + RuntimeOrigin::signed(coldkey), + parent, + netuid, + vec![(proportion, child)] + )); + // Make all stakes viable + pallet_subtensor::StakeDeltaSinceLastEmissionDrain::::set(parent, coldkey, -1); + pallet_subtensor::StakeDeltaSinceLastEmissionDrain::::set(child, nominator, -1); + + // Setup YUMA so that it creates emissions: + // Parent and child both set weights + // Parent and child register on root and + // Set root weights + pallet_subtensor::Weights::::insert(netuid, 0, vec![(0, 0xFFFF), (1, 0xFFFF)]); + pallet_subtensor::Weights::::insert(netuid, 1, vec![(0, 0xFFFF), (1, 0xFFFF)]); + assert_ok!(SubtensorModule::do_root_register( + RuntimeOrigin::signed(coldkey), + parent, + )); + assert_ok!(SubtensorModule::do_root_register( + RuntimeOrigin::signed(coldkey), + child, + )); + pallet_subtensor::Weights::::insert(root_id, 0, vec![(0, 0xFFFF), (1, 0xFFFF)]); + pallet_subtensor::Weights::::insert(root_id, 1, vec![(0, 0xFFFF), (1, 0xFFFF)]); + + // Run run_coinbase until PendingHotkeyEmission are populated + while pallet_subtensor::PendingdHotkeyEmission::::get(child) == 0 { + step_block(1); + } + + // Prevent further subnet epochs + pallet_subtensor::Tempo::::set(netuid, u16::MAX); + pallet_subtensor::Tempo::::set(root_id, u16::MAX); + + // Run run_coinbase until PendingHotkeyEmission is drained for both child and parent + step_block((hotkey_tempo * 2) as u16); + + // Verify how emission is split between keys + // - Child stake increased by its child key take (20% * 50% = 10% of total emission) plus childkey's delegate take (10%) + // - Parent stake increased by 40% of total emission + // - Nominator stake increased by 40% of total emission + let child_emission = pallet_subtensor::Stake::::get(child, coldkey); + let parent_emission = pallet_subtensor::Stake::::get(parent, coldkey) - stake; + let nominator_emission = pallet_subtensor::Stake::::get(child, nominator) - stake; + let total_emission = child_emission + parent_emission + nominator_emission; + + assert!(is_within_tolerance(child_emission, total_emission / 5, 500)); + assert!(is_within_tolerance( + parent_emission, + total_emission / 10 * 4, + 500 + )); + assert!(is_within_tolerance( + nominator_emission, + total_emission / 10 * 4, + 500 + )); + }); +} diff --git a/pallets/subtensor/tests/epoch.rs b/pallets/subtensor/tests/epoch.rs index 9c4bf87cc..df2c95d81 100644 --- a/pallets/subtensor/tests/epoch.rs +++ b/pallets/subtensor/tests/epoch.rs @@ -2857,7 +2857,7 @@ fn test_blocks_since_last_step() { /// * `left` - The first value to compare. /// * `right` - The second value to compare. /// * `epsilon` - The maximum allowed difference between the two values. -fn assert_approx_eq(left: I32F32, right: I32F32, epsilon: I32F32) { +pub fn assert_approx_eq(left: I32F32, right: I32F32, epsilon: I32F32) { if (left - right).abs() > epsilon { panic!( "assertion failed: `(left ≈ right)`\n left: `{:?}`,\n right: `{:?}`,\n epsilon: `{:?}`", diff --git a/pallets/subtensor/tests/staking.rs b/pallets/subtensor/tests/staking.rs index a55db996b..9b45e8b33 100644 --- a/pallets/subtensor/tests/staking.rs +++ b/pallets/subtensor/tests/staking.rs @@ -2409,3 +2409,390 @@ fn test_stake_delta_tracks_adds_and_removes() { ); }); } + +/// Test that drain_hotkey_emission sends mining emission fully to the miner, even +/// if miner is a delegate and someone is delegating. +#[test] +fn test_mining_emission_drain() { + new_test_ext(1).execute_with(|| { + let coldkey = U256::from(1); + let validator = U256::from(2); + let miner = U256::from(3); + let nominator = U256::from(4); + let netuid: u16 = 1; + let root_id: u16 = 0; + let root_tempo = 9; // neet root epoch to happen before subnet tempo + let subnet_tempo = 10; + let hotkey_tempo = 20; + let stake = 100_000_000_000; + let miner_stake = 1_000_000_000; + + // Add network, register hotkeys, and setup network parameters + add_network(root_id, root_tempo, 0); + add_network(netuid, subnet_tempo, 0); + register_ok_neuron(netuid, validator, coldkey, 0); + register_ok_neuron(netuid, miner, coldkey, 1); + SubtensorModule::add_balance_to_coldkey_account( + &coldkey, + 2 * stake + ExistentialDeposit::get(), + ); + SubtensorModule::add_balance_to_coldkey_account( + &nominator, + stake + ExistentialDeposit::get(), + ); + SubtensorModule::set_hotkey_emission_tempo(hotkey_tempo); + SubtensorModule::set_weights_set_rate_limit(netuid, 0); + step_block(subnet_tempo); + pallet_subtensor::SubnetOwnerCut::::set(0); + // All stake is active + pallet_subtensor::ActivityCutoff::::set(netuid, u16::MAX); + // There's only one validator + pallet_subtensor::MaxAllowedUids::::set(netuid, 2); + SubtensorModule::set_max_allowed_validators(netuid, 1); + + // Set zero hotkey take for validator + SubtensorModule::set_min_delegate_take(0); + assert_ok!(SubtensorModule::do_become_delegate( + RuntimeOrigin::signed(coldkey), + validator, + 0 + )); + + // Set zero hotkey take for miner + assert_ok!(SubtensorModule::do_become_delegate( + RuntimeOrigin::signed(coldkey), + miner, + 0 + )); + + // Setup stakes: + // Stake from validator + // Stake from miner + // Stake from nominator to miner + // Give 100% of parent stake to childkey + assert_ok!(SubtensorModule::add_stake( + RuntimeOrigin::signed(coldkey), + validator, + stake + )); + assert_ok!(SubtensorModule::add_stake( + RuntimeOrigin::signed(coldkey), + miner, + miner_stake + )); + assert_ok!(SubtensorModule::add_stake( + RuntimeOrigin::signed(nominator), + miner, + stake + )); + // Make all stakes viable + pallet_subtensor::StakeDeltaSinceLastEmissionDrain::::set(validator, coldkey, -1); + pallet_subtensor::StakeDeltaSinceLastEmissionDrain::::set(miner, nominator, -1); + + // Setup YUMA so that it creates emissions: + // Validator sets weight for miner + // Validator registers on root and + // Sets root weights + // Last weight update is after block at registration + pallet_subtensor::Weights::::insert(netuid, 0, vec![(1, 0xFFFF)]); + assert_ok!(SubtensorModule::do_root_register( + RuntimeOrigin::signed(coldkey), + validator, + )); + pallet_subtensor::Weights::::insert(root_id, 0, vec![(0, 0xFFFF), (1, 0xFFFF)]); + pallet_subtensor::BlockAtRegistration::::set(netuid, 0, 1); + pallet_subtensor::LastUpdate::::set(netuid, vec![2, 2]); + pallet_subtensor::Kappa::::set(netuid, u16::MAX / 5); + + // Run run_coinbase until root epoch is run + while pallet_subtensor::PendingEmission::::get(netuid) == 0 { + step_block(1); + } + + // Prevent further root epochs + pallet_subtensor::Tempo::::set(root_id, u16::MAX); + + // Run run_coinbase until PendingHotkeyEmission are populated + while pallet_subtensor::PendingdHotkeyEmission::::get(miner) == 0 { + step_block(1); + } + + // Prevent further subnet epochs + pallet_subtensor::Tempo::::set(netuid, u16::MAX); + + // Run run_coinbase until PendingHotkeyEmission is drained for both validator and miner + step_block((hotkey_tempo * 2) as u16); + + // Verify how emission is split between keys + // - Validator stake increased by 50% of total emission + // - Miner stake increased by 50% of total emission + // - Nominator gets nothing because he staked to miner + let miner_emission = pallet_subtensor::Stake::::get(miner, coldkey) - miner_stake; + let validator_emission = pallet_subtensor::Stake::::get(validator, coldkey) - stake; + let nominator_emission = pallet_subtensor::Stake::::get(miner, nominator) - stake; + let total_emission = validator_emission + miner_emission + nominator_emission; + + assert_eq!(validator_emission, total_emission / 2); + assert_eq!(miner_emission, total_emission / 2); + assert_eq!(nominator_emission, 0); + }); +} + +/// Test that drain_hotkey_emission sends mining emission fully to the miner, even +/// if miner is a delegate and someone is delegating, and miner gets some validation emissions +#[test] +fn test_mining_emission_drain_with_validation() { + new_test_ext(1).execute_with(|| { + let coldkey = U256::from(1); + let validator_miner1 = U256::from(2); + let validator_miner2 = U256::from(3); + let nominator = U256::from(4); + let netuid: u16 = 1; + let root_id: u16 = 0; + let root_tempo = 9; // neet root epoch to happen before subnet tempo + let subnet_tempo = 10; + let hotkey_tempo = 20; + let stake = 100_000_000_000; + let half_stake = 50_000_000_000; + + // Add network, register hotkeys, and setup network parameters + add_network(root_id, root_tempo, 0); + add_network(netuid, subnet_tempo, 0); + register_ok_neuron(netuid, validator_miner1, coldkey, 0); + register_ok_neuron(netuid, validator_miner2, coldkey, 1); + SubtensorModule::add_balance_to_coldkey_account( + &coldkey, + 2 * stake + ExistentialDeposit::get(), + ); + SubtensorModule::add_balance_to_coldkey_account( + &nominator, + stake + ExistentialDeposit::get(), + ); + SubtensorModule::set_hotkey_emission_tempo(hotkey_tempo); + SubtensorModule::set_weights_set_rate_limit(netuid, 0); + step_block(subnet_tempo); + pallet_subtensor::SubnetOwnerCut::::set(0); + // All stake is active + pallet_subtensor::ActivityCutoff::::set(netuid, u16::MAX); + // There are two validators + pallet_subtensor::MaxAllowedUids::::set(netuid, 2); + SubtensorModule::set_max_allowed_validators(netuid, 2); + + // Set zero hotkey take for validator + SubtensorModule::set_min_delegate_take(0); + assert_ok!(SubtensorModule::do_become_delegate( + RuntimeOrigin::signed(coldkey), + validator_miner1, + 0 + )); + + // Set zero hotkey take for miner + assert_ok!(SubtensorModule::do_become_delegate( + RuntimeOrigin::signed(coldkey), + validator_miner2, + 0 + )); + + // Setup stakes: + // Stake from validator + // Stake from miner + // Stake from nominator to miner + assert_ok!(SubtensorModule::add_stake( + RuntimeOrigin::signed(coldkey), + validator_miner1, + stake + )); + assert_ok!(SubtensorModule::add_stake( + RuntimeOrigin::signed(coldkey), + validator_miner2, + half_stake + )); + assert_ok!(SubtensorModule::add_stake( + RuntimeOrigin::signed(nominator), + validator_miner2, + half_stake + )); + // Make all stakes viable + pallet_subtensor::StakeDeltaSinceLastEmissionDrain::::set( + validator_miner1, + coldkey, + -1, + ); + pallet_subtensor::StakeDeltaSinceLastEmissionDrain::::set( + validator_miner2, + coldkey, + -1, + ); + pallet_subtensor::StakeDeltaSinceLastEmissionDrain::::set( + validator_miner2, + nominator, + -1, + ); + + // Setup YUMA so that it creates emissions: + // Validators set weights for each other + // Validator registers on root and + // Sets root weights + // Last weight update is after block at registration + pallet_subtensor::Weights::::insert(netuid, 0, vec![(0, 0xFFFF), (1, 0xFFFF)]); + pallet_subtensor::Weights::::insert(netuid, 1, vec![(0, 0xFFFF), (1, 0xFFFF)]); + assert_ok!(SubtensorModule::do_root_register( + RuntimeOrigin::signed(coldkey), + validator_miner1, + )); + pallet_subtensor::Weights::::insert(root_id, 0, vec![(0, 0xFFFF), (1, 0xFFFF)]); + pallet_subtensor::BlockAtRegistration::::set(netuid, 0, 1); + pallet_subtensor::BlockAtRegistration::::set(netuid, 1, 1); + pallet_subtensor::LastUpdate::::set(netuid, vec![2, 2]); + pallet_subtensor::Kappa::::set(netuid, u16::MAX / 5); + + // Run run_coinbase until root epoch is run + while pallet_subtensor::PendingEmission::::get(netuid) == 0 { + step_block(1); + } + + // Prevent further root epochs + pallet_subtensor::Tempo::::set(root_id, u16::MAX); + + // Run run_coinbase until PendingHotkeyEmission are populated + while pallet_subtensor::PendingdHotkeyEmission::::get(validator_miner1) == 0 { + step_block(1); + } + + // Prevent further subnet epochs + pallet_subtensor::Tempo::::set(netuid, u16::MAX); + + // Run run_coinbase until PendingHotkeyEmission is drained for both validator and miner + step_block((hotkey_tempo * 2) as u16); + + // Verify how emission is split between keys + // - 50% goes to miners and 50% goes to validators + // - Miner's reward is treated as half miner and half validator + // - Neuron 1 stake is increased by 50% of total emission + // - Neuron 2 stake is increased by 37.5% of total emission (mining portion is intact, validation portion is split 50%) + // - Nominator stake is increased by 12.5% of total emission (validation portion is distributed in 50% proportion) + let validator_miner_emission1 = + pallet_subtensor::Stake::::get(validator_miner1, coldkey) - stake; + let validator_miner_emission2 = + pallet_subtensor::Stake::::get(validator_miner2, coldkey) - half_stake; + let nominator_emission = + pallet_subtensor::Stake::::get(validator_miner2, nominator) - half_stake; + let total_emission = + validator_miner_emission1 + validator_miner_emission2 + nominator_emission; + + assert_eq!(validator_miner_emission1, total_emission / 2); + assert_eq!(validator_miner_emission2, total_emission / 1000 * 375); + assert_eq!(nominator_emission, total_emission / 1000 * 125); + }); +} + +/// Test that drain_hotkey_emission sends mining emission fully to the miners, for the +/// case of one validator, one vali-miner, and one miner +#[test] +fn test_mining_emission_drain_validator_valiminer_miner() { + new_test_ext(1).execute_with(|| { + let coldkey = U256::from(1); + let validator = U256::from(2); + let validator_miner = U256::from(3); + let miner = U256::from(4); + let netuid: u16 = 1; + let root_id: u16 = 0; + let root_tempo = 9; // neet root epoch to happen before subnet tempo + let subnet_tempo = 10; + let hotkey_tempo = 20; + let stake = 100_000_000_000; + + // Add network, register hotkeys, and setup network parameters + add_network(root_id, root_tempo, 0); + add_network(netuid, subnet_tempo, 0); + register_ok_neuron(netuid, validator, coldkey, 0); + register_ok_neuron(netuid, validator_miner, coldkey, 1); + register_ok_neuron(netuid, miner, coldkey, 2); + SubtensorModule::add_balance_to_coldkey_account( + &coldkey, + 3 * stake + ExistentialDeposit::get(), + ); + SubtensorModule::set_hotkey_emission_tempo(hotkey_tempo); + SubtensorModule::set_weights_set_rate_limit(netuid, 0); + step_block(subnet_tempo); + pallet_subtensor::SubnetOwnerCut::::set(0); + // All stake is active + pallet_subtensor::ActivityCutoff::::set(netuid, u16::MAX); + // There are two validators and three neurons + pallet_subtensor::MaxAllowedUids::::set(netuid, 3); + SubtensorModule::set_max_allowed_validators(netuid, 2); + + // Setup stakes: + // Stake from validator + // Stake from valiminer + assert_ok!(SubtensorModule::add_stake( + RuntimeOrigin::signed(coldkey), + validator, + stake + )); + assert_ok!(SubtensorModule::add_stake( + RuntimeOrigin::signed(coldkey), + validator_miner, + stake + )); + // Make all stakes viable + pallet_subtensor::StakeDeltaSinceLastEmissionDrain::::set(validator, coldkey, -1); + pallet_subtensor::StakeDeltaSinceLastEmissionDrain::::set( + validator_miner, + coldkey, + -1, + ); + + // Setup YUMA so that it creates emissions: + // Validator 1 sets weight for valiminer |- to achieve equal incentive for both miners + // Valiminer sets weights for the second miner | + // Validator registers on root and + // Sets root weights + // Last weight update is after block at registration + pallet_subtensor::Weights::::insert(netuid, 0, vec![(1, 0xFFFF)]); + pallet_subtensor::Weights::::insert(netuid, 1, vec![(2, 0xFFFF)]); + assert_ok!(SubtensorModule::do_root_register( + RuntimeOrigin::signed(coldkey), + validator, + )); + pallet_subtensor::Weights::::insert(root_id, 0, vec![(0, 0xFFFF), (1, 0xFFFF)]); + pallet_subtensor::BlockAtRegistration::::set(netuid, 0, 1); + pallet_subtensor::BlockAtRegistration::::set(netuid, 1, 1); + pallet_subtensor::LastUpdate::::set(netuid, vec![2, 2, 2]); + pallet_subtensor::Kappa::::set(netuid, u16::MAX / 5); + + // Run run_coinbase until root epoch is run + while pallet_subtensor::PendingEmission::::get(netuid) == 0 { + step_block(1); + } + + // Prevent further root epochs + pallet_subtensor::Tempo::::set(root_id, u16::MAX); + + // Run run_coinbase until PendingHotkeyEmission are populated + while pallet_subtensor::PendingdHotkeyEmission::::get(validator) == 0 { + step_block(1); + } + + // Prevent further subnet epochs + pallet_subtensor::Tempo::::set(netuid, u16::MAX); + + // Run run_coinbase until PendingHotkeyEmission is drained for both validator and miner + step_block((hotkey_tempo * 2) as u16); + + // Verify how emission is split between keys + // - 50% goes to miners and 50% goes to validators + // - Validator gets 25% because there are two validators + // - Valiminer gets 25% as a validator and 25% as miner + // - Miner gets 25% as miner + let validator_emission = pallet_subtensor::Stake::::get(validator, coldkey) - stake; + let valiminer_emission = + pallet_subtensor::Stake::::get(validator_miner, coldkey) - stake; + let miner_emission = pallet_subtensor::Stake::::get(miner, coldkey); + let total_emission = validator_emission + valiminer_emission + miner_emission; + + assert_eq!(validator_emission, total_emission / 4); + assert_eq!(valiminer_emission, total_emission / 2); + assert_eq!(miner_emission, total_emission / 4); + }); +} diff --git a/runtime/src/lib.rs b/runtime/src/lib.rs index 2455da5d0..7eec8fe15 100644 --- a/runtime/src/lib.rs +++ b/runtime/src/lib.rs @@ -160,7 +160,7 @@ pub const VERSION: RuntimeVersion = RuntimeVersion { // `spec_version`, and `authoring_version` are the same between Wasm and native. // This value is set to 100 to notify Polkadot-JS App (https://polkadot.js.org/apps) to use // the compatible custom types. - spec_version: 208, + spec_version: 209, impl_version: 1, apis: RUNTIME_API_VERSIONS, transaction_version: 1, @@ -1119,18 +1119,34 @@ parameter_types! { const EVM_DECIMALS_FACTOR: u64 = 1_000_000_000_u64; pub struct SubtensorEvmBalanceConverter; + impl BalanceConverter for SubtensorEvmBalanceConverter { + /// Convert from Substrate balance (u64) to EVM balance (U256) fn into_evm_balance(value: U256) -> Option { - U256::from(UniqueSaturatedInto::::unique_saturated_into(value)) + value .checked_mul(U256::from(EVM_DECIMALS_FACTOR)) + .and_then(|evm_value| { + // Ensure the result fits within the maximum U256 value + if evm_value <= U256::MAX { + Some(evm_value) + } else { + None + } + }) } + /// Convert from EVM balance (U256) to Substrate balance (u64) fn into_substrate_balance(value: U256) -> Option { - if value <= U256::from(u64::MAX) { - value.checked_div(U256::from(EVM_DECIMALS_FACTOR)) - } else { - None - } + value + .checked_div(U256::from(EVM_DECIMALS_FACTOR)) + .and_then(|substrate_value| { + // Ensure the result fits within the TAO balance type (u64) + if substrate_value <= U256::from(u64::MAX) { + Some(substrate_value) + } else { + None + } + }) } } @@ -2003,9 +2019,6 @@ impl_runtime_apis! { } } -// #[cfg(test)] -// mod tests { - #[test] fn check_whitelist() { use crate::*; @@ -2028,4 +2041,72 @@ fn check_whitelist() { // System Events assert!(whitelist.contains("26aa394eea5630e07c48ae0c9558cef780d41e5e16056765bc8461851072c9d7")); } -// } + +#[test] +fn test_into_substrate_balance_valid() { + // Valid conversion within u64 range + let evm_balance = U256::from(1_000_000_000_000_000_000u128); // 1 TAO in EVM + let expected_substrate_balance = U256::from(1_000_000_000u128); // 1 TAO in Substrate + + let result = SubtensorEvmBalanceConverter::into_substrate_balance(evm_balance); + assert_eq!(result, Some(expected_substrate_balance)); +} + +#[test] +fn test_into_substrate_balance_large_value() { + // Maximum valid balance for u64 + let evm_balance = U256::from(u64::MAX) * U256::from(EVM_DECIMALS_FACTOR); // Max u64 TAO in EVM + let expected_substrate_balance = U256::from(u64::MAX); + + let result = SubtensorEvmBalanceConverter::into_substrate_balance(evm_balance); + assert_eq!(result, Some(expected_substrate_balance)); +} + +#[test] +fn test_into_substrate_balance_exceeds_u64() { + // EVM balance that exceeds u64 after conversion + let evm_balance = (U256::from(u64::MAX) + U256::from(1)) * U256::from(EVM_DECIMALS_FACTOR); + + let result = SubtensorEvmBalanceConverter::into_substrate_balance(evm_balance); + assert_eq!(result, None); // Exceeds u64, should return None +} + +#[test] +fn test_into_substrate_balance_precision_loss() { + // EVM balance with precision loss + let evm_balance = U256::from(1_000_000_000_123_456_789u128); // 1 TAO + extra precision in EVM + let expected_substrate_balance = U256::from(1_000_000_000u128); // Truncated to 1 TAO in Substrate + + let result = SubtensorEvmBalanceConverter::into_substrate_balance(evm_balance); + assert_eq!(result, Some(expected_substrate_balance)); +} + +#[test] +fn test_into_substrate_balance_zero_value() { + // Zero balance should convert to zero + let evm_balance = U256::from(0); + let expected_substrate_balance = U256::from(0); + + let result = SubtensorEvmBalanceConverter::into_substrate_balance(evm_balance); + assert_eq!(result, Some(expected_substrate_balance)); +} + +#[test] +fn test_into_evm_balance_valid() { + // Valid conversion from Substrate to EVM + let substrate_balance = U256::from(1_000_000_000u128); // 1 TAO in Substrate + let expected_evm_balance = U256::from(1_000_000_000_000_000_000u128); // 1 TAO in EVM + + let result = SubtensorEvmBalanceConverter::into_evm_balance(substrate_balance); + assert_eq!(result, Some(expected_evm_balance)); +} + +#[test] +fn test_into_evm_balance_overflow() { + // Substrate balance larger than u64::MAX but valid within U256 + let substrate_balance = U256::from(u64::MAX) + U256::from(1); // Large balance + let expected_evm_balance = substrate_balance * U256::from(EVM_DECIMALS_FACTOR); + + let result = SubtensorEvmBalanceConverter::into_evm_balance(substrate_balance); + assert_eq!(result, Some(expected_evm_balance)); // Should return the scaled value +} diff --git a/runtime/src/precompiles/balance_transfer.rs b/runtime/src/precompiles/balance_transfer.rs index 6ae554fa4..99d911b02 100644 --- a/runtime/src/precompiles/balance_transfer.rs +++ b/runtime/src/precompiles/balance_transfer.rs @@ -25,21 +25,30 @@ impl BalanceTransferPrecompile { // Forward all received value to the destination address let amount: U256 = handle.context().apparent_value; - // This is hardcoded hashed address mapping of - // 0x0000000000000000000000000000000000000800 to ss58 public key - // i.e. the contract sends funds it received to the destination address - // from the method parameter - let address_bytes_src: [u8; 32] = [ + // Use BalanceConverter to convert EVM amount to Substrate balance + let amount_sub = + ::BalanceConverter::into_substrate_balance(amount) + .ok_or(ExitError::OutOfFund)?; + + if amount_sub.is_zero() { + return Ok(PrecompileOutput { + exit_status: ExitSucceed::Returned, + output: vec![], + }); + } + + // This is a hardcoded hashed address mapping of + // 0x0000000000000000000000000000000000000800 to an ss58 public key + // i.e., the contract sends funds it received to the destination address + // from the method parameter. + const ADDRESS_BYTES_SRC: [u8; 32] = [ 0x07, 0xec, 0x71, 0x2a, 0x5d, 0x38, 0x43, 0x4d, 0xdd, 0x03, 0x3f, 0x8f, 0x02, 0x4e, 0xcd, 0xfc, 0x4b, 0xb5, 0x95, 0x1c, 0x13, 0xc3, 0x08, 0x5c, 0x39, 0x9c, 0x8a, 0x5f, 0x62, 0x93, 0x70, 0x5d, ]; let address_bytes_dst: &[u8] = get_slice(txdata, 4, 36)?; - let account_id_src = bytes_to_account_id(&address_bytes_src)?; + let account_id_src = bytes_to_account_id(&ADDRESS_BYTES_SRC)?; let account_id_dst = bytes_to_account_id(address_bytes_dst)?; - let amount_sub = - ::BalanceConverter::into_substrate_balance(amount) - .ok_or(ExitError::OutOfFund)?; let call = RuntimeCall::Balances(pallet_balances::Call::::transfer_allow_death { @@ -47,6 +56,7 @@ impl BalanceTransferPrecompile { value: amount_sub.unique_saturated_into(), }); + // Dispatch the call let result = call.dispatch(RawOrigin::Signed(account_id_src).into()); if result.is_err() { return Err(PrecompileFailure::Error { diff --git a/support/linting/src/forbid_as_primitive.rs b/support/linting/src/forbid_as_primitive.rs index b60cf0a49..2af8e0132 100644 --- a/support/linting/src/forbid_as_primitive.rs +++ b/support/linting/src/forbid_as_primitive.rs @@ -45,10 +45,11 @@ fn is_as_primitive(ident: &Ident) -> bool { #[cfg(test)] mod tests { use super::*; + use quote::quote; - fn lint(input: &str) -> Result { - let expr: ExprMethodCall = syn::parse_str(input).expect("should only use on a method call"); + fn lint(input: proc_macro2::TokenStream) -> Result { let mut visitor = AsPrimitiveVisitor::default(); + let expr: ExprMethodCall = syn::parse2(input).expect("should be a valid method call"); visitor.visit_expr_method_call(&expr); if !visitor.errors.is_empty() { return Err(visitor.errors); @@ -58,21 +59,21 @@ mod tests { #[test] fn test_as_primitives() { - let input = r#"x.as_u32()"#; + let input = quote! {x.as_u32() }; assert!(lint(input).is_err()); - let input = r#"x.as_u64()"#; + let input = quote! {x.as_u64() }; assert!(lint(input).is_err()); - let input = r#"x.as_u128()"#; + let input = quote! {x.as_u128() }; assert!(lint(input).is_err()); - let input = r#"x.as_usize()"#; + let input = quote! {x.as_usize() }; assert!(lint(input).is_err()); } #[test] fn test_non_as_primitives() { - let input = r#"x.as_ref()"#; + let input = quote! {x.as_ref() }; assert!(lint(input).is_ok()); - let input = r#"x.as_slice()"#; + let input = quote! {x.as_slice() }; assert!(lint(input).is_ok()); } } diff --git a/support/linting/src/forbid_keys_remove.rs b/support/linting/src/forbid_keys_remove.rs new file mode 100644 index 000000000..e7e5011b1 --- /dev/null +++ b/support/linting/src/forbid_keys_remove.rs @@ -0,0 +1,119 @@ +use super::*; +use syn::{ + punctuated::Punctuated, spanned::Spanned, token::Comma, visit::Visit, Expr, ExprCall, ExprPath, + File, Path, +}; + +pub struct ForbidKeysRemoveCall; + +impl Lint for ForbidKeysRemoveCall { + fn lint(source: &File) -> Result { + let mut visitor = KeysRemoveVisitor::default(); + visitor.visit_file(source); + + if visitor.errors.is_empty() { + Ok(()) + } else { + Err(visitor.errors) + } + } +} + +#[derive(Default)] +struct KeysRemoveVisitor { + errors: Vec, +} + +impl<'ast> Visit<'ast> for KeysRemoveVisitor { + fn visit_expr_call(&mut self, node: &'ast syn::ExprCall) { + let ExprCall { + func, args, attrs, .. + } = node; + + if is_keys_remove_call(func, args) && !is_allowed(attrs) { + let msg = "Keys::::remove()` is banned to prevent accidentally breaking \ + the neuron sequence. If you need to replace neurons, try `SubtensorModule::replace_neuron()`"; + self.errors.push(syn::Error::new(node.func.span(), msg)); + } + } +} + +fn is_keys_remove_call(func: &Expr, args: &Punctuated) -> bool { + let Expr::Path(ExprPath { + path: Path { segments: func, .. }, + .. + }) = func + else { + return false; + }; + + func.len() == 2 + && args.len() == 2 + && func[0].ident == "Keys" + && !func[0].arguments.is_none() + && func[1].ident == "remove" + && func[1].arguments.is_none() +} + +#[cfg(test)] +mod tests { + use super::*; + use quote::quote; + + fn lint(input: proc_macro2::TokenStream) -> Result { + let mut visitor = KeysRemoveVisitor::default(); + let expr: syn::ExprCall = syn::parse2(input).expect("should be a valid function call"); + visitor.visit_expr_call(&expr); + + if visitor.errors.is_empty() { + Ok(()) + } else { + Err(visitor.errors) + } + } + + #[test] + fn test_keys_remove_forbidden() { + let input = quote! { Keys::::remove(netuid, uid_to_replace) }; + assert!(lint(input).is_err()); + let input = quote! { Keys::::remove(netuid, uid_to_replace) }; + assert!(lint(input).is_err()); + let input = quote! { Keys::::remove(1, "2".parse().unwrap(),) }; + assert!(lint(input).is_err()); + } + + #[test] + fn test_non_keys_remove_not_forbidden() { + let input = quote! { remove(netuid, uid_to_replace) }; + assert!(lint(input).is_ok()); + let input = quote! { Keys::remove(netuid, uid_to_replace) }; + assert!(lint(input).is_ok()); + let input = quote! { Keys::::remove::(netuid, uid_to_replace) }; + assert!(lint(input).is_ok()); + let input = quote! { Keys::::remove(netuid, uid_to_replace, third_wheel) }; + assert!(lint(input).is_ok()); + let input = quote! { ParentKeys::remove(netuid, uid_to_replace) }; + assert!(lint(input).is_ok()); + let input = quote! { ChildKeys::::remove(netuid, uid_to_replace) }; + assert!(lint(input).is_ok()); + } + + #[test] + fn test_keys_remove_allowed() { + let input = quote! { + #[allow(unknown_lints)] + Keys::::remove(netuid, uid_to_replace) + }; + assert!(lint(input).is_ok()); + let input = quote! { + #[allow(unknown_lints)] + Keys::::remove(netuid, uid_to_replace) + }; + assert!(lint(input).is_ok()); + let input = quote! { + #[allow(unknown_lints)] + Keys::::remove(1, "2".parse().unwrap(),) + }; + assert!(lint(input).is_ok()); + } +} diff --git a/support/linting/src/lib.rs b/support/linting/src/lib.rs index 7aaf471c7..a65466e6a 100644 --- a/support/linting/src/lib.rs +++ b/support/linting/src/lib.rs @@ -2,9 +2,11 @@ pub mod lint; pub use lint::*; mod forbid_as_primitive; +mod forbid_keys_remove; mod pallet_index; mod require_freeze_struct; pub use forbid_as_primitive::ForbidAsPrimitiveConversion; +pub use forbid_keys_remove::ForbidKeysRemoveCall; pub use pallet_index::RequireExplicitPalletIndex; pub use require_freeze_struct::RequireFreezeStruct; diff --git a/support/linting/src/lint.rs b/support/linting/src/lint.rs index 3c099d40c..fdf35ae7c 100644 --- a/support/linting/src/lint.rs +++ b/support/linting/src/lint.rs @@ -1,4 +1,5 @@ -use syn::File; +use proc_macro2::TokenTree; +use syn::{Attribute, File, Meta, MetaList, Path}; pub type Result = core::result::Result<(), Vec>; @@ -11,3 +12,29 @@ pub trait Lint: Send + Sync { /// Lints the given Rust source file, returning a compile error if any issues are found. fn lint(source: &File) -> Result; } + +pub fn is_allowed(attibutes: &[Attribute]) -> bool { + attibutes.iter().any(|attribute| { + let Attribute { + meta: + Meta::List(MetaList { + path: Path { segments: attr, .. }, + tokens: attr_args, + .. + }), + .. + } = attribute + else { + return false; + }; + + attr.len() == 1 + && attr[0].ident == "allow" + && attr_args.clone().into_iter().any(|arg| { + let TokenTree::Ident(ref id) = arg else { + return false; + }; + id == "unknown_lints" + }) + }) +}