SNB stands for SKALE Network Browser. SNB is build-in part of IMA responsible for downloading entire or partial descriptions of S-Chains and nodes from SKALE Manager on Main Net. SNB is needed to inform IMA about various node parameters for futher communication with nodes. S-Chain to S-Chain transfers are possible because SNB provides network access information about nodes and detect set of S-Chains connected with each particular S-Chain.
SNB runs as background worker thread in IMA. It preforms periodic downloads of SKALE network node descriptions. Once per hour by default.
SNB calls only SChainsInternal and Nodes smart contracts of SKALE Manager.
First we need to query SKALE Manager for count of S-Chains currently created inside SKALE network.
let count schains_internal.numberOfSchains()Then we walk through all S-Chains in range 0...(count-1) and download each S-Chain description by its number.
S-Chain loading starts with getting S-Chain's hash by its number.
let hash = await schains_internal.schainsAtSystem( idxSChain );Hash be used to download S-Chain parameters as single S-Chain description object.
let objectSChainData = await schains_internal.schains( hash );Single S-Chain description object contains only basic fields without node URLs. IMA needs ready to use node URLs to access them. So, node URLs require additional calls to SKALE Manager. First, we prepare group of computed properties.
objectSChainData.computed = {};Second, we get node IDs for S-Chain.
let schain_id = Keccak245( objectSChainData.name );
const node_ids = await schains_internal.getNodesInGroup( schain_id );Thrid step is the most important. We walk through nodes and perform URL computations for each one.
const nodes = [];
for( const node_id of node_ids ) {
const node = await nodes.nodes( node_id );
const node_dict = {
id: node_id,
name: node[0],
ip: ip_from_hex( node[1] ),
base_port: node[3],
domain: await nodes.getNodeDomainName( node_id ),
isMaintenance: await nodes.isNodeInMaintenance( node_id )
};
const schain_ids = await schains_internal.getSchainIdsForNode( node_id );
node_dict.schain_base_port = get_schain_base_port_on_node( schain_id, schain_ids, node_dict.base_port );
calc_ports( objectSChainData, node_dict.schain_base_port );
compose_endpoints( objectSChainData, node_dict, "ip" );
compose_endpoints( objectSChainData, node_dict, "domain" );
nodes.push( node_dict );
}Above we also check whether node is kept in maintenance, compute node domain port, compute ports for various protoculs from base port.
Finally, we save computed data.
objectSChainData.computed.schain_id = schain_id;
objectSChainData.computed.nodes = nodes;Algorithm above uses small set of utility operations. The main part is get_schain_base_port_on_node which computes base port of S-Chain node.
function get_schain_index_in_node( schain_id, schains_ids_on_node ) {
let i = 0;
for( const schain_id_on_node of schains_ids_on_node ) {
if( schain_id == schain_id_on_node )
return i;
++ i;
}
throw new Error( "S-Chain " + schain_id + " is not found in the list: " + JSON.stringify( schains_ids_on_node ) );
}
function get_schain_base_port_on_node( schain_id, schains_ids_on_node, node_base_port ) {
const schain_index = get_schain_index_in_node( schain_id, schains_ids_on_node );
return calc_schain_base_port( node_base_port, schain_index );
}
function calc_schain_base_port( node_base_port, schain_index ) {
return parseInt( node_base_port ) + parseInt( schain_index ) * PORTS_PER_SCHAIN;
}Base port of S-Chain node is used to compute per-protocol ports available on same node.
const SkaledPorts = {
PROPOSAL: 0,
CATCHUP: 1,
WS_JSON: 2,
HTTP_JSON: 3,
BINARY_CONSENSUS: 4,
ZMQ_BROADCAST: 5,
IMA_MONITORING: 6,
WSS_JSON: 7,
HTTPS_JSON: 8,
INFO_HTTP_JSON: 9,
IMA_AGENT_JSON: 10
};
function calc_ports( objectSChainData, schain_base_port ) {
objectSChainData.computed.ports = {
httpRpcPort: schain_base_port + SkaledPorts.HTTP_JSON,
httpsRpcPort: schain_base_port + SkaledPorts.HTTPS_JSON,
wsRpcPort: schain_base_port + SkaledPorts.WS_JSON,
wssRpcPort: schain_base_port + SkaledPorts.WSS_JSON,
infoHttpRpcPort: schain_base_port + SkaledPorts.INFO_HTTP_JSON,
imaAgentRpcPort: schain_base_port + SkaledPorts.IMA_AGENT_JSON
};
}Finally, we have all ports and domain names and IP addeses to compute ready to use per-protocol URLs.
function compose_endpoints( objectSChainData, node_dict, endpoint_type ) {
node_dict["http_endpoint_" + endpoint_type] = "http://" + node_dict[endpoint_type] + ":" + objectSChainData.computed.ports.httpRpcPort;
node_dict["https_endpoint_" + endpoint_type] = "https://" + node_dict[endpoint_type] + ":" + objectSChainData.computed.ports.httpsRpcPort;
node_dict["ws_endpoint_" + endpoint_type] = "ws://" + node_dict[endpoint_type] + ":" + objectSChainData.computed.ports.wsRpcPort;
node_dict["wss_endpoint_" + endpoint_type] = "wss://" + node_dict[endpoint_type] + ":" + objectSChainData.computed.ports.wssRpcPort;
node_dict["info_http_endpoint_" + endpoint_type] = "http://" + node_dict[endpoint_type] + ":" + objectSChainData.computed.ports.infoHttpRpcPort;
node_dict["ima_agent_endpoint_" + endpoint_type] = "http://" + node_dict[endpoint_type] + ":" + objectSChainData.computed.ports.imaAgentRpcPort;
}Loading S-Chain descriptions for all S-Chains connected to one particular S-Chain is same as loading all S-Chains of entire SKALE network. But each S-Chain should be checked for connected status with other particular S-Chain. This is performed via MessageProxy contract call on S-Chain.
const isConnected = await message_proxy_s_chain.isConnectedChain( strSChainName )The connected status check above is performed before loading detailed S-Chain data and compute its URLs. This optimizes un-needed calls to SKALE Manager on Main Net.