#!/usr/bin/python3 # This file is part of DarkFi (https://dark.fi) # # Copyright (C) 2020-2024 Dyne.org foundation # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU Affero General Public License as # published by the Free Software Foundation, either version 3 of the # License, or (at your option) any later version. # # This program 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 Affero General Public License for more details. # # You should have received a copy of the GNU Affero General Public License # along with this program. If not, see . import asyncio, random, re, sys, base58, json import base58 import urwid as u import networkx as nx # import matplotlib.pyplot as plt import src.util from os.path import join # # Create a directed graph # dag = nx.DiGraph() # # Add edges to the graph (this also adds nodes) # dag.add_edges_from([ # ("root", "a"), # ("a", "b"), # ("a", "e"), # ("b", "c"), # ("b", "d"), # ("d", "e") # ]) class JsonRpc: async def start(self, server, port): reader, writer = await asyncio.open_connection(server, port, limit=1024 * 128) self.reader = reader self.writer = writer async def stop(self): self.writer.close() await self.writer.wait_closed() async def _make_request(self, method, params): ident = random.randint(0, 2**16) #print(ident) request = { "jsonrpc": "2.0", "method": method, "params": params, "id": ident, } message = json.dumps(request) + "\n" self.writer.write(message.encode()) await self.writer.drain() data = await self.reader.readline() message = data.decode().strip() response = json.loads(message) #print(response) return response async def _subscribe(self, method, params): ident = random.randint(0, 2**16) request = { "jsonrpc": "2.0", "method": method, "params": params, "id": ident, } message = json.dumps(request) + "\n" self.writer.write(message.encode()) await self.writer.drain() #print("Subscribed") async def ping(self): return await self._make_request("ping", []) async def dnet_switch(self, state): return await self._make_request("dnet.switch", [state]) async def dnet_subscribe_events(self): return await self._subscribe("dnet.subscribe_events", []) async def deg_switch(self, state): return await self._make_request("deg.switch", [state]) class ListItem(u.WidgetWrap): def __init__ (self, event): self.content = event layer_num = int(event["layer"]) layer = "layer " + str(layer_num) if layer_num != 0 else "genesis" t = u.AttrWrap(u.Text(layer), "event", "event_selected") u.WidgetWrap.__init__(self, t) def selectable (self): return True def keypress(self, size, key): return key class ListView(u.WidgetWrap): def __init__(self): u.register_signal(self.__class__, ['show_details']) self.walker = u.SimpleFocusListWalker([]) lb = u.ListBox(self.walker) u.WidgetWrap.__init__(self, lb) def modified(self): focus_w, _ = self.walker.get_focus() u.emit_signal(self, 'show_details', focus_w.content) def set_data(self, events): events_widgets = [ListItem(e) for e in events] u.disconnect_signal(self.walker, 'modified', self.modified) while len(self.walker) > 0: self.walker.pop() self.walker.extend(events_widgets) u.connect_signal(self.walker, "modified", self.modified) self.walker.set_focus(0) class DetailView(u.WidgetWrap): def __init__ (self): t = u.Text("") u.WidgetWrap.__init__(self, t) def set_event(self, c): s = f'Hash: {c["hash"]}\nChildren: {c["children"]}\nContent: {c["content"]}\nLayer: {c["layer"]}' self._w.set_text(s) class App(object): def unhandled_input(self, key): if key in ('q',): raise u.ExitMainLoop() def show_details(self, event): self.detail_view.set_event(event) def __init__(self): self.palette = { ("bg", "white", "black"), ("event", "white", "black"), ("event_selected", "white", "yellow"), ("footer", "white, bold", "dark red") } self.list_view = ListView() self.detail_view = DetailView() u.connect_signal(self.list_view, 'show_details', self.show_details) footer = u.AttrWrap(u.Text(" Q to exit"), "footer") col_rows = u.raw_display.Screen().get_cols_rows() h = col_rows[0] - 2 f1 = u.Filler(self.list_view, valign='top', height=h) f2 = u.Filler(self.detail_view, valign='top') c_list = u.LineBox(f1, title="Layers") c_details = u.LineBox(f2, title="Details") columns = u.Columns([('weight', 15, c_list), ('weight', 85, c_details)]) frame = u.AttrMap(u.Frame(body=columns, footer=footer), 'bg') self.loop = u.MainLoop(frame, self.palette, unhandled_input=self.unhandled_input) async def update_data(self, config): host = config['host'] port = config['port'] rpc = JsonRpc() while True: try: await rpc.start(host, port) break except OSError: print("Error: Couldn't connent to rpc") exit(-1) await rpc.deg_switch(True) await rpc.deg_switch(False) json_result = await rpc._make_request("eventgraph.get_info", []) if json_result['result']['eventgraph_info']: dag_dict = json_result['result']['eventgraph_info']['dag'] dag_list = list(dag_dict.items()) # sorted_dag = sorted(dag_list, key=lambda x:x[1]['layer']) # genesis_hash = sorted_dag[0][0] parent_child_pairs = [] for item in dag_list: parents = item[1]['parents'] child = item[0] for parent in parents: if parent == '0' * 64: continue parent_child_pairs.append((parent, child)) # Create a directed graph dag = nx.DiGraph() # Add edges to the graph (this also adds nodes) dag.add_edges_from(parent_child_pairs) l = [] topological_order = list(nx.topological_sort(dag)) for node in topological_order: event_details = dag_dict.get(node) # details layer = int(event_details['layer']) content = event_details['content'] # event content # print(content) pattern = r'\\x[0-9A-Fa-f]{2}' decoded_str = str(base58.b58decode(content)) matches = re.split(pattern, decoded_str) children = dag.successors(node) l.append({"layer":f"{layer}", "hash":f"{node}", "children":f"{list(children)}", "content":f"{matches[1:]}"}) self.list_view.set_data(l) async def start(self, config): await self.update_data(config) self.loop.run() async def main(argv): os = src.util.get_os() config_path = src.util.user_config_dir('darkfi', os) suffix = '.toml' filename = 'deg_config' path = join(config_path, filename + suffix) config = src.util.spawn_config(path) config = config['nodes'][0] if len(argv) > 1: if argv[1] in ['darkirc', 'irc']: config['port'] = 26660 elif argv[1] in ['taud', 'tau']: config['port'] = 23330 app = App() await app.start(config) asyncio.run(main(sys.argv))