Subnet Node

A subnet node is a staked and participating validator node in a subnet that is registered on-chain.

Each subnet node has a unique hotkey, peer ID, and bootstrap peer ID.

Registering

A subnet node registers itself and transfers at least the minimum required balance as a stake. This registration is used as the subnets' proof-of-stake.

Call register_subnet_node to register

pub fn register_subnet_node(
    origin: OriginFor<T>, 
    subnet_id: u32, 
    hotkey: T::AccountId,
    peer_id: PeerId, 
    bootstrap_peer_id: PeerId,
    delegate_reward_rate: u128,
    stake_to_be_added: u128,
    a: Option<BoundedVec<u8, DefaultSubnetNodeUniqueParamLimit>>,
    b: Option<BoundedVec<u8, DefaultSubnetNodeUniqueParamLimit>>,
    c: Option<BoundedVec<u8, DefaultSubnetNodeUniqueParamLimit>>,
)

Registration Inputs

subnet_id: The subnet ID to register the node to.

hotkey: The hotkey of the node. This key is used to run non-fund-based extrinsics, such as consensus (validating and attesting).

peer_id: The peer ID of the node. This is used for signature validation within the subnet, proof-of-stake, and more.

bootstrap_peer_id: The bootstrap peer ID is the bootnode of the subnet node, used for the same purposes as the peer ID, but only expected to be a public-facing bootnode.

delegate_reward_rate: The rate of rewards given to users who stake to the node.

stake_to_be_added: The amount of stake to be added. The blockchains has a minimum required of 100 TENSOR, but subnets may require more (check subnet documentation for required proof-of-stake balance).

Classification

(See Classifications)

If the subnet is in its registration phase and the coldkey that registers is an initial coldkey (a user must be an initial_coldkey to be able to register a subnet node while a subnet is in registration), it will be automatically classified as a Validator node. If the subnet is activated, it will be classified as a registered node.

Each subnet is assumed to be a decentralized environment; thus, each subnet is treated as such. Therefore, any nodes that are whitelisted to be initial nodes in the subnet are the ones to be trusted to kick off consensus for that subnet. Otherwise, if a subnet is already activated, each newly registered node starts at the beginning of the classifications phase.

Registration Phase & Queue, and Start Epoch

Subnets are decentralized, and the model accounts for this. Similar to blockchains, nodes must enter a queue before they can begin to be involved in the subnet's consensus. Each node is dripped in based on the subnet's unique parameters. This is a defense tactic for many attack scenarios, including denial of service, consensus finality, sybil attacks, 33% and 66% attacks, etc.

Each registered node must stay in the subnets' queue period equated alongside the subnets' churn limit, which are both unique to each subnet.

On registration, the node will be given a start epoch which the node must activate itself on that specific epoch, up to the subnet's grace period.

Unique Hotkeys

Each hotkey in the network must be unique. On registration, the hotkey used will be validated to have never been used on that coldkey before. Therefore, a hotkey previously used in any subnet will not work if used during registration again.

Inserted & Updated Storage

TotalSubnetNodeUids::<T>::mutate(subnet_id, |n: &mut u32| *n += 1);
let current_uid = TotalSubnetNodeUids::<T>::get(subnet_id);

HotkeySubnetNodeId::<T>::insert(subnet_id, &hotkey, current_uid);

// Insert subnet node ID -> hotkey
SubnetNodeIdHotkey::<T>::insert(subnet_id, current_uid, &hotkey);

// Insert hotkey -> coldkey
HotkeyOwner::<T>::insert(&hotkey, &coldkey);

// Insert coldkey -> hotkeys
hotkeys.insert(hotkey.clone());
ColdkeyHotkeys::<T>::insert(&coldkey, hotkeys);

// Insert subnet peer and bootstrap peer to keep peer_ids unique within subnets
PeerIdSubnetNode::<T>::insert(subnet_id, &peer_id, current_uid);
BootstrapPeerIdSubnetNode::<T>::insert(subnet_id, &bootstrap_peer_id, current_uid);

let classification: SubnetNodeClassification = SubnetNodeClassification {
	class: SubnetNodeClass::Registered,
	start_epoch: start_epoch,
};

let subnet_node: SubnetNode<T::AccountId> = SubnetNode {
	id: current_uid,
	hotkey: hotkey.clone(),
	peer_id: peer_id.clone(),
	bootstrap_peer_id: bootstrap_peer_id.clone(),
	classification: classification,
	delegate_reward_rate: delegate_reward_rate,
	last_delegate_reward_rate_update: last_delegate_reward_rate_update,
	a: a,
	b: b,
	c: c,
};


// Insert RegisteredSubnetNodesData
RegisteredSubnetNodesData::<T>::insert(subnet_id, current_uid, subnet_node);

Activating

Subnet nodes can activate themselves after the queue period, up to the grace epochs. Nodes cannot activate themselves before or after this period. If a node fails to activate itself within the allowed timeframe, the node must remove itself to reregister again.

Call activate_subnet_node to activate

pub fn activate_subnet_node(
    origin: OriginFor<T>, 
    subnet_id: u32, 
    subnet_node_id: u32,
)

Inserted & Updated Storage

// Remove RegisteredSubnetNodesData
let mut subnet_node = RegisteredSubnetNodesData::<T>::take(subnet_id, subnet_node_id);

// Insert into active nodes
SubnetNodesData::<T>::insert(subnet_id, subnet_node.id, subnet_node);

// Increase active nodes count
TotalSubnetNodes::<T>::mutate(subnet_id, |n: &mut u32| *n += 1);

Removal

Nodes can remove themselves from a subnet on-chain as long as they are not the current epoch's validator; otherwise, it will remove them from the current epoch's attestation data, if it exists.

Call remove_subnet_node to remove

pub fn remove_subnet_node(
    origin: OriginFor<T>, 
    subnet_id: u32, 
    subnet_node_id: u32,
)

Inserted & Updated Storage

...
// Remove node from attestations
...
PeerIdSubnetNode::<T>::remove(subnet_id, &peer_id);
BootstrapPeerIdSubnetNode::<T>::remove(subnet_id, subnet_node.bootstrap_peer_id);
HotkeySubnetNodeId::<T>::remove(subnet_id, &hotkey);
SubnetNodeIdHotkey::<T>::remove(subnet_id, subnet_node_id);

// Update total subnet peers by subtracting 1
TotalSubnetNodes::<T>::mutate(subnet_id, |n: &mut u32| n.saturating_dec());

// Reset sequential absent subnet node count
SubnetNodePenalties::<T>::remove(subnet_id, subnet_node_id);

TotalActiveNodes::<T>::mutate(|n: &mut u32| n.saturating_dec());

Deactivating

Nodes can temporarily deactivate themselves up to the MaxDeactivationEpochs. Only nodes that are classified as Validators can deactivate; otherwise, the node must remove itself. Deactivations must last at least one epoch.

pub fn deactivate_subnet_node(
    origin: OriginFor<T>, 
    subnet_id: u32, 
    subnet_node_id: u32,
)

Inserted & Updated Storage

...
    SubnetNodesData::<T>::take(subnet_id, subnet_node_id)
...

DeactivatedSubnetNodesData::<T>::insert(subnet_id, subnet_node.id, subnet_node);

TotalSubnetNodes::<T>::mutate(subnet_id, |n: &mut u32| *n -= 1);

Reactivating

To reactivate from deactivation, the subnet node must call reactivate_subnet_node up to the MaxDeactivationEpochs from the deactivation epoch plus one. Once reactivated, the subnet node will begin to be involved in consensus on the following epoch as a Validator classified node.

If a deactivated node doesn't reactivate by the MaxDeactivationEpochs from the epoch they deactivated, it won't be able to reactivate and must remove itself from the blockchain and reregister.

pub fn reactivate_subnet_node(
    origin: OriginFor<T>, 
    subnet_id: u32, 
    subnet_node_id: u32,
)

Inserted & Updated Storage

let mut subnet_node = DeactivatedSubnetNodesData::<T>::take(subnet_id, subnet_node_id);

...

subnet_node.classification.class = SubnetNodeClass::Validator;
subnet_node.classification.start_epoch = epoch + 1;

...

// --- Enter node into the Queue class
SubnetNodesData::<T>::insert(subnet_id, subnet_node.id, subnet_node);

// Increase total subnet nodes
TotalSubnetNodes::<T>::mutate(subnet_id, |n: &mut u32| *n += 1);

Note

There are cleanup functions for registered and deactivated nodes that fail to activate or reactivate that anyone can call.


Updating

Subnet Node Delegate Stake Rate

Users can stake to a subnet node and receive a portion of the node's incentives based on its delegate stake rate.

This rate can be decreased by 1% per 24 hours, and can be increased with no limitations.

Call update_delegate_stake_rate to update the delegate stake rate

pub fn update_delegate_reward_rate(
    origin: OriginFor<T>, 
    subnet_id: u32,
    subnet_node_id: u32,
    new_delegate_reward_rate: u128
)

Last updated