From 214458322a6d5facaf025011c552ba02036d75dd Mon Sep 17 00:00:00 2001 From: dasman Date: Sun, 7 Apr 2024 04:40:18 +0300 Subject: [PATCH] add deg2 (dag_browser) code --- Cargo.toml | 1 + bin/deg/deg2 | 257 +++++++++++++++++++++++++++++++++++++++ src/event_graph/event.rs | 8 +- src/event_graph/mod.rs | 62 ++++++---- src/rpc/from_impl.rs | 14 +++ 5 files changed, 316 insertions(+), 26 deletions(-) create mode 100755 bin/deg/deg2 diff --git a/Cargo.toml b/Cargo.toml index cb0637e2e..b48c87178 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -202,6 +202,7 @@ event-graph = [ "sled-overlay", "smol", "tinyjson", + "bs58", "darkfi-serial", "darkfi-serial/collections", diff --git a/bin/deg/deg2 b/bin/deg/deg2 new file mode 100755 index 000000000..6a4ec570a --- /dev/null +++ b/bin/deg/deg2 @@ -0,0 +1,257 @@ +#!/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)) diff --git a/src/event_graph/event.rs b/src/event_graph/event.rs index 5e21d77b9..7561a0c29 100644 --- a/src/event_graph/event.rs +++ b/src/event_graph/event.rs @@ -32,13 +32,13 @@ use super::{ #[derive(Debug, Clone, SerialEncodable, SerialDecodable)] pub struct Event { /// Timestamp of the event - pub(super) timestamp: u64, + pub(crate) timestamp: u64, /// Content of the event - pub(super) content: Vec, + pub(crate) content: Vec, /// Parent nodes in the event DAG - pub(super) parents: [blake3::Hash; N_EVENT_PARENTS], + pub(crate) parents: [blake3::Hash; N_EVENT_PARENTS], /// DAG layer index of the event - pub(super) layer: u64, + pub(crate) layer: u64, } impl Event { diff --git a/src/event_graph/mod.rs b/src/event_graph/mod.rs index 4a156d5bf..a2ac359e5 100644 --- a/src/event_graph/mod.rs +++ b/src/event_graph/mod.rs @@ -838,31 +838,49 @@ impl EventGraph { } pub async fn eventgraph_info(&self, id: u16, _params: JsonValue) -> JsonResult { - let u_tips = self.unreferenced_tips.read().await.clone(); - let u_tips_vals = u_tips - .into_values() - .map(|v| v.into_iter().map(|x| JsonValue::String(x.to_string())).collect::>()) - .collect::>() - .concat(); + let mut graph = HashMap::new(); + for iter_elem in self.dag.iter() { + let (id, val) = iter_elem.unwrap(); + let id = blake3::Hash::from_bytes((&id as &[u8]).try_into().unwrap()); + let val: Event = deserialize_async(&val).await.unwrap(); + graph.insert(id, val); + } - let b_ids = self - .broadcasted_ids - .read() - .await - .clone() + let json_graph = graph .into_iter() - .map(|id| JsonValue::String(id.to_string())) - .collect::>(); + .map(|(k, v)| { + let key = k.to_string(); + let value = JsonValue::from(v); + (key, value) + }) + .collect(); + let values = json_map([("dag", JsonValue::Object(json_graph))]); - let values = json_map([ - ("unreferenced_tips", JsonValue::Array(u_tips_vals)), - ("broadcasted_ids", JsonValue::Array(b_ids)), - ("synced", JsonValue::Boolean(*self.synced.read().await)), - ( - "current_genesis", - JsonValue::String(self.current_genesis.read().await.clone().id().to_string()), - ), - ]); + // let u_tips = self.unreferenced_tips.read().await.clone(); + // let u_tips_vals = u_tips + // .into_values() + // .map(|v| v.into_iter().map(|x| JsonValue::String(x.to_string())).collect::>()) + // .collect::>() + // .concat(); + + // let b_ids = self + // .broadcasted_ids + // .read() + // .await + // .clone() + // .into_iter() + // .map(|id| JsonValue::String(id.to_string())) + // .collect::>(); + + // let values = json_map([ + // ("unreferenced_tips", JsonValue::Array(u_tips_vals)), + // ("broadcasted_ids", JsonValue::Array(b_ids)), + // ("synced", JsonValue::Boolean(*self.synced.read().await)), + // ( + // "current_genesis", + // JsonValue::String(self.current_genesis.read().await.clone().id().to_string()), + // ), + // ]); let result = JsonValue::Object(HashMap::from([("eventgraph_info".to_string(), values)])); diff --git a/src/rpc/from_impl.rs b/src/rpc/from_impl.rs index b870d6615..21b82159f 100644 --- a/src/rpc/from_impl.rs +++ b/src/rpc/from_impl.rs @@ -130,6 +130,20 @@ impl From for JsonValue { } } +#[cfg(feature = "event-graph")] +impl From for JsonValue { + fn from(event: event_graph::Event) -> JsonValue { + let parents = + event.parents.into_iter().map(|id| JsonStr(id.to_string())).collect::>(); + json_map([ + ("timestamp", JsonNum(event.timestamp as f64)), + ("content", JsonStr(bs58::encode(event.content()).into_string())), + ("parents", JsonArray(parents)), + ("layer", JsonNum(event.layer as f64)), + ]) + } +} + #[cfg(feature = "event-graph")] impl From for JsonValue { fn from(info: event_graph::deg::MessageInfo) -> JsonValue {