forked from STI/Astroport.ONE
121 lines
4.0 KiB
Python
121 lines
4.0 KiB
Python
# Copyright 2014-2024 Vincent Texier <vit@free.fr>
|
|
#
|
|
# DuniterPy is free software: you can redistribute it and/or modify
|
|
# it under the terms of the GNU General Public License as published by
|
|
# the Free Software Foundation, either version 3 of the License, or
|
|
# (at your option) any later version.
|
|
#
|
|
# DuniterPy is distributed in the hope that it will be useful,
|
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
# GNU General Public License for more details.
|
|
#
|
|
# You should have received a copy of the GNU General Public License
|
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
from itertools import groupby
|
|
from typing import Any, Dict, List
|
|
|
|
from duniterpy.api import bma
|
|
from duniterpy.api.client import Client
|
|
from duniterpy.documents.peer import MalformedDocumentError, Peer
|
|
from duniterpy.documents.ws2p.heads import HeadV2
|
|
|
|
|
|
def get_available_nodes(client: Client) -> List[List[Dict[str, Any]]]:
|
|
"""
|
|
Get available nodes grouped and sorted by descending block_id
|
|
|
|
Each entry is a list of nodes (HeadV2 instance, inline endpoint list) sharing the same block_id:
|
|
|
|
[
|
|
[{"head": HeadV2, "endpoints": [str, ...]}, ...],
|
|
[{"head": HeadV2, "endpoints": [str, ...]}, ...],
|
|
...
|
|
]
|
|
|
|
You can just select the first endpoint of the first node of the first group to quickly get an available node.
|
|
|
|
groups = get_available_nodes(client)
|
|
first_node_first_endpoint = groups[0][0]["endpoints"][0]
|
|
|
|
If node is down, you can select another node.
|
|
|
|
Warning: only nodes with BMAS, BASIC_MERKLED_API, and GVA endpoint are selected
|
|
and only those endpoints are available in the endpoint list
|
|
|
|
:param client: Client instance
|
|
:return:
|
|
"""
|
|
# capture heads and peers
|
|
heads_response = client(bma.network.ws2p_heads)
|
|
peers_response = client(bma.network.peers)
|
|
|
|
# get heads instances from WS2P messages
|
|
heads = []
|
|
for entry in heads_response["heads"]:
|
|
head, _ = HeadV2.from_inline(entry["messageV2"], entry["sigV2"])
|
|
heads.append(head)
|
|
|
|
# sort by block_id by descending order
|
|
heads = sorted(heads, key=lambda x: x.block_id, reverse=True)
|
|
|
|
# group heads by block_id
|
|
groups = []
|
|
for _, group in groupby(heads, key=lambda x: x.block_id):
|
|
nodes = []
|
|
for head in list(group):
|
|
# if head signature not valid...
|
|
if head.check_signature(head.pubkey) is False:
|
|
# skip this node
|
|
continue
|
|
|
|
bma_peers = [
|
|
bma_peer
|
|
for bma_peer in peers_response["peers"]
|
|
if bma_peer["pubkey"] == head.pubkey
|
|
]
|
|
|
|
# if no peer found...
|
|
if len(bma_peers) == 0:
|
|
# skip this node
|
|
continue
|
|
|
|
bma_peer = bma_peers[0]
|
|
|
|
try:
|
|
peer = Peer.from_bma(bma_peer)
|
|
# if bad peer... (mostly bad formatted endpoints)
|
|
except MalformedDocumentError:
|
|
# skip this node
|
|
continue
|
|
|
|
# set signature in Document
|
|
peer.signature = bma_peer["signature"]
|
|
# if peer signature not valid
|
|
if peer.check_signature(head.pubkey) is False:
|
|
# skip this node
|
|
continue
|
|
|
|
# filter endpoints to get only BMAS, BASIC_MERKLED_API or GVA
|
|
endpoints = [
|
|
endpoint
|
|
for endpoint in bma_peers[0]["endpoints"]
|
|
if endpoint.startswith("BMAS")
|
|
or endpoint.startswith("BASIC_MERKLED_API")
|
|
or endpoint.startswith("GVA")
|
|
]
|
|
if len(endpoints) == 0:
|
|
# skip this node
|
|
continue
|
|
|
|
# add node to group nodes
|
|
nodes.append({"head": head, "endpoints": endpoints})
|
|
|
|
# if nodes in group...
|
|
if len(nodes) > 0:
|
|
# add group to groups
|
|
groups.append(nodes)
|
|
|
|
return groups
|