From a8981eada2f644e5d4e999684f7da339eb4bf2e5 Mon Sep 17 00:00:00 2001 From: Ciju John Date: Fri, 1 Jun 2018 15:08:41 -0500 Subject: [PATCH 1/7] Add new test for dirty db flag stickiness. --- tests/CMakeLists.txt | 2 + tests/testUtils.py | 6 +- tests/validate-dirty-db.py | 130 +++++++++++++++++++++++++++++++++++++ 3 files changed, 137 insertions(+), 1 deletion(-) create mode 100755 tests/validate-dirty-db.py diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 8aecf57ba01..c933d024aa8 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -34,6 +34,7 @@ configure_file(${CMAKE_CURRENT_SOURCE_DIR}/testUtils.py ${CMAKE_CURRENT_BINARY_D configure_file(${CMAKE_CURRENT_SOURCE_DIR}/nodeos_run_test.py ${CMAKE_CURRENT_BINARY_DIR}/nodeos_run_test.py COPYONLY) configure_file(${CMAKE_CURRENT_SOURCE_DIR}/nodeos_run_remote_test.py ${CMAKE_CURRENT_BINARY_DIR}/nodeos_run_remote_test.py COPYONLY) configure_file(${CMAKE_CURRENT_SOURCE_DIR}/consensus-validation-malicious-producers.py ${CMAKE_CURRENT_BINARY_DIR}/consensus-validation-malicious-producers.py COPYONLY) +configure_file(${CMAKE_CURRENT_SOURCE_DIR}/validate-dirty-db.py ${CMAKE_CURRENT_BINARY_DIR}/validate-dirty-db.py COPYONLY) #To run plugin_test with all log from blockchain displayed, put --verbose after --, i.e. plugin_test -- --verbose add_test(NAME plugin_test COMMAND plugin_test --report_level=detailed --color_output) @@ -54,6 +55,7 @@ add_test(NAME distributed-transactions-remote-test COMMAND tests/distributed-tra # add_test(NAME restart-scenarios-test_replay COMMAND tests/restart-scenarios-test.py -c replay -p4 -v --dump-error-details WORKING_DIRECTORY ${CMAKE_BINARY_DIR}) add_test(NAME restart-scenarios-test_hard_replay COMMAND tests/restart-scenarios-test.py -c hardReplay -p4 -v --dump-error-details WORKING_DIRECTORY ${CMAKE_BINARY_DIR}) # TODO: add_test(NAME consensus-validation-malicious-producers COMMAND tests/consensus-validation-malicious-producers.py -w 80 --dump-error-details WORKING_DIRECTORY ${CMAKE_BINARY_DIR}) +add_test(NAME validate_dirty_db_test COMMAND tests/validate-dirty-db.py -v --dump-error-detail WORKING_DIRECTORY ${CMAKE_BINARY_DIR}) if(ENABLE_COVERAGE_TESTING) diff --git a/tests/testUtils.py b/tests/testUtils.py index 7a80aba9c64..50c84e251b9 100755 --- a/tests/testUtils.py +++ b/tests/testUtils.py @@ -1393,7 +1393,7 @@ def setWalletMgr(self, walletMgr): # pylint: disable=too-many-return-statements # pylint: disable=too-many-branches # pylint: disable=too-many-statements - def launch(self, pnodes=1, totalNodes=1, prodCount=1, topo="mesh", delay=1, onlyBios=False, dontKill=False): + def launch(self, pnodes=1, totalNodes=1, prodCount=1, topo="mesh", delay=1, onlyBios=False, dontKill=False, dontBootstrap=False): """Launch cluster. pnodes: producer nodes count totalNodes: producer + non-producer nodes count @@ -1456,6 +1456,10 @@ def launch(self, pnodes=1, totalNodes=1, prodCount=1, topo="mesh", delay=1, only Utils.Print("ERROR: Cluster doesn't seem to be in sync. Some nodes missing block 1") return False + if dontBootstrap: + Utils.Print("Skipping bootstrap.") + return True + Utils.Print("Bootstrap cluster.") if not Cluster.bootstrap(totalNodes, prodCount, Cluster.__BiosHost, Cluster.__BiosPort, dontKill, onlyBios): Utils.Print("ERROR: Bootstrap failed.") diff --git a/tests/validate-dirty-db.py b/tests/validate-dirty-db.py new file mode 100755 index 00000000000..d7a522713b6 --- /dev/null +++ b/tests/validate-dirty-db.py @@ -0,0 +1,130 @@ +#!/usr/bin/env python3 + +import testUtils + +import argparse +import random +import subprocess +import time +import os +import signal + +############################################################### +# Test for validating the dirty db flag sticks repeated nodeos restart attempts +############################################################### + + +Print=testUtils.Utils.Print + +def errorExit(msg="", errorCode=1): + Print("ERROR:", msg) + exit(errorCode) + +parser = argparse.ArgumentParser() +parser.add_argument("-v", help="verbose logging", action='store_true') +parser.add_argument("--dont-kill", help="Leave cluster running after test finishes", action='store_true') +parser.add_argument("--dump-error-details", + help="Upon error print etc/eosio/node_*/config.ini and var/lib/node_*/stderr.log to stdout", + action='store_true') +parser.add_argument("--keep-logs", help="Don't delete var/lib/node_* folders upon test completion", + action='store_true') + +args = parser.parse_args() +debug=args.v +pnodes=1 +topo="mesh" +delay=1 +chainSyncStrategyStr=testUtils.Utils.SyncResyncTag +total_nodes = pnodes +killCount=1 +killSignal=testUtils.Utils.SigKillTag + +killEosInstances= not args.dont_kill +dumpErrorDetails=args.dump_error_details +keepLogs=args.keep_logs + +seed=1 +testUtils.Utils.Debug=debug +testSuccessful=False + +random.seed(seed) # Use a fixed seed for repeatability. +cluster=testUtils.Cluster(walletd=True) + +try: + cluster.setChainStrategy(chainSyncStrategyStr) + + cluster.killall() + cluster.cleanup() + + Print ("producing nodes: %d, topology: %s, delay between nodes launch(seconds): %d, chain sync strategy: %s" % ( + pnodes, topo, delay, chainSyncStrategyStr)) + + Print("Stand up cluster") + if cluster.launch(pnodes, total_nodes, topo=topo, delay=delay, dontBootstrap=True) is False: + errorExit("Failed to stand up eos cluster.") + + node=cluster.getNode(0) + if node is None: + errorExit("Cluster in bad state, received None node") + + Print("Kill cluster nodes.") + cluster.killall() + + def runNodeosAndGetOutput(nodeId, waitTime=3): + """Startup nodeos, wait for waitTime (before forced shutdown) and collect output. Stdout, stderr and return code are returned in a dictionary. + Large waittimes have potential to deadlock as the popen PIPE buffer can get filled up.""" + Print("Launching nodeos process id: %d" % (nodeId)) + dataDir="var/lib/node_%02d" % (nodeId) + cmd="programs/nodeos/nodeos --config-dir etc/eosio/node_bios --data-dir var/lib/node_bios" + Print("cmd: %s" % (cmd)) + proc=subprocess.Popen(cmd.split(), stdout=subprocess.PIPE, stderr=subprocess.PIPE) + + try: + proc.wait(waitTime) + except (subprocess.TimeoutExpired) as _: + Print("ERROR: Nodeos is running beyond the defined wait time. Hard killing nodeos instance.") + proc.send_signal(signal.SIGKILL) + return (False, None) + + output={} + output["stdout"] = proc.stdout.read().decode("utf-8") + output["stderr"] = proc.stderr.read().decode("utf-8") + output["returncode"] = proc.returncode + return (True, output) + + Print("Restart nodeos repeatedly to ensure dirty database flag sticks.") + nodeId=0 + waitTime=3 + + for i in range(0,3): + Print("Attempt %d." % (i)) + ret = runNodeosAndGetOutput(nodeId, waitTime) + if not ret or not ret[0]: + exit(1) + + #Print(ret) + + stderr=ret[1]["stderr"] + retCode=ret[1]["returncode"] + assert(retCode == 2) + assert("database dirty flag set" in stderr) + + testSuccessful=True +finally: + if testSuccessful: + Print("Test succeeded.") + else: + Print("Test failed.") + + if not testSuccessful and dumpErrorDetails: + cluster.dumpErrorDetails() + Print("== Errors see above ==") + + if killEosInstances: + Print("Shut down the cluster.") + cluster.killall() + if testSuccessful and not keepLogs: + Print("Cleanup cluster data.") + cluster.cleanup() + +exit(0) From c65dba99ff57e0fda941ad423fc8fd7a180b66fb Mon Sep 17 00:00:00 2001 From: Anton Perkov Date: Mon, 4 Jun 2018 10:14:47 -0400 Subject: [PATCH 2/7] system contract bugfix: regproducer should always modify parameters (removed check if key is different) #3783 --- contracts/eosio.system/voting.cpp | 14 ++++++------ unittests/eosio.system_tests.cpp | 36 ++++++++++++++++++++++++------- 2 files changed, 34 insertions(+), 16 deletions(-) diff --git a/contracts/eosio.system/voting.cpp b/contracts/eosio.system/voting.cpp index 1ca8977513e..31bf7059336 100644 --- a/contracts/eosio.system/voting.cpp +++ b/contracts/eosio.system/voting.cpp @@ -42,14 +42,12 @@ namespace eosiosystem { auto prod = _producers.find( producer ); if ( prod != _producers.end() ) { - if( producer_key != prod->producer_key ) { - _producers.modify( prod, producer, [&]( producer_info& info ){ - info.producer_key = producer_key; - info.is_active = true; - info.url = url; - info.location = location; - }); - } + _producers.modify( prod, producer, [&]( producer_info& info ){ + info.producer_key = producer_key; + info.is_active = true; + info.url = url; + info.location = location; + }); } else { _producers.emplace( producer, [&]( producer_info& info ){ info.owner = producer; diff --git a/unittests/eosio.system_tests.cpp b/unittests/eosio.system_tests.cpp index 2a552dd7851..9b38b564dea 100644 --- a/unittests/eosio.system_tests.cpp +++ b/unittests/eosio.system_tests.cpp @@ -565,13 +565,13 @@ BOOST_FIXTURE_TEST_CASE( stake_from_refund, eosio_system_tester ) try { BOOST_FIXTURE_TEST_CASE( producer_register_unregister, eosio_system_tester ) try { issue( "alice1111111", core_from_string("1000.0000"), config::system_account_name ); - fc::variant params = producer_parameters_example(1); + //fc::variant params = producer_parameters_example(1); auto key = fc::crypto::public_key( std::string("EOS6MRyAjQq8ud7hVNYcfnVPJqcVpscN5So8BhtHuGYqET5GDW5CV") ); BOOST_REQUIRE_EQUAL( success(), push_action(N(alice1111111), N(regproducer), mvo() ("producer", "alice1111111") ("producer_key", key ) ("url", "http://block.one") - ("location", "0") + ("location", 1) ) ); @@ -580,13 +580,34 @@ BOOST_FIXTURE_TEST_CASE( producer_register_unregister, eosio_system_tester ) try BOOST_REQUIRE_EQUAL( 0, info["total_votes"].as_double() ); BOOST_REQUIRE_EQUAL( "http://block.one", info["url"].as_string() ); - //call regproducer again to change parameters - fc::variant params2 = producer_parameters_example(2); + //change parameters one by one to check for things like #3783 + //fc::variant params2 = producer_parameters_example(2); + BOOST_REQUIRE_EQUAL( success(), push_action(N(alice1111111), N(regproducer), mvo() + ("producer", "alice1111111") + ("producer_key", key ) + ("url", "http://block.two") + ("location", 1) + ) + ); + info = get_producer_info( "alice1111111" ); + BOOST_REQUIRE_EQUAL( "alice1111111", info["owner"].as_string() ); + BOOST_REQUIRE_EQUAL( key, fc::crypto::public_key(info["producer_key"].as_string()) ); + BOOST_REQUIRE_EQUAL( "http://block.two", info["url"].as_string() ); + BOOST_REQUIRE_EQUAL( 1, info["location"].as_int64() ); + auto key2 = fc::crypto::public_key( std::string("EOS5jnmSKrzdBHE9n8hw58y7yxFWBC8SNiG7m8S1crJH3KvAnf9o6") ); + BOOST_REQUIRE_EQUAL( success(), push_action(N(alice1111111), N(regproducer), mvo() + ("producer", "alice1111111") + ("producer_key", key2 ) + ("url", "http://block.two") + ("location", 2) + ) + ); info = get_producer_info( "alice1111111" ); BOOST_REQUIRE_EQUAL( "alice1111111", info["owner"].as_string() ); - BOOST_REQUIRE_EQUAL( 0, info["total_votes"].as_double() ); - BOOST_REQUIRE_EQUAL( "http://block.one", info["url"].as_string() ); + BOOST_REQUIRE_EQUAL( key2, fc::crypto::public_key(info["producer_key"].as_string()) ); + BOOST_REQUIRE_EQUAL( "http://block.two", info["url"].as_string() ); + BOOST_REQUIRE_EQUAL( 2, info["location"].as_int64() ); //unregister producer BOOST_REQUIRE_EQUAL( success(), push_action(N(alice1111111), N(unregprod), mvo() @@ -595,12 +616,11 @@ BOOST_FIXTURE_TEST_CASE( producer_register_unregister, eosio_system_tester ) try ); info = get_producer_info( "alice1111111" ); //key should be empty - wdump((info)); BOOST_REQUIRE_EQUAL( fc::crypto::public_key(), fc::crypto::public_key(info["producer_key"].as_string()) ); //everything else should stay the same BOOST_REQUIRE_EQUAL( "alice1111111", info["owner"].as_string() ); BOOST_REQUIRE_EQUAL( 0, info["total_votes"].as_double() ); - BOOST_REQUIRE_EQUAL( "http://block.one", info["url"].as_string() ); + BOOST_REQUIRE_EQUAL( "http://block.two", info["url"].as_string() ); //unregister bob111111111 who is not a producer BOOST_REQUIRE_EQUAL( wasm_assert_msg( "producer not found" ), From b0471b6faf454c5f7ed77e5c1be5c4188994f5d7 Mon Sep 17 00:00:00 2001 From: Anton Perkov Date: Mon, 4 Jun 2018 10:30:26 -0400 Subject: [PATCH 3/7] Docker README.md: command for installing exchange contract fixed #3777 --- Docker/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Docker/README.md b/Docker/README.md index 016ac239a0e..9f4bff570b9 100644 --- a/Docker/README.md +++ b/Docker/README.md @@ -81,7 +81,7 @@ cleos get account inita Upload sample exchange contract ```bash -cleos set contract exchange contracts/exchange/exchange.wast contracts/exchange/exchange.abi +cleos set contract exchange contracts/exchange/ ``` If you don't need keosd afterwards, you can stop the keosd service using From bc44f8b2356bdbc84fb5a9642c655ed5312a7c91 Mon Sep 17 00:00:00 2001 From: Ciju John Date: Mon, 4 Jun 2018 11:55:29 -0500 Subject: [PATCH 4/7] Fix deadlock prone test code. --- tests/validate-dirty-db.py | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/tests/validate-dirty-db.py b/tests/validate-dirty-db.py index d7a522713b6..6cef6b88879 100755 --- a/tests/validate-dirty-db.py +++ b/tests/validate-dirty-db.py @@ -70,35 +70,34 @@ def errorExit(msg="", errorCode=1): Print("Kill cluster nodes.") cluster.killall() - def runNodeosAndGetOutput(nodeId, waitTime=3): - """Startup nodeos, wait for waitTime (before forced shutdown) and collect output. Stdout, stderr and return code are returned in a dictionary. - Large waittimes have potential to deadlock as the popen PIPE buffer can get filled up.""" + def runNodeosAndGetOutput(nodeId, timeout=3): + """Startup nodeos, wait for timeout (before forced shutdown) and collect output. Stdout, stderr and return code are returned in a dictionary.""" Print("Launching nodeos process id: %d" % (nodeId)) dataDir="var/lib/node_%02d" % (nodeId) cmd="programs/nodeos/nodeos --config-dir etc/eosio/node_bios --data-dir var/lib/node_bios" Print("cmd: %s" % (cmd)) proc=subprocess.Popen(cmd.split(), stdout=subprocess.PIPE, stderr=subprocess.PIPE) + output={} try: - proc.wait(waitTime) + outs,errs = proc.communicate(timeout=timeout) + output["stdout"] = outs.decode("utf-8") + output["stderr"] = errs.decode("utf-8") + output["returncode"] = proc.returncode except (subprocess.TimeoutExpired) as _: Print("ERROR: Nodeos is running beyond the defined wait time. Hard killing nodeos instance.") proc.send_signal(signal.SIGKILL) return (False, None) - output={} - output["stdout"] = proc.stdout.read().decode("utf-8") - output["stderr"] = proc.stderr.read().decode("utf-8") - output["returncode"] = proc.returncode return (True, output) Print("Restart nodeos repeatedly to ensure dirty database flag sticks.") nodeId=0 - waitTime=3 + timeout=3 for i in range(0,3): Print("Attempt %d." % (i)) - ret = runNodeosAndGetOutput(nodeId, waitTime) + ret = runNodeosAndGetOutput(nodeId, timeout) if not ret or not ret[0]: exit(1) From 6480aa51d60d29f3650ab2d544a08711032bdac1 Mon Sep 17 00:00:00 2001 From: enumivo Date: Tue, 5 Jun 2018 19:02:19 +0800 Subject: [PATCH 5/7] small typo --- EXCHANGE_README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/EXCHANGE_README.md b/EXCHANGE_README.md index d27c79200bd..c205883b904 100644 --- a/EXCHANGE_README.md +++ b/EXCHANGE_README.md @@ -45,7 +45,7 @@ Accepting Deposits When designing this tutorial we assume that an exchange will poll `enunode` for incoming transactions and will want to know when a transfer is considered irreversible or final. -With enumivo based chains, finality of a transaction occurs once 2/3+1 of block produers have +With enumivo based chains, finality of a transaction occurs once 2/3+1 of block producers have either directly or indirectly confirmed the block. This could take from less than a second to a couple of minutes, but either way enunode will keep you posted on the status. From 54fc86c1d3e21169c2788f489a626825677c4afe Mon Sep 17 00:00:00 2001 From: enumivo Date: Tue, 5 Jun 2018 21:42:07 +0800 Subject: [PATCH 6/7] walletd to enuwalletd --- tests/validate-dirty-db.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/validate-dirty-db.py b/tests/validate-dirty-db.py index ab3482e4716..e0c99638343 100644 --- a/tests/validate-dirty-db.py +++ b/tests/validate-dirty-db.py @@ -48,7 +48,7 @@ def errorExit(msg="", errorCode=1): testSuccessful=False random.seed(seed) # Use a fixed seed for repeatability. -cluster=testUtils.Cluster(walletd=True) +cluster=testUtils.Cluster(enuwalletd=True) try: cluster.setChainStrategy(chainSyncStrategyStr) From d68ab0e816a3f726233dfcb9e6e7590942d59006 Mon Sep 17 00:00:00 2001 From: Enumivo Date: Tue, 5 Jun 2018 22:06:02 +0800 Subject: [PATCH 7/7] make executable --- tests/validate-dirty-db.py | 0 1 file changed, 0 insertions(+), 0 deletions(-) mode change 100644 => 100755 tests/validate-dirty-db.py diff --git a/tests/validate-dirty-db.py b/tests/validate-dirty-db.py old mode 100644 new mode 100755