deg2 8.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257
  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. # import matplotlib.pyplot as plt
  23. import src.util
  24. from os.path import join
  25. # # Create a directed graph
  26. # dag = nx.DiGraph()
  27. # # Add edges to the graph (this also adds nodes)
  28. # dag.add_edges_from([
  29. # ("root", "a"),
  30. # ("a", "b"),
  31. # ("a", "e"),
  32. # ("b", "c"),
  33. # ("b", "d"),
  34. # ("d", "e")
  35. # ])
  36. class JsonRpc:
  37. async def start(self, server, port):
  38. reader, writer = await asyncio.open_connection(server, port, limit=1024 * 128)
  39. self.reader = reader
  40. self.writer = writer
  41. async def stop(self):
  42. self.writer.close()
  43. await self.writer.wait_closed()
  44. async def _make_request(self, method, params):
  45. ident = random.randint(0, 2**16)
  46. #print(ident)
  47. request = {
  48. "jsonrpc": "2.0",
  49. "method": method,
  50. "params": params,
  51. "id": ident,
  52. }
  53. message = json.dumps(request) + "\n"
  54. self.writer.write(message.encode())
  55. await self.writer.drain()
  56. data = await self.reader.readline()
  57. message = data.decode().strip()
  58. response = json.loads(message)
  59. #print(response)
  60. return response
  61. async def _subscribe(self, method, params):
  62. ident = random.randint(0, 2**16)
  63. request = {
  64. "jsonrpc": "2.0",
  65. "method": method,
  66. "params": params,
  67. "id": ident,
  68. }
  69. message = json.dumps(request) + "\n"
  70. self.writer.write(message.encode())
  71. await self.writer.drain()
  72. #print("Subscribed")
  73. async def ping(self):
  74. return await self._make_request("ping", [])
  75. async def dnet_switch(self, state):
  76. return await self._make_request("dnet.switch", [state])
  77. async def dnet_subscribe_events(self):
  78. return await self._subscribe("dnet.subscribe_events", [])
  79. async def deg_switch(self, state):
  80. return await self._make_request("deg.switch", [state])
  81. class ListItem(u.WidgetWrap):
  82. def __init__ (self, event):
  83. self.content = event
  84. layer_num = int(event["layer"])
  85. layer = "layer " + str(layer_num) if layer_num != 0 else "genesis"
  86. t = u.AttrWrap(u.Text(layer), "event", "event_selected")
  87. u.WidgetWrap.__init__(self, t)
  88. def selectable (self):
  89. return True
  90. def keypress(self, size, key):
  91. return key
  92. class ListView(u.WidgetWrap):
  93. def __init__(self):
  94. u.register_signal(self.__class__, ['show_details'])
  95. self.walker = u.SimpleFocusListWalker([])
  96. lb = u.ListBox(self.walker)
  97. u.WidgetWrap.__init__(self, lb)
  98. def modified(self):
  99. focus_w, _ = self.walker.get_focus()
  100. u.emit_signal(self, 'show_details', focus_w.content)
  101. def set_data(self, events):
  102. events_widgets = [ListItem(e) for e in events]
  103. u.disconnect_signal(self.walker, 'modified', self.modified)
  104. while len(self.walker) > 0:
  105. self.walker.pop()
  106. self.walker.extend(events_widgets)
  107. u.connect_signal(self.walker, "modified", self.modified)
  108. self.walker.set_focus(0)
  109. class DetailView(u.WidgetWrap):
  110. def __init__ (self):
  111. t = u.Text("")
  112. u.WidgetWrap.__init__(self, t)
  113. def set_event(self, c):
  114. s = f'Hash: {c["hash"]}\nChildren: {c["children"]}\nContent: {c["content"]}\nLayer: {c["layer"]}'
  115. self._w.set_text(s)
  116. class App(object):
  117. def unhandled_input(self, key):
  118. if key in ('q',):
  119. raise u.ExitMainLoop()
  120. def show_details(self, event):
  121. self.detail_view.set_event(event)
  122. def __init__(self):
  123. self.palette = {
  124. ("bg", "white", "black"),
  125. ("event", "white", "black"),
  126. ("event_selected", "white", "yellow"),
  127. ("footer", "white, bold", "dark red")
  128. }
  129. self.list_view = ListView()
  130. self.detail_view = DetailView()
  131. u.connect_signal(self.list_view, 'show_details', self.show_details)
  132. footer = u.AttrWrap(u.Text(" Q to exit"), "footer")
  133. col_rows = u.raw_display.Screen().get_cols_rows()
  134. h = col_rows[0] - 2
  135. f1 = u.Filler(self.list_view, valign='top', height=h)
  136. f2 = u.Filler(self.detail_view, valign='top')
  137. c_list = u.LineBox(f1, title="Layers")
  138. c_details = u.LineBox(f2, title="Details")
  139. columns = u.Columns([('weight', 15, c_list), ('weight', 85, c_details)])
  140. frame = u.AttrMap(u.Frame(body=columns, footer=footer), 'bg')
  141. self.loop = u.MainLoop(frame, self.palette, unhandled_input=self.unhandled_input)
  142. async def update_data(self, config):
  143. host = config['host']
  144. port = config['port']
  145. rpc = JsonRpc()
  146. while True:
  147. try:
  148. await rpc.start(host, port)
  149. break
  150. except OSError:
  151. print("Error: Couldn't connent to rpc")
  152. exit(-1)
  153. await rpc.deg_switch(True)
  154. await rpc.deg_switch(False)
  155. json_result = await rpc._make_request("eventgraph.get_info", [])
  156. if json_result['result']['eventgraph_info']:
  157. dag_dict = json_result['result']['eventgraph_info']['dag']
  158. dag_list = list(dag_dict.items())
  159. # sorted_dag = sorted(dag_list, key=lambda x:x[1]['layer'])
  160. # genesis_hash = sorted_dag[0][0]
  161. parent_child_pairs = []
  162. for item in dag_list:
  163. parents = item[1]['parents']
  164. child = item[0]
  165. for parent in parents:
  166. if parent == '0' * 64:
  167. continue
  168. parent_child_pairs.append((parent, child))
  169. # Create a directed graph
  170. dag = nx.DiGraph()
  171. # Add edges to the graph (this also adds nodes)
  172. dag.add_edges_from(parent_child_pairs)
  173. l = []
  174. topological_order = list(nx.topological_sort(dag))
  175. for node in topological_order:
  176. event_details = dag_dict.get(node) # details
  177. layer = int(event_details['layer'])
  178. content = event_details['content'] # event content
  179. # print(content)
  180. pattern = r'\\x[0-9A-Fa-f]{2}'
  181. decoded_str = str(base58.b58decode(content))
  182. matches = re.split(pattern, decoded_str)
  183. children = dag.successors(node)
  184. l.append({"layer":f"{layer}", "hash":f"{node}", "children":f"{list(children)}", "content":f"{matches[1:]}"})
  185. self.list_view.set_data(l)
  186. async def start(self, config):
  187. await self.update_data(config)
  188. self.loop.run()
  189. async def main(argv):
  190. os = src.util.get_os()
  191. config_path = src.util.user_config_dir('darkfi', os)
  192. suffix = '.toml'
  193. filename = 'deg_config'
  194. path = join(config_path, filename + suffix)
  195. config = src.util.spawn_config(path)
  196. config = config['nodes'][0]
  197. if len(argv) > 1:
  198. if argv[1] in ['darkirc', 'irc']:
  199. config['port'] = 26660
  200. elif argv[1] in ['taud', 'tau']:
  201. config['port'] = 23330
  202. app = App()
  203. await app.start(config)
  204. asyncio.run(main(sys.argv))