/utils/generate-quorum-matrix.py

https://github.com/loki-project/loki · Python · 173 lines · 121 code · 32 blank · 20 comment · 77 complexity · 08154839d02caa1c586fca8afddcfc23 MD5 · raw file

  1. #!/usr/bin/python3
  2. # This script generates the quorum interconnection matrices for different quorum sizes stored in
  3. # src/quorumnet/conn_matrix.h and used to establish a quorum p2p mesh that establishes a set of
  4. # connections with good partial connectivity using a minimal number of outgoing connections.
  5. #
  6. # It works by looking at the number of different paths of 2 hops or less (1 hop meaning a direct
  7. # connection, 2 meaning a connection that passes through 1 intermediate node) and looks for
  8. # connections between least-connected nodes that increases the most two-hop minimum paths. It then
  9. # establishes this "best" connection, then restarts, continuing until it has achieved a minimum
  10. # 2-hop connectivity from every node to every other node.
  11. #
  12. # This isn't any guarantee that this procedure generates the absolute best connectivity mesh, but it
  13. # appears to do fairly well.
  14. # If you want to see every path that gets added and the before/after two-hop-connectivity graph
  15. # after each addition, set this to true:
  16. TRACE = False
  17. TRACE = True
  18. # N sizes to calculate for. The default calculates for all possible quorums that are capable of
  19. # achieving supermajority for blink and obligations quorums (10, 7 required) and checkpoint quorums
  20. # (20, 13 required)
  21. N = range(7, 21)
  22. # This defines how much 2-path connectivity we require for different values of N
  23. def min_connections(n):
  24. return 4 if n <= 10 else 2
  25. # Some stuff you might need: apt install python3-numpy python3-terminaltables
  26. import numpy as np
  27. from terminaltables import SingleTable
  28. nodes = None
  29. conns = None
  30. def count_paths_within_two(i, j):
  31. if i > j:
  32. i, j = j, i
  33. paths = 0
  34. neighbours = []
  35. for r in range(0, j):
  36. if conns[r, j] or conns[j, r] > 0:
  37. neighbours.append(r)
  38. for c in range(j+1, nodes):
  39. if conns[j, c] or conns[c, j] > 0:
  40. neighbours.append(c)
  41. for n in neighbours:
  42. if n == i or conns[n, i] > 0 or conns[i, n] > 0:
  43. paths += 1
  44. return paths
  45. def within_two_matrix():
  46. z = np.zeros([nodes, nodes], dtype=int)
  47. for i in range(nodes):
  48. for j in range(nodes):
  49. if i == j:
  50. continue
  51. z[i, j] = count_paths_within_two(i, j)
  52. return z
  53. def print_conns():
  54. table_data = [["i", "Connections (1 = out, x = in)", "≤2 paths", "Connectivity:"], ["\n".join(str(x) for x in range(nodes)), "", "", ""]]
  55. for r in range(nodes):
  56. if table_data[1][1]:
  57. table_data[1][1] += "\n"
  58. table_data[1][1] += "".join('-' if r == c else 'x' if conns[c,r] else str(conns[r,c]) for c in range(nodes))
  59. z = within_two_matrix()
  60. for r in range(nodes):
  61. if table_data[1][2]:
  62. table_data[1][2] += "\n"
  63. table_data[1][2] += "".join('-' if r == c else str(z[r,c]) for c in range(nodes))
  64. for i in range(nodes):
  65. myouts, myins = 0, 0
  66. for j in range(nodes):
  67. if conns[i, j]:
  68. myouts += 1
  69. elif conns[j, i]:
  70. myins += 1
  71. if table_data[1][3]:
  72. table_data[1][3] += "\n"
  73. table_data[1][3] += "{} (= {} out + {} in)".format(myouts + myins, myouts, myins)
  74. print(SingleTable(table_data).table)
  75. def min_within_two():
  76. m = None
  77. for i in range(nodes):
  78. for j in range(i+1, nodes):
  79. c = count_paths_within_two(i, j)
  80. if m is None or c < m:
  81. m = c
  82. return m
  83. def count_not_within_two(min_paths):
  84. c = 0
  85. for i in range(nodes):
  86. for j in range(i+1, nodes):
  87. if count_paths_within_two(i, j) <= min_paths:
  88. c += 1
  89. return c
  90. def highlight(s, hl, code="\033[32m"):
  91. return "{}{}\033[0m".format(code, s) if hl else "{}".format(s)
  92. cpp = ""
  93. for n in N:
  94. nodes = n
  95. conns = np.zeros([nodes, nodes], dtype=int)
  96. target = min_connections(nodes)
  97. min_paths = 0
  98. last_min_paths = 0
  99. while min_paths < target:
  100. best = (nodes + nodes, count_not_within_two(min_paths))
  101. best_ij = (0, 0)
  102. for i in range(nodes):
  103. outgoing_conns = sum(conns[i,j] for j in range(nodes)) + 1
  104. for j in range(nodes):
  105. incoming_conns = sum(conns[k,j] for k in range(nodes)) + 1
  106. if i == j or conns[i, j] or conns[j, i]:
  107. continue
  108. conns[i, j] = 1
  109. c = (outgoing_conns + incoming_conns, count_not_within_two(min_paths))
  110. if c < best:
  111. best_ij = (i, j)
  112. best = c
  113. best_conns = outgoing_conns
  114. conns[i, j] = 0
  115. if TRACE:
  116. before = within_two_matrix()
  117. conns[best_ij[0], best_ij[1]] = 1
  118. if TRACE:
  119. print("Chose connection [{},{}]".format(*best_ij))
  120. after = within_two_matrix()
  121. for r in range(nodes):
  122. print("".join(
  123. highlight('-' if r == c else '#' if conns[c,r] and conns[r,c] else 'x' if conns[c,r] else conns[r,c], (r,c)==best_ij) for c in range(nodes)
  124. ), end='')
  125. print(" : " if r == nodes // 2 else " ", end='')
  126. print("".join(highlight('-' if r == c else before[r,c], after[r,c] > before[r,c], "\033[33m") for c in range(nodes)), end='')
  127. print(" => " if r == nodes // 2 else " ", end='')
  128. print("".join(highlight('-' if r == c else after[r,c], after[r,c] > before[r,c]) for c in range(nodes)))
  129. min_paths = min_within_two()
  130. if min_paths > last_min_paths:
  131. print("\n\n\n\n====================================================\nConstructed {}-min-two-hop-paths (N={})\n====================================================\n".format(
  132. min_paths, nodes))
  133. print_conns()
  134. last_min_paths = min_paths
  135. print("\n\n\n\n")
  136. cpp += "template<> constexpr std::array<bool, {N}*{N}> quorum_conn_matrix<{N}>{{{{\n".format(N=n);
  137. for r in range(nodes):
  138. cpp += " " + ",".join(str(conns[r,c]) for c in range(nodes)) + ",\n"
  139. cpp += "}};\n\n"
  140. print("C++ code for quorumnet/conn_matrix.h:\n\n\n")
  141. print(cpp)