From 34784a663f5180cb653383c00f3571e59ec74ab2 Mon Sep 17 00:00:00 2001 From: Glenn Willen Date: Thu, 6 Feb 2020 23:08:56 -0800 Subject: [PATCH 1/8] Fix claimpegin and createrawpegin support for multiwallet. Fix multiple issues with claimpegin and createrawpegin support for multiple loaded wallets: - Failure to call EnsureWalletIsAvailable would cause a crash when multiple wallets were loaded, but one was not specified in the RPC call. - Failure to propagate the request URL from claimpegin when making an internal call to another RPC would result in failure when multiple wallets were loaded, by failing to specify one for that call. Add a regression test to the feature_fedpeg.py test: at a critical point, create a second wallet on the sidechaind, and set up the RPC client to use the first one, to check that it still works correctly. --- src/wallet/rpcwallet.cpp | 34 +++++++++++++++++++++++-------- test/functional/feature_fedpeg.py | 8 ++++++++ 2 files changed, 33 insertions(+), 9 deletions(-) diff --git a/src/wallet/rpcwallet.cpp b/src/wallet/rpcwallet.cpp index baa18f701f..af74ecc74f 100644 --- a/src/wallet/rpcwallet.cpp +++ b/src/wallet/rpcwallet.cpp @@ -5418,6 +5418,12 @@ unsigned int GetPeginTxnOutputIndex(const T_tx& txn, const CScript& witnessProgr template static UniValue createrawpegin(const JSONRPCRequest& request, T_tx_ref& txBTCRef, T_tx& tx_aux, T_merkle_block& merkleBlock) { + std::shared_ptr const wallet = GetWalletForJSONRPCRequest(request); + CWallet* const pwallet = wallet.get(); + + if (!EnsureWalletIsAvailable(pwallet, request.fHelp)) + return NullUniValue; + if (request.fHelp || request.params.size() < 2 || request.params.size() > 3) throw std::runtime_error( RPCHelpMan{"createrawpegin", @@ -5441,9 +5447,6 @@ static UniValue createrawpegin(const JSONRPCRequest& request, T_tx_ref& txBTCRef }, }.ToString()); - std::shared_ptr const wallet = GetWalletForJSONRPCRequest(request); - CWallet* const pwallet = wallet.get(); - auto locked_chain = pwallet->chain().lock(); LOCK(pwallet->cs_wallet); @@ -5640,6 +5643,11 @@ UniValue createrawpegin(const JSONRPCRequest& request) UniValue claimpegin(const JSONRPCRequest& request) { + std::shared_ptr const wallet = GetWalletForJSONRPCRequest(request); + CWallet* const pwallet = wallet.get(); + + if (!EnsureWalletIsAvailable(pwallet, request.fHelp)) + return NullUniValue; if (request.fHelp || request.params.size() < 2 || request.params.size() > 3) throw std::runtime_error( @@ -5661,8 +5669,6 @@ UniValue claimpegin(const JSONRPCRequest& request) }, }.ToString()); - std::shared_ptr const wallet = GetWalletForJSONRPCRequest(request); - CWallet* const pwallet = wallet.get(); CTransactionRef tx_ref; CMutableTransaction mtx; @@ -5673,8 +5679,17 @@ UniValue claimpegin(const JSONRPCRequest& request) throw JSONRPCError(RPC_WALLET_ERROR, "Peg-ins cannot be completed during initial sync or reindexing."); } + // NOTE: Making an RPC from within another RPC is not generally a good idea. In particular, it + // is necessary to copy the URI, which contains the wallet if one was given; otherwise + // multi-wallet support will silently break. The resulting request object is still missing a + // bunch of other fields, although they are usually not used by RPC handlers. This is a + // brittle hack, and further examples of this pattern should not be introduced. + // Get raw peg-in transaction - UniValue ret(createrawpegin(request)); + JSONRPCRequest req; + req.URI = request.URI; + req.params = request.params; + UniValue ret(createrawpegin(req)); // See the note above, on why this is a bad idea. // Make sure it can be propagated and confirmed if (!ret["mature"].isNull() && ret["mature"].get_bool() == false) { @@ -5682,11 +5697,12 @@ UniValue claimpegin(const JSONRPCRequest& request) } // Sign it - JSONRPCRequest request2; + JSONRPCRequest req2; + req2.URI = request.URI; UniValue varr(UniValue::VARR); varr.push_back(ret["hex"]); - request2.params = varr; - UniValue result = signrawtransactionwithwallet(request2); + req2.params = varr; + UniValue result = signrawtransactionwithwallet(req2); // See the note above, on why this is a bad idea. if (!DecodeHexTx(mtx, result["hex"].get_str(), false, true)) { throw JSONRPCError(RPC_DESERIALIZATION_ERROR, "TX decode failed"); diff --git a/test/functional/feature_fedpeg.py b/test/functional/feature_fedpeg.py index 4dc44788a8..6b10ef54cd 100755 --- a/test/functional/feature_fedpeg.py +++ b/test/functional/feature_fedpeg.py @@ -225,6 +225,14 @@ class FedPegTest(BitcoinTestFramework): raw = parent.gettransaction(txid1)["hex"] + # Create a wallet in order to test that multi-wallet support works correctly for claimpegin + # (Regression test for https://github.com/ElementsProject/elements/issues/812 .) + sidechain.createwallet("throwaway") + # Set up our sidechain RPCs to use the first wallet (with empty name). We do this by + # overriding the RPC object in a hacky way, to avoid breaking a different hack on TestNode + # that enables generate() to work despite the deprecation of the generate RPC. + sidechain.rpc = sidechain.get_wallet_rpc("") + print("Attempting peg-ins") # First attempt fails the consensus check but gives useful result try: From 214789b6930589c8cc87973c6842f172a3818119 Mon Sep 17 00:00:00 2001 From: Steven Roose Date: Tue, 18 Feb 2020 15:15:52 +0000 Subject: [PATCH 2/8] Place the mainchain RPC warmup message in a better place --- src/init.cpp | 1 - src/validation.cpp | 1 + 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/src/init.cpp b/src/init.cpp index 941ac9bd83..56a5075067 100644 --- a/src/init.cpp +++ b/src/init.cpp @@ -1877,7 +1877,6 @@ bool AppInitMain(InitInterfaces& interfaces) SetRPCWarmupFinished(); // ELEMENTS: - uiInterface.InitMessage(_("Awaiting mainchain RPC warmup")); if (!MainchainRPCCheck(true)) { //Initial check only const std::string err_msg = "ERROR: elements is set to verify pegins but cannot get valid response from the mainchain daemon. Please check debug.log for more information.\n\nIf you haven't setup a bitcoind please get the latest stable version from https://bitcoincore.org/en/download/ or if you do not need to validate pegins set in your elements configuration validatepegin=0"; // We fail immediately if this node has RPC server enabled diff --git a/src/validation.cpp b/src/validation.cpp index 3c5694e3d9..aa890b4822 100644 --- a/src/validation.cpp +++ b/src/validation.cpp @@ -5288,6 +5288,7 @@ bool MainchainRPCCheck(const bool init) // Next, check for working and valid rpc if (gArgs.GetBoolArg("-validatepegin", Params().GetConsensus().has_parent_chain)) { + uiInterface.InitMessage(_("Awaiting mainchain RPC warmup")); // During init try until a non-RPC_IN_WARMUP result while (true) { try { From ed88521841f9331d9e9479a2480b32533ab9c972 Mon Sep 17 00:00:00 2001 From: Gregory Sanders Date: Mon, 24 Feb 2020 09:32:21 -0500 Subject: [PATCH 3/8] Create witness outputs for (re)issueasset RPC --- src/wallet/rpcwallet.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/wallet/rpcwallet.cpp b/src/wallet/rpcwallet.cpp index af74ecc74f..28c0f78e68 100644 --- a/src/wallet/rpcwallet.cpp +++ b/src/wallet/rpcwallet.cpp @@ -6184,7 +6184,7 @@ UniValue issueasset(const JSONRPCRequest& request) if (!pwallet->GetKeyFromPool(newKey)) { throw JSONRPCError(RPC_WALLET_KEYPOOL_RAN_OUT, "Error: Keypool ran out, please call keypoolrefill first"); } - asset_dest = PKHash(newKey.GetID()); + asset_dest = WitnessV0KeyHash(newKey.GetID()); pwallet->SetAddressBook(asset_dest, "", "receive"); asset_dest_blindpub = pwallet->GetBlindingPubKey(GetScriptForDestination(asset_dest)); } @@ -6192,7 +6192,7 @@ UniValue issueasset(const JSONRPCRequest& request) if (!pwallet->GetKeyFromPool(newKey)) { throw JSONRPCError(RPC_WALLET_KEYPOOL_RAN_OUT, "Error: Keypool ran out, please call keypoolrefill first"); } - token_dest = PKHash(newKey.GetID()); + token_dest = WitnessV0KeyHash(newKey.GetID()); pwallet->SetAddressBook(token_dest, "", "receive"); token_dest_blindpub = pwallet->GetBlindingPubKey(GetScriptForDestination(token_dest)); } @@ -6291,7 +6291,7 @@ UniValue reissueasset(const JSONRPCRequest& request) if (!pwallet->GetKeyFromPool(newAssetKey)) { throw JSONRPCError(RPC_WALLET_KEYPOOL_RAN_OUT, "Error: Keypool ran out, please call keypoolrefill first"); } - CTxDestination asset_dest = PKHash(newAssetKey.GetID()); + CTxDestination asset_dest = WitnessV0KeyHash(newAssetKey.GetID()); pwallet->SetAddressBook(asset_dest, "", "receive"); CPubKey asset_dest_blindpub = pwallet->GetBlindingPubKey(GetScriptForDestination(asset_dest)); @@ -6300,7 +6300,7 @@ UniValue reissueasset(const JSONRPCRequest& request) if (!pwallet->GetKeyFromPool(newTokenKey)) { throw JSONRPCError(RPC_WALLET_KEYPOOL_RAN_OUT, "Error: Keypool ran out, please call keypoolrefill first"); } - CTxDestination token_dest = PKHash(newTokenKey.GetID()); + CTxDestination token_dest = WitnessV0KeyHash(newTokenKey.GetID()); pwallet->SetAddressBook(token_dest, "", "receive"); CPubKey token_dest_blindpub = pwallet->GetBlindingPubKey(GetScriptForDestination(token_dest)); From 32792e3dc7167d3a76aa3b3217c9663eaa80e0b1 Mon Sep 17 00:00:00 2001 From: Gregory Sanders Date: Tue, 3 Mar 2020 09:52:57 -0500 Subject: [PATCH 4/8] initpegoutwallet: check for hardened derivation steps early --- src/wallet/rpcwallet.cpp | 23 +++++++++++++---------- 1 file changed, 13 insertions(+), 10 deletions(-) diff --git a/src/wallet/rpcwallet.cpp b/src/wallet/rpcwallet.cpp index 28c0f78e68..c5aa169397 100644 --- a/src/wallet/rpcwallet.cpp +++ b/src/wallet/rpcwallet.cpp @@ -4984,6 +4984,19 @@ UniValue initpegoutwallet(const JSONRPCRequest& request) throw JSONRPCError(RPC_INVALID_PARAMETER, "bitcoin_descriptor must be a ranged descriptor."); } + // Check if we can actually generate addresses(catches hardened derivation steps etc) before + // writing to cache + UniValue address_list(UniValue::VARR); + for (int i = counter; i < counter+3; i++) { + std::vector scripts; + if (!desc->Expand(i, provider, scripts, provider)) { + throw JSONRPCError(RPC_WALLET_ERROR, "Could not generate lookahead addresses with descriptor. Are there hardened derivations after the xpub?"); + } + CTxDestination destination; + ExtractDestination(scripts[0], destination); + address_list.push_back(EncodeParentDestination(destination)); + } + // For our manual pattern matching, we don't want the checksum part. auto checksum_char = bitcoin_desc.find('#'); if (checksum_char != std::string::npos) { @@ -5052,16 +5065,6 @@ UniValue initpegoutwallet(const JSONRPCRequest& request) assert(len == 33); assert(negatedpubkeybytes.size() == 33); - UniValue address_list(UniValue::VARR); - for (int i = counter; i < counter+3; i++) { - std::vector scripts; - if (!desc->Expand(i, provider, scripts, provider)) { - throw JSONRPCError(RPC_WALLET_ERROR, "Could not generate lookahead addresses with descriptor. This is a bug."); - } - CTxDestination destination; - ExtractDestination(scripts[0], destination); - address_list.push_back(EncodeParentDestination(destination)); - } UniValue pak(UniValue::VOBJ); pak.pushKV("pakentry", "pak=" + HexStr(negatedpubkeybytes) + ":" + HexStr(online_pubkey)); pak.pushKV("liquid_pak", HexStr(online_pubkey)); From a72d9c6468b06e0719db91d432d15b4ffdaf60a5 Mon Sep 17 00:00:00 2001 From: Gregory Sanders Date: Tue, 3 Dec 2019 16:14:13 -0500 Subject: [PATCH 5/8] Switch default bits of hiding to 52 --- src/blind.cpp | 2 +- src/blind.h | 4 ++-- src/init.cpp | 2 +- src/test/blind_tests.cpp | 10 +++++----- test/functional/feature_confidential_transactions.py | 4 +++- 5 files changed, 12 insertions(+), 10 deletions(-) diff --git a/src/blind.cpp b/src/blind.cpp index 8c0ec2cf0b..c25c6f8281 100644 --- a/src/blind.cpp +++ b/src/blind.cpp @@ -181,7 +181,7 @@ bool GenerateRangeproof(std::vector& rangeproof, const std::vecto // Sign rangeproof // If min_value is 0, scriptPubKey must be unspendable - int res = secp256k1_rangeproof_sign(secp256k1_blind_context, rangeproof.data(), &nRangeProofLen, scriptPubKey.IsUnspendable() ? 0 : 1, &value_commit, value_blindptrs.back(), nonce.begin(), std::min(std::max((int)gArgs.GetArg("-ct_exponent", 0), -1),18), std::min(std::max((int)gArgs.GetArg("-ct_bits", 36), 1), 51), amount, asset_message, sizeof(asset_message), scriptPubKey.size() ? &scriptPubKey.front() : NULL, scriptPubKey.size(), &gen); + int res = secp256k1_rangeproof_sign(secp256k1_blind_context, rangeproof.data(), &nRangeProofLen, scriptPubKey.IsUnspendable() ? 0 : 1, &value_commit, value_blindptrs.back(), nonce.begin(), std::min(std::max((int)gArgs.GetArg("-ct_exponent", 0), -1),18), (int)gArgs.GetArg("-ct_bits", 51), amount, asset_message, sizeof(asset_message), scriptPubKey.size() ? &scriptPubKey.front() : NULL, scriptPubKey.size(), &gen); rangeproof.resize(nRangeProofLen); return (res == 1); } diff --git a/src/blind.h b/src/blind.h index 4296b70ce7..0b668a7b3d 100644 --- a/src/blind.h +++ b/src/blind.h @@ -14,8 +14,8 @@ #include #include -//! ELEMENTS: 36-bit rangeproof size -static const size_t DEFAULT_RANGEPROOF_SIZE = 2893; +//! ELEMENTS: 52-bit rangeproof size +static const size_t DEFAULT_RANGEPROOF_SIZE = 4174; /* * Unblind a pair of confidential asset and value. diff --git a/src/init.cpp b/src/init.cpp index 56a5075067..1db635e00f 100644 --- a/src/init.cpp +++ b/src/init.cpp @@ -591,7 +591,7 @@ void SetupServerArgs() gArgs.AddArg("-feeasset=", strprintf("Asset ID (hex) for mempool/relay fees (default: %s)", defaultChainParams->GetConsensus().pegged_asset.GetHex()), false, OptionsCategory::CHAINPARAMS); gArgs.AddArg("-subsidyasset=", strprintf("Asset ID (hex) for the block subsidy (default: %s)", defaultChainParams->GetConsensus().pegged_asset.GetHex()), false, OptionsCategory::CHAINPARAMS); gArgs.AddArg("-initialreissuancetokens=", "The amount of reissuance tokens created in the genesis block. (default: 0)", false, OptionsCategory::CHAINPARAMS); - gArgs.AddArg("-ct_bits", strprintf("The default number of hiding bits in a rangeproof. Will be exceeded to cover amounts exceeding the maximum hiding value. (default: %d)", 36), false, OptionsCategory::CHAINPARAMS); + gArgs.AddArg("-ct_bits", strprintf("The default number of hiding bits in a rangeproof. Will be exceeded to cover amounts exceeding the maximum hiding value. (default: %d)", 52), false, OptionsCategory::CHAINPARAMS); gArgs.AddArg("-ct_exponent", strprintf("The hiding exponent. (default: %s)", 0), false, OptionsCategory::CHAINPARAMS); // Add the hidden options diff --git a/src/test/blind_tests.cpp b/src/test/blind_tests.cpp index e3cf57e8f7..b3f8f27487 100644 --- a/src/test/blind_tests.cpp +++ b/src/test/blind_tests.cpp @@ -265,17 +265,17 @@ BOOST_AUTO_TEST_CASE(naive_blinding_test) // Check wallet borromean-based rangeproof results against expected args size_t proof_size = DEFAULT_RANGEPROOF_SIZE; - BOOST_CHECK(tx4.witness.vtxoutwit[2].vchRangeproof.size() == proof_size); + BOOST_CHECK_EQUAL(tx4.witness.vtxoutwit[2].vchRangeproof.size(), proof_size); secp256k1_context *ctx = secp256k1_context_create(SECP256K1_CONTEXT_VERIFY); int exp = 0; int mantissa = 0; uint64_t min_value = 0; uint64_t max_value = 0; BOOST_CHECK(secp256k1_rangeproof_info(ctx, &exp, &mantissa, &min_value, &max_value, tx4.witness.vtxoutwit[2].vchRangeproof.data(), proof_size) == 1); - BOOST_CHECK(exp == 0); - BOOST_CHECK(mantissa == 36); // 36 bit default - BOOST_CHECK(min_value == 1); - BOOST_CHECK(max_value == 68719476736); + BOOST_CHECK_EQUAL(exp, 0); + BOOST_CHECK_EQUAL(mantissa, 52); // 36 bit default + BOOST_CHECK_EQUAL(min_value, 1); + BOOST_CHECK_EQUAL(max_value, 4503599627370496); } { inputs.clear(); diff --git a/test/functional/feature_confidential_transactions.py b/test/functional/feature_confidential_transactions.py index 70ee02a633..3ff38afecb 100755 --- a/test/functional/feature_confidential_transactions.py +++ b/test/functional/feature_confidential_transactions.py @@ -361,7 +361,9 @@ class CTTest (BitcoinTestFramework): # Send some bitcoin and other assets over as well to fund wallet addr = self.nodes[2].getnewaddress() - self.nodes[0].sendtoaddress(addr, 5) + txid = self.nodes[0].sendtoaddress(addr, 5) + # Make sure we're doing 52 bits of hiding which covers 21M BTC worth + assert_equal(self.nodes[0].getrawtransaction(txid, 1)["vout"][0]["ct-bits"], 52) self.nodes[0].sendmany("", {addr: 1, self.nodes[2].getnewaddress(): 13}, 0, "", [], False, 1, "UNSET", {addr: test_asset}) self.sync_all() From 356f25bf86a31c4deaa10fca42f8b4eee2ab7e83 Mon Sep 17 00:00:00 2001 From: Steven Roose Date: Mon, 16 Mar 2020 17:45:30 +0000 Subject: [PATCH 6/8] Some readability improvements in blind.cpp --- src/blind.cpp | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/blind.cpp b/src/blind.cpp index c25c6f8281..446fb447f3 100644 --- a/src/blind.cpp +++ b/src/blind.cpp @@ -180,8 +180,11 @@ bool GenerateRangeproof(std::vector& rangeproof, const std::vecto memcpy(asset_message+32, asset_blindptrs[asset_blindptrs.size()-1], 32); // Sign rangeproof + int ct_exponent = std::min(std::max((int)gArgs.GetArg("-ct_exponent", 0), -1), 18); + int ct_bits = (int)gArgs.GetArg("-ct_bits", 52); // If min_value is 0, scriptPubKey must be unspendable - int res = secp256k1_rangeproof_sign(secp256k1_blind_context, rangeproof.data(), &nRangeProofLen, scriptPubKey.IsUnspendable() ? 0 : 1, &value_commit, value_blindptrs.back(), nonce.begin(), std::min(std::max((int)gArgs.GetArg("-ct_exponent", 0), -1),18), (int)gArgs.GetArg("-ct_bits", 51), amount, asset_message, sizeof(asset_message), scriptPubKey.size() ? &scriptPubKey.front() : NULL, scriptPubKey.size(), &gen); + uint64_t min_value = scriptPubKey.IsUnspendable() ? 0 : 1; + int res = secp256k1_rangeproof_sign(secp256k1_blind_context, rangeproof.data(), &nRangeProofLen, min_value, &value_commit, value_blindptrs.back(), nonce.begin(), ct_exponent, ct_bits, amount, asset_message, sizeof(asset_message), scriptPubKey.size() ? &scriptPubKey.front() : NULL, scriptPubKey.size(), &gen); rangeproof.resize(nRangeProofLen); return (res == 1); } From 8de91ec505f249c36859557c6d42df34caed5c68 Mon Sep 17 00:00:00 2001 From: Steven Roose Date: Tue, 17 Mar 2020 12:47:03 +0000 Subject: [PATCH 7/8] Reduce mintxfee to 0.1 sat/vbyte and fallback fee to 2 sat/vb --- src/wallet/wallet.h | 4 ++-- test/functional/feature_issuance.py | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/wallet/wallet.h b/src/wallet/wallet.h index 837540ef0a..6e01a00178 100644 --- a/src/wallet/wallet.h +++ b/src/wallet/wallet.h @@ -74,11 +74,11 @@ static const unsigned int DEFAULT_KEYPOOL_SIZE = 1000; //! -paytxfee default constexpr CAmount DEFAULT_PAY_TX_FEE = 0; //! -fallbackfee default -static const CAmount DEFAULT_FALLBACK_FEE = 20000; +static const CAmount DEFAULT_FALLBACK_FEE = 2000; //! -discardfee default static const CAmount DEFAULT_DISCARD_FEE = 10000; //! -mintxfee default -static const CAmount DEFAULT_TRANSACTION_MINFEE = 1000; +static const CAmount DEFAULT_TRANSACTION_MINFEE = 100; //! minimum recommended increment for BIP 125 replacement txs static const CAmount WALLET_INCREMENTAL_RELAY_FEE = 5000; //! Default for -spendzeroconfchange diff --git a/test/functional/feature_issuance.py b/test/functional/feature_issuance.py index 661006f415..57add7a003 100755 --- a/test/functional/feature_issuance.py +++ b/test/functional/feature_issuance.py @@ -16,7 +16,7 @@ def process_raw_issuance(node, issuance_list): raise Exception('Issuance list too long') # Make enough outputs for any subsequent spend next_destinations = {} - output_values = (node.getbalance()['bitcoin']-1)/5 + output_values = Decimal('%.8f' % ((node.getbalance()['bitcoin']-1)/5)) for i in range(5): next_destinations[node.getnewaddress()] = output_values @@ -382,7 +382,7 @@ class IssuanceTest(BitcoinTestFramework): assert_equal(blinded_multisig, self.nodes[0].getaddressinfo(utxo_info["address"])["confidential"]) break assert(utxo_info is not None) - assert(utxo_info["amountblinder"] is not "0000000000000000000000000000000000000000000000000000000000000000") + assert(utxo_info["amountblinder"] != "0000000000000000000000000000000000000000000000000000000000000000") # Now make transaction spending that input raw_tx = self.nodes[0].createrawtransaction([], {issued_address:1}, 0, False, {issued_address:issued_asset["token"]}) From 17f8e747e44e2281f33f941cca34e61a5c90b7b2 Mon Sep 17 00:00:00 2001 From: Steven Roose Date: Tue, 17 Mar 2020 17:50:33 +0000 Subject: [PATCH 8/8] Bump version to 0.18.1.6 --- configure.ac | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/configure.ac b/configure.ac index cdf5481325..0cae531ffc 100644 --- a/configure.ac +++ b/configure.ac @@ -3,7 +3,7 @@ AC_PREREQ([2.60]) define(_CLIENT_VERSION_MAJOR, 0) define(_CLIENT_VERSION_MINOR, 18) define(_CLIENT_VERSION_REVISION, 1) -define(_CLIENT_VERSION_BUILD, 5) +define(_CLIENT_VERSION_BUILD, 6) define(_CLIENT_VERSION_RC, 0) define(_CLIENT_VERSION_IS_RELEASE, true) define(_COPYRIGHT_YEAR, 2019)