From 47dff66fa60267558001dbd5f4caf179a7ec4946 Mon Sep 17 00:00:00 2001 From: James Dorfman Date: Fri, 3 Nov 2023 20:40:26 +0000 Subject: [PATCH 01/11] pubkey: fix bug in VerifySchnorr introduced in merge of bitcoin/bitcoin#22448 in 09333e2aca --- src/pubkey.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pubkey.cpp b/src/pubkey.cpp index 179239af2f..bfd0b60d7f 100644 --- a/src/pubkey.cpp +++ b/src/pubkey.cpp @@ -208,7 +208,7 @@ bool XOnlyPubKey::VerifySchnorr(const Span msg, Span Date: Thu, 9 Nov 2023 06:31:49 +0000 Subject: [PATCH 02/11] test: fix failing schnorr sig creation in bip341_keypath_test_vectors --- src/test/script_tests.cpp | 22 ++++++++++++++++------ 1 file changed, 16 insertions(+), 6 deletions(-) diff --git a/src/test/script_tests.cpp b/src/test/script_tests.cpp index a80e8a98f7..ee8d972b0e 100644 --- a/src/test/script_tests.cpp +++ b/src/test/script_tests.cpp @@ -33,6 +33,8 @@ #include +using namespace std; + // Uncomment if you want to output updated JSON tests. // #define UPDATE_JSON_TESTS @@ -1756,6 +1758,7 @@ BOOST_AUTO_TEST_CASE(script_assets_test) file.close(); } +// ELEMENTS: TODO: Some of these test vectors need updating BOOST_AUTO_TEST_CASE(bip341_keypath_test_vectors) { UniValue tests; @@ -1777,9 +1780,14 @@ BOOST_AUTO_TEST_CASE(bip341_keypath_test_vectors) PrecomputedTransactionData txdata; txdata.Init(tx, std::vector{utxos}, true); + // ELEMENTS: add a txout witness for each output + for (u_int i = 0; i < (u_int)utxos.size(); i++) { + tx.witness.vtxoutwit.emplace_back(CTxOutWitness()); + } BOOST_CHECK(txdata.m_bip341_taproot_ready); - // BOOST_CHECK_EQUAL(HexStr(txdata.m_spent_amounts_single_hash), vec["intermediary"]["hashAmounts"].get_str()); + // ELEMENTS: FIXME + //BOOST_CHECK_EQUAL(HexStr(txdata.m_spent_amounts_single_hash), vec["intermediary"]["hashAmounts"].get_str()); BOOST_CHECK_EQUAL(HexStr(txdata.m_outputs_single_hash), vec["intermediary"]["hashOutputs"].get_str()); BOOST_CHECK_EQUAL(HexStr(txdata.m_prevouts_single_hash), vec["intermediary"]["hashPrevouts"].get_str()); BOOST_CHECK_EQUAL(HexStr(txdata.m_spent_scripts_single_hash), vec["intermediary"]["hashScriptPubkeys"].get_str()); @@ -1809,22 +1817,24 @@ BOOST_AUTO_TEST_CASE(bip341_keypath_test_vectors) provider.keys[key.GetPubKey().GetID()] = key; MutableTransactionSignatureCreator creator(&tx, txinpos, utxos[txinpos].nValue, &txdata, hashtype); std::vector signature; + BOOST_CHECK(creator.CreateSchnorrSig(provider, signature, pubkey, nullptr, &merkle_root, SigVersion::TAPROOT)); // ELEMENTS: FIXME - // BOOST_CHECK(creator.CreateSchnorrSig(provider, signature, pubkey, nullptr, &merkle_root, SigVersion::TAPROOT)); - // BOOST_CHECK_EQUAL(HexStr(signature), input["expected"]["witness"][0].get_str()); + //BOOST_CHECK_EQUAL(HexStr(signature), input["expected"]["witness"][0].get_str()); // We can't observe the tweak used inside the signing logic, so verify by recomputing it. - // BOOST_CHECK_EQUAL(HexStr(pubkey.ComputeTapTweakHash(merkle_root.IsNull() ? nullptr : &merkle_root)), input["intermediary"]["tweak"].get_str()); + // ELEMENTS: FIXME + //BOOST_CHECK_EQUAL(HexStr(pubkey.ComputeTapTweakHash(merkle_root.IsNull() ? nullptr : &merkle_root)), input["intermediary"]["tweak"].get_str()); // We can't observe the sighash used inside the signing logic, so verify by recomputing it. ScriptExecutionData sed; sed.m_annex_init = true; sed.m_annex_present = false; - // uint256 sighash; - // BOOST_CHECK(SignatureHashSchnorr(sighash, sed, tx, txinpos, hashtype, SigVersion::TAPROOT, txdata, MissingDataBehavior::FAIL)); + uint256 sighash; + BOOST_CHECK(SignatureHashSchnorr(sighash, sed, tx, txinpos, hashtype, SigVersion::TAPROOT, txdata, MissingDataBehavior::FAIL)); // BOOST_CHECK_EQUAL(HexStr(sighash), input["intermediary"]["sigHash"].get_str()); // To verify the sigmsg, hash the expected sigmsg, and compare it with the (expected) sighash. + // ELEMENTS: FIXME //BOOST_CHECK_EQUAL(HexStr((CHashWriter(HASHER_TAPSIGHASH_ELEMENTS) << Span{ParseHex(input["intermediary"]["sigMsg"].get_str())}).GetSHA256()), input["intermediary"]["sigHash"].get_str()); } From 06423a6aac286a9d790516a1e3f5b145d98cd4ed Mon Sep 17 00:00:00 2001 From: James Dorfman Date: Thu, 9 Nov 2023 06:34:34 +0000 Subject: [PATCH 03/11] test: remove useless comment from feature_taproot_opcodes The line immediately below the commented out code I removed explicitly tests the exact behaviour which was being questioned in the comment. This indicates that the comment was incorrect. --- test/functional/feature_tapscript_opcodes.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/test/functional/feature_tapscript_opcodes.py b/test/functional/feature_tapscript_opcodes.py index ea8321091d..d606b05553 100755 --- a/test/functional/feature_tapscript_opcodes.py +++ b/test/functional/feature_tapscript_opcodes.py @@ -582,9 +582,6 @@ def csfs_test(msg, mutute_sig = False, fail=None): self.tapscript_satisfy_test(CScript([msg, pub, OP_CHECKSIGFROMSTACK]), inputs = [sig], fail=fail) csfs_test("e168c349d0d2499caf3a6d71734c743d517f94f8571fa52c04285b68deec1936") - # Elements: FIXME this test fails - # with test_framework.authproxy.JSONRPCException: non-mandatory-script-verify-flag (Invalid Schnorr signature) (-26) - # csfs_test("Hello World!".encode('utf-8').hex()) csfs_test("Hello World!".encode('utf-8').hex(), fail="Invalid Schnorr signature", mutute_sig=True) msg = bytes.fromhex("e168c349d0d2499caf3a6d71734c743d517f94f8571fa52c04285b68deec1936") pub = bytes.fromhex("3f67e97da0df6931189cfb0072447da22707897bd5de04936a277ed7e00b35b3") From eb24b3eb393177739468fdf2338e9ba214311e94 Mon Sep 17 00:00:00 2001 From: James Dorfman Date: Thu, 9 Nov 2023 06:41:31 +0000 Subject: [PATCH 04/11] test_runner: re-enable the now-passing 'wallet_basic.py --descriptors' --- test/functional/test_runner.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/test/functional/test_runner.py b/test/functional/test_runner.py index c76e07b85f..6c6e182836 100755 --- a/test/functional/test_runner.py +++ b/test/functional/test_runner.py @@ -131,8 +131,7 @@ 'feature_segwit.py --descriptors', # vv Tests less than 2m vv 'wallet_basic.py --legacy-wallet', - # ELEMENTS: FIXME - # 'wallet_basic.py --descriptors', + 'wallet_basic.py --descriptors', 'wallet_labels.py --legacy-wallet', 'wallet_labels.py --descriptors', 'p2p_segwit.py', From f4243128eeaad039cf0e70347935acf6c61789c7 Mon Sep 17 00:00:00 2001 From: Christian Lewe Date: Fri, 10 Nov 2023 15:50:24 +0100 Subject: [PATCH 05/11] Script: Move SCRIPT_ERR_COUNT SCRIPT_ERR_COUNT is a pseudo error that returns the number of errors. It must always be last in the enum. --- src/script/script_error.h | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/script/script_error.h b/src/script/script_error.h index 1e9862454e..712ed07e35 100644 --- a/src/script/script_error.h +++ b/src/script/script_error.h @@ -82,8 +82,6 @@ typedef enum ScriptError_t SCRIPT_ERR_OP_CODESEPARATOR, SCRIPT_ERR_SIG_FINDANDDELETE, - SCRIPT_ERR_ERROR_COUNT, - // ELEMENTS: SCRIPT_ERR_RANGEPROOF, SCRIPT_ERR_PEDERSEN_TALLY, @@ -96,10 +94,13 @@ typedef enum ScriptError_t SCRIPT_ERR_INTROSPECT_INDEX_OUT_OF_BOUNDS, SCRIPT_ERR_EXPECTED_8BYTES, SCRIPT_ERR_ARITHMETIC64, - SCRIPT_ERR_ECMULTVERIFYFAIL + SCRIPT_ERR_ECMULTVERIFYFAIL, + + /* Must go last */ + SCRIPT_ERR_ERROR_COUNT } ScriptError; -#define SCRIPT_ERR_LAST SCRIPT_ERR_ECMULTVERIFYFAIL +#define SCRIPT_ERR_LAST SCRIPT_ERR_ERROR_COUNT std::string ScriptErrorString(const ScriptError error); From 8ccb3e1604626a630e9f2b940f8e94e313a6b2ac Mon Sep 17 00:00:00 2001 From: James Dorfman Date: Mon, 13 Nov 2023 03:17:08 +0000 Subject: [PATCH 06/11] test_runner: re-enable the now-passing 'feature_taproot.py' --- test/functional/test_runner.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/test/functional/test_runner.py b/test/functional/test_runner.py index 6c6e182836..dbf47347e0 100755 --- a/test/functional/test_runner.py +++ b/test/functional/test_runner.py @@ -140,8 +140,7 @@ 'mempool_updatefromblock.py', 'wallet_dump.py --legacy-wallet', 'feature_taproot.py --previous_release', - # ELEMENTS: FIXME taproot test needs to be updated - # 'feature_taproot.py', + 'feature_taproot.py', 'rpc_signer.py', 'wallet_signer.py --descriptors', # ELEMENTS: FIXME failing tests From d215bf7f9105b9626aa2c1ab6cc9dd7ee818da51 Mon Sep 17 00:00:00 2001 From: Byron Hambly Date: Mon, 13 Nov 2023 09:06:43 +0000 Subject: [PATCH 07/11] test: fix feature_tapscript_opcodes.py The first transaction from create_taproot_utxo was failing with bad-txns-inputs-missingorspent because the "input" selected with fundrawtransaction was the anyonecanspend initialfreecoins. Fixed by first spending the initialfreecoins to "real" outputs. --- test/functional/feature_tapscript_opcodes.py | 12 ++++++++---- test/functional/test_runner.py | 2 +- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/test/functional/feature_tapscript_opcodes.py b/test/functional/feature_tapscript_opcodes.py index d606b05553..5dc3c0d45c 100755 --- a/test/functional/feature_tapscript_opcodes.py +++ b/test/functional/feature_tapscript_opcodes.py @@ -96,7 +96,7 @@ def create_taproot_utxo(self, scripts = None, blind = False): self.nodes[0].sendrawtransaction(signed_raw_tx['hex']) tx = tx_from_hex(signed_raw_tx['hex']) tx.rehash() - self.nodes[0].generate(1) + self.generate(self.nodes[0], 1) last_blk = self.nodes[0].getblock(self.nodes[0].getbestblockhash()) assert(tx.hash in last_blk['tx']) @@ -118,7 +118,7 @@ def tapscript_satisfy_test(self, script, inputs = None, add_issuance = False, peg_id = self.nodes[0].sendtoaddress(fund_info["mainchain_address"], 1) raw_peg_tx = self.nodes[0].gettransaction(peg_id)["hex"] peg_txid = self.nodes[0].sendrawtransaction(raw_peg_tx) - self.nodes[0].generate(101) + self.generate(self.nodes[0], 101) peg_prf = self.nodes[0].gettxoutproof([peg_txid]) claim_script = fund_info["claim_script"] @@ -249,15 +249,19 @@ def tapscript_satisfy_test(self, script, inputs = None, add_issuance = False, self.nodes[0].sendrawtransaction(hexstring = tx.serialize().hex()) - self.nodes[0].generate(1) + self.generate(self.nodes[0], 1) last_blk = self.nodes[0].getblock(self.nodes[0].getbestblockhash()) tx.rehash() assert(tx.hash in last_blk['tx']) def run_test(self): - self.nodes[0].generate(101) + self.generate(self.nodes[0], 101) self.wait_until(lambda: self.nodes[0].getblockcount() == 101, timeout=5) + # spend the initialfreecoins to node0, to fix bad-txns-inputs-missingorspent error when creating the first taproot utxo + self.nodes[0].sendtoaddress(self.nodes[0].getnewaddress(), 50) + self.generate(self.nodes[0], 1) + # Test whether the above test framework is working self.log.info("Test simple op_1") self.tapscript_satisfy_test(CScript([OP_1])) diff --git a/test/functional/test_runner.py b/test/functional/test_runner.py index dbf47347e0..a58a4e98dc 100755 --- a/test/functional/test_runner.py +++ b/test/functional/test_runner.py @@ -145,7 +145,7 @@ 'wallet_signer.py --descriptors', # ELEMENTS: FIXME failing tests # 'feature_taphash_pegins_issuances.py', - # 'feature_tapscript_opcodes.py', + 'feature_tapscript_opcodes.py', # vv Tests less than 60s vv 'p2p_sendheaders.py', 'wallet_importmulti.py --legacy-wallet', From ec4c2a489821940494e7adfde77b80d410bdf55e Mon Sep 17 00:00:00 2001 From: James Dorfman Date: Mon, 13 Nov 2023 15:10:30 +0000 Subject: [PATCH 08/11] test: fix feature_taphash_pegins_issuances.py The first transaction was failing with bad-txns-inputs-missingorspent because the "input" selected with fundrawtransaction was the anyonecanspend initialfreecoins. Fixed by first spending the initialfreecoins to "real" outputs. --- test/functional/feature_taphash_pegins_issuances.py | 13 ++++++++----- test/functional/feature_tapscript_opcodes.py | 2 +- test/functional/test_runner.py | 3 +-- 3 files changed, 10 insertions(+), 8 deletions(-) diff --git a/test/functional/feature_taphash_pegins_issuances.py b/test/functional/feature_taphash_pegins_issuances.py index dbcba17c68..e4716e8ebc 100755 --- a/test/functional/feature_taphash_pegins_issuances.py +++ b/test/functional/feature_taphash_pegins_issuances.py @@ -80,7 +80,7 @@ def create_taproot_utxo(self): self.nodes[0].sendrawtransaction(signed_raw_tx['hex']) tx = tx_from_hex(signed_raw_tx['hex']) tx.rehash() - self.nodes[0].generate(1) + self.generate(self.nodes[0], 1) last_blk = self.nodes[0].getblock(self.nodes[0].getbestblockhash()) assert(tx.hash in last_blk['tx']) @@ -95,7 +95,7 @@ def pegin_test(self, sighash_ty): peg_id = self.nodes[0].sendtoaddress(fund_info["mainchain_address"], 1) raw_peg_tx = self.nodes[0].gettransaction(peg_id)["hex"] peg_txid = self.nodes[0].sendrawtransaction(raw_peg_tx) - self.nodes[0].generate(101) + self.generate(self.nodes[0], 101) peg_prf = self.nodes[0].gettxoutproof([peg_txid]) claim_script = fund_info["claim_script"] @@ -126,7 +126,7 @@ def pegin_test(self, sighash_ty): assert(verify_schnorr(pub_tweak, sig, msg)) # Since we add in/outputs the min feerate is no longer maintained. self.nodes[0].sendrawtransaction(hexstring = raw_claim.serialize().hex()) - self.nodes[0].generate(1) + self.generate(self.nodes[0], 1) last_blk = self.nodes[0].getblock(self.nodes[0].getbestblockhash()) raw_claim.rehash() assert(raw_claim.hash in last_blk['tx']) @@ -166,15 +166,18 @@ def issuance_test(self, sighash_ty): assert(verify_schnorr(pub_tweak, sig, msg)) # Since we add in/outputs the min feerate is no longer maintained. self.nodes[0].sendrawtransaction(hexstring = issued_tx.serialize().hex()) - self.nodes[0].generate(1) + self.generate(self.nodes[0], 1) last_blk = self.nodes[0].getblock(self.nodes[0].getbestblockhash()) issued_tx.rehash() assert(issued_tx.hash in last_blk['tx']) def run_test(self): - self.nodes[0].generate(101) + self.generate(self.nodes[0], 101) self.wait_until(lambda: self.nodes[0].getblockcount() == 101, timeout=5) + # spend the initialfreecoins to node0, to fix bad-txns-inputs-missingorspent error + self.nodes[0].sendtoaddress(self.nodes[0].getnewaddress(), 50) + self.generate(self.nodes[0], 1) self.log.info("Testing sighash taproot pegins") # Note that this does not test deposit to taproot pegin addresses # because there is no support for taproot pegins in rpc. The current rpc assumes diff --git a/test/functional/feature_tapscript_opcodes.py b/test/functional/feature_tapscript_opcodes.py index 5dc3c0d45c..8772a08d72 100755 --- a/test/functional/feature_tapscript_opcodes.py +++ b/test/functional/feature_tapscript_opcodes.py @@ -258,7 +258,7 @@ def tapscript_satisfy_test(self, script, inputs = None, add_issuance = False, def run_test(self): self.generate(self.nodes[0], 101) self.wait_until(lambda: self.nodes[0].getblockcount() == 101, timeout=5) - # spend the initialfreecoins to node0, to fix bad-txns-inputs-missingorspent error when creating the first taproot utxo + # ELEMENTS: spend the initialfreecoins to node0, to fix bad-txns-inputs-missingorspent error when creating the first taproot utxo self.nodes[0].sendtoaddress(self.nodes[0].getnewaddress(), 50) self.generate(self.nodes[0], 1) diff --git a/test/functional/test_runner.py b/test/functional/test_runner.py index a58a4e98dc..5aac4cbb54 100755 --- a/test/functional/test_runner.py +++ b/test/functional/test_runner.py @@ -143,8 +143,7 @@ 'feature_taproot.py', 'rpc_signer.py', 'wallet_signer.py --descriptors', - # ELEMENTS: FIXME failing tests - # 'feature_taphash_pegins_issuances.py', + 'feature_taphash_pegins_issuances.py', 'feature_tapscript_opcodes.py', # vv Tests less than 60s vv 'p2p_sendheaders.py', From 48ca8b0f7602165a51246e5e7c9ab650d5b89a0a Mon Sep 17 00:00:00 2001 From: James Dorfman Date: Wed, 15 Nov 2023 20:01:50 +0000 Subject: [PATCH 09/11] test: fix bug in wallet_groups.py functional test that was introduced in merge of bitcoin/bitcoin#22364 in 00f2e32 --- test/functional/test_runner.py | 5 ++--- test/functional/wallet_groups.py | 13 ++++--------- 2 files changed, 6 insertions(+), 12 deletions(-) diff --git a/test/functional/test_runner.py b/test/functional/test_runner.py index 5aac4cbb54..8cde0b32f4 100755 --- a/test/functional/test_runner.py +++ b/test/functional/test_runner.py @@ -208,9 +208,8 @@ 'rpc_signrawtransaction.py --descriptors', 'rpc_rawtransaction.py --legacy-wallet', 'rpc_rawtransaction.py --descriptors', - # ELEMENTS: FIXME failing tests - # 'wallet_groups.py --legacy-wallet', - # 'wallet_groups.py --descriptors', + 'wallet_groups.py --legacy-wallet', + 'wallet_groups.py --descriptors', 'wallet_transactiontime_rescan.py --descriptors', 'wallet_transactiontime_rescan.py --legacy-wallet', 'p2p_addrv2_relay.py', diff --git a/test/functional/wallet_groups.py b/test/functional/wallet_groups.py index d41b6cfd47..7f284e1b63 100755 --- a/test/functional/wallet_groups.py +++ b/test/functional/wallet_groups.py @@ -60,11 +60,6 @@ def run_test(self): # one output should be 0.2, the other should be ~0.3 v = [vout["value"] for vout in tx1["vout"] if vout["scriptPubKey"]["type"] != "fee"] v.sort() - # JAMES/BYRON DELETE ME - #print(self.nodes[1].listunspent()) - print("vin amount = {}".format(self.nodes[0].gettxout(tx1["vin"][0]["txid"], tx1["vin"][0]["vout"], True)["value"])) - print("vout amounts = {}".format(v)) - # END JAMES?BYRON assert_approx(v[0], vexp=0.2, vspan=0.0001) assert_approx(v[1], vexp=0.3, vspan=0.0001) @@ -114,10 +109,10 @@ def run_test(self): assert_equal(input_addrs[0], input_addrs[1]) # Node 2 enforces avoidpartialspends so needs no checking here - tx4_ungrouped_fee = 2820 - tx4_grouped_fee = 4160 - tx5_6_ungrouped_fee = 5520 - tx5_6_grouped_fee = 8240 + tx4_ungrouped_fee = 5140 + tx4_grouped_fee = 6520 + tx5_6_ungrouped_fee = 7880 + tx5_6_grouped_fee = 10620 self.log.info("Test wallet option maxapsfee") addr_aps = self.nodes[3].getnewaddress() From 9368fbac1cbdeef76960cc9e2eb5955846287596 Mon Sep 17 00:00:00 2001 From: Byron Hambly Date: Wed, 22 Nov 2023 08:55:40 +0200 Subject: [PATCH 10/11] test: fix flaky test wallet_groups.py since the SRD coin selection was added, Elements sometimes picks a 1.0 input instead of 0.5 in this test --- test/functional/wallet_groups.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/test/functional/wallet_groups.py b/test/functional/wallet_groups.py index 7f284e1b63..3c74a97167 100755 --- a/test/functional/wallet_groups.py +++ b/test/functional/wallet_groups.py @@ -49,6 +49,7 @@ def run_test(self): # For each node, send 0.2 coins back to 0; # - node[1] should pick one 0.5 UTXO and leave the rest + # ELEMENTS: since SRD was added, node[1] sometimes picks a 1.0 UTXO # - node[2] should pick one (1.0 + 0.5) UTXO group corresponding to a # given address, and leave the rest self.log.info("Test sending transactions picks one UTXO group and leaves the rest") @@ -61,7 +62,12 @@ def run_test(self): v = [vout["value"] for vout in tx1["vout"] if vout["scriptPubKey"]["type"] != "fee"] v.sort() assert_approx(v[0], vexp=0.2, vspan=0.0001) - assert_approx(v[1], vexp=0.3, vspan=0.0001) + # ELEMENTS + try: + assert_approx(v[1], vexp=0.3, vspan=0.0001) + except AssertionError: + assert_approx(v[1], vexp=0.8, vspan=0.0001) + txid2 = self.nodes[2].sendtoaddress(self.nodes[0].getnewaddress(), 0.2) tx2 = self.nodes[2].getrawtransaction(txid2, True) From d4ea89c6cb4be688479d8d452413531140a2ebf2 Mon Sep 17 00:00:00 2001 From: Pablo Greco Date: Wed, 22 Nov 2023 05:08:27 -0800 Subject: [PATCH 11/11] Bump version to 23.2.1 final --- configure.ac | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/configure.ac b/configure.ac index deb7fe9bb0..5372961cf2 100644 --- a/configure.ac +++ b/configure.ac @@ -2,7 +2,7 @@ AC_PREREQ([2.69]) define(_CLIENT_VERSION_MAJOR, 23) define(_CLIENT_VERSION_MINOR, 2) define(_CLIENT_VERSION_BUILD, 1) -define(_CLIENT_VERSION_RC, 3) +define(_CLIENT_VERSION_RC, 0) define(_CLIENT_VERSION_IS_RELEASE, true) define(_COPYRIGHT_YEAR, 2023) define(_COPYRIGHT_HOLDERS,[The %s developers])