diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index fec5b43a94..0e6944c7b7 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -44,6 +44,8 @@ configure_file(${CMAKE_CURRENT_SOURCE_DIR}/keosd_auto_launch_test.py ${CMAKE_CUR configure_file(${CMAKE_CURRENT_SOURCE_DIR}/db_modes_test.sh ${CMAKE_CURRENT_BINARY_DIR}/db_modes_test.sh COPYONLY) configure_file(${CMAKE_CURRENT_SOURCE_DIR}/prod_preactivation_test.py ${CMAKE_CURRENT_BINARY_DIR}/prod_preactivation_test.py COPYONLY) configure_file(${CMAKE_CURRENT_SOURCE_DIR}/release-build.sh ${CMAKE_CURRENT_BINARY_DIR}/release-build.sh COPYONLY) +configure_file(${CMAKE_CURRENT_SOURCE_DIR}/separate_prod_fin_test_shape.json ${CMAKE_CURRENT_BINARY_DIR}/separate_prod_fin_test_shape.json COPYONLY) +configure_file(${CMAKE_CURRENT_SOURCE_DIR}/separate_prod_fin_test.py ${CMAKE_CURRENT_BINARY_DIR}/separate_prod_fin_test.py COPYONLY) configure_file(${CMAKE_CURRENT_SOURCE_DIR}/snapshot_in_svnn_transition_test.py ${CMAKE_CURRENT_BINARY_DIR}/snapshot_in_svnn_transition_test.py COPYONLY) configure_file(${CMAKE_CURRENT_SOURCE_DIR}/version-label.sh ${CMAKE_CURRENT_BINARY_DIR}/version-label.sh COPYONLY) configure_file(${CMAKE_CURRENT_SOURCE_DIR}/full-version-label.sh ${CMAKE_CURRENT_BINARY_DIR}/full-version-label.sh COPYONLY) @@ -168,6 +170,8 @@ set_property(TEST p2p_dawn515_test PROPERTY LABELS nonparallelizable_tests) add_test(NAME producer-preactivate-feature-test COMMAND tests/prod_preactivation_test.py ${UNSHARE} WORKING_DIRECTORY ${CMAKE_BINARY_DIR}) set_property(TEST producer-preactivate-feature-test PROPERTY LABELS nonparallelizable_tests) +add_test(NAME separate_prod_fin_test COMMAND tests/separate_prod_fin_test.py ${UNSHARE} WORKING_DIRECTORY ${CMAKE_BINARY_DIR}) +set_property(TEST separate_prod_fin_test PROPERTY LABELS nonparallelizable_tests) add_test(NAME snapshot_in_svnn_transition_test COMMAND tests/snapshot_in_svnn_transition_test.py ${UNSHARE} WORKING_DIRECTORY ${CMAKE_BINARY_DIR}) set_property(TEST snapshot_in_svnn_transition_test PROPERTY LABELS nonparallelizable_tests) add_test(NAME nodeos_protocol_feature_test COMMAND tests/nodeos_protocol_feature_test.py -v ${UNSHARE} WORKING_DIRECTORY ${CMAKE_BINARY_DIR}) diff --git a/tests/TestHarness/Cluster.py b/tests/TestHarness/Cluster.py index 37d42a8dd4..103c230234 100644 --- a/tests/TestHarness/Cluster.py +++ b/tests/TestHarness/Cluster.py @@ -167,6 +167,7 @@ def launch(self, pnodes=1, unstartedNodes=0, totalNodes=1, prodCount=21, topo="m totalProducers=None, sharedProducers=0, extraNodeosArgs="", specificExtraNodeosArgs=None, specificNodeosInstances=None, onlySetProds=False, pfSetupPolicy=PFSetupPolicy.FULL, alternateVersionLabelsFile=None, associatedNodeLabels=None, loadSystemContract=True, activateIF=False, biosFinalizer=True, + signatureProviderForNonProducer=False, nodeosLogPath=Path(Utils.TestLogRoot) / Path(f'{Path(sys.argv[0]).stem}{os.getpid()}'), genesisPath=None, maximumP2pPerHost=0, maximumClients=25, prodsEnableTraceApi=True): """Launch cluster. @@ -191,6 +192,7 @@ def launch(self, pnodes=1, unstartedNodes=0, totalNodes=1, prodCount=21, topo="m loadSystemContract: indicate whether the eosio.system contract should be loaded activateIF: Activate/enable instant-finality by setting finalizers biosFinalizer: True if the biosNode should act as a finalizer + signatureProviderForNonProducer: Add signature provider for non-producer genesisPath: set the path to a specific genesis.json to use maximumP2pPerHost: Maximum number of client nodes from any single IP address. Defaults to totalNodes if not set. maximumClients: Maximum number of clients from which connections are accepted, use 0 for no limit. Defaults to 25. @@ -465,6 +467,8 @@ def connectGroup(group, producerNodes, bridgeNodes) : if "--plugin eosio::history_api_plugin" in args: argsArr.append("--is-nodeos-v2") break + if signatureProviderForNonProducer: + argsArr.append("--signature-provider") # Handle common case of specifying no block offset for older versions if "v2" in self.nodeosVers or "v3" in self.nodeosVers or "v4" in self.nodeosVers: @@ -525,7 +529,7 @@ def connectGroup(group, producerNodes, bridgeNodes) : return True Utils.Print("Bootstrap cluster.") - if not self.bootstrap(launcher, self.biosNode, self.startedNodesCount, prodCount + sharedProducers, totalProducers, pfSetupPolicy, onlyBios, onlySetProds, loadSystemContract, activateIF, biosFinalizer): + if not self.bootstrap(launcher, self.biosNode, self.startedNodesCount, prodCount + sharedProducers, totalProducers, pfSetupPolicy, onlyBios, onlySetProds, loadSystemContract, activateIF, biosFinalizer, signatureProviderForNonProducer): Utils.Print("ERROR: Bootstrap failed.") return False @@ -997,13 +1001,13 @@ def parseClusterKeys(totalNodes): Utils.Print(f'Found {len(producerKeys)} producer keys') return producerKeys - def activateInstantFinality(self, biosFinalizer=True, waitForFinalization=True): + def activateInstantFinality(self, biosFinalizer=True, waitForFinalization=True, signatureProviderForNonProducer=False): # call setfinalizer numFins = 0 for n in (self.nodes + [self.biosNode]): if not n or not n.keys or not n.keys[0].blspubkey: continue - if not n.isProducer: + if not signatureProviderForNonProducer and not n.isProducer: continue if n.nodeId == 'bios' and not biosFinalizer: continue @@ -1021,7 +1025,7 @@ def activateInstantFinality(self, biosFinalizer=True, waitForFinalization=True): for n in (self.nodes + [self.biosNode]): if not n or not n.keys or not n.keys[0].blspubkey: continue - if not n.isProducer: + if not signatureProviderForNonProducer and not n.isProducer: continue if n.nodeId == 'bios' and not biosFinalizer: continue @@ -1050,7 +1054,7 @@ def activateInstantFinality(self, biosFinalizer=True, waitForFinalization=True): return None, transId return True, transId - def bootstrap(self, launcher, biosNode, totalNodes, prodCount, totalProducers, pfSetupPolicy, onlyBios=False, onlySetProds=False, loadSystemContract=True, activateIF=False, biosFinalizer=True): + def bootstrap(self, launcher, biosNode, totalNodes, prodCount, totalProducers, pfSetupPolicy, onlyBios=False, onlySetProds=False, loadSystemContract=True, activateIF=False, biosFinalizer=True, signatureProviderForNonProducer=False): """Create 'prodCount' init accounts and deposits 10000000000 SYS in each. If prodCount is -1 will initialize all possible producers. Ensure nodes are inter-connected prior to this call. One way to validate this will be to check if every node has block 1.""" @@ -1114,7 +1118,7 @@ def bootstrap(self, launcher, biosNode, totalNodes, prodCount, totalProducers, return None if activateIF: - success, transId = self.activateInstantFinality(biosFinalizer=biosFinalizer) + success, transId = self.activateInstantFinality(biosFinalizer=biosFinalizer, signatureProviderForNonProducer=signatureProviderForNonProducer) if not success: Utils.Print("ERROR: Activate instant finality failed") return None diff --git a/tests/TestHarness/launcher.py b/tests/TestHarness/launcher.py index e42cf649cb..61ab3db033 100644 --- a/tests/TestHarness/launcher.py +++ b/tests/TestHarness/launcher.py @@ -208,6 +208,7 @@ def comma_separated(string): cfg.add_argument('--logging-level', type=fc_log_level, help='Provide the "level" value to use in the logging.json file') cfg.add_argument('--logging-level-map', type=json.loads, help='JSON string of a logging level dictionary to use in the logging.json file for specific nodes, matching based on node number. Ex: {"bios":"off","00":"info"}') cfg.add_argument('--is-nodeos-v2', action='store_true', help='Toggles old nodeos compatibility', default=False) + cfg.add_argument('--signature-provider', action='store_true', help='add signature provider (BLS key pair) for non-producers', default=False) r = parser.parse_args(args) if r.launch != 'none' and r.topology_filename: Utils.Print('Output file specified--overriding launch to "none"') @@ -523,6 +524,10 @@ def construct_command_line(self, instance: nodeDefinition): eosdcmd.extend(producer_names) else: a(a(eosdcmd, '--transaction-retry-max-storage-size-gb'), '100') + if self.args.signature_provider: + finalizer_keys = list(sum([('--signature-provider', f'{key.blspubkey}=KEY:{key.blsprivkey}') for key in instance.keys if key.blspubkey is not None], ())) + if finalizer_keys: + eosdcmd.extend(finalizer_keys) a(a(eosdcmd, '--plugin'), 'eosio::net_plugin') a(a(eosdcmd, '--plugin'), 'eosio::chain_api_plugin') diff --git a/tests/separate_prod_fin_test.py b/tests/separate_prod_fin_test.py new file mode 100755 index 0000000000..8333c2978d --- /dev/null +++ b/tests/separate_prod_fin_test.py @@ -0,0 +1,80 @@ +#!/usr/bin/env python3 + +from TestHarness import Cluster, TestHelper, Utils, WalletMgr +from TestHarness.TestHelper import AppArgs + +############################################################### +# separate_prod_fin_test +# +# Test producer nodes and finalizer nodes which are separate. Configure 2 producer nodes and +# 3 non-producer nodes; each of them has a finalizer key. Since threshold is 4, +# if LIB advances, it implies at least 2 non-producer finalizer participates in +# the finalization process. +# +############################################################### + + +Print=Utils.Print +errorExit=Utils.errorExit + +appArgs = AppArgs() +args=TestHelper.parse_args({"-d","--keep-logs","--dump-error-details","-v","--leave-running","--unshared"}, + applicationSpecificArgs=appArgs) +pnodes=2 # producer node +delay=args.d +debug=args.v +total_nodes=pnodes+3 # 3 non-producer nodes +dumpErrorDetails=args.dump_error_details + +Utils.Debug=debug +testSuccessful=False + +cluster=Cluster(unshared=args.unshared, keepRunning=args.leave_running, keepLogs=args.keep_logs) +walletMgr=WalletMgr(True, keepRunning=args.leave_running, keepLogs=args.keep_logs) + +try: + TestHelper.printSystemInfo("BEGIN") + + cluster.setWalletMgr(walletMgr) + + Print(f'producing nodes: {pnodes}, delay between nodes launch: {delay} second{"s" if delay != 1 else ""}') + + Print("Stand up cluster") + # For now do not load system contract as it does not support setfinalizer + # separate_prod_fin_test_shape.json defines 2 producer nodes each has 1 + # producer and 3 non-producer nodes + if cluster.launch(pnodes=pnodes, totalNodes=total_nodes, totalProducers=pnodes, + topo="./tests/separate_prod_fin_test_shape.json", delay=delay, + activateIF=True, signatureProviderForNonProducer=True) is False: + errorExit("Failed to stand up eos cluster.") + + assert cluster.biosNode.getInfo(exitOnError=True)["head_block_producer"] != "eosio", "launch should have waited for production to change" + + cluster.biosNode.waitForHeadToAdvance() + + Print("Wait for LIB advancing") + assert cluster.biosNode.waitForLibToAdvance(), "Lib should advance after instant finality activated" + assert cluster.biosNode.waitForProducer("defproducera"), "Did not see defproducera" + assert cluster.biosNode.waitForHeadToAdvance(blocksToAdvance=13), "Head did not advance 13 blocks to next producer" + assert cluster.biosNode.waitForLibToAdvance(), "Lib stopped advancing on biosNode" + assert cluster.getNode(1).waitForLibToAdvance(), "Lib stopped advancing on Node 1" + + info = cluster.biosNode.getInfo(exitOnError=True) + assert (info["head_block_num"] - info["last_irreversible_block_num"]) < 9, "Instant finality enabled LIB diff should be small" + + # LIB has advanced, which indicate at least 2 of non-producer finalizers have voted. + # Double check that's indeed the case in qc_extension + info = cluster.getNode(1).getInfo(exitOnError=True) + block_num = info["last_irreversible_block_num"] + block = cluster.getNode(1).getBlock(block_num) + qc_ext = block["qc_extension"] + Print(f'{qc_ext}') + # "11111" is the representation of a bitset showing which finalizers have voted (we have five total finalizers) + assert qc_ext["qc"]["data"]["strong_votes"] == "11111", 'Not all finalizers voted' + + testSuccessful=True +finally: + TestHelper.shutdown(cluster, walletMgr, testSuccessful=testSuccessful, dumpErrorDetails=dumpErrorDetails) + +exitCode = 0 if testSuccessful else 1 +exit(exitCode) diff --git a/tests/separate_prod_fin_test_shape.json b/tests/separate_prod_fin_test_shape.json new file mode 100644 index 0000000000..c40470d8d2 --- /dev/null +++ b/tests/separate_prod_fin_test_shape.json @@ -0,0 +1,137 @@ +{ + "name": "testnet_", + "ssh_helper": { + "ssh_cmd": "/usr/bin/ssh", + "scp_cmd": "/usr/bin/scp", + "ssh_identity": "", + "ssh_args": "" + }, + "nodes": { + "bios":{ + "name": "bios", + "keys": [ + { + "privkey":"5KQwrPbwdL6PhXujxW37FSSQZ1JiwsST4cqQzDeyXtP79zkvFD3", + "pubkey":"EOS6MRyAjQq8ud7hVNYcfnVPJqcVpscN5So8BhtHuGYqET5GDW5CV" + } + ], + "peers": [], + "producers": [ + "eosio" + ], + "dont_start": false + }, + "testnet_00":{ + "name": "testnet_00", + "keys": [ + { + "privkey":"5Jf4sTk7vwX1MYpLJ2eQFanVvKYXFqGBrCyANPukuP2BJ5WAAKZ", + "pubkey":"EOS58B33q9S7oNkgeFfcoW3VJYu4obfDiqn5RHGE2ige6jVjUhymR", + "blspubkey":"PUB_BLS_Uf3df_EqPpR31ZkenPtwgGUtd69cahyuY2lc9jPwEta7Q6t7REV-Hd35hUIDel4N7pQdCGZdnVZzs_UmJghEjGhVHN1QVVAQjOca8Fs10D_jqTiUzffzqyBAvTHyZtoEEPyXkg", + "blsprivkey":"PVT_BLS_t2sZsoDWTQFIKg75bhJn8pBA0iDYcWyn3HlEfKIzTzKozgKO", + "blspop":"SIG_BLS_TnwBY4dpG54mCue3ZXwjCio0AIdWYwFdz5ipLdnXlg64FkYkhMUtkOdQIs1IYbMWOXlD6OnCP6jcCWi5VziWKNbLfMX64SdIkNPKOHrfE_8fBfIk9Onj7GbWx3q0LbYP7NfJQk1mk-gOjz1G3elZDDHt367YUgzYDKhtl1FSkfZzDRzDsCSei7H1MjLi_e0RVdUfgqAznGaq2Yss6gY-HzwzgHU4y-SNQpzdCuDlLEEIjkHq8fXuMiPWT2Dlt8kOML0uqg" + } + ], + "peers": [ + "bios", + "testnet_01", + "testnet_02", + "testnet_03", + "testnet_04" + ], + "producers": [ + "defproducera" + ], + "dont_start": false + }, + "testnet_01":{ + "name": "testnet_01", + "keys": [ + { + "privkey":"5HviUPkTEtvF2B1nm8aZUnjma2TzgpKRjuXjwHyy3FME4xDbkZF", + "pubkey":"EOS5CbcTDgbks2ptTxvyCbT9HFbzX7PDHUY2wN4DDnVBhhQr2ZNDE", + "blspubkey":"PUB_BLS_Y8ndNvnrEpnzJcNUg49ncWDiDGRgR7WUmRRDR9yMURoS6zF14sPnbb-DsTGp0cEM628a4CmG6KXMhPJMqGZvb7RM_MGIwgbEhVaENL8rXeYLOuFDS375KHFgXxs2P5sZuaN7aA", + "blsprivkey":"PVT_BLS_A1Mifu5xyaxiveyjnZ-qN2zOt-5_KLMpjTrDI9udcQNV1NBR", + "blspop":"SIG_BLS_7D0OUU1h7E0AKkAmqV4v3Ot9oSPWJBOss4yDejr2x1g5G31cSSAYIAtqZOYC-ioNzddY7zkvTcbhKgBzv5a-G1HmV1pOCXXPJ5TL0iqU8Ks5abeEWCdhArGATmRQiSMYNcj9rMQcm3H6Z0pOlOdbDdt8Cg-SY_H4jEGmAY2ZqudAH_U8gS19aydJU-2uQq0SPIr2Okl-WNbc-q3NVQw6Y0sAHAwN4BOIHup2MJyDDDIbpSEkBchRp3zna1XJf6oBuUzpqQ" + } + ], + "peers": [ + "bios", + "testnet_00", + "testnet_02", + "testnet_03", + "testnet_04" + ], + "producers": [ + "defproducerb" + ], + "dont_start": false + }, + "testnet_02":{ + "name": "testnet_02", + "keys": [ + { + "privkey":"5KkQbdxFHr8Pg1N3DEMDdU7emFgUTwQvh99FDJrodFhUbbsAtQT", + "pubkey":"EOS6Tkpf8kcDfa32WA9B4nTcEJ64ZdDMSNioDcaL6rzdMwnpzaWJB", + "blspubkey":"PUB_BLS_Wf_O_QeyVhekDXS5q3qBxTyj_qxSrX_uiCY4z8ClpW0X2jrAVgAVHOQ9IR2H40QTWveD8QIGhhSbmSFPa0zFbs5k3yfnjfuuwpA7T1O13_LSdtxT19ehYiE4chZX6SUMJ09JFA", + "blsprivkey":"PVT_BLS_1ZLWim0k80ssXswSZp1T3ydHO9U3gLnKKlEBIDy8927XDLLj", + "blspop":"SIG_BLS_EL09aI3w-qCgarLM2Z5-T6sisSHBN0J4vMZxtGQklkOcAxgnCaPPXe0roxY4W0gVe2y6T01YrklmT_qZu2tAwqiNrVJcScY8QKvRSeczGBBab1MgnHvaAOuf6bA4JPAELIu2iPWfsS6-oLyLbNP5xtZpMXPHu3yaSJssXNOb5rcVs1KXaIUEagJeAlBBQEcKmFWfeAsJ_R8JDw4i9gSNmROzUjm6LVBpvB7vrnPDPFRA0BQ19H4FED6PtuFPShwJGVz4dg" + } + ], + "peers": [ + "bios", + "testnet_00", + "testnet_01", + "testnet_02", + "testnet_04" + ], + "producers": [ + ], + "dont_start": false + }, + "testnet_03":{ + "name": "testnet_03", + "keys": [ + { + "privkey":"5JxTJJegQBpEL1p77TzkN1ompMB9gDwAfjM9chPzFCB4chxmwrE", + "pubkey":"EOS52ntDHqA2qj4xVo7KmxdezMRhvvBqpZBuKYJCsgihisxmywpAx", + "blspubkey":"PUB_BLS_C-FprIiry6X-8dlLYH7xUAhIuKXBQv56zJPgtcdmKeHf8AAy750eRrOYBtKG0-QEIN5l_yl9dTLvAYmOios6Q5t3ybWBUVVQ2WWcbZLVxzwBftLwYvo1zPXH7LHEE_sAgP1i7g", + "blsprivkey":"PVT_BLS_ubElmjajfsYP_9HRSpmV-Fi_IPWKTyJS4XFSWrU8ezMZ_mL_", + "blspop":"SIG_BLS_k3wrhVl2GUG_lGsPr9io-zoamPw7eiaxMDExk-yOqcpXtu0zALHoUWJRh0WOerAS1-_RQNhbi4q-BWO9IbiNWRKP9CYIhNIL6ochGHHy4aBmZ-IzEjfBrDt7inDtFTYY0Gl372e5OqPXAwi6J3GeHipXuzAiw7SV8XdWFefthxId4meKX6vw5_RWx4XQ4ScRYoCG7UQtIZkQPEsu1SfJGL6z-cfTTSq-naKbzp0QQYfqtQkFfmL7qQUH1iohnb0HbTbRbQ" + } + ], + "peers": [ + "bios", + "testnet_00", + "testnet_01", + "testnet_02", + "testnet_04" + ], + "producers": [ + ], + "dont_start": false + }, + "testnet_04":{ + "name": "testnet_04", + "keys": [ + { + "privkey":"5K3h9XiAmrx9EuqD8CRxHgQwEVDaWpqrhrnpdvwHtVzwJFMhNmE", + "pubkey":"EOS7K5pQCk22ojetRdyumrqp6nJX6eiQiTWWcGkZAMGhoBxgcsxhK", + "blspubkey":"PUB_BLS_kGOCEX1MM5Xl928OOvGLyNo3_GpV8av1HnoaCEGOD8bAu3MDvazu0gCZGA1G7msTh1ZTPMEMVdXMuRVS0tv_9bW9Ohz9XvgtjgbPpxxc_NaeENkGg4uDBOro0Rk8DCEW4ToLKA", + "blsprivkey":"PVT_BLS_EnQXObGKvYqfubrKjxpCqNkHeLlkQg7LERjDGm1RKjgyFZnk", + "blspop":"SIG_BLS_bXrzPVc-ahxOCWrcl-iWIMuS8ego54iz7vi38A8h_ViqtxklH9O3A2z0eiw5j40M08ejiTm7JbCY_GOwulv1oXb9SaLYQkCTZjzCVssDkghLBRTVCZW2oJmU9WbZXikNw6nkygTs5sUTtCda2a_M5jqY_Rw92_NWmbolgBNkFvMcAgSHexdETA-b7QgJX_oYBWkyP0Pt8LzO6bJueZSjH8wZ8VuPc9o8taY85mt_qgdOTbXVBG2m5ud0eAUps2UHAHt-Ig" + } + ], + "peers": [ + "bios", + "testnet_00", + "testnet_01", + "testnet_02", + "testnet_03" + ], + "producers": [ + ], + "dont_start": false + } + } +}