main.py 5.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201
  1. from hashlib import sha256
  2. from datetime import datetime
  3. import asyncio
  4. EventId = str
  5. EventIds = list[EventId]
  6. class Event:
  7. def __init__(self, parents: EventIds):
  8. self.timestamp = datetime.now().timestamp
  9. self.parents = parents
  10. def hash(self) -> str:
  11. m = sha256()
  12. m.update(str.encode(str(self.timestamp)))
  13. for p in self.parents:
  14. m.update(str.encode(str(p)))
  15. return m.digest().hex()
  16. def __str__(self):
  17. res = f"{self.hash()}"
  18. for p in self.parents:
  19. res += f"\n |"
  20. res += f"\n - {p}"
  21. res += f"\n"
  22. return res
  23. """
  24. ## Graph Example
  25. E1: []
  26. E2: [E1]
  27. E3: [E1]
  28. E4: [E3]
  29. E5: [E3]
  30. E6: [E4, E5]
  31. E7: [E4]
  32. E8: [E2]
  33. """
  34. class Graph:
  35. def __init__(self):
  36. self.events = dict()
  37. # NOTE: we will need to keep track of heads for creating new events.
  38. # Not needed for this demo though.
  39. def add_event(self, event: Event):
  40. self.events[event.hash()] = event
  41. def remove_event(self, event_id: EventId):
  42. if event_id in self.events:
  43. del self.events[event_id]
  44. # check if given events are exist in the graph
  45. # return a list of missing events
  46. def check(self, events: EventIds) -> EventIds:
  47. missing_events = []
  48. for e in events:
  49. if e not in self.events:
  50. missing_events.append(e)
  51. return missing_events
  52. def __str__(self):
  53. res = ""
  54. for event in self.events.values():
  55. res += f"\n {event}"
  56. return res
  57. class Node:
  58. def __init__(self, name: str):
  59. self.name = name
  60. self.orphan_pool = Graph()
  61. self.active_pool = Graph()
  62. def receive_new_event(self, event: Event):
  63. # TODO: the active pool should always start with one event
  64. # which is hardcoded into the software. The genesis event.
  65. # Then we can remove this code below.
  66. # check if the event has no parents, and the active pool
  67. # is empty, then add the event directly to the active pool
  68. if len(event.parents) == 0:
  69. if len(self.active_pool.events) == 0:
  70. self.active_pool.add_event(event)
  71. self.relink(event)
  72. return
  73. missing_parents = self.active_pool.check(event.parents)
  74. if len(missing_parents) == 0:
  75. # if there are no missing parents
  76. # add the event to active pool
  77. self.active_pool.add_event(event)
  78. self.relink(event)
  79. else:
  80. # add the received event to the orphan pool
  81. self.orphan_pool.add_event(event)
  82. # check if all the missing parents are in orphan pool
  83. # if the missing parents and their links not in orphan pool, request
  84. # them from the network
  85. request_list = []
  86. self.check_parents(request_list, missing_parents)
  87. # XXX
  88. # send all the missing parents in request_list
  89. # to the node who send this event
  90. def check_parents(self, request_list, parents: EventIds):
  91. for parent_hash in parents:
  92. if parent_hash in self.orphan_pool.events:
  93. parent = self.orphan_pool.events[parent_hash]
  94. # recursive call
  95. self.check_parents(request_list, parent.parents)
  96. else:
  97. request_list.append(parent_hash)
  98. def relink(self, event: Event):
  99. # check if the orphan pool has an event linked
  100. # to the new added event
  101. # TODO: you cannot call this recursively.
  102. # You must clear the orphan_pool before iteration, and keep
  103. # track of all remaining orphans.
  104. # Then add them back after the for loop is finished.
  105. # You have a bool if things change:
  106. #
  107. # is_reorganized = False
  108. # remaining_orphans = []
  109. # while not is_reorganized:
  110. for (orphan_hash, orphan) in dict(self.orphan_pool.events).items():
  111. if event.hash() not in orphan.parents:
  112. continue
  113. missing_parents = self.active_pool.check(orphan.parents)
  114. if len(missing_parents) == 0:
  115. self.active_pool.add_event(orphan)
  116. # Error, you cannot do this. You will invalidate the iterator.
  117. self.orphan_pool.remove_event(orphan_hash)
  118. # is_reorganized = True
  119. # recursive call
  120. self.relink(orphan)
  121. def __str__(self):
  122. return f"""------
  123. \n Name: {self.name}
  124. \n Active Pool: {self.active_pool}
  125. \n Orphan Pool: {self.orphan_pool}"""
  126. async def run_node(name):
  127. print(f"{name} Started")
  128. node = Node(name)
  129. print(f"{name} End")
  130. async def main():
  131. tasks = await asyncio.gather(
  132. run_node("NodeA"),
  133. run_node("NodeB"),
  134. run_node("NodeC"))
  135. def test_node():
  136. node_a = Node("NodeA")
  137. event0 = Event([])
  138. event1 = Event([event0.hash()])
  139. event2 = Event([event1.hash()])
  140. event3 = Event([event2.hash(), event0.hash()])
  141. event4 = Event([event1.hash(), event3.hash()])
  142. event5 = Event([event4.hash(), "FAKEHASH"])
  143. event6 = Event([event5.hash(), event3.hash()])
  144. node_a.receive_new_event(event0)
  145. node_a.receive_new_event(event3)
  146. node_a.receive_new_event(event2)
  147. node_a.receive_new_event(event1)
  148. node_a.receive_new_event(event5)
  149. node_a.receive_new_event(event6)
  150. node_a.receive_new_event(event4)
  151. print(node_a)
  152. if __name__ == "__main__":
  153. test_node()
  154. asyncio.run(main())