| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254 |
- #!/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 <https://www.gnu.org/licenses/>.
- import asyncio, re, sys, base58
- import urwid as u
- import networkx as nx
- from datetime import datetime
- # import matplotlib.pyplot as plt
- import src.rpc
- import src.util
- from os.path import join
- # this is counter-intuitive because the dag is reversed
- resolved = True
- def graph(event, dag, longest_path):
- global resolved
- merge = len(list(dag.predecessors(event['hash']))) > 1
- fork = len(list(dag.successors(event['hash']))) > 1
-
- if merge and not fork:
- resolved = False
- return "M━┑"
- if fork and not merge:
- resolved = True
- return "o─┘"
- if merge and fork:
- return "M━┪"
- if not merge and not fork:
- if not resolved:
- if event['hash'] in longest_path:
- return "o │"
- else:
- return "│ o"
- else:
- return "o "
- # "│ o"
- # "o │"
- # "o━┪"
- # "o─┴"
- # "o━┯"
- # because tab character is broken in urwid texts
- def indent(num):
- return " " * (8 - len(str(num)))
- class ListItem(u.WidgetWrap):
-
- def __init__ (self, event, dag, longest_path):
- g = graph(event, dag, longest_path)
- self.content = event
- layer_num = int(event["layer"])
- layer = "layer " + str(layer_num) + indent(layer_num) if layer_num != 0 else "genesis "
- dt = event['hash'][:10] + " │ " + str(datetime.fromtimestamp(int(event['timestamp'])))
- t = u.AttrMap(u.Text([('word', dt),
- ('layer-num', " " + layer),
- ('word', g),
- ('cont', event['content'])], wrap="ellipsis"),
- {'word':'datetime', 'layer-num': 'reporter', 'cont': 'content'},
- {'word':'event_selected', 'layer-num': 'event_selected', 'cont': '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, dag, longest_path):
- events_widgets = [ListItem(e, dag, longest_path) 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"]}\nParents: {c["parents"]}\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()
- # if key == 'r':
- # await self.update_data(self.config)
- if key == 'enter':
- self.current_view = self.frame2
- self.loop.widget = self.frame2
- if key == 'b':
- self.current_view = self.frame1
- self.loop.widget = self.frame1
- def show_details(self, event):
- self.view_two.set_event(event)
-
- def __init__(self):
- self.view_one = ListView()
- u.connect_signal(self.view_one, '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.view_one, valign='top', height=h)
- c_list = u.LineBox(f1, title="Events")
- columns = u.Columns([('weight', 100, c_list)])
- frame1 = u.AttrMap(u.Frame(body=columns, footer=footer), 'bg')
- self.frame1 = frame1
- ############
- self.view_two = DetailView()
- f2 = u.Filler(self.view_two, valign='top')
- c_details = u.LineBox(f2, title="Details")
- footer = u.AttrWrap(u.Text(" Q to exit, B to main view"), "footer")
- columns = u.Columns([('weight', 100, c_details)])
- frame2 = u.AttrMap(u.Frame(body=columns, footer=footer), 'bg')
- self.frame2 = frame2
- ##########
- self.current_view = self.frame1 # Start with View One
- self.palette = {
- ("bg", "white", "black"),
- ("event", "white", "black"),
- ("event_selected", "white", "light green"),
- ('datetime', "light blue", "black"),
- ('reporter', "dark green", "black"),
- ('content', "", "black"),
- ("footer", "white, bold", "dark red")
- }
-
- async def update_data(self, config):
- self.config = config
- dag_dict = await recreate_dag(config)
- 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 reversed(topological_order):
- event_details = dag_dict.get(node) # details
- layer = int(event_details['layer'])
- content = event_details['content'] # event content
- timestamp = event_details['timestamp']
- children = list(dag.successors(node))
- parents = list(dag.predecessors(node))
- pattern = r'\\x[0-9A-Fa-f]{2}'
- decoded_str = str(base58.b58decode(content))
- matches = re.sub(pattern, ' ', decoded_str).replace("b'", "")[:-1]
-
- l.append({"layer":f"{layer}", "hash":f"{node}", "children":children, "parents":parents, "content":f"{matches}", "timestamp": f"{timestamp}"})
- longest_path = nx.dag_longest_path(dag)
- self.view_one.set_data(l, dag, longest_path)
- async def start(self, config):
- await self.update_data(config)
- self.loop = u.MainLoop(self.current_view, self.palette, unhandled_input=self.unhandled_input)
- self.loop.run()
- async def recreate_dag(config):
- host = config['host']
- port = config['port']
- rpc = src.rpc.JsonRpc()
- while True:
- try:
- await rpc.start(host, port)
- break
- except OSError:
- print(f"Error: Connection Refused to '{host}:{port}', Either because the daemon is down, is currently syncing or wrong url.")
- sys.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']:
- return json_result['result']['eventgraph_info']['dag']
-
- 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))
|