Mise à jour de GitLab prévue ce dimanche 6 décembre 2020 à partir de 9h00 CEST | GitLab upgrade planned this Sunday December 6th 2020 from 9:00 AM CEST

Commit fbfccc49 authored by Moul's avatar Moul

Public availability for Silkiaj:

- Command line client for Duniter network.
- Four commands:
 - Currency information.
 - Proof-of-Work difficulties.
 - Network information.
 - List issuers.
- Node requested as an option.
parents
# ---> Python
# Byte-compiled / optimized / DLL files
__pycache__/
*.py[cod]
*$py.class
# C extensions
*.so
# Distribution / packaging
.Python
env/
build/
develop-eggs/
dist/
downloads/
eggs/
.eggs/
lib/
lib64/
parts/
sdist/
var/
*.egg-info/
.installed.cfg
*.egg
# PyInstaller
# Usually these files are written by a python script from a template
# before PyInstaller builds the exe, so as to inject date/other infos into it.
*.manifest
*.spec
# Installer logs
pip-log.txt
pip-delete-this-directory.txt
# Unit test / coverage reports
htmlcov/
.tox/
.coverage
.coverage.*
.cache
nosetests.xml
coverage.xml
*,cover
# Translations
*.mo
*.pot
# Django stuff:
*.log
# Sphinx documentation
docs/_build/
# PyBuilder
target/
# Idea
.idea
# Vim swap files
*~
*.swp
*.swo
This diff is collapsed.
# Silkaj
CLI Duniter client written with Python 3.
## Dependencies
```bash
sudo pip3 install requirements.txt
```
## Usage
- Get help usage with `-h`, `--help` or `--usage` options, then run:
```bash
./src/silkaj.py <subcommand>
```
- Will automatically request `duniter.org 8999` node.
- Specify a custom node with `--peer` or `-p` option:
```bash
./src/silkaj.py <subcommand> --peer <address>:<port>
```
## Features
- Currency information
- Proof-of-Work difficulties
- network information tab:
- endpoints, ip6, ip4, domain name and port. Handle nodes sharing same pubkey.
- pubkey, uid, if member.
- Proof-of-Work difficulties.
- generated block and median times.
- nodes versions.
- current block n°, hash and time.
- Issuers last ones or all
### Names
I wanted to call that program:
- bamiyan
- margouillat
- lsociety
- cashmere
import datetime
from tabulate import tabulate
from operator import itemgetter
from network_tools import *
from tools import *
def currency_info(ep):
info_type = ["newcomers", "certs", "actives", "leavers", "excluded", "ud", "tx"]
i, info_data = 0, dict()
while (i < len(info_type)):
info_data[info_type[i]] = request(ep, "blockchain/with/" + info_type[i])["result"]["blocks"]
i +=1
current = get_current_block(ep)
print("Connected to node:", ep[best_node(ep, 1)], ep["port"],
"\nCurrent block number:", current["number"],
"\nCurrency name:", current["currency"],
"\nNumber of members:", current["membersCount"],
"\nMinimal Proof-of-Work:", current["powMin"],
"\nCurrent time:", convert_time(current["time"], "all"),
"\nMedian time:", convert_time(current["medianTime"], "all"),
"\nDifference time:", convert_time(current["time"] - current["medianTime"], "hour"),
"\nNumber of blocks containing: \
\n- new comers:", len(info_data["newcomers"]),
"\n- Certifications:", len(info_data["certs"]),
"\n- Actives (members updating their membership):", len(info_data["actives"]),
"\n- Leavers:", len(info_data["leavers"]),
"\n- Excluded:", len(info_data["excluded"]),
"\n- UD created:", len(info_data["ud"]),
"\n- transactions:", len(info_data["tx"]))
def difficulties(ep):
current = get_current_block(ep)
powmin, match = int(current["powMin"]), "`"
while powmin > 0:
if powmin >= 16: match += "0"; powmin -= 16
else:
end = "[0-" + hex(15 - powmin)[2:].upper() + "]"
match += end; powmin = 0
match += "*`"
diffi = request(ep, "blockchain/difficulties")
sorted_diffi = sorted(diffi["levels"], key=itemgetter("level"))
print("Minimal Proof-of-Work:", current["powMin"], "to match", match)
print("\n### Difficulty to generate next block", diffi["block"], "for", len(diffi["levels"]), "nodes:")
print(tabulate(sorted_diffi, headers="keys", tablefmt="orgtbl"))
def network_info(ep, columns):
endpoints = discover_peers(ep)
### Todo : renommer endpoints en info ###
diffi = request(ep, "blockchain/difficulties")
i, members = 0, 0
while (i < len(endpoints)):
try:
endpoints[i]["uid"] = get_uid_from_pubkey(ep, endpoints[i]["pubkey"])
endpoints[i]["member"] = "yes"; members+=1
except: pass
if endpoints[i].get("member") is None: endpoints[i]["member"] = "no"
endpoints[i]["pubkey"] = endpoints[i]["pubkey"][:5] + "…"
### Todo: request difficulty from node point of view: two nodes with same pubkey id could be on diffrent branches and have different difficulties ###
# diffi = request(endpoints[i], "blockchain/difficulties") # super long, doit être requetté uniquement pour les nœuds d’une autre branche
for d in diffi["levels"]:
if endpoints[i].get("uid") is not None:
if endpoints[i]["uid"] == d["uid"]:
endpoints[i]["diffi"] = d["level"]
if len(endpoints[i]["uid"]) > 10:
endpoints[i]["uid"] = endpoints[i]["uid"][:9] + "…"
current_blk = get_current_block(endpoints[i])
endpoints[i]["gen_time"] = convert_time(current_blk["time"], "hour")
if columns > 171: endpoints[i]["mediantime"] = convert_time(current_blk["medianTime"], "hour")
if columns > 185: endpoints[i]["difftime"] = convert_time(current_blk["time"] - current_blk["medianTime"], "hour")
endpoints[i]["block"] = current_blk["number"]
endpoints[i]["hash"] = current_blk["hash"][:10] + "…"
endpoints[i]["version"] = request(endpoints[i], "node/summary")["duniter"]["version"]
if endpoints[i].get("domain") is not None and len(endpoints[i]["domain"]) > 20:
endpoints[i]["domain"] = "…" + endpoints[i]["domain"][-20:]
if endpoints[i].get("ip6") is not None:
if columns < 156: endpoints[i].pop("ip6")
else: endpoints[i]["ip6"] = endpoints[i]["ip6"][:8] + "…"
i+=1
print("###", len(endpoints), "peers ups, with", members, "members and", len(endpoints) - members, "non-members\n")
### Todo: keep same columns order: issue on tabulate bitbucket ###
## Todo: too much data which could not be displayed on small wide screens: wide terminal could be gather to only display more important data #
print(tabulate(endpoints, headers="keys", tablefmt="orgtbl"))
def list_issuers(ep, nbr, last):
current_nbr = get_current_block(ep)["number"]
if nbr == 0: nbr = current_nbr
blk_nbr, list_issuers = current_nbr, list()
while (blk_nbr + nbr > current_nbr):
issuer, issuer["block"] = dict(), blk_nbr
issuer["pubkey"] = request(ep, "blockchain/block/" + str(blk_nbr))["issuer"]
blk_nbr-=1
list_issuers.append(issuer)
for issuer in list_issuers:
uid = get_uid_from_pubkey(ep, issuer["pubkey"])
for issuer2 in list_issuers:
if issuer2.get("pubkey") is not None and issuer.get("pubkey") is not None and \
issuer2["pubkey"] == issuer["pubkey"]:
issuer2["uid"] = uid
issuer2.pop("pubkey")
print("### Issuers for last", nbr, "blocks from block n°", current_nbr - nbr, "to block n°", current_nbr)
if last or nbr <= 30:
print(tabulate(list_issuers, headers="keys", tablefmt="orgtbl"))
else:
## todo: requêttes possiblement bloquées par l’anti-spam DDOS des nœuds, faudrait passer en P2P: récupérer 200 blocs par nœuds.
i, list_issued = 0, list()
while i < len(list_issuers):
j, found = 0, 0
while j < len(list_issued):
if list_issued[j].get("uid") is not None and \
list_issued[j]["uid"] == list_issuers[i]["uid"]:
list_issued[j]["blocks"] += 1
found = 1; break
j+=1
if found == 0:
issued = dict()
issued["uid"] = list_issuers[i]["uid"]
issued["blocks"] = 1
list_issued.append(issued)
i+=1
i = 0
while i < len(list_issued):
list_issued[i]["percent"] = list_issued[i]["blocks"] / nbr * 100
i+=1
sorted_list = sorted(list_issued, key=itemgetter("blocks"), reverse=True)
print(tabulate(sorted_list, headers="keys", tablefmt="orgtbl", floatfmt=".1f"))
from __future__ import unicode_literals
import ipaddress
import json
import socket
import urllib.request
def discover_peers(ep):
peers = request(ep, "network/peers")["peers"]
endpoints = parse_endpoints(peers)
# Todo: discover network some nodes are missing
### Todo: search for other nodes asking on other nodes because nodes are missing comperated to Sakia ###
return (endpoints)
def parse_endpoints(rep):
"""
endpoints[]{"domain", "ip4", "ip6", "pubkey"}
list of dictionaries
reps: raw endpoints
"""
i, j, endpoints = 0, 0, []
while (i < len(rep)):
if (rep[i]["status"] == "UP"):
while j < len(rep[i]["endpoints"]):
ep = parse_endpoint(rep[i]["endpoints"][j])
ep["pubkey"] = rep[i]["pubkey"]
j+=1
endpoints.append(ep)
i+=1; j = 0
return (endpoints)
def parse_endpoint(rep):
"""
rep: raw endpoint, sep: split endpoint
domain, ip4 or ip6 could miss on raw endpoint
"""
ep, sep = {}, rep.split(" ")
if sep[0] == "BASIC_MERKLED_API":
if check_port(sep[-1]): ep["port"] = sep[-1]
if len(sep) == 5 and check_ip(sep[1]) == 0 and check_ip(sep[2]) == 4 and check_ip(sep[3]) == 6:
ep["domain"], ep["ip4"], ep["ip6"] = sep[1], sep[2], sep[3]
if len(sep) == 4:
ep = endpoint_type(sep[1], ep)
ep = endpoint_type(sep[2], ep)
if len(sep) == 3:
ep = endpoint_type(sep[1], ep)
return (ep)
def endpoint_type(sep, ep):
typ = check_ip(sep)
if typ == 0: ep["domain"] = sep
if typ == 4: ep["ip4"] = sep
if typ == 6: ep["ip6"] = sep
return (ep)
def check_ip(address):
try: return (ipaddress.ip_address(address).version)
except: return (0)
def request(ep, path):
url = "http://" + ep[best_node(ep, 0)] + ":" + ep["port"] + "/" + path
request = urllib.request.Request(url)
response = urllib.request.urlopen(request)
encoding = response.info().get_content_charset('utf8')
return (json.loads(response.read().decode(encoding)))
def best_node(ep, main):
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
addresses, port = {"ip6", "ip4", "domain"}, int(ep["port"])
for address in addresses:
if address in ep:
try:
s.connect((ep[address], port))
return (address)
except: pass
s.close()
print("exit") # debug
if main: exit()
# Sometimes, the programm display nothing. It's due to this exit()
# Found a solution to display an error message when main node address is wrong
def check_port(port):
try:
port = int(port)
except:
print("Port must be an integer"); exit()
if (port < 0 or port > 65536):
print("Wrong port number"); exit()
return (1)
#!/usr/bin/env python3
import os
from commandlines import Command
from commands import *
def cli():
# ep: endpoint, node's network interface
ep, c = dict(), Command()
ep["domain"], ep["port"] = "duniter.org", "8999"
try: ep["domain"], ep["port"] = c.get_definition('p').split(':')
except: pass
try: ep["domain"], ep["port"] = c.get_definition('peer').split(':')
except: pass
if c.is_help_request() or c.is_usage_request(): usage()
if c.is_version_request(): print("silkaj 0.1.0")
return (ep, c)
def manage_cmd(ep, c):
if c.subcmd == "info":
currency_info(ep)
elif c.subcmd == "diffi":
difficulties(ep)
elif c.subcmd == "network":
rows, columns = os.popen('stty size', 'r').read().split()
# print(rows, columns) # debug
if int(columns) >= 146: network_info(ep, int(columns))
else: print("Current wide screen need to be larger than 146. Current wide:", columns)
elif c.subcmd == "issuers" and c.subsubcmd and int(c.subsubcmd) >= 0:
list_issuers(ep, int(c.subsubcmd), c.contains_switches('last'))
else: usage()
def usage():
print("Silkaj: command line Duniter client \
\n\nhelp: -h, --help, --usage \
\nversion: -v, --version \
\ncommands: \
\n - info: display information about currency \
\n - diffi: list proof-of-work difficulty to generate next block \
\n - network: display current network with many information \
\n - issuers n: display last n issuers (`0` for all blockchain) \
\n - last issuers are displayed under n <= 30. To force display last ones, use `--last` option \
\n - members: list members \
\ncustom endpoint with options `-p` or `--peer` and <domain>:<port>")
exit()
if __name__ == '__main__':
os.system("clear")
ep, c = cli()
check_port(ep["port"])
best_node(ep, 1)
manage_cmd(ep, c)
import datetime
from network_tools import *
def convert_time(timestamp, kind):
ts = int(timestamp)
if kind == "all": pattern = "%Y-%m-%d %H:%M:%S"
elif kind == "hour":
if ts >= 3600: pattern = "%H:%M:%S"
else: pattern = "%M:%S"
return (datetime.datetime.fromtimestamp(ts).strftime(pattern))
def get_uid_from_pubkey(ep, pubkey):
i, results = 0, request(ep, "wot/lookup/" + pubkey)["results"]
while (i < len(results)):
if results[i]["uids"][0]["uid"] != pubkey:
return (results[i]["uids"][0]["uid"])
i+=1
def get_current_block(ep):
return (request(ep, "blockchain/current"))
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment