main.py 7.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277
  1. from hashlib import sha256
  2. from datetime import datetime
  3. from random import randint
  4. import asyncio
  5. import matplotlib.pyplot as plt
  6. import networkx as nx
  7. EventId = str
  8. EventIds = list[EventId]
  9. class Event:
  10. def __init__(self, parents: EventIds):
  11. self.timestamp = datetime.now().timestamp
  12. self.parents = parents
  13. def set_timestamp(self, timestamp):
  14. self.timestamp = timestamp
  15. def hash(self) -> str:
  16. m = sha256()
  17. m.update(str.encode(str(self.timestamp)))
  18. for p in self.parents:
  19. m.update(str.encode(str(p)))
  20. return m.digest().hex()
  21. def __str__(self):
  22. res = f"{self.hash()}"
  23. for p in self.parents:
  24. res += f"\n |"
  25. res += f"\n - {p}"
  26. res += f"\n"
  27. return res
  28. """
  29. ## Graph Example
  30. E1: []
  31. E2: [E1]
  32. E3: [E1]
  33. E4: [E3]
  34. E5: [E3]
  35. E6: [E4, E5]
  36. E7: [E4]
  37. E8: [E2]
  38. """
  39. class Graph:
  40. def __init__(self):
  41. self.events = dict()
  42. def heads(self):
  43. # NOTE: we will need to keep track of heads for creating new events.
  44. # Not needed for this demo though.
  45. # XXX this for testing purpose
  46. return [list(self.events.keys())[-1]]
  47. def add_event(self, event: Event):
  48. self.events[event.hash()] = event
  49. def remove_event(self, event_id: EventId):
  50. if event_id in self.events:
  51. del self.events[event_id]
  52. # check if given events are exist in the graph
  53. # return a list of missing events
  54. def check(self, events: EventIds) -> EventIds:
  55. missing_events = []
  56. for e in events:
  57. if e not in self.events:
  58. missing_events.append(e)
  59. return missing_events
  60. def __str__(self):
  61. res = ""
  62. for event in self.events.values():
  63. res += f"\n {event}"
  64. return res
  65. class Node:
  66. def __init__(self, name: str):
  67. self.name = name
  68. self.orphan_pool = Graph()
  69. self.active_pool = Graph()
  70. # the active pool should always start with one event
  71. genesis_event = Event([])
  72. genesis_event.set_timestamp(0.0)
  73. self.genesis_event = genesis_event
  74. self.active_pool.add_event(genesis_event)
  75. def last_event(self):
  76. return self.active_pool.heads()
  77. def receive_new_event(self, event: Event):
  78. # reject events with no parents
  79. if len(event.parents) == 0:
  80. return
  81. missing_parents = self.active_pool.check(event.parents)
  82. if len(missing_parents) == 0:
  83. # if there are no missing parents
  84. # add the event to active pool
  85. self.active_pool.add_event(event)
  86. # events list to be removed from orphan pool
  87. # after relink
  88. remove_list: EventIds = []
  89. self.relink(event, remove_list)
  90. # clean up orphan pool
  91. for ev in remove_list:
  92. self.orphan_pool.remove_event(ev)
  93. else:
  94. # add the received event to the orphan pool
  95. self.orphan_pool.add_event(event)
  96. # check if all the missing parents are in orphan pool
  97. # if the missing parents and their links not in orphan pool, request
  98. # them from the network
  99. request_list = []
  100. self.check_parents(request_list, missing_parents)
  101. # XXX
  102. # send all the missing parents in request_list
  103. # to the node who send this event
  104. def check_parents(self, request_list, parents: EventIds):
  105. for parent_hash in parents:
  106. if parent_hash in self.orphan_pool.events:
  107. parent = self.orphan_pool.events[parent_hash]
  108. # recursive call
  109. self.check_parents(request_list, parent.parents)
  110. else:
  111. request_list.append(parent_hash)
  112. def relink(self, event: Event, remove_list=[]):
  113. # check if the orphan pool has an event linked
  114. # to the new added event
  115. for (orphan_hash, orphan) in self.orphan_pool.events.items():
  116. if orphan_hash in remove_list:
  117. continue
  118. if event.hash() not in orphan.parents:
  119. continue
  120. missing_parents = self.active_pool.check(orphan.parents)
  121. if len(missing_parents) == 0:
  122. self.active_pool.add_event(orphan)
  123. remove_list.append(orphan_hash)
  124. # recursive call
  125. self.relink(orphan, remove_list)
  126. def __str__(self):
  127. return f"""------
  128. \n Name: {self.name}
  129. \n Active Pool: {self.active_pool}
  130. \n Orphan Pool: {self.orphan_pool}"""
  131. NODES_N = 10
  132. BROADCAST_ATTEMPT = 3
  133. MAX_BROADCAST_DELAY = 2
  134. MIN_BROADCAST_DELAY = 0
  135. async def recv_loop(node, peer, queue):
  136. while True:
  137. event = await queue.get()
  138. queue.task_done()
  139. node.receive_new_event(event)
  140. print(f"{node.name} receive: {event.hash()} from {peer}")
  141. async def send_loop(node, queue):
  142. for _ in range(BROADCAST_ATTEMPT):
  143. await asyncio.sleep(randint(MIN_BROADCAST_DELAY, MAX_BROADCAST_DELAY))
  144. event = Event(node.last_event())
  145. for _ in range(NODES_N):
  146. await queue.put(event)
  147. await queue.join()
  148. print(f"{node.name} broadcast: {event.hash()}")
  149. async def main():
  150. send_tasks = []
  151. recv_tasks = []
  152. nodes = []
  153. queues = dict()
  154. print(f"Run {NODES_N} Nodes")
  155. for i in range(NODES_N):
  156. node = Node(f"Node{i}")
  157. nodes.append(node)
  158. queue = asyncio.Queue()
  159. queues[node.name] = queue
  160. send_task = asyncio.create_task(send_loop(node, queue))
  161. send_tasks.append(send_task)
  162. for node in nodes:
  163. for (peer, queue) in queues.items():
  164. recv_task = asyncio.create_task(recv_loop(node, peer, queue))
  165. recv_tasks.append(recv_task)
  166. g = asyncio.gather(*recv_tasks)
  167. await asyncio.gather(*send_tasks)
  168. for node in nodes:
  169. print(node.name)
  170. print(len(node.active_pool.events))
  171. print(len(node.orphan_pool.events))
  172. print("###############")
  173. graph = nx.Graph()
  174. node_to_draw = nodes[0]
  175. print(node_to_draw)
  176. for (h, ev) in node_to_draw.active_pool.events.items():
  177. graph.add_node(h[:5])
  178. graph.add_edges_from([(h[:5], p[:5]) for p in ev.parents])
  179. nx.draw(graph, with_labels=True, node_color="#69aaff", node_size=400)
  180. plt.show()
  181. await g
  182. def test_node():
  183. node_a = Node("NodeA")
  184. event0 = node_a.genesis_event
  185. event1 = Event([event0.hash()])
  186. event2 = Event([event1.hash()])
  187. event3 = Event([event2.hash(), event0.hash()])
  188. event4 = Event([event1.hash(), event3.hash()])
  189. event5 = Event([event4.hash(), "FAKEHASH"])
  190. event6 = Event([event5.hash(), event3.hash()])
  191. node_a.receive_new_event(event3)
  192. node_a.receive_new_event(event2)
  193. node_a.receive_new_event(event1)
  194. node_a.receive_new_event(event5)
  195. node_a.receive_new_event(event6)
  196. node_a.receive_new_event(event4)
  197. print(node_a)
  198. if __name__ == "__main__":
  199. # test_node()
  200. asyncio.run(main())