diff --git a/.github/env_nightly_upgrade b/.github/env_nightly_upgrade index 7858f85c5..4d64b1041 100644 --- a/.github/env_nightly_upgrade +++ b/.github/env_nightly_upgrade @@ -1,2 +1,3 @@ BASE_TAR_URL=https://github.com/IntersectMBO/cardano-node/releases/download/8.12.2/cardano-node-8.12.2-linux.tar.gz CI_BYRON_CLUSTER=true +UPGRADE_CLI_REVISION=release/cardano-cli-9.0.0.1 diff --git a/.github/node_upgrade_pytest.sh b/.github/node_upgrade_pytest.sh index fbad7ff4a..8597981ff 100755 --- a/.github/node_upgrade_pytest.sh +++ b/.github/node_upgrade_pytest.sh @@ -11,6 +11,7 @@ export TX_ERA="$CLUSTER_ERA" CLUSTER_SCRIPTS_DIR="$WORKDIR/cluster0_${CLUSTER_ERA}" STATE_CLUSTER="${CARDANO_NODE_SOCKET_PATH_CI%/*}" +NUM_CC=5 # init dir for step1 binaries STEP1_BIN="$WORKDIR/step1-bin" @@ -67,9 +68,9 @@ if [ "$1" = "step1" ]; then ln -s "$(command -v cardano-cli)" "$STEP1_BIN/cardano-cli-step1" # backup the original genesis files - cp -f "$STATE_CLUSTER/shelley/genesis.alonzo.json" "$STATE_CLUSTER/shelley/genesis.alonzo-step1.json" + cp -f "$STATE_CLUSTER/shelley/genesis.alonzo.json" "$STATE_CLUSTER/shelley/genesis.alonzo.step1.json" if [ -e "$STATE_CLUSTER/shelley/genesis.conway.json" ]; then - cp -f "$STATE_CLUSTER/shelley/genesis.conway.json" "$STATE_CLUSTER/shelley/genesis.conway-step1.json" + cp -f "$STATE_CLUSTER/shelley/genesis.conway.json" "$STATE_CLUSTER/shelley/genesis.conway.step1.json" fi # run smoke tests @@ -103,6 +104,14 @@ elif [ "$1" = "step2" ]; then export UPGRADE_TESTS_STEP=2 + # Setup `cardano-cli` binary + if [ -n "${UPGRADE_CLI_REVISION:-""}" ]; then + export CARDANO_CLI_REV="$UPGRADE_CLI_REVISION" + # shellcheck disable=SC1090,SC1091 + . .github/source_cardano_cli.sh + export PATH="$WORKDIR/cardano-cli/cardano-cli-build/bin":"$PATH" + fi + # add binaries saved in step1 to the PATH export PATH="${STEP1_BIN}:${PATH}" @@ -143,9 +152,9 @@ elif [ "$1" = "step2" ]; then if [ "$fname" = "config-pool3.json" ]; then # use old Alonzo and Conway genesis on pool3 selected_alonzo_hash="$ALONZO_GENESIS_STEP1_HASH" - selected_alonzo_file="shelley/genesis.alonzo-step1.json" + selected_alonzo_file="shelley/genesis.alonzo.step1.json" selected_conway_hash="$CONWAY_GENESIS_STEP1_HASH" - selected_conway_file="shelley/genesis.conway-step1.json" + selected_conway_file="shelley/genesis.conway.step1.json" else # use new Alonzo and Conway genesis on upgraded nodes selected_alonzo_hash="$ALONZO_GENESIS_HASH" @@ -186,7 +195,7 @@ elif [ "$1" = "step2" ]; then done # run the pool3 with the original cardano-node binary - cp -a "$STATE_CLUSTER/cardano-node-pool3" "$STATE_CLUSTER/cardano-node-pool3.orig" + cp -f "$STATE_CLUSTER/cardano-node-pool3" "$STATE_CLUSTER/cardano-node-pool3.orig" sed -i 's/cardano-node run/cardano-node-step1 run/' "$STATE_CLUSTER/cardano-node-pool3" # Restart local cluster nodes with binaries from new cluster-node version. @@ -233,6 +242,7 @@ elif [ "$1" = "step2" ]; then # Test for ignoring expected errors in log files. Run separately to make sure it runs first. pytest cardano_node_tests/tests/test_node_upgrade.py -k test_ignore_log_errors + err_retval="$?" # run smoke tests pytest \ @@ -253,6 +263,8 @@ elif [ "$1" = "step2" ]; then ./.github/results.sh . mv allure-results.tar.xz allure-results-step2.tar.xz + [ "$err_retval" -gt "$retval" ] && retval=1 + printf "STEP2 finish: %(%H:%M:%S)T\n" -1 @@ -265,6 +277,17 @@ elif [ "$1" = "step3" ]; then export UPGRADE_TESTS_STEP=3 + # Setup `cardano-cli` binary + if [ -n "${UPGRADE_CLI_REVISION:-""}" ]; then + export CARDANO_CLI_REV="$UPGRADE_CLI_REVISION" + # the cardano-cli binary is already built in step2 + if [ ! -e "$WORKDIR/cardano-cli/cardano-cli-build/bin/cardano-cli" ]; then + echo "Failed to find the requested 'cardano-cli' binary" >&2 + exit 6 + fi + export PATH="$WORKDIR/cardano-cli/cardano-cli-build/bin":"$PATH" + fi + # generate config and topology files for p2p mode CARDANO_NODE_SOCKET_PATH="$WORKDIR/dry_p2p/state-cluster0/bft1.socket" \ ENABLE_P2P=1 \ @@ -274,11 +297,42 @@ elif [ "$1" = "step3" ]; then # copy newly generated topology files to the cluster state dir cp -f "$WORKDIR"/dry_p2p/state-cluster0/topology-*.json "$STATE_CLUSTER" - # copy newly generated config files to the cluster state dir, but use the original genesis files + # Create committee keys + mkdir -p "$STATE_CLUSTER/governance_data" + for i in $(seq 1 "$NUM_CC"); do + cardano-cli conway governance committee key-gen-cold \ + --cold-verification-key-file "$STATE_CLUSTER/governance_data/cc_member${i}_committee_cold.vkey" \ + --cold-signing-key-file "$STATE_CLUSTER/governance_data/cc_member${i}_committee_cold.skey" + cardano-cli conway governance committee key-gen-hot \ + --verification-key-file "$STATE_CLUSTER/governance_data/cc_member${i}_committee_hot.vkey" \ + --signing-key-file "$STATE_CLUSTER/governance_data/cc_member${i}_committee_hot.skey" + cardano-cli conway governance committee create-hot-key-authorization-certificate \ + --cold-verification-key-file "$STATE_CLUSTER/governance_data/cc_member${i}_committee_cold.vkey" \ + --hot-verification-key-file "$STATE_CLUSTER/governance_data/cc_member${i}_committee_hot.vkey" \ + --out-file "$STATE_CLUSTER/governance_data/cc_member${i}_committee_hot_auth.cert" + cardano-cli conway governance committee key-hash \ + --verification-key-file "$STATE_CLUSTER/governance_data/cc_member${i}_committee_cold.vkey" \ + > "$STATE_CLUSTER/governance_data/cc_member${i}_committee_cold.hash" + done + + # Pre-register committee in genesis + cp -f "$STATE_CLUSTER/shelley/genesis.conway.json" "$STATE_CLUSTER/shelley/genesis.conway.step2.json" + KEY_HASH_JSON=$(jq -nR '[inputs | {("keyHash-" + .): 10000}] | add' \ + "$STATE_CLUSTER"/governance_data/cc_member*_committee_cold.hash) + jq \ + --argjson keyHashJson "$KEY_HASH_JSON" \ + '.committee.members = $keyHashJson + | .committee.threshold = 0.6 + | .committeeMinSize = 2' \ + "$STATE_CLUSTER/shelley/genesis.conway.step2.json" > "$STATE_CLUSTER/shelley/genesis.conway.json" + + # Copy newly generated config files to the cluster state dir, but use the original genesis files BYRON_GENESIS_HASH="$(jq -r ".ByronGenesisHash" "$STATE_CLUSTER/config-bft1.json")" SHELLEY_GENESIS_HASH="$(jq -r ".ShelleyGenesisHash" "$STATE_CLUSTER/config-bft1.json")" ALONZO_GENESIS_HASH="$(jq -r ".AlonzoGenesisHash" "$STATE_CLUSTER/config-bft1.json")" - CONWAY_GENESIS_HASH="$(jq -r ".ConwayGenesisHash" "$STATE_CLUSTER/config-bft1.json")" + # Use the new Conway genesis + CONWAY_GENESIS_HASH="$(cardano-cli genesis hash --genesis \ + "$STATE_CLUSTER/shelley/genesis.conway.json")" for conf in "$WORKDIR"/dry_p2p/state-cluster0/config-*.json; do fname="${conf##*/}" jq \ @@ -289,12 +343,12 @@ elif [ "$1" = "step3" ]; then '.ByronGenesisHash = $byron_hash | .ShelleyGenesisHash = $shelley_hash | .AlonzoGenesisHash = $alonzo_hash - | .ConwayHash = $conway_hash' \ + | .ConwayGenesisHash = $conway_hash' \ "$conf" > "$STATE_CLUSTER/$fname" done # use the upgraded cardano-node binary for pool3 - cp -a "$STATE_CLUSTER/cardano-node-pool3.orig" "$STATE_CLUSTER/cardano-node-pool3" + cp -f "$STATE_CLUSTER/cardano-node-pool3.orig" "$STATE_CLUSTER/cardano-node-pool3" # restart all nodes "$STATE_CLUSTER/supervisorctl" restart nodes: @@ -315,8 +369,16 @@ elif [ "$1" = "step3" ]; then # Test for ignoring expected errors in log files. Run separately to make sure it runs first. pytest cardano_node_tests/tests/test_node_upgrade.py -k test_ignore_log_errors + err_retval="$?" - # run smoke tests + # Update to Conway + pytest cardano_node_tests/tests/test_node_upgrade.py -k test_update_to_conway_pv9 || exit 6 + + # From now on, we are in the Conway era + unset TX_ERA + export CLUSTER_ERA=conway COMMAND_ERA=conway + + # Run smoke tests pytest \ cardano_node_tests \ -n "$TEST_THREADS" \ @@ -332,6 +394,8 @@ elif [ "$1" = "step3" ]; then ./.github/results.sh . mv allure-results.tar.xz allure-results-step3.tar.xz + [ "$err_retval" -gt "$retval" ] && retval=1 + printf "STEP3 finish: %(%H:%M:%S)T\n" -1 # diff --git a/cardano_node_tests/tests/test_node_upgrade.py b/cardano_node_tests/tests/test_node_upgrade.py index 9598a3f20..9f877683d 100644 --- a/cardano_node_tests/tests/test_node_upgrade.py +++ b/cardano_node_tests/tests/test_node_upgrade.py @@ -12,7 +12,9 @@ from cardano_node_tests.cluster_management import cluster_management from cardano_node_tests.tests import common +from cardano_node_tests.utils import cluster_nodes from cardano_node_tests.utils import clusterlib_utils +from cardano_node_tests.utils import governance_utils from cardano_node_tests.utils import helpers from cardano_node_tests.utils import logfiles from cardano_node_tests.utils import temptools @@ -22,6 +24,7 @@ UPGRADE_TESTS_STEP = int(os.environ.get("UPGRADE_TESTS_STEP") or 0) BASE_REVISION = version.parse(os.environ.get("BASE_REVISION") or "0.0.0") UPGRADE_REVISION = version.parse(os.environ.get("UPGRADE_REVISION") or "0.0.0") +GOV_DATA_DIR = "governance_data" pytestmark = [ pytest.mark.skipif(not UPGRADE_TESTS_STEP, reason="not upgrade testing"), @@ -29,33 +32,27 @@ @pytest.fixture -def payment_addrs_locked( +def payment_addr_locked( cluster_manager: cluster_management.ClusterManager, cluster_singleton: clusterlib.ClusterLib, -) -> tp.List[clusterlib.AddressRecord]: +) -> clusterlib.AddressRecord: """Create new payment addresses.""" cluster = cluster_singleton temp_template = common.get_test_id(cluster) - with cluster_manager.cache_fixture() as fixture_cache: - if fixture_cache.value: - return fixture_cache.value # type: ignore - - addrs = clusterlib_utils.create_payment_addr_records( - f"{temp_template}_payment_addr_0", - f"{temp_template}_payment_addr_1", - cluster_obj=cluster, - ) - fixture_cache.value = addrs + addr = clusterlib_utils.create_payment_addr_records( + f"{temp_template}_payment_addr_0", + cluster_obj=cluster, + )[0] # fund source addresses clusterlib_utils.fund_from_faucet( - addrs[0], + addr, cluster_obj=cluster, faucet_data=cluster_manager.cache.addrs_data["user1"], ) - return addrs + return addr @pytest.fixture @@ -99,29 +96,123 @@ def test_ignore_log_errors( cluster = cluster_singleton common.get_test_id(cluster) - # Ignore ledger replay when upgrading from node version 1.34.1. - # The error message appears only right after the node is upgraded. This ignore rule has - # effect only in this test. - if version.parse("1.34.1") == BASE_REVISION: + if UPGRADE_REVISION >= version.parse("9.0.0") > BASE_REVISION: logfiles.add_ignore_rule( files_glob="*.stdout", - regex="ChainDB:Error:.* Invalid snapshot DiskSnapshot .*DeserialiseFailure 168 ", + regex="ChainDB:Error:.* Invalid snapshot DiskSnapshot .*DeserialiseFailure " + ".*expected list len or indef", ignore_file_id=worker_id, ) - elif UPGRADE_REVISION >= version.parse("1.36.0") > BASE_REVISION: - logfiles.add_ignore_rule( - files_glob="*.stdout", - regex="ChainDB:Error:.* Invalid snapshot DiskSnapshot .*DeserialiseFailure 5 ", - ignore_file_id=worker_id, + + @allure.link(helpers.get_vcs_link()) + @pytest.mark.skipif(UPGRADE_TESTS_STEP != 3, reason="runs only on step 3 of upgrade testing") + def test_update_to_conway_pv9( + self, + cluster_singleton: clusterlib.ClusterLib, + payment_addr_locked: clusterlib.AddressRecord, + ): + """Update cluster to Conway PV9.""" + cluster = cluster_singleton + temp_template = common.get_test_id(cluster) + + def _update_to_pv9() -> None: + cluster.wait_for_new_epoch() + + update_proposal_pv9 = [ + clusterlib_utils.UpdateProposal( + arg="--protocol-major-version", + value=9, + name="", # needs custom check + ), + clusterlib_utils.UpdateProposal( + arg="--protocol-minor-version", + value=0, + name="", # needs custom check + ), + ] + + clusterlib_utils.update_params( + cluster_obj=cluster, + src_addr_record=payment_addr_locked, + update_proposals=update_proposal_pv9, ) - elif UPGRADE_REVISION >= version.parse("8.1.0") > BASE_REVISION: - logfiles.add_ignore_rule( - files_glob="*.stdout", - regex="ChainDB:Error:.* Invalid snapshot DiskSnapshot .*DeserialiseFailure " - ".*Size mismatch when decoding Record RecD", - ignore_file_id=worker_id, + + cluster.wait_for_new_epoch(padding_seconds=3) + + prot_ver = cluster.g_query.get_protocol_params()["protocolVersion"] + assert prot_ver["major"] == 9 + assert prot_ver["minor"] == 0 + + def _load_cc_members( + cluster_obj: clusterlib.ClusterLib, + ) -> tp.List[governance_utils.CCMemberAuth]: + data_dir = cluster_obj.state_dir / GOV_DATA_DIR + + cc_members = [] + for vkey_file in sorted(data_dir.glob("cc_member*_committee_cold.vkey")): + fpath = vkey_file.parent + fbase = vkey_file.name.replace("cold.vkey", "") + hot_vkey_file = fpath / f"{fbase}hot.vkey" + cold_vkey_hash = cluster_obj.g_conway_governance.committee.get_key_hash( + vkey_file=vkey_file + ) + auth_cert = fpath / f"{fbase}hot_auth.cert" + cold_key_pair = clusterlib.KeyPair( + vkey_file=vkey_file, skey_file=fpath / f"{fbase}cold.skey" + ) + hot_key_pair = clusterlib.KeyPair( + vkey_file=hot_vkey_file, skey_file=fpath / f"{fbase}hot.skey" + ) + cc_members.append( + governance_utils.CCMemberAuth( + auth_cert=auth_cert, + cold_key_pair=cold_key_pair, + hot_key_pair=hot_key_pair, + key_hash=cold_vkey_hash, + ) + ) + + return cc_members + + def _reg_cc_members( + cluster_obj: clusterlib.ClusterLib, cc_members: tp.List[governance_utils.CCMemberAuth] + ) -> None: + tx_files = clusterlib.TxFiles( + certificate_files=[c.auth_cert for c in cc_members], + signing_key_files=[ + payment_addr_locked.skey_file, + *[c.cold_key_pair.skey_file for c in cc_members], + ], ) + tx_output_auth = clusterlib_utils.build_and_submit_tx( + cluster_obj=cluster_obj, + name_template=f"{temp_template}_auth", + src_address=payment_addr_locked.address, + tx_files=tx_files, + ) + + auth_out_utxos = cluster_obj.g_query.get_utxo(tx_raw_output=tx_output_auth) + assert ( + clusterlib.filter_utxos(utxos=auth_out_utxos, address=payment_addr_locked.address)[ + 0 + ].amount + == clusterlib.calculate_utxos_balance(tx_output_auth.txins) - tx_output_auth.fee + ), f"Incorrect balance for source address `{payment_addr_locked.address}`" + + auth_committee_state = cluster_obj.g_conway_governance.query.committee_state() + for cm in cc_members: + member_key = f"keyHash-{cm.key_hash}" + member_rec = auth_committee_state["committee"][member_key] + assert ( + member_rec["hotCredsAuthStatus"]["tag"] == "MemberAuthorized" + ), "CC Member was NOT authorized" + + _update_to_pv9() + cluster_conway = cluster_nodes.get_cluster_type().get_cluster_obj(command_era="conway") + cc_members = _load_cc_members(cluster_obj=cluster_conway) + _reg_cc_members(cluster_obj=cluster_conway, cc_members=cc_members) + @pytest.mark.upgrade class TestUpgrade: