/tests/pubsub/epochs_test.py

https://github.com/Conflux-Chain/conflux-rust · Python · 151 lines · 82 code · 37 blank · 32 comment · 17 complexity · 309a3048f0e5333d62b6f54c3b6eb292 MD5 · raw file

  1. #!/usr/bin/env python3
  2. # allow imports from parent directory
  3. # source: https://stackoverflow.com/a/11158224
  4. import os, sys
  5. sys.path.insert(1, os.path.join(sys.path[0], '..'))
  6. import asyncio
  7. from conflux.rpc import RpcClient
  8. from conflux.pubsub import PubSubClient
  9. from test_framework.test_framework import ConfluxTestFramework
  10. from test_framework.util import assert_equal, connect_nodes
  11. FULLNODE0 = 0
  12. FULLNODE1 = 1
  13. LIGHTNODE = 2
  14. NUM_FORKS = 5
  15. PREFIX_LEN = 10
  16. SHORT_FORK_LEN = 20
  17. LONG_FORK_LEN = 30
  18. def flatten(l):
  19. return [item for sublist in l for item in sublist]
  20. class PubSubTest(ConfluxTestFramework):
  21. def set_test_params(self):
  22. self.num_nodes = 3
  23. def setup_network(self):
  24. self.add_nodes(self.num_nodes)
  25. self.start_node(FULLNODE0, ["--archive"])
  26. self.start_node(FULLNODE1, ["--archive"])
  27. self.start_node(LIGHTNODE, ["--light"], phase_to_wait=None)
  28. # set up RPC clients
  29. self.rpc = [None] * self.num_nodes
  30. self.rpc[FULLNODE0] = RpcClient(self.nodes[FULLNODE0])
  31. self.rpc[FULLNODE1] = RpcClient(self.nodes[FULLNODE1])
  32. self.rpc[LIGHTNODE] = RpcClient(self.nodes[LIGHTNODE])
  33. # set up PubSub clients
  34. self.pubsub = [None] * self.num_nodes
  35. self.pubsub[FULLNODE0] = PubSubClient(self.nodes[FULLNODE0])
  36. self.pubsub[FULLNODE1] = PubSubClient(self.nodes[FULLNODE1])
  37. self.pubsub[LIGHTNODE] = PubSubClient(self.nodes[LIGHTNODE])
  38. # connect nodes
  39. connect_nodes(self.nodes, FULLNODE0, FULLNODE1)
  40. connect_nodes(self.nodes, LIGHTNODE, FULLNODE0)
  41. connect_nodes(self.nodes, LIGHTNODE, FULLNODE1)
  42. # wait for phase changes to complete
  43. self.nodes[FULLNODE0].wait_for_phase(["NormalSyncPhase"])
  44. self.nodes[FULLNODE1].wait_for_phase(["NormalSyncPhase"])
  45. async def run_async(self):
  46. # Generate a chain with multiple forks like this:
  47. # 1 -- 2 -- 3 -- 4
  48. # \-- 3 -- 4 -- 5 -- 6
  49. # \-- 5 -- 6 -- 7 -- 8
  50. # we expect to see: 1, 2, 3, 4, 3, 4, 5, 6, 5, 6, 7, 8, ...
  51. # Subscription results are of the format:
  52. # {'epochHashesOrdered': ['0x6fbbcb69c3f2247dc8bc756648a55f324afb30236c0ecaaf477cbea3b00de6dc'], 'epochNumber': '0x8'}
  53. # subscribe
  54. sub_full = await self.pubsub[FULLNODE1].subscribe("epochs")
  55. sub_light = await self.pubsub[LIGHTNODE].subscribe("epochs")
  56. # genesis hash
  57. root_hash = self.nodes[FULLNODE0].best_block_hash()
  58. root_epoch = 0
  59. for ii in range(NUM_FORKS):
  60. self.log.info(f"[{ii}] Root: {root_hash[:10]}... (epoch {root_epoch})")
  61. # ---------------------------------------------
  62. # generate shared prefix of length `PREFIX_LEN`
  63. generated = self.generate_chain(root_hash, PREFIX_LEN)
  64. # collect results from subscription
  65. epochs = [e async for e in sub_full.iter()]
  66. assert_equal(epochs, [e async for e in sub_light.iter()])
  67. # check hashes
  68. hashes = flatten([e["epochHashesOrdered"] for e in epochs])
  69. assert_equal(hashes, generated)
  70. # check epoch numbers
  71. epoch_nums = [int(e["epochNumber"], 16) for e in epochs]
  72. assert_equal(epoch_nums, list(range(root_epoch + 1, root_epoch + 1 + PREFIX_LEN)))
  73. fork_hash = hashes[-1]
  74. fork_epoch = epoch_nums[-1]
  75. self.log.info(f"[{ii}] Forking at: {fork_hash[:10]}... (epoch {fork_epoch})")
  76. # ----------------------------------------
  77. # generate fork of length `SHORT_FORK_LEN`
  78. generated = self.generate_chain(fork_hash, SHORT_FORK_LEN)
  79. # collect results from subscription
  80. epochs = [e async for e in sub_full.iter()]
  81. assert_equal(epochs, [e async for e in sub_light.iter()])
  82. # check hashes
  83. hashes = flatten([e["epochHashesOrdered"] for e in epochs])
  84. assert_equal(hashes, generated)
  85. # check epoch numbers
  86. epoch_nums = [int(e["epochNumber"], 16) for e in epochs]
  87. assert_equal(epoch_nums, list(range(fork_epoch + 1, fork_epoch + 1 + SHORT_FORK_LEN)))
  88. # ----------------------------------------
  89. # generate fork of length `LONG_FORK_LEN`
  90. generated = self.generate_chain(fork_hash, LONG_FORK_LEN)
  91. # collect results from subscription
  92. epochs = [e async for e in sub_full.iter()]
  93. assert_equal(epochs, [e async for e in sub_light.iter()])
  94. # check hashes
  95. hashes = flatten([e["epochHashesOrdered"] for e in epochs])
  96. assert_equal(hashes, generated)
  97. # check epoch numbers
  98. epoch_nums = [int(e["epochNumber"], 16) for e in epochs]
  99. assert_equal(epoch_nums, list(range(fork_epoch + 1, fork_epoch + 1 + LONG_FORK_LEN)))
  100. # in the next iteration, we continue with the longer fork
  101. root_epoch = epoch_nums[-1]
  102. root_hash = hashes[-1]
  103. self.log.info(f"[{ii}] Pass")
  104. def run_test(self):
  105. assert(SHORT_FORK_LEN < LONG_FORK_LEN)
  106. asyncio.get_event_loop().run_until_complete(self.run_async())
  107. def generate_chain(self, parent, len):
  108. hashes = [parent]
  109. for _ in range(len):
  110. hash = self.rpc[FULLNODE0].generate_block_with_parent(hashes[-1])
  111. hashes.append(hash)
  112. return hashes[1:]
  113. if __name__ == "__main__":
  114. PubSubTest().main()