deg2 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323
  1. #!/usr/bin/python3
  2. # This file is part of DarkFi (https://dark.fi)
  3. #
  4. # Copyright (C) 2020-2024 Dyne.org foundation
  5. #
  6. # This program is free software: you can redistribute it and/or modify
  7. # it under the terms of the GNU Affero General Public License as
  8. # published by the Free Software Foundation, either version 3 of the
  9. # License, or (at your option) any later version.
  10. #
  11. # This program is distributed in the hope that it will be useful,
  12. # but WITHOUT ANY WARRANTY; without even the implied warranty of
  13. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  14. # GNU Affero General Public License for more details.
  15. #
  16. # You should have received a copy of the GNU Affero General Public License
  17. # along with this program. If not, see <https://www.gnu.org/licenses/>.
  18. import asyncio, random, re, sys, base58, json
  19. import base58
  20. import urwid as u
  21. import networkx as nx
  22. from datetime import datetime
  23. # import matplotlib.pyplot as plt
  24. import src.util
  25. from os.path import join
  26. resolved = True
  27. class JsonRpc:
  28. async def start(self, server, port):
  29. reader, writer = await asyncio.open_connection(server, port, limit=1024 * 128)
  30. self.reader = reader
  31. self.writer = writer
  32. async def stop(self):
  33. self.writer.close()
  34. await self.writer.wait_closed()
  35. async def _make_request(self, method, params):
  36. ident = random.randint(0, 2**16)
  37. #print(ident)
  38. request = {
  39. "jsonrpc": "2.0",
  40. "method": method,
  41. "params": params,
  42. "id": ident,
  43. }
  44. message = json.dumps(request) + "\n"
  45. self.writer.write(message.encode())
  46. await self.writer.drain()
  47. data = await self.reader.readline()
  48. message = data.decode().strip()
  49. response = json.loads(message)
  50. #print(response)
  51. return response
  52. async def _subscribe(self, method, params):
  53. ident = random.randint(0, 2**16)
  54. request = {
  55. "jsonrpc": "2.0",
  56. "method": method,
  57. "params": params,
  58. "id": ident,
  59. }
  60. message = json.dumps(request) + "\n"
  61. self.writer.write(message.encode())
  62. await self.writer.drain()
  63. #print("Subscribed")
  64. async def ping(self):
  65. return await self._make_request("ping", [])
  66. async def dnet_switch(self, state):
  67. return await self._make_request("dnet.switch", [state])
  68. async def dnet_subscribe_events(self):
  69. return await self._subscribe("dnet.subscribe_events", [])
  70. async def deg_switch(self, state):
  71. return await self._make_request("deg.switch", [state])
  72. def check_multi_ev_in_layer(dag_dict, dag):
  73. gen = list(nx.topological_generations(dag))
  74. for i in gen:
  75. # fork
  76. if len(i) == 2:
  77. event1 = dag_dict[i[0]]
  78. event2 = dag_dict[i[1]]
  79. if int(event1['timestamp']) > int(event2['timestamp']):
  80. return i[0]
  81. else:
  82. return i[1]
  83. def graph(event, dag, x):
  84. global resolved
  85. merge = len(list(dag.predecessors(event['hash']))) > 1
  86. fork = len(list(dag.successors(event['hash']))) > 1
  87. if merge and not fork:
  88. resolved = False
  89. return " M━┑"
  90. if fork and not merge:
  91. resolved = True
  92. return " o─┘"
  93. if merge and fork:
  94. return " M━┪"
  95. if not merge and not fork:
  96. if event['hash'] == x:
  97. return " │ o"
  98. else:
  99. if not resolved:
  100. return " o │"
  101. else:
  102. return " o "
  103. # " │ o"
  104. # " o │"
  105. # " o━┪"
  106. # " o─┴"
  107. # " o━┯"
  108. class ListItem(u.WidgetWrap):
  109. def __init__ (self, event, dag, x):
  110. g = graph(event, dag, x)
  111. self.content = event
  112. layer_num = int(event["layer"])
  113. layer = "layer " + str(layer_num) if layer_num != 0 else "genesis"
  114. dt = datetime.fromtimestamp(int(event['timestamp']))
  115. t = u.AttrMap(u.Text([('word', str(dt)),
  116. ('committer', " " + layer),
  117. ('word', g),
  118. ('cont', event['content'])], wrap="ellipsis"),
  119. {'word':'datetime', 'committer': 'reporter', 'cont': 'content'},
  120. {'word':'event_selected', 'committer': 'event_selected', 'cont': 'event_selected'})
  121. u.WidgetWrap.__init__(self, t)
  122. def selectable (self):
  123. return True
  124. def keypress(self, size, key):
  125. return key
  126. class ListView(u.WidgetWrap):
  127. def __init__(self):
  128. u.register_signal(self.__class__, ['show_details'])
  129. self.walker = u.SimpleFocusListWalker([])
  130. lb = u.ListBox(self.walker)
  131. u.WidgetWrap.__init__(self, lb)
  132. def modified(self):
  133. focus_w, _ = self.walker.get_focus()
  134. u.emit_signal(self, 'show_details', focus_w.content)
  135. def set_data(self, events, dag, x):
  136. events_widgets = [ListItem(e, dag, x) for e in events]
  137. u.disconnect_signal(self.walker, 'modified', self.modified)
  138. while len(self.walker) > 0:
  139. self.walker.pop()
  140. self.walker.extend(events_widgets)
  141. u.connect_signal(self.walker, "modified", self.modified)
  142. self.walker.set_focus(0)
  143. class DetailView(u.WidgetWrap):
  144. def __init__ (self):
  145. t = u.Text("")
  146. u.WidgetWrap.__init__(self, t)
  147. def set_event(self, c):
  148. s = f'Hash: {c["hash"]}\nChildren: {c["children"]}\nContent: {c["content"]}\nLayer: {c["layer"]}'
  149. self._w.set_text(s)
  150. class App(object):
  151. def unhandled_input(self, key):
  152. if key in ('q',):
  153. raise u.ExitMainLoop()
  154. # if key == 'r':
  155. # await self.update_data(self.config)
  156. if key == 'enter':
  157. self.current_view = self.frame2
  158. if key == 'b':
  159. self.current_view = self.frame1
  160. def show_details(self, event):
  161. self.view_two.set_event(event)
  162. def __init__(self):
  163. self.view_one = ListView()
  164. u.connect_signal(self.view_one, 'show_details', self.show_details)
  165. footer = u.AttrWrap(u.Text(" Q to exit"), "footer")
  166. col_rows = u.raw_display.Screen().get_cols_rows()
  167. h = col_rows[0] - 2
  168. f1 = u.Filler(self.view_one, valign='top', height=h)
  169. c_list = u.LineBox(f1, title="Layers")
  170. columns = u.Columns([('weight', 100, c_list)])
  171. frame1 = u.AttrMap(u.Frame(body=columns, footer=footer), 'bg')
  172. self.frame1 = frame1
  173. ############
  174. # content = self.content
  175. # print(content)
  176. self.view_two = DetailView()
  177. # self.detail_view = DetailView()
  178. f2 = u.Filler(self.view_two, valign='top')
  179. c_details = u.LineBox(f2, title="Details")
  180. columns = u.Columns([('weight', 100, c_details)])
  181. frame2 = u.AttrMap(u.Frame(body=columns), 'bg')
  182. self.frame2 = frame2
  183. ##########
  184. self.current_view = self.frame1 # Start with View One
  185. self.palette = {
  186. ("bg", "white", "black"),
  187. ("event", "white", "black"),
  188. ("event_selected", "white", "light green"),
  189. ('datetime', "light blue", "black"),
  190. ('reporter', "dark green", "black"),
  191. ('content', "", "black"),
  192. ("footer", "white, bold", "dark red")
  193. }
  194. def build_main(self):
  195. return self.current_view
  196. async def update_data(self, config):
  197. self.config = config
  198. dag_dict = await recreate_dag(config)
  199. dag_list = list(dag_dict.items())
  200. # sorted_dag = sorted(dag_list, key=lambda x:x[1]['layer'])
  201. # genesis_hash = sorted_dag[0][0]
  202. parent_child_pairs = []
  203. for item in dag_list:
  204. parents = item[1]['parents']
  205. child = item[0]
  206. for parent in parents:
  207. if parent == '0' * 64:
  208. continue
  209. parent_child_pairs.append((parent, child))
  210. # Create a directed graph
  211. dag = nx.DiGraph()
  212. # Add edges to the graph (this also adds nodes)
  213. dag.add_edges_from(parent_child_pairs)
  214. l = []
  215. topological_order = list(nx.topological_sort(dag))
  216. for node in reversed(topological_order):
  217. event_details = dag_dict.get(node) # details
  218. layer = int(event_details['layer'])
  219. content = event_details['content'] # event content
  220. timestamp = event_details['timestamp']
  221. pattern = r'\\x[0-9A-Fa-f]{2}'
  222. decoded_str = str(base58.b58decode(content))
  223. matches = re.sub(pattern, ' ', decoded_str).replace("b'", "")[:-1]
  224. children = list(dag.successors(node))
  225. l.append({"layer":f"{layer}", "hash":f"{node}", "children":children, "content":f"{matches}", "timestamp": f"{timestamp}"})
  226. x = check_multi_ev_in_layer(dag_dict, dag)
  227. self.view_one.set_data(l, dag, x)
  228. async def start(self, config):
  229. # main_frame = self.build_main()
  230. await self.update_data(config)
  231. self.loop = u.MainLoop(self.current_view, self.palette, unhandled_input=self.unhandled_input)
  232. self.loop.run()
  233. async def recreate_dag(config):
  234. host = config['host']
  235. port = config['port']
  236. rpc = JsonRpc()
  237. while True:
  238. try:
  239. await rpc.start(host, port)
  240. break
  241. except OSError:
  242. pass
  243. await rpc.deg_switch(True)
  244. await rpc.deg_switch(False)
  245. json_result = await rpc._make_request("eventgraph.get_info", [])
  246. if json_result['result']['eventgraph_info']:
  247. return json_result['result']['eventgraph_info']['dag']
  248. async def main(argv):
  249. os = src.util.get_os()
  250. config_path = src.util.user_config_dir('darkfi', os)
  251. suffix = '.toml'
  252. filename = 'deg_config'
  253. path = join(config_path, filename + suffix)
  254. config = src.util.spawn_config(path)
  255. config = config['nodes'][0]
  256. if len(argv) > 1:
  257. if argv[1] in ['darkirc', 'irc']:
  258. config['port'] = 26660
  259. elif argv[1] in ['taud', 'tau']:
  260. config['port'] = 23330
  261. app = App()
  262. await app.start(config)
  263. asyncio.run(main(sys.argv))