Skip to content
Snippets Groups Projects
Commit 9eb69728 authored by Vincent Texier's avatar Vincent Texier
Browse files

[enh] #141 add get_available_nodes helper

Fix peer typing
Add Peer.from_bma method
parent a9d8ab0a
No related branches found
No related tags found
2 merge requests!128Release 0.62.0,!121Helper available nodes
Pipeline #10786 waiting for manual action
...@@ -149,3 +149,19 @@ Endpoints: ...@@ -149,3 +149,19 @@ Endpoints:
doc += "{0}\n".format(_endpoint.inline()) doc += "{0}\n".format(_endpoint.inline())
return doc return doc
@classmethod
def from_bma(cls: Type[PeerType], data: dict) -> PeerType:
# get Peer Document from bma dict
version = data["version"]
currency = data["currency"]
pubkey = data["pubkey"]
block_uid = BlockUID.from_str(data["block"])
endpoints = []
for _endpoint in data["endpoints"]:
endpoints.append(endpoint(_endpoint))
signature = str(Peer.re_signature.match(data["signature"]))
return cls(version, currency, pubkey, block_uid, endpoints, signature)
"""
Copyright 2014-2020 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 typing import List, Dict, Any
from duniterpy.api import bma
from duniterpy.api.client import Client
from duniterpy.documents.peer import Peer, MalformedDocumentError
from duniterpy.documents.ws2p.heads import HeadV2
from itertools import groupby
from duniterpy.key import VerifyingKey
async def get_available_nodes(client: Client) -> List[List[Dict[str, Any]]]:
"""
Get available nodes grouped and sorted by descending blockstamp
Each entry is a list of nodes (HeadV2 instance, inline endpoint list) sharing the same blockstamp:
[
[{"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, GVA and GVASUB endpoint are selected
and only those endpoints are available in the endpoint list
:param client: Client instance
:return:
"""
# capture heads and peers
heads_response = await client(bma.network.ws2p_heads)
peers_response = await 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 blockstamp by descending order
heads = sorted(heads, key=lambda x: x.blockstamp, reverse=True)
# group heads by blockstamp
groups = []
for _, group in groupby(heads, key=lambda x: x.blockstamp):
nodes = []
for head in list(group):
# if head signature not valid...
if VerifyingKey(head.pubkey).verify_ws2p_head(head) 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.signatures = [bma_peer["signature"]]
# if peer signature not valid
if VerifyingKey(head.pubkey).verify_document(peer) is False:
# skip this node
continue
# filter endpoints to get only BMAS, BASIC_MERKLED_API, GVA or GVASUB
endpoints = [
endpoint
for endpoint in bma_peers[0]["endpoints"]
if endpoint.startswith("BMAS")
or endpoint.startswith("BASIC_MERKLED_API")
or endpoint.startswith("GVA")
or endpoint.startswith("GVASUB")
]
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
"""
Copyright 2014-2020 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/>.
"""
import asyncio
from duniterpy.helpers import network
from duniterpy.api.client import Client
# CONFIG #######################################
# You can either use a complete defined endpoint : [NAME_OF_THE_API] [DOMAIN] [IPv4] [IPv6] [PORT] [PATH]
# or the simple definition : [NAME_OF_THE_API] [DOMAIN] [PORT] [PATH]
# Here we use the secure BASIC_MERKLED_API (BMAS)
BMAS_ENDPOINT = "BMAS g1-test.duniter.org 443"
BMAS_ENDPOINT = "BMAS g1.duniter.org 443"
################################################
async def main():
"""
Main code
"""
# Create Client from endpoint string in Duniter format
client = Client(BMAS_ENDPOINT)
groups = await network.get_available_nodes(client)
for group in groups:
block = group[0]["head"].blockstamp
print(f"block {block} shared by {len(group)} nodes")
print("\nAvailable endpoints:")
for node in groups[0]:
for endpoint in node["endpoints"]:
print(endpoint)
# Close client aiohttp session
await client.close()
# Latest duniter-python-api is asynchronous and you have to use asyncio, an asyncio loop and a "as" on the data.
# ( https://docs.python.org/3/library/asyncio.html )
asyncio.get_event_loop().run_until_complete(main())
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment