Skip to content

devnet deploy 12-11-2024 #1088

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 24 commits into from
Dec 11, 2024
Merged
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
326 changes: 184 additions & 142 deletions Cargo.lock

Large diffs are not rendered by default.

160 changes: 107 additions & 53 deletions pallets/drand/src/lib.rs
Original file line number Diff line number Diff line change
@@ -37,7 +37,6 @@
pub use pallet::*;

extern crate alloc;
use crate::alloc::string::ToString;

use alloc::{format, string::String, vec, vec::Vec};
use codec::Encode;
@@ -53,7 +52,6 @@ use scale_info::prelude::cmp;
use sha2::{Digest, Sha256};
use sp_core::blake2_256;
use sp_runtime::{
offchain::{http, Duration},
traits::{Hash, One},
transaction_validity::{InvalidTransaction, TransactionValidity, ValidTransaction},
KeyTypeId, Saturating,
@@ -79,7 +77,14 @@ pub mod weights;
pub use weights::*;

/// the main drand api endpoint
pub const API_ENDPOINT: &str = "https://drand.cloudflare.com";
const ENDPOINTS: [&str; 5] = [
"https://api.drand.sh",
"https://api2.drand.sh",
"https://api3.drand.sh",
"https://drand.cloudflare.com",
"https://api.drand.secureweb3.com:6875",
];

/// the drand quicknet chain hash
/// quicknet uses 'Tiny' BLS381, with small 48-byte sigs in G1 and 96-byte pubkeys in G2
pub const QUICKNET_CHAIN_HASH: &str =
@@ -390,15 +395,8 @@ impl<T: Config> Pallet<T> {
}

let mut last_stored_round = LastStoredRound::<T>::get();
let latest_pulse_body = Self::fetch_drand_latest().map_err(|_| "Failed to query drand")?;
let latest_unbounded_pulse: DrandResponseBody = serde_json::from_str(&latest_pulse_body)
.map_err(|_| {
log::warn!(
"Drand: Response that failed to deserialize: {}",
latest_pulse_body
);
"Drand: Failed to serialize response body to pulse"
})?;
let latest_unbounded_pulse =
Self::fetch_drand_latest().map_err(|_| "Failed to query drand")?;
let latest_pulse = latest_unbounded_pulse
.try_into_pulse()
.map_err(|_| "Drand: Received pulse contains invalid data")?;
@@ -420,17 +418,8 @@ impl<T: Config> Pallet<T> {
for round in (last_stored_round.saturating_add(1))
..=(last_stored_round.saturating_add(rounds_to_fetch))
{
let pulse_body = Self::fetch_drand_by_round(round)
let unbounded_pulse = Self::fetch_drand_by_round(round)
.map_err(|_| "Drand: Failed to query drand for round")?;
let unbounded_pulse: DrandResponseBody = serde_json::from_str(&pulse_body)
.map_err(|_| {
log::warn!(
"Drand: Response that failed to deserialize for round {}: {}",
round,
pulse_body
);
"Drand: Failed to serialize response body to pulse"
})?;
let pulse = unbounded_pulse
.try_into_pulse()
.map_err(|_| "Drand: Received pulse contains invalid data")?;
@@ -470,42 +459,107 @@ impl<T: Config> Pallet<T> {
Ok(())
}

/// Query the endpoint `{api}/{chainHash}/info` to receive information about the drand chain
/// Valid response bodies are deserialized into `BeaconInfoResponse`
fn fetch_drand_by_round(round: RoundNumber) -> Result<String, http::Error> {
let uri: &str = &format!("{}/{}/public/{}", API_ENDPOINT, CHAIN_HASH, round);
Self::fetch(uri)
fn fetch_drand_by_round(round: RoundNumber) -> Result<DrandResponseBody, &'static str> {
let relative_path = format!("/{}/public/{}", CHAIN_HASH, round);
Self::fetch_and_decode_from_any_endpoint(&relative_path)
}
fn fetch_drand_latest() -> Result<String, http::Error> {
let uri: &str = &format!("{}/{}/public/latest", API_ENDPOINT, CHAIN_HASH);
Self::fetch(uri)

fn fetch_drand_latest() -> Result<DrandResponseBody, &'static str> {
let relative_path = format!("/{}/public/latest", CHAIN_HASH);
Self::fetch_and_decode_from_any_endpoint(&relative_path)
}

/// Fetch a remote URL and return the body of the response as a string.
fn fetch(uri: &str) -> Result<String, http::Error> {
let deadline =
sp_io::offchain::timestamp().add(Duration::from_millis(T::HttpFetchTimeout::get()));
let request = http::Request::get(uri);
let pending = request.deadline(deadline).send().map_err(|_| {
log::warn!("Drand: HTTP IO Error");
http::Error::IoError
})?;
let response = pending.try_wait(deadline).map_err(|_| {
log::warn!("Drand: HTTP Deadline Reached");
http::Error::DeadlineReached
})??;

if response.code != 200 {
log::warn!("Drand: Unexpected status code: {}", response.code);
return Err(http::Error::Unknown);
/// Try to fetch from multiple endpoints simultaneously and return the first successfully decoded JSON response.
fn fetch_and_decode_from_any_endpoint(
relative_path: &str,
) -> Result<DrandResponseBody, &'static str> {
let uris: Vec<String> = ENDPOINTS
.iter()
.map(|e| format!("{}{}", e, relative_path))
.collect();
let deadline = sp_io::offchain::timestamp().add(
sp_runtime::offchain::Duration::from_millis(T::HttpFetchTimeout::get()),
);

let mut pending_requests: Vec<(String, sp_runtime::offchain::http::PendingRequest)> =
vec![];

// Try sending requests to all endpoints.
for uri in &uris {
let request = sp_runtime::offchain::http::Request::get(uri);
match request.deadline(deadline).send() {
Ok(pending_req) => {
pending_requests.push((uri.clone(), pending_req));
}
Err(_) => {
log::warn!("Drand: HTTP IO Error on endpoint {}", uri);
}
}
}

if pending_requests.is_empty() {
log::warn!("Drand: No endpoints could be queried");
return Err("Drand: No endpoints could be queried");
}

loop {
let now = sp_io::offchain::timestamp();
if now > deadline {
// We've passed our deadline without getting a valid response.
log::warn!("Drand: HTTP Deadline Reached");
break;
}

let mut still_pending = false;
let mut next_iteration_requests = Vec::new();

for (uri, request) in pending_requests.drain(..) {
match request.try_wait(Some(deadline)) {
Ok(Ok(response)) => {
if response.code != 200 {
log::warn!(
"Drand: Unexpected status code: {} from {}",
response.code,
uri
);
continue;
}

let body = response.body().collect::<Vec<u8>>();
match serde_json::from_slice::<DrandResponseBody>(&body) {
Ok(decoded) => {
return Ok(decoded);
}
Err(e) => {
log::warn!(
"Drand: JSON decode error from {}: {}. Response body: {}",
uri,
e,
String::from_utf8_lossy(&body)
);
}
}
}
Ok(Err(e)) => {
log::warn!("Drand: HTTP error from {}: {:?}", uri, e);
}
Err(pending_req) => {
still_pending = true;
next_iteration_requests.push((uri, pending_req));
}
}
}

pending_requests = next_iteration_requests;

if !still_pending {
break;
}
}
let body = response.body().collect::<Vec<u8>>();
let body_str = alloc::str::from_utf8(&body).map_err(|_| {
log::warn!("Drand: No UTF8 body");
http::Error::Unknown
})?;

Ok(body_str.to_string())
// If we reached here, no valid response was obtained from any endpoint.
log::warn!("Drand: No valid response from any endpoint");
Err("Drand: No valid response from any endpoint")
}

/// get the randomness at a specific block height
202 changes: 167 additions & 35 deletions pallets/drand/src/tests.rs
Original file line number Diff line number Diff line change
@@ -16,7 +16,7 @@

use crate::{
mock::*, BeaconConfig, BeaconConfigurationPayload, BeaconInfoResponse, Call, DrandResponseBody,
Error, Pulse, Pulses, PulsesPayload, RoundNumber,
Error, Pulse, Pulses, PulsesPayload, ENDPOINTS, QUICKNET_CHAIN_HASH,
};
use codec::Encode;
use frame_support::{
@@ -38,6 +38,7 @@ pub const ROUND_NUMBER: u64 = 1000;
// Quicknet parameters
pub const DRAND_PULSE: &str = "{\"round\":1000,\"randomness\":\"fe290beca10872ef2fb164d2aa4442de4566183ec51c56ff3cd603d930e54fdd\",\"signature\":\"b44679b9a59af2ec876b1a6b1ad52ea9b1615fc3982b19576350f93447cb1125e342b73a8dd2bacbe47e4b6b63ed5e39\"}";
pub const DRAND_INFO_RESPONSE: &str = "{\"public_key\":\"83cf0f2896adee7eb8b5f01fcad3912212c437e0073e911fb90022d3e760183c8c4b450b6a0a6c3ac6a5776a2d1064510d1fec758c921cc22b0e17e63aaf4bcb5ed66304de9cf809bd274ca73bab4af5a6e9c76a4bc09e76eae8991ef5ece45a\",\"period\":3,\"genesis_time\":1692803367,\"hash\":\"52db9ba70e0cc0f6eaf7803dd07447a1f5477735fd3f661792ba94600c84e971\",\"groupHash\":\"f477d5c89f21a17c863a7f937c6a6d15859414d2be09cd448d4279af331c5d3e\",\"schemeID\":\"bls-unchained-g1-rfc9380\",\"metadata\":{\"beaconID\":\"quicknet\"}}";
const INVALID_JSON: &str = r#"{"round":1000,"randomness":"not base64??","signature":}"#;

#[test]
fn it_can_submit_valid_pulse_when_beacon_config_exists() {
@@ -342,53 +343,45 @@ fn test_not_validate_unsigned_write_pulse_with_no_payload_signature() {
}

#[test]
#[ignore]
fn test_validate_unsigned_write_pulse_by_non_authority() {
// TODO: https://github.com/ideal-lab5/pallet-drand/issues/3
todo!(
"the transaction should be validated even if the signer of the payload is not an authority"
);
}
fn can_execute_and_handle_valid_http_responses() {
use serde_json;

#[test]
#[ignore]
fn test_not_validate_unsigned_set_beacon_config_by_non_authority() {
// TODO: https://github.com/ideal-lab5/pallet-drand/issues/3
todo!(
"the transaction should not be validated if the signer of the payload is not an authority"
);
}
let expected_pulse: DrandResponseBody = serde_json::from_str(DRAND_PULSE).unwrap();

#[test]
fn can_execute_and_handle_valid_http_responses() {
let (offchain, state) = TestOffchainExt::new();
let mut t = sp_io::TestExternalities::default();
t.register_extension(OffchainWorkerExt::new(offchain));

{
let mut state = state.write();
state.expect_request(PendingRequest {
method: "GET".into(),
uri: "https://drand.cloudflare.com/52db9ba70e0cc0f6eaf7803dd07447a1f5477735fd3f661792ba94600c84e971/public/1".into(),
response: Some(DRAND_PULSE.as_bytes().to_vec()),
sent: true,
..Default::default()
});
state.expect_request(PendingRequest {
method: "GET".into(),
uri: "https://drand.cloudflare.com/52db9ba70e0cc0f6eaf7803dd07447a1f5477735fd3f661792ba94600c84e971/public/latest".into(),
response: Some(DRAND_PULSE.as_bytes().to_vec()),
sent: true,
..Default::default()
});

for endpoint in ENDPOINTS.iter() {
state.expect_request(PendingRequest {
method: "GET".into(),
uri: format!("{}/{}/public/1000", endpoint, QUICKNET_CHAIN_HASH),
response: Some(DRAND_PULSE.as_bytes().to_vec()),
sent: true,
..Default::default()
});
}

for endpoint in ENDPOINTS.iter() {
state.expect_request(PendingRequest {
method: "GET".into(),
uri: format!("{}/{}/public/latest", endpoint, QUICKNET_CHAIN_HASH),
response: Some(DRAND_PULSE.as_bytes().to_vec()),
sent: true,
..Default::default()
});
}
}

t.execute_with(|| {
let actual_specific = Drand::fetch_drand_by_round(RoundNumber::from(1u64)).unwrap();
assert_eq!(actual_specific, DRAND_PULSE);
let actual_specific = Drand::fetch_drand_by_round(1000u64).unwrap();
assert_eq!(actual_specific, expected_pulse);

let actual_pulse = Drand::fetch_drand_latest().unwrap();
assert_eq!(actual_pulse, DRAND_PULSE);
assert_eq!(actual_pulse, expected_pulse);
});
}

@@ -417,3 +410,142 @@ fn validate_unsigned_rejects_future_block_number() {
assert_noop!(validity, InvalidTransaction::Future);
});
}

#[test]
fn test_all_endpoints_fail() {
let (offchain, state) = TestOffchainExt::new();
let mut t = sp_io::TestExternalities::default();
t.register_extension(OffchainWorkerExt::new(offchain));

{
let mut state = state.write();
let endpoints = ENDPOINTS;

for endpoint in endpoints.iter() {
state.expect_request(PendingRequest {
method: "GET".into(),
uri: format!("{}/{}/public/1000", endpoint, QUICKNET_CHAIN_HASH),
response: Some(INVALID_JSON.as_bytes().to_vec()),
sent: true,
..Default::default()
});
}
}

t.execute_with(|| {
let result = Drand::fetch_drand_by_round(1000u64);
assert!(
result.is_err(),
"All endpoints should fail due to invalid JSON responses"
);
});
}

#[test]
fn test_eventual_success() {
let expected_pulse: DrandResponseBody = serde_json::from_str(DRAND_PULSE).unwrap();

let (offchain, state) = TestOffchainExt::new();
let mut t = sp_io::TestExternalities::default();
t.register_extension(OffchainWorkerExt::new(offchain));

{
let mut state = state.write();
let endpoints = ENDPOINTS;

// We'll make all endpoints except the last return invalid JSON.
// Since no meta is provided, these are "200 OK" but invalid JSON, causing decode failures.
// The last endpoint returns the valid DRAND_PULSE JSON, leading to success.

// Endpoint 0: Invalid JSON (decode fail)
state.expect_request(PendingRequest {
method: "GET".into(),
uri: format!("{}/{}/public/1000", endpoints[0], QUICKNET_CHAIN_HASH),
response: Some(INVALID_JSON.as_bytes().to_vec()),
sent: true,
..Default::default()
});

// Endpoint 1: Invalid JSON
state.expect_request(PendingRequest {
method: "GET".into(),
uri: format!("{}/{}/public/1000", endpoints[1], QUICKNET_CHAIN_HASH),
response: Some(Vec::new()),
sent: true,
..Default::default()
});

// Endpoint 2: Invalid JSON
state.expect_request(PendingRequest {
method: "GET".into(),
uri: format!("{}/{}/public/1000", endpoints[2], QUICKNET_CHAIN_HASH),
response: Some(INVALID_JSON.as_bytes().to_vec()),
sent: true,
..Default::default()
});

// Endpoint 3: Invalid JSON
state.expect_request(PendingRequest {
method: "GET".into(),
uri: format!("{}/{}/public/1000", endpoints[3], QUICKNET_CHAIN_HASH),
response: Some(INVALID_JSON.as_bytes().to_vec()),
sent: true,
..Default::default()
});

// Endpoint 4: Valid JSON (success)
state.expect_request(PendingRequest {
method: "GET".into(),
uri: format!("{}/{}/public/1000", endpoints[4], QUICKNET_CHAIN_HASH),
response: Some(DRAND_PULSE.as_bytes().to_vec()),
sent: true,
..Default::default()
});
}

t.execute_with(|| {
let actual = Drand::fetch_drand_by_round(1000u64).unwrap();
assert_eq!(
actual, expected_pulse,
"Should succeed on the last endpoint after failing at the previous ones"
);
});
}

#[test]
fn test_invalid_json_then_success() {
let expected_pulse: DrandResponseBody = serde_json::from_str(DRAND_PULSE).unwrap();

let (offchain, state) = TestOffchainExt::new();
let mut t = sp_io::TestExternalities::default();
t.register_extension(OffchainWorkerExt::new(offchain));

{
let mut state = state.write();

let endpoints = ENDPOINTS;

// Endpoint 1: Invalid JSON
state.expect_request(PendingRequest {
method: "GET".into(),
uri: format!("{}/{}/public/1000", endpoints[0], QUICKNET_CHAIN_HASH),
response: Some(INVALID_JSON.as_bytes().to_vec()),
sent: true,
..Default::default()
});

// Endpoint 2: Valid response
state.expect_request(PendingRequest {
method: "GET".into(),
uri: format!("{}/{}/public/1000", endpoints[1], QUICKNET_CHAIN_HASH),
response: Some(DRAND_PULSE.as_bytes().to_vec()),
sent: true,
..Default::default()
});
}

t.execute_with(|| {
let actual = Drand::fetch_drand_by_round(1000u64).unwrap();
assert_eq!(actual, expected_pulse);
});
}
7 changes: 4 additions & 3 deletions pallets/subtensor/src/coinbase/root.rs
Original file line number Diff line number Diff line change
@@ -917,10 +917,8 @@ impl<T: Config> Pallet<T> {
let coldkey = ensure_signed(origin)?;

// --- 1. Rate limit for network registrations.
let current_block = Self::get_current_block_as_u64();
let last_lock_block = Self::get_network_last_lock_block();
ensure!(
current_block.saturating_sub(last_lock_block) >= NetworkRateLimit::<T>::get(),
Self::passes_rate_limit(&TransactionType::RegisterNetwork, &coldkey),
Error::<T>::NetworkTxRateLimitExceeded
);

@@ -1339,6 +1337,9 @@ impl<T: Config> Pallet<T> {
pub fn get_network_last_lock_block() -> u64 {
NetworkLastRegistered::<T>::get()
}
pub fn set_network_last_lock_block(block: u64) {
NetworkLastRegistered::<T>::set(block);
}
pub fn set_lock_reduction_interval(interval: u64) {
NetworkLockReductionInterval::<T>::set(interval);
Self::deposit_event(Event::NetworkLockCostReductionIntervalSet(interval));
6 changes: 3 additions & 3 deletions pallets/subtensor/src/staking/set_children.rs
Original file line number Diff line number Diff line change
@@ -61,7 +61,7 @@ impl<T: Config> Pallet<T> {

// Set last transaction block
let current_block = Self::get_current_block_as_u64();
Self::set_last_transaction_block(
Self::set_last_transaction_block_on_subnet(
&hotkey,
netuid,
&TransactionType::SetChildren,
@@ -325,7 +325,7 @@ impl<T: Config> Pallet<T> {

// Set last transaction block
let current_block = Self::get_current_block_as_u64();
Self::set_last_transaction_block(
Self::set_last_transaction_block_on_subnet(
&hotkey,
netuid,
&TransactionType::SetChildkeyTake,
@@ -336,7 +336,7 @@ impl<T: Config> Pallet<T> {
ChildkeyTake::<T>::insert(hotkey.clone(), netuid, take);

// Update the last transaction block
Self::set_last_transaction_block(
Self::set_last_transaction_block_on_subnet(
&hotkey,
netuid,
&TransactionType::SetChildkeyTake,
7 changes: 4 additions & 3 deletions pallets/subtensor/src/tests/children.rs
Original file line number Diff line number Diff line change
@@ -937,7 +937,7 @@ fn test_childkey_take_rate_limiting() {
// Helper function to log rate limit information
let log_rate_limit_info = || {
let current_block = SubtensorModule::get_current_block_as_u64();
let last_block = SubtensorModule::get_last_transaction_block(
let last_block = SubtensorModule::get_last_transaction_block_on_subnet(
&hotkey,
netuid,
&TransactionType::SetChildkeyTake,
@@ -947,7 +947,7 @@ fn test_childkey_take_rate_limiting() {
&hotkey,
netuid,
);
let limit = SubtensorModule::get_rate_limit(&TransactionType::SetChildkeyTake, 0);
let limit = SubtensorModule::get_rate_limit_on_subnet(&TransactionType::SetChildkeyTake, netuid);
log::info!(
"Rate limit info: current_block: {}, last_block: {}, limit: {}, passes: {}, diff: {}",
current_block,
@@ -3609,7 +3609,8 @@ fn test_set_children_rate_limit_fail_then_succeed() {

// Try again after rate limit period has passed
// Check rate limit
let limit = SubtensorModule::get_rate_limit(&TransactionType::SetChildren, netuid);
let limit =
SubtensorModule::get_rate_limit_on_subnet(&TransactionType::SetChildren, netuid);

// Step that many blocks
step_block(limit as u16);
2 changes: 1 addition & 1 deletion pallets/subtensor/src/tests/mock.rs
Original file line number Diff line number Diff line change
@@ -709,7 +709,7 @@ pub fn mock_set_children(coldkey: &U256, parent: &U256, netuid: u16, child_vec:
#[allow(dead_code)]
pub fn step_rate_limit(transaction_type: &TransactionType, netuid: u16) {
// Check rate limit
let limit = SubtensorModule::get_rate_limit(transaction_type, netuid);
let limit = SubtensorModule::get_rate_limit_on_subnet(transaction_type, netuid);

// Step that many blocks
step_block(limit as u16);
48 changes: 46 additions & 2 deletions pallets/subtensor/src/tests/root.rs
Original file line number Diff line number Diff line change
@@ -2,8 +2,10 @@

use super::mock::*;
use crate::Error;
use crate::{migrations, SubnetIdentity};
use crate::{SubnetIdentities, SubnetIdentityOf};
use crate::{
migrations, utils::rate_limiting::TransactionType, NetworkRateLimit, SubnetIdentities,
SubnetIdentity, SubnetIdentityOf,
};
use frame_support::{assert_err, assert_ok};
use frame_system::Config;
use frame_system::{EventRecord, Phase};
@@ -1050,3 +1052,45 @@ fn test_user_add_network_with_identity_fields_ok() {
);
});
}

#[test]
fn test_register_network_rate_limit() {
new_test_ext(1).execute_with(|| {
let coldkey = U256::from(1);

// Set rate limit
let rate_limit = 1;
NetworkRateLimit::<Test>::put(rate_limit);

// Give enough balance to register a network.
let balance = SubtensorModule::get_network_lock_cost() + 10_000;
SubtensorModule::add_balance_to_coldkey_account(&coldkey, balance);

// Register network.
assert_ok!(SubtensorModule::register_network(RuntimeOrigin::signed(
coldkey
)));

// Give more TA
let mut lock_cost = SubtensorModule::get_network_lock_cost();
SubtensorModule::add_balance_to_coldkey_account(&coldkey, lock_cost + 10_000);

// Try to register another network.
assert_err!(
SubtensorModule::register_network(RuntimeOrigin::signed(coldkey)),
Error::<Test>::NetworkTxRateLimitExceeded
);

// Step the rate limit.
step_rate_limit(&TransactionType::RegisterNetwork, 0);

// Give more TAO
lock_cost = SubtensorModule::get_network_lock_cost();
SubtensorModule::add_balance_to_coldkey_account(&coldkey, lock_cost + 10_000);

// Register network again.
assert_ok!(SubtensorModule::register_network(RuntimeOrigin::signed(
coldkey
)));
});
}
75 changes: 62 additions & 13 deletions pallets/subtensor/src/utils/rate_limiting.rs
Original file line number Diff line number Diff line change
@@ -6,6 +6,7 @@ pub enum TransactionType {
SetChildren,
SetChildkeyTake,
Unknown,
RegisterNetwork,
}

/// Implement conversion from TransactionType to u16
@@ -15,6 +16,7 @@ impl From<TransactionType> for u16 {
TransactionType::SetChildren => 0,
TransactionType::SetChildkeyTake => 1,
TransactionType::Unknown => 2,
TransactionType::RegisterNetwork => 3,
}
}
}
@@ -25,6 +27,7 @@ impl From<u16> for TransactionType {
match value {
0 => TransactionType::SetChildren,
1 => TransactionType::SetChildkeyTake,
3 => TransactionType::RegisterNetwork,
_ => TransactionType::Unknown,
}
}
@@ -34,47 +37,93 @@ impl<T: Config> Pallet<T> {
// ==== Rate Limiting =====
// ========================
/// Get the rate limit for a specific transaction type
pub fn get_rate_limit(tx_type: &TransactionType, _netuid: u16) -> u64 {
pub fn get_rate_limit(tx_type: &TransactionType) -> u64 {
match tx_type {
TransactionType::SetChildren => 7200, // Cannot set children twice within a day
TransactionType::SetChildren => 150, // 30 minutes
TransactionType::SetChildkeyTake => TxChildkeyTakeRateLimit::<T>::get(),
TransactionType::Unknown => 0, // Default to no limit for unknown types (no limit)
TransactionType::RegisterNetwork => NetworkRateLimit::<T>::get(),
}
}

pub fn get_rate_limit_on_subnet(tx_type: &TransactionType, _netuid: u16) -> u64 {
#[allow(clippy::match_single_binding)]
match tx_type {
_ => Self::get_rate_limit(tx_type),
}
}

pub fn check_passes_rate_limit(limit: u64, block: u64, last_block: u64) -> bool {
// Allow the first transaction (when last_block is 0) or if the rate limit has passed
last_block == 0 || block.saturating_sub(last_block) >= limit
}

pub fn passes_rate_limit(tx_type: &TransactionType, key: &T::AccountId) -> bool {
let block: u64 = Self::get_current_block_as_u64();
let limit: u64 = Self::get_rate_limit(tx_type);
let last_block: u64 = Self::get_last_transaction_block(key, tx_type);

Self::check_passes_rate_limit(limit, block, last_block)
}

/// Check if a transaction should be rate limited on a specific subnet
pub fn passes_rate_limit_on_subnet(
tx_type: &TransactionType,
hotkey: &T::AccountId,
netuid: u16,
) -> bool {
let block: u64 = Self::get_current_block_as_u64();
let limit: u64 = Self::get_rate_limit(tx_type, netuid);
let last_block: u64 = Self::get_last_transaction_block(hotkey, netuid, tx_type);
let limit: u64 = Self::get_rate_limit_on_subnet(tx_type, netuid);
let last_block: u64 = Self::get_last_transaction_block_on_subnet(hotkey, netuid, tx_type);

// Allow the first transaction (when last_block is 0) or if the rate limit has passed
last_block == 0 || block.saturating_sub(last_block) >= limit
Self::check_passes_rate_limit(limit, block, last_block)
}

/// Get the block number of the last transaction for a specific key, and transaction type
pub fn get_last_transaction_block(key: &T::AccountId, tx_type: &TransactionType) -> u64 {
match tx_type {
TransactionType::RegisterNetwork => Self::get_network_last_lock_block(),
_ => Self::get_last_transaction_block_on_subnet(key, 0, tx_type),
}
}

/// Get the block number of the last transaction for a specific hotkey, network, and transaction type
pub fn get_last_transaction_block(
pub fn get_last_transaction_block_on_subnet(
hotkey: &T::AccountId,
netuid: u16,
tx_type: &TransactionType,
) -> u64 {
let tx_as_u16: u16 = (*tx_type).into();
TransactionKeyLastBlock::<T>::get((hotkey, netuid, tx_as_u16))
match tx_type {
TransactionType::RegisterNetwork => Self::get_network_last_lock_block(),
_ => {
let tx_as_u16: u16 = (*tx_type).into();
TransactionKeyLastBlock::<T>::get((hotkey, netuid, tx_as_u16))
}
}
}

/// Set the block number of the last transaction for a specific key, and transaction type
pub fn set_last_transaction_block(key: &T::AccountId, tx_type: &TransactionType, block: u64) {
match tx_type {
TransactionType::RegisterNetwork => Self::set_network_last_lock_block(block),
_ => Self::set_last_transaction_block_on_subnet(key, 0, tx_type, block),
}
}

/// Set the block number of the last transaction for a specific hotkey, network, and transaction type
pub fn set_last_transaction_block(
hotkey: &T::AccountId,
pub fn set_last_transaction_block_on_subnet(
key: &T::AccountId,
netuid: u16,
tx_type: &TransactionType,
block: u64,
) {
let tx_as_u16: u16 = (*tx_type).into();
TransactionKeyLastBlock::<T>::insert((hotkey, netuid, tx_as_u16), block);
match tx_type {
TransactionType::RegisterNetwork => Self::set_network_last_lock_block(block),
_ => {
let tx_as_u16: u16 = (*tx_type).into();
TransactionKeyLastBlock::<T>::insert((key, netuid, tx_as_u16), block);
}
}
}

pub fn set_last_tx_block(key: &T::AccountId, block: u64) {
2 changes: 1 addition & 1 deletion runtime/src/lib.rs
Original file line number Diff line number Diff line change
@@ -220,7 +220,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: 215,
spec_version: 216,
impl_version: 1,
apis: RUNTIME_API_VERSIONS,
transaction_version: 1,
360 changes: 360 additions & 0 deletions runtime/src/precompiles/metagraph.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,360 @@
extern crate alloc;
use crate::precompiles::{get_method_id, get_slice};
use crate::Runtime;
use fp_evm::{
ExitError, ExitSucceed, PrecompileFailure, PrecompileHandle, PrecompileOutput, PrecompileResult,
};
use sp_core::{ByteArray, U256};
use sp_std::vec;
pub const METAGRAPH_PRECOMPILE_INDEX: u64 = 2050;
pub struct MetagraphPrecompile;

const NO_HOTKEY: &str = "no hotkey";

impl MetagraphPrecompile {
pub fn execute(handle: &mut impl PrecompileHandle) -> PrecompileResult {
let txdata = handle.input();
let method_id = get_slice(txdata, 0, 4)?;
let method_input = txdata
.get(4..)
.map_or_else(vec::Vec::new, |slice| slice.to_vec()); // Avoiding borrowing conflicts

match method_id {
id if id == get_method_id("getUidCount(uint16)") => Self::get_uid_count(&method_input),
id if id == get_method_id("getStake(uint16,uint16)") => Self::get_stake(&method_input),
id if id == get_method_id("getRank(uint16,uint16)") => Self::get_rank(&method_input),
id if id == get_method_id("getTrust(uint16,uint16)") => Self::get_trust(&method_input),
id if id == get_method_id("getConsensus(uint16,uint16)") => {
Self::get_consensus(&method_input)
}
id if id == get_method_id("getIncentive(uint16,uint16)") => {
Self::get_incentive(&method_input)
}
id if id == get_method_id("getDividends(uint16,uint16)") => {
Self::get_dividends(&method_input)
}
id if id == get_method_id("getEmission(uint16,uint16)") => {
Self::get_emission(&method_input)
}
id if id == get_method_id("getVtrust(uint16,uint16)") => {
Self::get_vtrust(&method_input)
}
id if id == get_method_id("getValidatorStatus(uint16,uint16)") => {
Self::get_validator_status(&method_input)
}
id if id == get_method_id("getLastUpdate(uint16,uint16)") => {
Self::get_last_update(&method_input)
}
id if id == get_method_id("getIsActive(uint16,uint16)") => {
Self::get_is_active(&method_input)
}
id if id == get_method_id("getAxon(uint16,uint16)") => Self::get_axon(&method_input),
id if id == get_method_id("getHotkey(uint16,uint16)") => {
Self::get_hotkey(&method_input)
}
id if id == get_method_id("getColdkey(uint16,uint16)") => {
Self::get_coldkey(&method_input)
}

_ => Err(PrecompileFailure::Error {
exit_status: ExitError::InvalidRange,
}),
}
}

fn get_uid_count(data: &[u8]) -> PrecompileResult {
let netuid = Self::parse_netuid(data)?;
let uid_count = pallet_subtensor::SubnetworkN::<Runtime>::get(netuid);

let uid_count_u256 = U256::from(uid_count);
let mut result = [0_u8; 32];
U256::to_big_endian(&uid_count_u256, &mut result);

Ok(PrecompileOutput {
exit_status: ExitSucceed::Returned,
output: result.into(),
})
}

fn get_stake(data: &[u8]) -> PrecompileResult {
let netuid = Self::parse_netuid(data)?;
let uid = Self::parse_uid(get_slice(data, 32, 64)?)?;
let hotkey = pallet_subtensor::Pallet::<Runtime>::get_hotkey_for_net_and_uid(netuid, uid)
.map_err(|_| PrecompileFailure::Error {
exit_status: ExitError::InvalidRange,
})?;

let stake = pallet_subtensor::TotalHotkeyStake::<Runtime>::get(&hotkey);
let result_u256 = U256::from(stake);
let mut result = [0_u8; 32];
U256::to_big_endian(&result_u256, &mut result);

Ok(PrecompileOutput {
exit_status: ExitSucceed::Returned,
output: result.into(),
})
}

fn get_rank(data: &[u8]) -> PrecompileResult {
let netuid = Self::parse_netuid(data)?;
let uid = Self::parse_uid(get_slice(data, 32, 64)?)?;
let rank = pallet_subtensor::Pallet::<Runtime>::get_rank_for_uid(netuid, uid);

let result_u256 = U256::from(rank);
let mut result = [0_u8; 32];
U256::to_big_endian(&result_u256, &mut result);

Ok(PrecompileOutput {
exit_status: ExitSucceed::Returned,
output: result.into(),
})
}

fn get_trust(data: &[u8]) -> PrecompileResult {
let netuid = Self::parse_netuid(data)?;
let uid = Self::parse_uid(get_slice(data, 32, 64)?)?;

let trust = pallet_subtensor::Pallet::<Runtime>::get_trust_for_uid(netuid, uid);

let result_u256 = U256::from(trust);
let mut result = [0_u8; 32];
U256::to_big_endian(&result_u256, &mut result);

Ok(PrecompileOutput {
exit_status: ExitSucceed::Returned,
output: result.into(),
})
}

fn get_consensus(data: &[u8]) -> PrecompileResult {
let netuid = Self::parse_netuid(data)?;
let uid = Self::parse_uid(get_slice(data, 32, 64)?)?;

let consensus = pallet_subtensor::Pallet::<Runtime>::get_consensus_for_uid(netuid, uid);

let result_u256 = U256::from(consensus);
let mut result = [0_u8; 32];
U256::to_big_endian(&result_u256, &mut result);

Ok(PrecompileOutput {
exit_status: ExitSucceed::Returned,
output: result.into(),
})
}

fn get_incentive(data: &[u8]) -> PrecompileResult {
let netuid = Self::parse_netuid(data)?;
let uid = Self::parse_uid(get_slice(data, 32, 64)?)?;

let incentive = pallet_subtensor::Pallet::<Runtime>::get_incentive_for_uid(netuid, uid);

let result_u256 = U256::from(incentive);
let mut result = [0_u8; 32];
U256::to_big_endian(&result_u256, &mut result);

Ok(PrecompileOutput {
exit_status: ExitSucceed::Returned,
output: result.into(),
})
}

fn get_dividends(data: &[u8]) -> PrecompileResult {
let netuid = Self::parse_netuid(data)?;
let uid = Self::parse_uid(get_slice(data, 32, 64)?)?;

let dividends = pallet_subtensor::Pallet::<Runtime>::get_dividends_for_uid(netuid, uid);

let result_u256 = U256::from(dividends);
let mut result = [0_u8; 32];
U256::to_big_endian(&result_u256, &mut result);

Ok(PrecompileOutput {
exit_status: ExitSucceed::Returned,
output: result.into(),
})
}

fn get_emission(data: &[u8]) -> PrecompileResult {
let netuid = Self::parse_netuid(data)?;
let uid = Self::parse_uid(get_slice(data, 32, 64)?)?;

let emission = pallet_subtensor::Pallet::<Runtime>::get_emission_for_uid(netuid, uid);

let result_u256 = U256::from(emission);
let mut result = [0_u8; 32];
U256::to_big_endian(&result_u256, &mut result);

Ok(PrecompileOutput {
exit_status: ExitSucceed::Returned,
output: result.into(),
})
}

fn get_vtrust(data: &[u8]) -> PrecompileResult {
let netuid = Self::parse_netuid(data)?;
let uid = Self::parse_uid(get_slice(data, 32, 64)?)?;

let vtrust = pallet_subtensor::Pallet::<Runtime>::get_validator_trust_for_uid(netuid, uid);

let result_u256 = U256::from(vtrust);
let mut result = [0_u8; 32];
U256::to_big_endian(&result_u256, &mut result);

Ok(PrecompileOutput {
exit_status: ExitSucceed::Returned,
output: result.into(),
})
}

fn get_validator_status(data: &[u8]) -> PrecompileResult {
let netuid = Self::parse_netuid(data)?;
let uid = Self::parse_uid(get_slice(data, 32, 64)?)?;

let validator_permit =
pallet_subtensor::Pallet::<Runtime>::get_validator_permit_for_uid(netuid, uid);

let result_u256 = if validator_permit {
U256::from(1)
} else {
U256::from(0)
};
let mut result = [0_u8; 32];
U256::to_big_endian(&result_u256, &mut result);

Ok(PrecompileOutput {
exit_status: ExitSucceed::Returned,
output: result.into(),
})
}

fn get_last_update(data: &[u8]) -> PrecompileResult {
let netuid = Self::parse_netuid(data)?;
let uid = Self::parse_uid(get_slice(data, 32, 64)?)?;

let last_update = pallet_subtensor::Pallet::<Runtime>::get_last_update_for_uid(netuid, uid);

let result_u256 = U256::from(last_update);
let mut result = [0_u8; 32];
U256::to_big_endian(&result_u256, &mut result);

Ok(PrecompileOutput {
exit_status: ExitSucceed::Returned,
output: result.into(),
})
}

fn get_is_active(data: &[u8]) -> PrecompileResult {
let netuid = Self::parse_netuid(data)?;
let uid = Self::parse_uid(get_slice(data, 32, 64)?)?;

let active = pallet_subtensor::Pallet::<Runtime>::get_active_for_uid(netuid, uid);

let result_u256 = if active { U256::from(1) } else { U256::from(0) };
let mut result = [0_u8; 32];
U256::to_big_endian(&result_u256, &mut result);

Ok(PrecompileOutput {
exit_status: ExitSucceed::Returned,
output: result.into(),
})
}

fn get_axon(data: &[u8]) -> PrecompileResult {
let netuid = Self::parse_netuid(data)?;
let uid = Self::parse_uid(get_slice(data, 32, 64)?)?;

let hotkey = pallet_subtensor::Pallet::<Runtime>::get_hotkey_for_net_and_uid(netuid, uid)
.map_err(|_| PrecompileFailure::Error {
exit_status: ExitError::Other(sp_version::Cow::Borrowed(NO_HOTKEY)),
})?;

let axon = pallet_subtensor::Pallet::<Runtime>::get_axon_info(netuid, &hotkey);

let mut block_result = [0_u8; 32];
U256::to_big_endian(&U256::from(axon.block), &mut block_result);

let mut version_result = [0_u8; 32];
U256::to_big_endian(&U256::from(axon.version), &mut version_result);

let mut ip_result = [0_u8; 32];
U256::to_big_endian(&U256::from(axon.ip), &mut ip_result);

let mut port_result = [0_u8; 32];
U256::to_big_endian(&U256::from(axon.port), &mut port_result);

let mut ip_type_result = [0_u8; 32];
U256::to_big_endian(&U256::from(axon.ip_type), &mut ip_type_result);

let mut protocol_result = [0_u8; 32];
U256::to_big_endian(&U256::from(axon.protocol), &mut protocol_result);

let mut result = [0_u8; 192];
result[..32].copy_from_slice(&block_result);
result[32..64].copy_from_slice(&version_result);
result[64..96].copy_from_slice(&ip_result);
result[96..128].copy_from_slice(&port_result);
result[128..160].copy_from_slice(&ip_type_result);
result[160..].copy_from_slice(&protocol_result);

Ok(PrecompileOutput {
exit_status: ExitSucceed::Returned,
output: result.into(),
})
}

fn get_hotkey(data: &[u8]) -> PrecompileResult {
let netuid = Self::parse_netuid(data)?;
let uid = Self::parse_uid(get_slice(data, 32, 64)?)?;

let hotkey = pallet_subtensor::Pallet::<Runtime>::get_hotkey_for_net_and_uid(netuid, uid)
.map_err(|_| PrecompileFailure::Error {
exit_status: ExitError::InvalidRange,
})?;

Ok(PrecompileOutput {
exit_status: ExitSucceed::Returned,
output: hotkey.as_slice().into(),
})
}

fn get_coldkey(data: &[u8]) -> PrecompileResult {
let netuid = Self::parse_netuid(data)?;
let uid = Self::parse_uid(get_slice(data, 32, 64)?)?;

let hotkey = pallet_subtensor::Pallet::<Runtime>::get_hotkey_for_net_and_uid(netuid, uid)
.map_err(|_| PrecompileFailure::Error {
exit_status: ExitError::InvalidRange,
})?;

let coldkey = pallet_subtensor::Owner::<Runtime>::get(&hotkey);

Ok(PrecompileOutput {
exit_status: ExitSucceed::Returned,
output: coldkey.as_slice().into(),
})
}

fn parse_netuid(data: &[u8]) -> Result<u16, PrecompileFailure> {
if data.len() < 32 {
return Err(PrecompileFailure::Error {
exit_status: ExitError::InvalidRange,
});
}
let mut netuid = [0u8; 2];
netuid.copy_from_slice(get_slice(data, 30, 32)?);
let result = u16::from_be_bytes(netuid);
Ok(result)
}

fn parse_uid(data: &[u8]) -> Result<u16, PrecompileFailure> {
if data.len() < 32 {
return Err(PrecompileFailure::Error {
exit_status: ExitError::InvalidRange,
});
}
let mut uid = [0u8; 2];
uid.copy_from_slice(get_slice(data, 30, 32)?);
let result = u16::from_be_bytes(uid);
Ok(result)
}
}
9 changes: 8 additions & 1 deletion runtime/src/precompiles/mod.rs
Original file line number Diff line number Diff line change
@@ -13,10 +13,12 @@ use pallet_evm_precompile_simple::{ECRecover, ECRecoverPublicKey, Identity, Ripe
// Include custom precompiles
mod balance_transfer;
mod ed25519;
mod metagraph;
mod staking;

use balance_transfer::*;
use ed25519::*;
use metagraph::*;
use staking::*;

pub struct FrontierPrecompiles<R>(PhantomData<R>);
@@ -37,7 +39,7 @@ where
pub fn new() -> Self {
Self(Default::default())
}
pub fn used_addresses() -> [H160; 10] {
pub fn used_addresses() -> [H160; 11] {
[
hash(1),
hash(2),
@@ -49,6 +51,7 @@ where
hash(EDVERIFY_PRECOMPILE_INDEX),
hash(BALANCE_TRANSFER_INDEX),
hash(STAKING_PRECOMPILE_INDEX),
hash(METAGRAPH_PRECOMPILE_INDEX),
]
}
}
@@ -73,6 +76,10 @@ where
Some(BalanceTransferPrecompile::execute(handle))
}
a if a == hash(STAKING_PRECOMPILE_INDEX) => Some(StakingPrecompile::execute(handle)),
a if a == hash(METAGRAPH_PRECOMPILE_INDEX) => {
Some(MetagraphPrecompile::execute(handle))
}

_ => None,
}
}
389 changes: 389 additions & 0 deletions runtime/src/precompiles/solidity/metagraph.abi
Original file line number Diff line number Diff line change
@@ -0,0 +1,389 @@
[
{
"inputs": [
{
"internalType": "uint16",
"name": "netuid",
"type": "uint16"
},
{
"internalType": "uint16",
"name": "uid",
"type": "uint16"
}
],
"name": "getAxon",
"outputs": [
{
"components": [
{
"internalType": "uint64",
"name": "block",
"type": "uint64"
},
{
"internalType": "uint32",
"name": "version",
"type": "uint32"
},
{
"internalType": "uint128",
"name": "ip",
"type": "uint128"
},
{
"internalType": "uint16",
"name": "port",
"type": "uint16"
},
{
"internalType": "uint8",
"name": "ip_type",
"type": "uint8"
},
{
"internalType": "uint8",
"name": "protocol",
"type": "uint8"
}
],
"internalType": "struct AxonInfo",
"name": "",
"type": "tuple"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "uint16",
"name": "netuid",
"type": "uint16"
},
{
"internalType": "uint16",
"name": "uid",
"type": "uint16"
}
],
"name": "getColdkey",
"outputs": [
{
"internalType": "bytes32",
"name": "",
"type": "bytes32"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "uint16",
"name": "netuid",
"type": "uint16"
},
{
"internalType": "uint16",
"name": "uid",
"type": "uint16"
}
],
"name": "getConsensus",
"outputs": [
{
"internalType": "uint16",
"name": "",
"type": "uint16"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "uint16",
"name": "netuid",
"type": "uint16"
},
{
"internalType": "uint16",
"name": "uid",
"type": "uint16"
}
],
"name": "getDividends",
"outputs": [
{
"internalType": "uint16",
"name": "",
"type": "uint16"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "uint16",
"name": "netuid",
"type": "uint16"
},
{
"internalType": "uint16",
"name": "uid",
"type": "uint16"
}
],
"name": "getEmission",
"outputs": [
{
"internalType": "uint64",
"name": "",
"type": "uint64"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "uint16",
"name": "netuid",
"type": "uint16"
},
{
"internalType": "uint16",
"name": "uid",
"type": "uint16"
}
],
"name": "getHotkey",
"outputs": [
{
"internalType": "bytes32",
"name": "",
"type": "bytes32"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "uint16",
"name": "netuid",
"type": "uint16"
},
{
"internalType": "uint16",
"name": "uid",
"type": "uint16"
}
],
"name": "getIncentive",
"outputs": [
{
"internalType": "uint16",
"name": "",
"type": "uint16"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "uint16",
"name": "netuid",
"type": "uint16"
},
{
"internalType": "uint16",
"name": "uid",
"type": "uint16"
}
],
"name": "getIsActive",
"outputs": [
{
"internalType": "bool",
"name": "",
"type": "bool"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "uint16",
"name": "netuid",
"type": "uint16"
},
{
"internalType": "uint16",
"name": "uid",
"type": "uint16"
}
],
"name": "getLastUpdate",
"outputs": [
{
"internalType": "uint64",
"name": "",
"type": "uint64"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "uint16",
"name": "netuid",
"type": "uint16"
},
{
"internalType": "uint16",
"name": "uid",
"type": "uint16"
}
],
"name": "getRank",
"outputs": [
{
"internalType": "uint16",
"name": "",
"type": "uint16"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "uint16",
"name": "netuid",
"type": "uint16"
},
{
"internalType": "uint16",
"name": "uid",
"type": "uint16"
}
],
"name": "getStake",
"outputs": [
{
"internalType": "uint64",
"name": "",
"type": "uint64"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "uint16",
"name": "netuid",
"type": "uint16"
},
{
"internalType": "uint16",
"name": "uid",
"type": "uint16"
}
],
"name": "getTrust",
"outputs": [
{
"internalType": "uint16",
"name": "",
"type": "uint16"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "uint16",
"name": "netuid",
"type": "uint16"
}
],
"name": "getUidCount",
"outputs": [
{
"internalType": "uint16",
"name": "",
"type": "uint16"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "uint16",
"name": "netuid",
"type": "uint16"
},
{
"internalType": "uint16",
"name": "uid",
"type": "uint16"
}
],
"name": "getValidatorStatus",
"outputs": [
{
"internalType": "bool",
"name": "",
"type": "bool"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "uint16",
"name": "netuid",
"type": "uint16"
},
{
"internalType": "uint16",
"name": "uid",
"type": "uint16"
}
],
"name": "getVtrust",
"outputs": [
{
"internalType": "uint16",
"name": "",
"type": "uint16"
}
],
"stateMutability": "view",
"type": "function"
}
]
134 changes: 134 additions & 0 deletions runtime/src/precompiles/solidity/metagraph.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
pragma solidity ^0.8.0;

address constant IMetagraph_ADDRESS = 0x0000000000000000000000000000000000000802;

struct AxonInfo {
uint64 block;
uint32 version;
uint128 ip;
uint16 port;
uint8 ip_type;
uint8 protocol;
}

interface IMetagraph {

/**
* @dev Returns the count of unique identifiers (UIDs) associated with a given network identifier (netuid).
* @param netuid The network identifier for which to retrieve the UID count.
* @return The count of UIDs associated with the specified netuid.
*/
function getUidCount(uint16 netuid) external view returns (uint16);

/**
* @dev Retrieves the stake amount associated with a given network identifier (netuid) and unique identifier (uid).
* @param netuid The network identifier for which to retrieve the stake.
* @param uid The unique identifier for which to retrieve the stake.
* @return The stake amount associated with the specified netuid and uid.
*/
function getStake(uint16 netuid, uint16 uid) external view returns (uint64);

/**
* @dev Retrieves the rank of a node with a given network identifier (netuid) and unique identifier (uid).
* @param netuid The network identifier for which to retrieve the rank.
* @param uid The unique identifier for which to retrieve the rank.
* @return The rank of the node with the specified netuid and uid.
*/
function getRank(uint16 netuid, uint16 uid) external view returns (uint16);

/**
* @dev Retrieves the trust value of a node with a given network identifier (netuid) and unique identifier (uid).
* @param netuid The network identifier for which to retrieve the trust value.
* @param uid The unique identifier for which to retrieve the trust value.
* @return The trust value of the node with the specified netuid and uid.
*/
function getTrust(uint16 netuid, uint16 uid) external view returns (uint16);

/**
* @dev Retrieves the consensus value of a node with a given network identifier (netuid) and unique identifier (uid).
* @param netuid The network identifier for which to retrieve the consensus value.
* @param uid The unique identifier for which to retrieve the consensus value.
* @return The consensus value of the node with the specified netuid and uid.
*/
function getConsensus(uint16 netuid, uint16 uid) external view returns (uint16);

/**
* @dev Retrieves the incentive value of a node with a given network identifier (netuid) and unique identifier (uid).
* @param netuid The network identifier for which to retrieve the incentive value.
* @param uid The unique identifier for which to retrieve the incentive value.
* @return The incentive value of the node with the specified netuid and uid.
*/
function getIncentive(uint16 netuid, uint16 uid) external view returns (uint16);

/**
* @dev Retrieves the dividend value of a node with a given network identifier (netuid) and unique identifier (uid).
* @param netuid The network identifier for which to retrieve the dividend value.
* @param uid The unique identifier for which to retrieve the dividend value.
* @return The dividend value of the node with the specified netuid and uid.
*/
function getDividends(uint16 netuid, uint16 uid) external view returns (uint16);

/**
* @dev Retrieves the emission value of a node with a given network identifier (netuid) and unique identifier (uid).
* @param netuid The network identifier for which to retrieve the emission value.
* @param uid The unique identifier for which to retrieve the emission value.
* @return The emission value of the node with the specified netuid and uid.
*/
function getEmission(uint16 netuid, uint16 uid) external view returns (uint64);

/**
* @dev Retrieves the v-trust value of a node with a given network identifier (netuid) and unique identifier (uid).
* @param netuid The network identifier for which to retrieve the v-trust value.
* @param uid The unique identifier for which to retrieve the v-trust value.
* @return The v-trust value of the node with the specified netuid and uid.
*/
function getVtrust(uint16 netuid, uint16 uid) external view returns (uint16);

/**
* @dev Checks the validator status of a node with a given network identifier (netuid) and unique identifier (uid).
* @param netuid The network identifier for which to check the validator status.
* @param uid The unique identifier for which to check the validator status.
* @return Returns true if the node is a validator, false otherwise.
*/
function getValidatorStatus(uint16 netuid, uint16 uid) external view returns (bool);

/**
* @dev Retrieves the last update timestamp of a node with a given network identifier (netuid) and unique identifier (uid).
* @param netuid The network identifier for which to retrieve the last update timestamp.
* @param uid The unique identifier for which to retrieve the last update timestamp.
* @return The last update timestamp of the node with the specified netuid and uid.
*/
function getLastUpdate(uint16 netuid, uint16 uid) external view returns (uint64);

/**
* @dev Checks if a node with a given network identifier (netuid) and unique identifier (uid) is active.
* @param netuid The network identifier for which to check the node's activity.
* @param uid The unique identifier for which to check the node's activity.
* @return Returns true if the node is active, false otherwise.
*/
function getIsActive(uint16 netuid, uint16 uid) external view returns (bool);

/**
* @dev Retrieves the axon information of a node with a given network identifier (netuid) and unique identifier (uid).
* @param netuid The network identifier for which to retrieve the axon information.
* @param uid The unique identifier for which to retrieve the axon information.
* @return The axon information of the node with the specified netuid and uid.
*/
function getAxon(uint16 netuid, uint16 uid) external view returns (AxonInfo memory);

/**
* @dev Retrieves the hotkey of a node with a given network identifier (netuid) and unique identifier (uid).
* @param netuid The network identifier for which to retrieve the hotkey.
* @param uid The unique identifier for which to retrieve the hotkey.
* @return The hotkey of the node with the specified netuid and uid.
*/
function getHotkey(uint16 netuid, uint16 uid) external view returns (bytes32);

/**
* @dev Retrieves the coldkey of a node with a given network identifier (netuid) and unique identifier (uid).
* @param netuid The network identifier for which to retrieve the coldkey.
* @param uid The unique identifier for which to retrieve the coldkey.
* @return The coldkey of the node with the specified netuid and uid.
*/
function getColdkey(uint16 netuid, uint16 uid) external view returns (bytes32);
}