From 9a291df04af801b42684b8e947913d6c02e19fce Mon Sep 17 00:00:00 2001 From: Liquid Date: Mon, 8 Jan 2024 13:25:36 -0600 Subject: [PATCH 01/18] Core OP_EXCHANGEADDR implementation, scripts, consensus Define in wallet and rpc Adjust destination wrapper typo Add conversion operator Update DecodeDestination --- src/addressbook.cpp | 6 +- src/addressbook.h | 2 + src/blocksignature.cpp | 4 + src/chainparams.cpp | 9 +++ src/chainparams.h | 1 + src/consensus/params.h | 1 + src/consensus/tx_verify.cpp | 16 ++++ src/destination_io.cpp | 29 +++---- src/destination_io.h | 6 +- src/key_io.cpp | 21 ++++- src/key_io.h | 2 +- src/keystore.h | 1 + src/primitives/transaction.cpp | 10 +++ src/primitives/transaction.h | 1 + src/pubkey.h | 13 +++ src/qt/addresstablemodel.cpp | 11 ++- src/qt/pivx/addresseswidget.cpp | 7 +- src/qt/pivx/send.cpp | 7 +- src/qt/pivx/sendchangeaddressdialog.cpp | 12 +-- src/qt/pivx/sendconfirmdialog.cpp | 3 +- src/qt/walletmodel.cpp | 47 +++++++---- src/rpc/misc.cpp | 20 ++++- src/rpc/rpcevo.cpp | 14 +++- src/sapling/sapling_validation.cpp | 4 +- src/script/interpreter.cpp | 8 ++ src/script/ismine.cpp | 1 + src/script/script.cpp | 14 ++++ src/script/script.h | 4 + src/script/sign.cpp | 2 + src/script/standard.cpp | 20 ++++- src/script/standard.h | 6 +- src/wallet/rpcdump.cpp | 10 ++- src/wallet/rpcwallet.cpp | 102 ++++++++++++++++++------ src/wallet/wallet.cpp | 16 +++- 34 files changed, 334 insertions(+), 96 deletions(-) diff --git a/src/addressbook.cpp b/src/addressbook.cpp index 6b590a7ad2335..13b042b873cb4 100644 --- a/src/addressbook.cpp +++ b/src/addressbook.cpp @@ -16,6 +16,7 @@ namespace AddressBook { const std::string COLD_STAKING_SEND{"coldstaking_send"}; const std::string SHIELDED_RECEIVE{"shielded_receive"}; const std::string SHIELDED_SEND{"shielded_spend"}; + const std::string EXCHANGE_ADDRESS{"exchange_address"}; } bool IsColdStakingPurpose(const std::string& purpose) { @@ -28,6 +29,10 @@ namespace AddressBook { || purpose == AddressBookPurpose::SHIELDED_SEND; } + bool IsExchangePurpose(const std::string& purpose) { + return purpose == AddressBookPurpose::EXCHANGE_ADDRESS; + } + bool CAddressBookData::isSendColdStakingPurpose() const { return purpose == AddressBookPurpose::COLD_STAKING_SEND; } @@ -48,6 +53,5 @@ namespace AddressBook { return IsShieldedPurpose(purpose); } - } diff --git a/src/addressbook.h b/src/addressbook.h index 09a4f454443b2..37f15b3018568 100644 --- a/src/addressbook.h +++ b/src/addressbook.h @@ -20,10 +20,12 @@ namespace AddressBook { extern const std::string COLD_STAKING_SEND; extern const std::string SHIELDED_RECEIVE; extern const std::string SHIELDED_SEND; + extern const std::string EXCHANGE_ADDRESS; } bool IsColdStakingPurpose(const std::string& purpose); bool IsShieldedPurpose(const std::string& purpose); + bool IsExchangePurpose(const std::string& purpose); /** Address book data */ class CAddressBookData { diff --git a/src/blocksignature.cpp b/src/blocksignature.cpp index a8f21904c6f04..6fb9b6058a9c1 100644 --- a/src/blocksignature.cpp +++ b/src/blocksignature.cpp @@ -21,6 +21,10 @@ static bool GetKeyIDFromUTXO(const CTxOut& utxo, CKeyID& keyIDRet) keyIDRet = CKeyID(uint160(vSolutions[0])); return true; } + if (whichType == TX_EXCHANGEADDR) { + keyIDRet = CExchangeKeyID(uint160(vSolutions[0])); + return true; + } return false; } diff --git a/src/chainparams.cpp b/src/chainparams.cpp index e07477288c4b8..2e3cfac97fb78 100644 --- a/src/chainparams.cpp +++ b/src/chainparams.cpp @@ -246,6 +246,8 @@ class CMainParams : public CChainParams consensus.nTimeSlotLength = 15; consensus.nMaxProposalPayments = 6; + consensus.nExchangeAddrStart = 4204000; + // spork keys consensus.strSporkPubKey = "0410050aa740d280b134b40b40658781fc1116ba7700764e0ce27af3e1737586b3257d19232e0cb5084947f5107e44bcd577f126c9eb4a30ea2807b271d2145298"; consensus.strSporkPubKeyOld = "040F129DE6546FE405995329A887329BED4321325B1A73B0A257423C05C1FCFE9E40EF0678AEF59036A22C42E61DFD29DF7EFB09F56CC73CADF64E05741880E3E7"; @@ -328,6 +330,7 @@ class CMainParams : public CChainParams base58Prefixes[PUBKEY_ADDRESS] = std::vector(1, 30); base58Prefixes[SCRIPT_ADDRESS] = std::vector(1, 13); base58Prefixes[STAKING_ADDRESS] = std::vector(1, 63); // starting with 'S' + base58Prefixes[EXCHANGE_ADDRESS] = std::vector(1, 33); // starts with E base58Prefixes[SECRET_KEY] = std::vector(1, 212); base58Prefixes[EXT_PUBLIC_KEY] = {0x02, 0x2D, 0x25, 0x33}; base58Prefixes[EXT_SECRET_KEY] = {0x02, 0x21, 0x31, 0x2B}; @@ -408,6 +411,8 @@ class CTestNetParams : public CChainParams consensus.nTimeSlotLength = 15; consensus.nMaxProposalPayments = 20; + consensus.nExchangeAddrStart = 1559000; + // spork keys consensus.strSporkPubKey = "04677c34726c491117265f4b1c83cef085684f36c8df5a97a3a42fc499316d0c4e63959c9eca0dba239d9aaaf72011afffeb3ef9f51b9017811dec686e412eb504"; consensus.strSporkPubKeyOld = "04E88BB455E2A04E65FCC41D88CD367E9CCE1F5A409BE94D8C2B4B35D223DED9C8E2F4E061349BA3A38839282508066B6DC4DB72DD432AC4067991E6BF20176127"; @@ -472,6 +477,7 @@ class CTestNetParams : public CChainParams base58Prefixes[PUBKEY_ADDRESS] = std::vector(1, 139); // Testnet pivx addresses start with 'x' or 'y' base58Prefixes[SCRIPT_ADDRESS] = std::vector(1, 19); // Testnet pivx script addresses start with '8' or '9' base58Prefixes[STAKING_ADDRESS] = std::vector(1, 73); // starting with 'W' + base58Prefixes[EXCHANGE_ADDRESS] = std::vector(1, 33); // starts with E base58Prefixes[SECRET_KEY] = std::vector(1, 239); // Testnet private keys start with '9' or 'c' (Bitcoin defaults) // Testnet pivx BIP32 pubkeys start with 'DRKV' base58Prefixes[EXT_PUBLIC_KEY] = {0x3a, 0x80, 0x61, 0xa0}; @@ -552,6 +558,8 @@ class CRegTestParams : public CChainParams consensus.nTimeSlotLength = 15; consensus.nMaxProposalPayments = 20; + consensus.nExchangeAddrStart = 1000; + /* Spork Key for RegTest: WIF private key: 932HEevBSujW2ud7RfB1YF91AFygbBRQj3de3LyaCRqNzKKgWXi private key hex: bd4960dcbd9e7f2223f24e7164ecb6f1fe96fc3a416f5d3a830ba5720c84b8ca @@ -618,6 +626,7 @@ class CRegTestParams : public CChainParams base58Prefixes[PUBKEY_ADDRESS] = std::vector(1, 139); // Testnet pivx addresses start with 'x' or 'y' base58Prefixes[SCRIPT_ADDRESS] = std::vector(1, 19); // Testnet pivx script addresses start with '8' or '9' base58Prefixes[STAKING_ADDRESS] = std::vector(1, 73); // starting with 'W' + base58Prefixes[EXCHANGE_ADDRESS] = std::vector(1, 33); // starts with E base58Prefixes[SECRET_KEY] = std::vector(1, 239); // Testnet private keys start with '9' or 'c' (Bitcoin defaults) // Testnet pivx BIP32 pubkeys start with 'DRKV' base58Prefixes[EXT_PUBLIC_KEY] = {0x3a, 0x80, 0x61, 0xa0}; diff --git a/src/chainparams.h b/src/chainparams.h index 1b95c3281afed..0fd25459c7162 100644 --- a/src/chainparams.h +++ b/src/chainparams.h @@ -51,6 +51,7 @@ class CChainParams EXT_SECRET_KEY, // BIP32 EXT_COIN_TYPE, // BIP44 STAKING_ADDRESS, + EXCHANGE_ADDRESS, MAX_BASE58_TYPES }; diff --git a/src/consensus/params.h b/src/consensus/params.h index b8523b6b14ead..7f5b9e4f4b10d 100644 --- a/src/consensus/params.h +++ b/src/consensus/params.h @@ -192,6 +192,7 @@ struct Params { int64_t nTargetSpacing; int nTimeSlotLength; int nMaxProposalPayments; + int nExchangeAddrStart; // spork keys std::string strSporkPubKey; diff --git a/src/consensus/tx_verify.cpp b/src/consensus/tx_verify.cpp index 027a0eb3ae849..1ed07b515d0bc 100644 --- a/src/consensus/tx_verify.cpp +++ b/src/consensus/tx_verify.cpp @@ -115,9 +115,25 @@ bool CheckTransaction(const CTransaction& tx, CValidationState& state, bool fCol } } + // input scripts cannot have OP_EXCHANGEADDR at all + for (const auto &vin: tx.vin) { + if (vin.scriptSig.size() >= 1 && vin.scriptSig[0] == OP_EXCHANGEADDR) { + return state.DoS(100, false, REJECT_INVALID, "bad-exchange-address"); + } + } + + bool hasExchangeUTXOs = false; + if (tx.HasExchangeAddr()) + hasExchangeUTXOs = true; + int nTxHeight = chainActive.Height(); + if (hasExchangeUTXOs && nTxHeight < ::Params().GetConsensus().nExchangeAddrStart) + return state.DoS(100, false, REJECT_INVALID, "bad-exchange-address"); + if (tx.IsCoinBase()) { if (tx.vin[0].scriptSig.size() < 2 || tx.vin[0].scriptSig.size() > 150) return state.DoS(100, false, REJECT_INVALID, "bad-cb-length"); + if (hasExchangeUTXOs) + return state.DoS(100, false, REJECT_INVALID, "bad-exchange-address"); } else { for (const CTxIn& txin : tx.vin) if (txin.prevout.IsNull() && !txin.IsZerocoinSpend()) diff --git a/src/destination_io.cpp b/src/destination_io.cpp index 11f9a4ac2d578..d60109d5a6128 100644 --- a/src/destination_io.cpp +++ b/src/destination_io.cpp @@ -7,6 +7,11 @@ #include "sapling/key_io_sapling.h" namespace Standard { + struct DecodeOptions { + bool isStaking = false; + bool isShielded = false; + bool isExchange = false; + }; std::string EncodeDestination(const CWDestination &address, const CChainParams::Base58Type addrType) { const CTxDestination *dest = boost::get(&address); @@ -16,27 +21,15 @@ namespace Standard { return EncodeDestination(*dest, addrType); }; - CWDestination DecodeDestination(const std::string& strAddress) - { - bool isStaking = false; - return DecodeDestination(strAddress, isStaking); - } - - CWDestination DecodeDestination(const std::string& strAddress, bool& isStaking) - { - bool isShielded = false; - return DecodeDestination(strAddress, isStaking, isShielded); - } - // agregar isShielded - CWDestination DecodeDestination(const std::string& strAddress, bool& isStaking, bool& isShielded) + CWDestination DecodeDestination(const std::string& strAddress, DecodeOptions options) { CWDestination dest; - CTxDestination regDest = ::DecodeDestination(strAddress, isStaking); + CTxDestination regDest = ::DecodeDestination(strAddress, options.isStaking); if (!IsValidDestination(regDest)) { const auto sapDest = KeyIO::DecodeSaplingPaymentAddress(strAddress); if (sapDest) { - isShielded = true; + options.isShielded = true; return *sapDest; } } @@ -70,6 +63,7 @@ Destination& Destination::operator=(const Destination& from) { this->dest = from.dest; this->isP2CS = from.isP2CS; + this->isExchange = from.isExchange; return *this; } @@ -86,6 +80,9 @@ std::string Destination::ToString() const // Invalid address return ""; } - return Standard::EncodeDestination(dest, isP2CS ? CChainParams::STAKING_ADDRESS : CChainParams::PUBKEY_ADDRESS); + CChainParams::Base58Type addrType = isP2CS ? CChainParams::STAKING_ADDRESS + : (isExchange ? CChainParams::EXCHANGE_ADDRESS + : CChainParams::PUBKEY_ADDRESS); + return Standard::EncodeDestination(dest, addrType); } diff --git a/src/destination_io.h b/src/destination_io.h index 0697f6b9fabb3..9c83c4b80092d 100644 --- a/src/destination_io.h +++ b/src/destination_io.h @@ -15,9 +15,7 @@ namespace Standard { std::string EncodeDestination(const CWDestination &address, const CChainParams::Base58Type addrType = CChainParams::PUBKEY_ADDRESS); - CWDestination DecodeDestination(const std::string& strAddress); - CWDestination DecodeDestination(const std::string& strAddress, bool& isStaking); - CWDestination DecodeDestination(const std::string& strAddress, bool& isStaking, bool& isShielded); + CWDestination DecodeDestination(const std::string& strAddress, DecodeOptions options); bool IsValidDestination(const CWDestination& dest); @@ -34,10 +32,12 @@ class Destination { public: explicit Destination() {} explicit Destination(const CTxDestination& _dest, bool _isP2CS) : dest(_dest), isP2CS(_isP2CS) {} + explicit Destination(const CTxDestination& _dest, bool _isP2CS, bool _isEXCHANGE) : dest(_dest), isP2CS(_isP2CS), isExchange(_isEXCHANGE) {} explicit Destination(const libzcash::SaplingPaymentAddress& _dest) : dest(_dest) {} CWDestination dest{CNoDestination()}; bool isP2CS{false}; + bool isExchange{false}; Destination& operator=(const Destination& from); // Returns the key ID if Destination is a transparent "regular" destination diff --git a/src/key_io.cpp b/src/key_io.cpp index e0b881743bf51..444a566e39757 100644 --- a/src/key_io.cpp +++ b/src/key_io.cpp @@ -31,6 +31,13 @@ namespace return EncodeBase58Check(data); } + std::string operator()(const CExchangeKeyID& id) const + { + std::vector data = m_params.Base58Prefix(CChainParams::EXCHANGE_ADDRESS); + data.insert(data.end(), id.begin(), id.end()); + return EncodeBase58Check(data); + } + std::string operator()(const CScriptID& id) const { std::vector data = m_params.Base58Prefix(CChainParams::SCRIPT_ADDRESS); @@ -54,6 +61,12 @@ namespace std::copy(data.begin() + pubkey_prefix.size(), data.end(), hash.begin()); return CKeyID(hash); } + // Exchange Transparent addresses have version 31 + const std::vector& exchange_pubkey_prefix = params.Base58Prefix(CChainParams::EXCHANGE_ADDRESS); + if (data.size() == hash.size() + exchange_pubkey_prefix.size() && std::equal(exchange_pubkey_prefix.begin(), exchange_pubkey_prefix.end(), data.begin())) { + std::copy(data.begin() + exchange_pubkey_prefix.size(), data.end(), hash.begin()); + return CExchangeKeyID(hash); + } // Public-key-hash-coldstaking-addresses have version 63 (or 73 testnet). const std::vector& staking_prefix = params.Base58Prefix(CChainParams::STAKING_ADDRESS); if (data.size() == hash.size() + staking_prefix.size() && std::equal(staking_prefix.begin(), staking_prefix.end(), data.begin())) { @@ -74,9 +87,13 @@ namespace } // anon namespace -std::string EncodeDestination(const CTxDestination& dest, bool isStaking) +std::string EncodeDestination(const CTxDestination& dest, bool isStaking, bool isExchange) { - return EncodeDestination(dest, isStaking ? CChainParams::STAKING_ADDRESS : CChainParams::PUBKEY_ADDRESS); + if (isExchange) { + return EncodeDestination(dest, CChainParams::EXCHANGE_ADDRESS); + } else { + return EncodeDestination(dest, isStaking ? CChainParams::STAKING_ADDRESS : CChainParams::PUBKEY_ADDRESS); + } } std::string EncodeDestination(const CTxDestination& dest, const CChainParams::Base58Type addrType) diff --git a/src/key_io.h b/src/key_io.h index b6cfb58621ccb..d5fe1e626bb58 100644 --- a/src/key_io.h +++ b/src/key_io.h @@ -13,7 +13,7 @@ #include -std::string EncodeDestination(const CTxDestination& dest, bool isStaking); +std::string EncodeDestination(const CTxDestination& dest, bool isStaking, bool isExchange); std::string EncodeDestination(const CTxDestination& dest, const CChainParams::Base58Type addrType = CChainParams::PUBKEY_ADDRESS); // DecodeDestinationisStaking flag is set to true when the string arg is from an staking address CTxDestination DecodeDestination(const std::string& str, bool& isStaking); diff --git a/src/keystore.h b/src/keystore.h index 40eb633a03956..5ecba8c3aa50d 100644 --- a/src/keystore.h +++ b/src/keystore.h @@ -113,6 +113,7 @@ class CBasicKeyStore : public CKeyStore bool AddKeyPubKey(const CKey& key, const CPubKey& pubkey); bool GetPubKey(const CKeyID &address, CPubKey& vchPubKeyOut) const; bool HaveKey(const CKeyID& address) const; + bool HaveKey(const CExchangeKeyID& address) const; std::set GetKeys() const; bool GetKey(const CKeyID& address, CKey& keyOut) const; diff --git a/src/primitives/transaction.cpp b/src/primitives/transaction.cpp index e84af1dbf8dd2..abf8d554291b1 100644 --- a/src/primitives/transaction.cpp +++ b/src/primitives/transaction.cpp @@ -143,6 +143,16 @@ bool CTransaction::IsCoinStake() const return (vout.size() >= 2 && vout[0].IsEmpty()); } +bool CTransaction::HasExchangeAddr() const +{ + for(const CTxOut& txout : vout) { + if (txout.scriptPubKey.size() >= 1 && !txout.scriptPubKey.IsPayToExchangeAddress()) { + return false; + } + } + return true; +} + bool CTransaction::HasP2CSOutputs() const { for(const CTxOut& txout : vout) { diff --git a/src/primitives/transaction.h b/src/primitives/transaction.h index 7147fe966d27b..bbe4e453b16d1 100644 --- a/src/primitives/transaction.h +++ b/src/primitives/transaction.h @@ -380,6 +380,7 @@ class CTransaction bool IsCoinStake() const; bool HasP2CSOutputs() const; + bool HasExchangeAddr() const; friend bool operator==(const CTransaction& a, const CTransaction& b) { diff --git a/src/pubkey.h b/src/pubkey.h index c3916875be4c5..418078ff6bc94 100644 --- a/src/pubkey.h +++ b/src/pubkey.h @@ -24,6 +24,19 @@ class CKeyID : public uint160 explicit CKeyID(const uint160& in) : uint160(in) {} }; +/** A reference to a CKey: the Hash160 of its serialized public key, special case for exchange key */ + +class CExchangeKeyID : public uint160 +{ +public: + CExchangeKeyID() : uint160() {} + explicit CExchangeKeyID(const uint160& in) : uint160(in) {} + + operator CKeyID() const { + return CKeyID(static_cast(*this)); + } +}; + typedef uint256 ChainCode; /** An encapsulated public key. */ diff --git a/src/qt/addresstablemodel.cpp b/src/qt/addresstablemodel.cpp index 9aba9ea64e956..b872925054588 100644 --- a/src/qt/addresstablemodel.cpp +++ b/src/qt/addresstablemodel.cpp @@ -150,9 +150,14 @@ class AddressTablePriv LOCK(wallet->cs_wallet); for (auto it = wallet->NewAddressBookIterator(); it.IsValid(); it.Next()) { auto addrBookData = it.GetValue(); - const CChainParams::Base58Type addrType = - AddressBook::IsColdStakingPurpose(addrBookData.purpose) ? - CChainParams::STAKING_ADDRESS : CChainParams::PUBKEY_ADDRESS; + CChainParams::Base58Type addrType; + if (AddressBook::IsColdStakingPurpose(addrBookData.purpose)) { + addrType = CChainParams::STAKING_ADDRESS; + } else if (AddressBook::IsExchangePurpose(addrBookData.purpose)) { + addrType = CChainParams::EXCHANGE_ADDRESS; + } else { + addrType = CChainParams::PUBKEY_ADDRESS; + } const CWDestination& dest = *it.GetDestKey(); bool fMine = IsMine(*wallet, dest); diff --git a/src/qt/pivx/addresseswidget.cpp b/src/qt/pivx/addresseswidget.cpp index d30f794a88770..40a02f2787a3a 100644 --- a/src/qt/pivx/addresseswidget.cpp +++ b/src/qt/pivx/addresseswidget.cpp @@ -184,8 +184,11 @@ void AddressesWidget::onStoreContactClicked() QString label = ui->lineEditName->text(); QString address = ui->lineEditAddress->text(); - bool isStakingAddress = false; - auto pivAdd = Standard::DecodeDestination(address.toUtf8().constData(), isStakingAddress); + Standard::DecodeOptions options; + options.isStaking = false; + options.isShielded = false; + options.isExchange = false; + auto pivAdd = Standard::DecodeDestination(address.toUtf8().constData(), options.isStaking); if (!Standard::IsValidDestination(pivAdd)) { setCssEditLine(ui->lineEditAddress, false, true); diff --git a/src/qt/pivx/send.cpp b/src/qt/pivx/send.cpp index ac5f9318eae7f..507a9ab2a50ad 100644 --- a/src/qt/pivx/send.cpp +++ b/src/qt/pivx/send.cpp @@ -906,8 +906,11 @@ void SendWidget::onContactMultiClicked() return; } - bool isStakingAddr = false; - auto pivAdd = Standard::DecodeDestination(address.toStdString(), isStakingAddr); + Standard::DecodeOptions options; + options.isStaking = false; + options.isShielded = false; + options.isExchange = false; + auto pivAdd = Standard::DecodeDestination(address.toStdString(), options.isStaking); if (!Standard::IsValidDestination(pivAdd) || isStakingAddr) { inform(tr("Invalid address")); diff --git a/src/qt/pivx/sendchangeaddressdialog.cpp b/src/qt/pivx/sendchangeaddressdialog.cpp index 6ab1e151fc867..7d7602988e76b 100644 --- a/src/qt/pivx/sendchangeaddressdialog.cpp +++ b/src/qt/pivx/sendchangeaddressdialog.cpp @@ -73,15 +73,17 @@ void SendChangeAddressDialog::accept() QDialog::accept(); } else { // validate address - bool isStakingAddr = false; - bool isShield = false; - dest = Standard::DecodeDestination(ui->lineEditAddress->text().toStdString(), isStakingAddr, isShield); + Standard::DecodeOptions options; + options.isStaking = false; + options.isShielded = false; + options.isExchange = false; + dest = Standard::DecodeDestination(ui->lineEditAddress->text().toStdString(), options); if (!Standard::IsValidDestination(dest)) { inform(tr("Invalid address")); - } else if (isStakingAddr) { + } else if (options.isStaking) { inform(tr("Cannot use cold staking addresses for change")); - } else if (!isShield && !isTransparent) { + } else if (!options.isShielded && !isTransparent) { inform(tr("Cannot use a transparent change for a shield transaction")); } else { QDialog::accept(); diff --git a/src/qt/pivx/sendconfirmdialog.cpp b/src/qt/pivx/sendconfirmdialog.cpp index ef1d745f10d80..87ef97e87ab63 100644 --- a/src/qt/pivx/sendconfirmdialog.cpp +++ b/src/qt/pivx/sendconfirmdialog.cpp @@ -349,8 +349,9 @@ void TxDetailDialog::onOutputsClicked() QString labelRes; CTxDestination dest; bool isCsAddress = out.scriptPubKey.IsPayToColdStaking(); + bool isExchange = out.scriptPubKey.IsPayToExchangeAddress(); if (ExtractDestination(out.scriptPubKey, dest, isCsAddress)) { - std::string address = EncodeDestination(dest, isCsAddress); + std::string address = EncodeDestination(dest, isCsAddress, isExchange); labelRes = QString::fromStdString(address); labelRes = labelRes.left(16) + "..." + labelRes.right(16); } else { diff --git a/src/qt/walletmodel.cpp b/src/qt/walletmodel.cpp index 88f685bb23964..18c7e89ade621 100644 --- a/src/qt/walletmodel.cpp +++ b/src/qt/walletmodel.cpp @@ -398,11 +398,14 @@ void WalletModel::updateWatchOnlyFlag(bool fHaveWatchonly) bool WalletModel::validateAddress(const QString& address) { - // Only regular base58 addresses and shielded addresses accepted here - bool isStaking = false; - CWDestination dest = Standard::DecodeDestination(address.toStdString(), isStaking); + // Only regular base58 addresses, shielded addresses and exchange addresses accepted here + Standard::DecodeOptions options; + options.isStaking = false; + options.isShielded = false; + options.isExchange = false; + CWDestination dest = Standard::DecodeDestination(address.toStdString(), options); const auto regDest = boost::get(&dest); - if (regDest && IsValidDestination(*regDest) && isStaking) return false; + if (regDest && IsValidDestination(*regDest) && options.isStaking) return false; return Standard::IsValidDestination(dest); } @@ -413,13 +416,16 @@ bool WalletModel::validateAddress(const QString& address, bool fStaking) bool WalletModel::validateAddress(const QString& address, bool fStaking, bool& isShielded) { - bool isStaking = false; - CWDestination dest = Standard::DecodeDestination(address.toStdString(), isStaking); + Standard::DecodeOptions options; + options.isStaking = false; + options.isShielded = isShielded; + options.isExchange = false; + CWDestination dest = Standard::DecodeDestination(address.toStdString(), options.isStaking); if (IsShieldedDestination(dest)) { isShielded = true; return true; } - return Standard::IsValidDestination(dest) && (isStaking == fStaking); + return Standard::IsValidDestination(dest) && (options.isStaking == fStaking); } bool WalletModel::updateAddressBookLabels(const CWDestination& dest, const std::string& strName, const std::string& strPurpose) @@ -596,11 +602,13 @@ WalletModel::SendCoinsReturn WalletModel::sendCoins(WalletModelTransaction& tran for (const SendCoinsRecipient& rcp : transaction.getRecipients()) { // Don't touch the address book when we have a payment request { - bool isStaking = false; - bool isShielded = false; - auto address = Standard::DecodeDestination(rcp.address.toStdString(), isStaking, isShielded); - std::string purpose = isShielded ? AddressBook::AddressBookPurpose::SHIELDED_SEND : - isStaking ? AddressBook::AddressBookPurpose::COLD_STAKING_SEND : AddressBook::AddressBookPurpose::SEND; + Standard::DecodeOptions options; + options.isStaking = false; + options.isShielded = false; + options.isExchange = false; + auto address = Standard::DecodeDestination(rcp.address.toStdString(), options.isStaking); + std::string purpose = options.isShielded ? AddressBook::AddressBookPurpose::SHIELDED_SEND : + options.isStaking ? AddressBook::AddressBookPurpose::COLD_STAKING_SEND : AddressBook::AddressBookPurpose::SEND; std::string strLabel = rcp.label.toStdString(); updateAddressBookLabels(address, strLabel, purpose); } @@ -943,7 +951,11 @@ int64_t WalletModel::getKeyCreationTime(const CTxDestination& address) int64_t WalletModel::getKeyCreationTime(const std::string& address) { - return wallet->GetKeyCreationTime(Standard::DecodeDestination(address)); + Standard::DecodeOptions options; + options.isStaking = false; + options.isShielded = false; + options.isExchange = false; + return wallet->GetKeyCreationTime(Standard::DecodeDestination(address, options)); } int64_t WalletModel::getKeyCreationTime(const libzcash::SaplingPaymentAddress& address) @@ -985,9 +997,12 @@ bool WalletModel::blacklistAddressFromColdStaking(const QString &addressStr) bool WalletModel::updateAddressBookPurpose(const QString &addressStr, const std::string& purpose) { - bool isStaking = false; - CTxDestination address = DecodeDestination(addressStr.toStdString(), isStaking); - if (isStaking) + Standard::DecodeOptions options; + options.isStaking = false; + options.isShielded = false; + options.isExchange = false; + CTxDestination address = DecodeDestination(addressStr.toStdString(), options.isStaking); + if (options.isStaking) return error("Invalid PIVX address, cold staking address"); CKeyID keyID; if (!getKeyId(address, keyID)) diff --git a/src/rpc/misc.cpp b/src/rpc/misc.cpp index 50689f79a1f7a..4029b9f8605f1 100644 --- a/src/rpc/misc.cpp +++ b/src/rpc/misc.cpp @@ -246,6 +246,17 @@ class DescribeAddressVisitor : public boost::static_visitor return obj; } + UniValue operator()(const CExchangeKeyID &keyID) const { + UniValue obj(UniValue::VOBJ); + CPubKey vchPubKey; + obj.pushKV("isscript", false); + if (pwallet && pwallet->GetPubKey(keyID, vchPubKey)) { + obj.pushKV("exchangepubkey", HexStr(vchPubKey)); + obj.pushKV("iscompressed", vchPubKey.IsCompressed()); + } + return obj; + } + UniValue operator()(const CScriptID &scriptID) const { UniValue obj(UniValue::VOBJ); obj.pushKV("isscript", true); @@ -420,8 +431,11 @@ UniValue validateaddress(const JSONRPCRequest& request) std::string strAddress = request.params[0].get_str(); // First check if it's a regular address - bool isStakingAddress = false; - CTxDestination dest = DecodeDestination(strAddress, isStakingAddress); + Standard::DecodeOptions options; + options.isStaking = false; + options.isShielded = false; + options.isExchange = false; + CTxDestination dest = DecodeDestination(strAddress, options); bool isValid = IsValidDestination(dest); PPaymentAddress finalAddress; @@ -436,7 +450,7 @@ UniValue validateaddress(const JSONRPCRequest& request) ret.pushKV("isvalid", isValid); if (isValid) { ret.pushKV("address", strAddress); - UniValue detail = boost::apply_visitor(DescribePaymentAddressVisitor(pwallet, isStakingAddress), finalAddress); + UniValue detail = boost::apply_visitor(DescribePaymentAddressVisitor(pwallet, options.isStaking), finalAddress); ret.pushKVs(detail); } diff --git a/src/rpc/rpcevo.cpp b/src/rpc/rpcevo.cpp index df6890e723182..3ac027208fb86 100644 --- a/src/rpc/rpcevo.cpp +++ b/src/rpc/rpcevo.cpp @@ -168,8 +168,11 @@ static void CheckEvoUpgradeEnforcement() // Allows to specify PIVX address or priv key (as strings). In case of PIVX address, the priv key is taken from the wallet static CKey ParsePrivKey(CWallet* pwallet, const std::string &strKeyOrAddress, bool allowAddresses = true) { - bool isStaking{false}, isShield{false}; - const CWDestination& cwdest = Standard::DecodeDestination(strKeyOrAddress, isStaking, isShield); + Standard::DecodeOptions options; + options.isStaking = false; + options.isShielded = false; + options.isExchange = false; + const CWDestination& cwdest = Standard::DecodeDestination(strKeyOrAddress, options); if (isStaking) { throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "cold staking addresses not supported"); } @@ -198,8 +201,11 @@ static CKey ParsePrivKey(CWallet* pwallet, const std::string &strKeyOrAddress, b static CKeyID ParsePubKeyIDFromAddress(const std::string& strAddress) { - bool isStaking{false}, isShield{false}; - const CWDestination& cwdest = Standard::DecodeDestination(strAddress, isStaking, isShield); + Standard::DecodeOptions options; + options.isStaking = false; + options.isShielded = false; + options.isExchange = false; + const CWDestination& cwdest = Standard::DecodeDestination(strAddress, options); if (isStaking) { throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "cold staking addresses not supported"); } diff --git a/src/sapling/sapling_validation.cpp b/src/sapling/sapling_validation.cpp index 2216003a357cb..383e61394f429 100644 --- a/src/sapling/sapling_validation.cpp +++ b/src/sapling/sapling_validation.cpp @@ -33,8 +33,8 @@ bool CheckTransaction(const CTransaction& tx, CValidationState& state, CAmount& // From here, all of the checks are done in v3+ transactions. - // if the tx has shielded data, cannot be a coinstake, coinbase, zcspend and zcmint - if (tx.IsCoinStake() || tx.IsCoinBase() || tx.HasZerocoinSpendInputs() || tx.HasZerocoinMintOutputs()) + // if the tx has shielded data, cannot be a coinstake, coinbase, zcspend and zcmint or exchange address + if (tx.IsCoinStake() || tx.IsCoinBase() || tx.HasZerocoinSpendInputs() || tx.HasZerocoinMintOutputs() || tx.HasExchangeAddr()) return state.DoS(100, error("%s: Sapling version with invalid data", __func__), REJECT_INVALID, "bad-txns-invalid-sapling"); diff --git a/src/script/interpreter.cpp b/src/script/interpreter.cpp index 2b260474fd21c..8f6e06e2c45ef 100644 --- a/src/script/interpreter.cpp +++ b/src/script/interpreter.cpp @@ -256,6 +256,7 @@ bool EvalScript(std::vector >& stack, const CScript& try { + bool fExchangeAddr = true; while (pc < pend) { bool fExec = !count(vfExec.begin(), vfExec.end(), false); @@ -333,6 +334,11 @@ bool EvalScript(std::vector >& stack, const CScript& case OP_NOP: break; + case OP_EXCHANGEADDR: + if (!fExchangeAddr) + return set_error(serror, SCRIPT_ERR_BAD_OPCODE); + break; + case OP_CHECKLOCKTIMEVERIFY: { if (!(flags & SCRIPT_VERIFY_CHECKLOCKTIMEVERIFY)) { @@ -981,6 +987,8 @@ bool EvalScript(std::vector >& stack, const CScript& // Size limits if (stack.size() + altstack.size() > 1000) return set_error(serror, SCRIPT_ERR_STACK_SIZE); + + fExchangeAddr = false; } } catch (...) diff --git a/src/script/ismine.cpp b/src/script/ismine.cpp index 9236258385525..3c9700e09b3f6 100644 --- a/src/script/ismine.cpp +++ b/src/script/ismine.cpp @@ -94,6 +94,7 @@ isminetype IsMine(const CKeyStore& keystore, const CScript& scriptPubKey) return ISMINE_SPENDABLE; break; case TX_PUBKEYHASH: + case TX_EXCHANGEADDR: keyID = CKeyID(uint160(vSolutions[0])); if(keystore.HaveKey(keyID)) return ISMINE_SPENDABLE; diff --git a/src/script/script.cpp b/src/script/script.cpp index 7a8723d89e547..f9c1a578746a6 100644 --- a/src/script/script.cpp +++ b/src/script/script.cpp @@ -151,6 +151,9 @@ const char* GetOpName(opcodetype opcode) case OP_CHECKCOLDSTAKEVERIFY_LOF : return "OP_CHECKCOLDSTAKEVERIFY_LOF"; case OP_CHECKCOLDSTAKEVERIFY : return "OP_CHECKCOLDSTAKEVERIFY"; + // exchange address + case OP_EXCHANGEADDR : return "OP_EXCHANGEADDR"; + case OP_INVALIDOPCODE : return "OP_INVALIDOPCODE"; default: @@ -248,6 +251,17 @@ bool CScript::IsPayToColdStakingLOF() const return IsPayToColdStaking() && (*this)[4] == OP_CHECKCOLDSTAKEVERIFY_LOF; } +bool CScript::IsPayToExchangeAddress() const +{ + return (this->size() == 26 && + (*this)[0] == OP_EXCHANGEADDR && + (*this)[1] == OP_DUP && + (*this)[2] == OP_HASH160 && + (*this)[3] == 0x14 && + (*this)[24] == OP_EQUALVERIFY && + (*this)[25] == OP_CHECKSIG); +} + bool CScript::StartsWithOpcode(const opcodetype opcode) const { return (!this->empty() && (*this)[0] == opcode); diff --git a/src/script/script.h b/src/script/script.h index 85d1b32ad0f39..894d03b57f4db 100644 --- a/src/script/script.h +++ b/src/script/script.h @@ -187,6 +187,9 @@ enum opcodetype OP_CHECKCOLDSTAKEVERIFY_LOF = 0xd1, // last output free for masternode/budget payments OP_CHECKCOLDSTAKEVERIFY = 0xd2, + // exchange address, NOP but identifies as a address not allowing private outputs + OP_EXCHANGEADDR = 0xe0, + OP_INVALIDOPCODE = 0xff, }; @@ -630,6 +633,7 @@ class CScript : public CScriptBase bool IsPayToScriptHash() const; bool IsPayToColdStaking() const; bool IsPayToColdStakingLOF() const; + bool IsPayToExchangeAddress() const; bool StartsWithOpcode(const opcodetype opcode) const; bool IsZerocoinMint() const; bool IsZerocoinSpend() const; diff --git a/src/script/sign.cpp b/src/script/sign.cpp index fe79d0df06ebb..98e351376dbfd 100644 --- a/src/script/sign.cpp +++ b/src/script/sign.cpp @@ -87,6 +87,7 @@ static bool SignStep(const BaseSignatureCreator& creator, const CScript& scriptP keyID = CPubKey(vSolutions[0]).GetID(); return Sign1(keyID, creator, scriptPubKey, ret, sigversion); case TX_PUBKEYHASH: + case TX_EXCHANGEADDR: keyID = CKeyID(uint160(vSolutions[0])); if (!Sign1(keyID, creator, scriptPubKey, ret, sigversion)) return false; @@ -300,6 +301,7 @@ static Stacks CombineSignatures(const CScript& scriptPubKey, const BaseSignature return sigs2; case TX_PUBKEY: case TX_PUBKEYHASH: + case TX_EXCHANGEADDR: case TX_COLDSTAKE: // Signatures are bigger than placeholders or empty scripts: if (sigs1.script.empty() || sigs1.script[0].empty()) diff --git a/src/script/standard.cpp b/src/script/standard.cpp index 6352ee5258e33..9079382b5b4d3 100644 --- a/src/script/standard.cpp +++ b/src/script/standard.cpp @@ -22,6 +22,7 @@ const char* GetTxnOutputType(txnouttype t) case TX_NONSTANDARD: return "nonstandard"; case TX_PUBKEY: return "pubkey"; case TX_PUBKEYHASH: return "pubkeyhash"; + case TX_EXCHANGEADDR: return "exchangeaddress"; case TX_SCRIPTHASH: return "scripthash"; case TX_MULTISIG: return "multisig"; case TX_COLDSTAKE: return "coldstake"; @@ -126,6 +127,15 @@ bool Solver(const CScript& scriptPubKey, txnouttype& typeRet, std::vector hashBytes(scriptPubKey.begin()+4, scriptPubKey.begin()+24); + vSolutionsRet.push_back(hashBytes); + return true; + } + + std::vector data1; if (MatchPayToColdStaking(scriptPubKey, data, data1)) { typeRet = TX_COLDSTAKE; @@ -167,10 +177,12 @@ bool ExtractDestination(const CScript& scriptPubKey, CTxDestination& addressRet, } else if (whichType == TX_PUBKEYHASH) { addressRet = CKeyID(uint160(vSolutions[0])); return true; - } else if (whichType == TX_SCRIPTHASH) { addressRet = CScriptID(uint160(vSolutions[0])); return true; + } else if (whichType == TX_EXCHANGEADDR) { + addressRet = CExchangeKeyID(uint160(vSolutions[0])); + return true; } else if (whichType == TX_COLDSTAKE) { addressRet = CKeyID(uint160(vSolutions[!fColdStake])); return true; @@ -248,6 +260,12 @@ class CScriptVisitor : public boost::static_visitor return true; } + bool operator()(const CExchangeKeyID &keyID) const { + script->clear(); + *script << OP_EXCHANGEADDR << OP_DUP << OP_HASH160 << ToByteVector(keyID) << OP_EQUALVERIFY << OP_CHECKSIG; + return true; + } + bool operator()(const CScriptID &scriptID) const { script->clear(); *script << OP_HASH160 << ToByteVector(scriptID) << OP_EQUAL; diff --git a/src/script/standard.h b/src/script/standard.h index 4ea3aefc2c469..b8dd5d713de0f 100644 --- a/src/script/standard.h +++ b/src/script/standard.h @@ -51,7 +51,8 @@ enum txnouttype TX_SCRIPTHASH, TX_MULTISIG, TX_NULL_DATA, - TX_COLDSTAKE + TX_COLDSTAKE, + TX_EXCHANGEADDR }; class CNoDestination { @@ -64,10 +65,11 @@ class CNoDestination { * A txout script template with a specific destination. It is either: * * CNoDestination: no destination set * * CKeyID: TX_PUBKEYHASH destination + * * CExchangeKeyID: CKeyID for exchange key * * CScriptID: TX_SCRIPTHASH destination * A CTxDestination is the internal data type encoded in a PIVX address */ -typedef boost::variant CTxDestination; +typedef boost::variant CTxDestination; /** Check whether a CTxDestination is a CNoDestination. */ bool IsValidDestination(const CTxDestination& dest); diff --git a/src/wallet/rpcdump.cpp b/src/wallet/rpcdump.cpp index 37dfa5f86319f..1e6438a9a3ae1 100644 --- a/src/wallet/rpcdump.cpp +++ b/src/wallet/rpcdump.cpp @@ -6,6 +6,7 @@ #include "bip38.h" #include "key_io.h" +#include "destination_io.h" #include "rpc/server.h" #include "sapling/key_io_sapling.h" #include "script/script.h" @@ -243,13 +244,16 @@ UniValue importaddress(const JSONRPCRequest& request) { LOCK2(cs_main, pwallet->cs_wallet); - bool isStakingAddress = false; - CTxDestination dest = DecodeDestination(request.params[0].get_str(), isStakingAddress); + Standard::DecodeOptions options; + options.isStaking = false; + options.isShielded = false; + options.isExchange = false; + CTxDestination dest = DecodeDestination(request.params[0].get_str(), options); if (IsValidDestination(dest)) { if (fP2SH) throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Cannot use the p2sh flag with an address - use a script instead"); - ImportAddress(pwallet, dest, strLabel, isStakingAddress ? + ImportAddress(pwallet, dest, strLabel, options.isStaking ? AddressBook::AddressBookPurpose::COLD_STAKING : AddressBook::AddressBookPurpose::RECEIVE); diff --git a/src/wallet/rpcwallet.cpp b/src/wallet/rpcwallet.cpp index 85904299e88ca..82495b485c8b0 100644 --- a/src/wallet/rpcwallet.cpp +++ b/src/wallet/rpcwallet.cpp @@ -308,7 +308,7 @@ UniValue getaddressesbylabel(const JSONRPCRequest& request) auto addrBook = it.GetValue(); if (addrBook.name == label) { if (!addrBook.isShielded()) { - ret.pushKV(EncodeDestination(*it.GetCTxDestKey(), AddressBook::IsColdStakingPurpose(addrBook.purpose)), AddressBookDataToJSON(addrBook, false)); + ret.pushKV(EncodeDestination(*it.GetCTxDestKey(), AddressBook::IsColdStakingPurpose(addrBook.purpose), AddressBook::IsExchangePurpose(addrBook.purpose)), AddressBookDataToJSON(addrBook, false)); } else { ret.pushKV(Standard::EncodeDestination(*it.GetShieldedDestKey()), AddressBookDataToJSON(addrBook, false)); } @@ -528,6 +528,34 @@ UniValue getnewaddress(const JSONRPCRequest& request) return EncodeDestination(GetNewAddressFromLabel(pwallet, AddressBook::AddressBookPurpose::RECEIVE, request.params)); } +UniValue getnewexchangeaddress(const JSONRPCRequest& request) +{ + CWallet * const pwallet = GetWalletForJSONRPCRequest(request); + + if (!EnsureWalletIsAvailable(pwallet, request.fHelp)) { + return NullUniValue; + } + + if (request.fHelp || request.params.size() > 1) + throw std::runtime_error( + "getnewexchangeaddress ( \"label\" )\n" + "\nReturns a new PIVX exchange address for receiving transparent funds only.\n" + + "\nArguments:\n" + "1. \"label\" (string, optional) The label name for the address to be linked to. if not provided, the default label \"\" is used. It can also be set to the empty string \"\" to represent the default label. The label does not need to exist, it will be created if there is no label by the given name.\n" + + + "\nResult:\n" + "\"pivxaddress\" (string) The new pivx exchange address\n" + + "\nExamples:\n" + + HelpExampleCli("getnewexchangeaddress", "") + HelpExampleRpc("getnewexchangeaddress", "")); + + LOCK2(cs_main, pwallet->cs_wallet); + + return EncodeDestination(GetNewAddressFromLabel(pwallet, "exchange", request.params, CChainParams::EXCHANGE_ADDRESS), CChainParams::EXCHANGE_ADDRESS); +} + UniValue getnewstakingaddress(const JSONRPCRequest& request) { CWallet * const pwallet = GetWalletForJSONRPCRequest(request); @@ -754,9 +782,12 @@ UniValue delegatoradd(const JSONRPCRequest& request) HelpExampleRpc("delegatoradd", "\"DMJRSsuU9zfyrvxVaAEFQqK4MxZg6vgeS6\" \"myPaperWallet\"")); - bool isStakingAddress = false; - CTxDestination dest = DecodeDestination(request.params[0].get_str(), isStakingAddress); - if (!IsValidDestination(dest) || isStakingAddress) + Standard::DecodeOptions options; + options.isStaking = false; + options.isShielded = false; + options.isExchange = false; + CTxDestination dest = DecodeDestination(request.params[0].get_str(), options.isStaking); + if (!IsValidDestination(dest) || options.isStaking) throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid PIVX address"); const std::string strLabel = (request.params.size() > 1 ? request.params[1].get_str() : ""); @@ -791,9 +822,12 @@ UniValue delegatorremove(const JSONRPCRequest& request) HelpExampleCli("delegatorremove", "DMJRSsuU9zfyrvxVaAEFQqK4MxZg6vgeS6") + HelpExampleRpc("delegatorremove", "\"DMJRSsuU9zfyrvxVaAEFQqK4MxZg6vgeS6\"")); - bool isStakingAddress = false; - CTxDestination dest = DecodeDestination(request.params[0].get_str(), isStakingAddress); - if (!IsValidDestination(dest) || isStakingAddress) + Standard::DecodeOptions options; + options.isStaking = false; + options.isShielded = false; + options.isExchange = false; + CTxDestination dest = DecodeDestination(request.params[0].get_str(), options.isStaking; + if (!IsValidDestination(dest) || options.isStaking) throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid PIVX address"); const CKeyID* keyID = boost::get(&dest); @@ -814,10 +848,14 @@ UniValue delegatorremove(const JSONRPCRequest& request) static UniValue ListaddressesForPurpose(CWallet* const pwallet, const std::string strPurpose) { - const CChainParams::Base58Type addrType = ( - AddressBook::IsColdStakingPurpose(strPurpose) ? - CChainParams::STAKING_ADDRESS : - CChainParams::PUBKEY_ADDRESS); + CChainParams::Base58Type addrType; + if (AddressBook::IsColdStakingPurpose(strPurpose)) { + addrType = CChainParams::STAKING_ADDRESS; + } else if (AddressBook::IsExchangePurpose(strPurpose)) { + addrType = CChainParams::EXCHANGE_ADDRESS; + } else { + addrType = CChainParams::PUBKEY_ADDRESS; + } UniValue ret(UniValue::VARR); { LOCK(pwallet->cs_wallet); @@ -1147,9 +1185,12 @@ UniValue sendtoaddress(const JSONRPCRequest& request) // the user could have gotten from another RPC command prior to now pwallet->BlockUntilSyncedToCurrentChain(); - bool isStaking = false, isShielded = false; + Standard::DecodeOptions options; + options.isStaking = false; + options.isShielded = false; + options.isExchange = false; const std::string addrStr = request.params[0].get_str(); - const CWDestination& destination = Standard::DecodeDestination(addrStr, isStaking, isShielded); + const CWDestination& destination = Standard::DecodeDestination(addrStr, options); if (!Standard::IsValidDestination(destination) || isStaking) throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid PIVX address"); const std::string commentStr = (request.params.size() > 2 && !request.params[2].isNull()) ? @@ -1203,9 +1244,12 @@ static UniValue CreateColdStakeDelegation(CWallet* const pwallet, const UniValue } // Get Staking Address - bool isStaking = false; - CTxDestination stakeAddr = DecodeDestination(params[0].get_str(), isStaking); - if (!IsValidDestination(stakeAddr) || !isStaking) + Standard::DecodeOptions options; + options.isStaking = false; + options.isShielded = false; + options.isExchange = false; + CTxDestination stakeAddr = DecodeDestination(params[0].get_str(), options); + if (!IsValidDestination(stakeAddr) || !options.isStaking) throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid PIVX staking address"); CKeyID* stakeKey = boost::get(&stakeAddr); @@ -1235,9 +1279,8 @@ static UniValue CreateColdStakeDelegation(CWallet* const pwallet, const UniValue CKeyID ownerKey; if (params.size() > 2 && !params[2].isNull() && !params[2].get_str().empty()) { // Address provided - bool isStaking = false; - CTxDestination dest = DecodeDestination(params[2].get_str(), isStaking); - if (!IsValidDestination(dest) || isStaking) + CTxDestination dest = DecodeDestination(params[2].get_str(), options.isStaking); + if (!IsValidDestination(dest) || options.isStaking) throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid PIVX spending address"); ownerKey = *boost::get(&dest); // Check that the owner address belongs to this wallet, or fForceExternalAddr is true @@ -1293,7 +1336,7 @@ static UniValue CreateColdStakeDelegation(CWallet* const pwallet, const UniValue UniValue result(UniValue::VOBJ); result.pushKV("owner_address", ownerAddressStr); - result.pushKV("staker_address", EncodeDestination(stakeAddr, true)); + result.pushKV("staker_address", EncodeDestination(stakeAddr, true, false)); return result; } @@ -2324,9 +2367,12 @@ static UniValue legacy_sendmany(CWallet* const pwallet, const UniValue& sendTo, CAmount totalAmount = 0; std::vector keys = sendTo.getKeys(); for (const std::string& name_ : keys) { - bool isStaking = false; - CTxDestination dest = DecodeDestination(name_,isStaking); - if (!IsValidDestination(dest) || isStaking) + Standard::DecodeOptions options; + options.isStaking = false; + options.isShielded = false; + options.isExchange = false; + CTxDestination dest = DecodeDestination(name_, options); + if (!IsValidDestination(dest) || options.isStaking) throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, std::string("Invalid PIVX address: ")+name_); if (setAddress.count(dest)) @@ -2458,8 +2504,11 @@ UniValue sendmany(const JSONRPCRequest& request) // Check if any recipient address is shield bool fShieldSend = false; for (const std::string& key : sendTo.getKeys()) { - bool isStaking = false, isShielded = false; - Standard::DecodeDestination(key, isStaking, isShielded); + Standard::DecodeOptions options; + options.isStaking = false; + options.isShielded = false; + options.isExchange = false; + Standard::DecodeDestination(key, options); if (isShielded) { fShieldSend = true; break; @@ -2646,7 +2695,7 @@ static UniValue ListReceived(CWallet* const pwallet, const UniValue& params, boo UniValue obj(UniValue::VOBJ); if (fIsWatchonly) obj.pushKV("involvesWatchonly", true); - obj.pushKV("address", EncodeDestination(address, AddressBook::IsColdStakingPurpose(label))); + obj.pushKV("address", EncodeDestination(address, AddressBook::IsColdStakingPurpose(label), AddressBook::IsExchangePurpose(label))); obj.pushKV("amount", ValueFromAmount(nAmount)); if (nConf == std::numeric_limits::max()) nConf = 0; obj.pushKV("confirmations", nConf); @@ -4749,6 +4798,7 @@ static const CRPCCommand commands[] = { "wallet", "upgradewallet", &upgradewallet, true, {} }, { "wallet", "sethdseed", &sethdseed, true, {"newkeypool","seed"} }, { "wallet", "getnewaddress", &getnewaddress, true, {"label"} }, + { "hidden", "getnewexchangeaddress", &getnewexchangeaddress, true, {"label"} }, { "wallet", "getnewstakingaddress", &getnewstakingaddress, true, {"label"} }, { "wallet", "getrawchangeaddress", &getrawchangeaddress, true, {} }, { "wallet", "getreceivedbyaddress", &getreceivedbyaddress, false, {"address","minconf"} }, diff --git a/src/wallet/wallet.cpp b/src/wallet/wallet.cpp index b51a1f9794942..10f583ffdb4c8 100644 --- a/src/wallet/wallet.cpp +++ b/src/wallet/wallet.cpp @@ -100,6 +100,11 @@ class CAffectedKeysVisitor : public boost::static_visitor vKeys.push_back(keyId); } + void operator()(const CExchangeKeyID& keyId) { + if (keystore.HaveKey(keyId)) + vKeys.push_back(keyId); + } + void operator()(const CScriptID& scriptId) { CScript script; @@ -3601,9 +3606,14 @@ DBErrors CWallet::ZapWalletTx(std::vector& vWtx) } std::string CWallet::ParseIntoAddress(const CWDestination& dest, const std::string& purpose) { - const CChainParams::Base58Type addrType = - AddressBook::IsColdStakingPurpose(purpose) ? - CChainParams::STAKING_ADDRESS : CChainParams::PUBKEY_ADDRESS; + CChainParams::Base58Type addrType; + if (AddressBook::IsColdStakingPurpose(purpose)) { + addrType = CChainParams::STAKING_ADDRESS; + } else if (AddressBook::IsExchangePurpose(purpose)) { + addrType = CChainParams::EXCHANGE_ADDRESS; + } else { + addrType = CChainParams::PUBKEY_ADDRESS; + } return Standard::EncodeDestination(dest, addrType); } From a521fdf9d37d566b0d1a37bf2a1817ccc6254733 Mon Sep 17 00:00:00 2001 From: Liquid Date: Thu, 11 Jan 2024 17:18:17 -0600 Subject: [PATCH 02/18] Expand CTxDestination to check for ExchangeAddresses and GUI Updates Prefixes from E to EXM, EXT, EXR respectively --- src/chainparams.cpp | 6 ++-- src/consensus/tx_verify.cpp | 12 ++++--- src/destination_io.cpp | 17 +++++---- src/destination_io.h | 7 ++++ src/key_io.cpp | 13 ++++--- src/key_io.h | 4 +-- src/primitives/transaction.cpp | 6 ++-- src/qt/pivx/addresseswidget.cpp | 4 +-- src/qt/pivx/send.cpp | 4 +-- src/qt/walletmodel.cpp | 6 ++-- src/rpc/misc.cpp | 10 +++--- src/rpc/rpcevo.cpp | 8 ++--- src/wallet/rpcdump.cpp | 10 +++--- src/wallet/rpcwallet.cpp | 64 ++++++++++++++++----------------- 14 files changed, 91 insertions(+), 80 deletions(-) diff --git a/src/chainparams.cpp b/src/chainparams.cpp index 2e3cfac97fb78..356f92b013ac0 100644 --- a/src/chainparams.cpp +++ b/src/chainparams.cpp @@ -330,7 +330,7 @@ class CMainParams : public CChainParams base58Prefixes[PUBKEY_ADDRESS] = std::vector(1, 30); base58Prefixes[SCRIPT_ADDRESS] = std::vector(1, 13); base58Prefixes[STAKING_ADDRESS] = std::vector(1, 63); // starting with 'S' - base58Prefixes[EXCHANGE_ADDRESS] = std::vector(1, 33); // starts with E + base58Prefixes[EXCHANGE_ADDRESS] = {0x01, 0xb9, 0xa2}; // starts with EXM base58Prefixes[SECRET_KEY] = std::vector(1, 212); base58Prefixes[EXT_PUBLIC_KEY] = {0x02, 0x2D, 0x25, 0x33}; base58Prefixes[EXT_SECRET_KEY] = {0x02, 0x21, 0x31, 0x2B}; @@ -477,7 +477,7 @@ class CTestNetParams : public CChainParams base58Prefixes[PUBKEY_ADDRESS] = std::vector(1, 139); // Testnet pivx addresses start with 'x' or 'y' base58Prefixes[SCRIPT_ADDRESS] = std::vector(1, 19); // Testnet pivx script addresses start with '8' or '9' base58Prefixes[STAKING_ADDRESS] = std::vector(1, 73); // starting with 'W' - base58Prefixes[EXCHANGE_ADDRESS] = std::vector(1, 33); // starts with E + base58Prefixes[EXCHANGE_ADDRESS] = {0x01, 0xb9, 0xb1}; // EXT prefix for the address base58Prefixes[SECRET_KEY] = std::vector(1, 239); // Testnet private keys start with '9' or 'c' (Bitcoin defaults) // Testnet pivx BIP32 pubkeys start with 'DRKV' base58Prefixes[EXT_PUBLIC_KEY] = {0x3a, 0x80, 0x61, 0xa0}; @@ -626,7 +626,7 @@ class CRegTestParams : public CChainParams base58Prefixes[PUBKEY_ADDRESS] = std::vector(1, 139); // Testnet pivx addresses start with 'x' or 'y' base58Prefixes[SCRIPT_ADDRESS] = std::vector(1, 19); // Testnet pivx script addresses start with '8' or '9' base58Prefixes[STAKING_ADDRESS] = std::vector(1, 73); // starting with 'W' - base58Prefixes[EXCHANGE_ADDRESS] = std::vector(1, 33); // starts with E + base58Prefixes[EXCHANGE_ADDRESS] = {0x01, 0xb9, 0xac}; // EXR prefix for the address base58Prefixes[SECRET_KEY] = std::vector(1, 239); // Testnet private keys start with '9' or 'c' (Bitcoin defaults) // Testnet pivx BIP32 pubkeys start with 'DRKV' base58Prefixes[EXT_PUBLIC_KEY] = {0x3a, 0x80, 0x61, 0xa0}; diff --git a/src/consensus/tx_verify.cpp b/src/consensus/tx_verify.cpp index 1ed07b515d0bc..b4677f44507ae 100644 --- a/src/consensus/tx_verify.cpp +++ b/src/consensus/tx_verify.cpp @@ -118,22 +118,24 @@ bool CheckTransaction(const CTransaction& tx, CValidationState& state, bool fCol // input scripts cannot have OP_EXCHANGEADDR at all for (const auto &vin: tx.vin) { if (vin.scriptSig.size() >= 1 && vin.scriptSig[0] == OP_EXCHANGEADDR) { - return state.DoS(100, false, REJECT_INVALID, "bad-exchange-address"); + return state.DoS(100, false, REJECT_INVALID, "bad-exchange-address-vin"); } } bool hasExchangeUTXOs = false; - if (tx.HasExchangeAddr()) + if (tx.HasExchangeAddr()) { hasExchangeUTXOs = true; + } + bool isIDB = IsInitialBlockDownload(); int nTxHeight = chainActive.Height(); - if (hasExchangeUTXOs && nTxHeight < ::Params().GetConsensus().nExchangeAddrStart) - return state.DoS(100, false, REJECT_INVALID, "bad-exchange-address"); + if (hasExchangeUTXOs && !isIDB && nTxHeight < ::Params().GetConsensus().nExchangeAddrStart) + return state.DoS(100, false, REJECT_INVALID, "bad-exchange-address-not-started"); if (tx.IsCoinBase()) { if (tx.vin[0].scriptSig.size() < 2 || tx.vin[0].scriptSig.size() > 150) return state.DoS(100, false, REJECT_INVALID, "bad-cb-length"); if (hasExchangeUTXOs) - return state.DoS(100, false, REJECT_INVALID, "bad-exchange-address"); + return state.DoS(100, false, REJECT_INVALID, "bad-exchange-address-in-cb"); } else { for (const CTxIn& txin : tx.vin) if (txin.prevout.IsNull() && !txin.IsZerocoinSpend()) diff --git a/src/destination_io.cpp b/src/destination_io.cpp index d60109d5a6128..0b30293ab2128 100644 --- a/src/destination_io.cpp +++ b/src/destination_io.cpp @@ -7,12 +7,6 @@ #include "sapling/key_io_sapling.h" namespace Standard { - struct DecodeOptions { - bool isStaking = false; - bool isShielded = false; - bool isExchange = false; - }; - std::string EncodeDestination(const CWDestination &address, const CChainParams::Base58Type addrType) { const CTxDestination *dest = boost::get(&address); if (!dest) { @@ -21,11 +15,20 @@ namespace Standard { return EncodeDestination(*dest, addrType); }; + CWDestination DecodeDestination(const std::string& strAddress) + { + Standard::DecodeOptions options; + options.isStaking = false; + options.isShielded = false; + options.isExchange = false; + return DecodeDestination(strAddress, options); + } + // agregar isShielded CWDestination DecodeDestination(const std::string& strAddress, DecodeOptions options) { CWDestination dest; - CTxDestination regDest = ::DecodeDestination(strAddress, options.isStaking); + CTxDestination regDest = ::DecodeDestination(strAddress, options.isStaking, options.isExchange); if (!IsValidDestination(regDest)) { const auto sapDest = KeyIO::DecodeSaplingPaymentAddress(strAddress); if (sapDest) { diff --git a/src/destination_io.h b/src/destination_io.h index 9c83c4b80092d..44fb9c38290c9 100644 --- a/src/destination_io.h +++ b/src/destination_io.h @@ -13,8 +13,15 @@ typedef boost::variant CWDestin namespace Standard { + struct DecodeOptions { + bool isStaking = false; + bool isShielded = false; + bool isExchange = false; + }; + std::string EncodeDestination(const CWDestination &address, const CChainParams::Base58Type addrType = CChainParams::PUBKEY_ADDRESS); + CWDestination DecodeDestination(const std::string& strAddress); CWDestination DecodeDestination(const std::string& strAddress, DecodeOptions options); bool IsValidDestination(const CWDestination& dest); diff --git a/src/key_io.cpp b/src/key_io.cpp index 444a566e39757..a7a2adce49688 100644 --- a/src/key_io.cpp +++ b/src/key_io.cpp @@ -48,7 +48,7 @@ namespace std::string operator()(const CNoDestination& no) const { return ""; } }; - CTxDestination DecodeDestination(const std::string& str, const CChainParams& params, bool& isStaking) + CTxDestination DecodeDestination(const std::string& str, const CChainParams& params, bool& isStaking, bool& isExchange) { std::vector data; uint160 hash; @@ -64,6 +64,7 @@ namespace // Exchange Transparent addresses have version 31 const std::vector& exchange_pubkey_prefix = params.Base58Prefix(CChainParams::EXCHANGE_ADDRESS); if (data.size() == hash.size() + exchange_pubkey_prefix.size() && std::equal(exchange_pubkey_prefix.begin(), exchange_pubkey_prefix.end(), data.begin())) { + isExchange = true; std::copy(data.begin() + exchange_pubkey_prefix.size(), data.end(), hash.begin()); return CExchangeKeyID(hash); } @@ -104,18 +105,20 @@ std::string EncodeDestination(const CTxDestination& dest, const CChainParams::Ba CTxDestination DecodeDestination(const std::string& str) { bool isStaking; - return DecodeDestination(str, Params(), isStaking); + bool isExchange; + return DecodeDestination(str, Params(), isStaking, isExchange); } -CTxDestination DecodeDestination(const std::string& str, bool& isStaking) +CTxDestination DecodeDestination(const std::string& str, bool& isStaking, bool& isExchange) { - return DecodeDestination(str, Params(), isStaking); + return DecodeDestination(str, Params(), isStaking, isExchange); } bool IsValidDestinationString(const std::string& str, bool fStaking, const CChainParams& params) { bool isStaking = false; - return IsValidDestination(DecodeDestination(str, params, isStaking)) && (isStaking == fStaking); + bool isExchange = false; + return IsValidDestination(DecodeDestination(str, params, isStaking, isExchange)) && (isStaking == fStaking); } bool IsValidDestinationString(const std::string& str, bool isStaking) diff --git a/src/key_io.h b/src/key_io.h index d5fe1e626bb58..8eeb807eeb13c 100644 --- a/src/key_io.h +++ b/src/key_io.h @@ -15,8 +15,8 @@ std::string EncodeDestination(const CTxDestination& dest, bool isStaking, bool isExchange); std::string EncodeDestination(const CTxDestination& dest, const CChainParams::Base58Type addrType = CChainParams::PUBKEY_ADDRESS); -// DecodeDestinationisStaking flag is set to true when the string arg is from an staking address -CTxDestination DecodeDestination(const std::string& str, bool& isStaking); +// DecodeDestination isStaking flag is set to true when the string arg is from an staking address +CTxDestination DecodeDestination(const std::string& str, bool& isStaking, bool& isExchange); CTxDestination DecodeDestination(const std::string& str); // Return true if the address is valid and is following the fStaking flag type (true means that the destination must be a staking address, false the opposite). diff --git a/src/primitives/transaction.cpp b/src/primitives/transaction.cpp index abf8d554291b1..76a1620863e9b 100644 --- a/src/primitives/transaction.cpp +++ b/src/primitives/transaction.cpp @@ -146,11 +146,11 @@ bool CTransaction::IsCoinStake() const bool CTransaction::HasExchangeAddr() const { for(const CTxOut& txout : vout) { - if (txout.scriptPubKey.size() >= 1 && !txout.scriptPubKey.IsPayToExchangeAddress()) { - return false; + if (txout.scriptPubKey.size() >= 1 && txout.scriptPubKey.IsPayToExchangeAddress()) { + return true; } } - return true; + return false; } bool CTransaction::HasP2CSOutputs() const diff --git a/src/qt/pivx/addresseswidget.cpp b/src/qt/pivx/addresseswidget.cpp index 40a02f2787a3a..a7505befacf37 100644 --- a/src/qt/pivx/addresseswidget.cpp +++ b/src/qt/pivx/addresseswidget.cpp @@ -188,7 +188,7 @@ void AddressesWidget::onStoreContactClicked() options.isStaking = false; options.isShielded = false; options.isExchange = false; - auto pivAdd = Standard::DecodeDestination(address.toUtf8().constData(), options.isStaking); + auto pivAdd = Standard::DecodeDestination(address.toUtf8().constData(), options); if (!Standard::IsValidDestination(pivAdd)) { setCssEditLine(ui->lineEditAddress, false, true); @@ -212,7 +212,7 @@ void AddressesWidget::onStoreContactClicked() bool isShielded = walletModel->IsShieldedDestination(pivAdd); if (walletModel->updateAddressBookLabels(pivAdd, label.toUtf8().constData(), isShielded ? AddressBook::AddressBookPurpose::SHIELDED_SEND : - isStakingAddress ? AddressBook::AddressBookPurpose::COLD_STAKING_SEND : AddressBook::AddressBookPurpose::SEND) + options.isStaking ? AddressBook::AddressBookPurpose::COLD_STAKING_SEND : AddressBook::AddressBookPurpose::SEND) ) { ui->lineEditAddress->setText(""); ui->lineEditName->setText(""); diff --git a/src/qt/pivx/send.cpp b/src/qt/pivx/send.cpp index 507a9ab2a50ad..6d2f8aea9cb9f 100644 --- a/src/qt/pivx/send.cpp +++ b/src/qt/pivx/send.cpp @@ -910,9 +910,9 @@ void SendWidget::onContactMultiClicked() options.isStaking = false; options.isShielded = false; options.isExchange = false; - auto pivAdd = Standard::DecodeDestination(address.toStdString(), options.isStaking); + auto pivAdd = Standard::DecodeDestination(address.toStdString(), options); - if (!Standard::IsValidDestination(pivAdd) || isStakingAddr) { + if (!Standard::IsValidDestination(pivAdd) || options.isStaking) { inform(tr("Invalid address")); return; } diff --git a/src/qt/walletmodel.cpp b/src/qt/walletmodel.cpp index 18c7e89ade621..e4f020f5dfa02 100644 --- a/src/qt/walletmodel.cpp +++ b/src/qt/walletmodel.cpp @@ -420,7 +420,7 @@ bool WalletModel::validateAddress(const QString& address, bool fStaking, bool& i options.isStaking = false; options.isShielded = isShielded; options.isExchange = false; - CWDestination dest = Standard::DecodeDestination(address.toStdString(), options.isStaking); + CWDestination dest = Standard::DecodeDestination(address.toStdString(), options); if (IsShieldedDestination(dest)) { isShielded = true; return true; @@ -606,7 +606,7 @@ WalletModel::SendCoinsReturn WalletModel::sendCoins(WalletModelTransaction& tran options.isStaking = false; options.isShielded = false; options.isExchange = false; - auto address = Standard::DecodeDestination(rcp.address.toStdString(), options.isStaking); + auto address = Standard::DecodeDestination(rcp.address.toStdString(), options); std::string purpose = options.isShielded ? AddressBook::AddressBookPurpose::SHIELDED_SEND : options.isStaking ? AddressBook::AddressBookPurpose::COLD_STAKING_SEND : AddressBook::AddressBookPurpose::SEND; std::string strLabel = rcp.label.toStdString(); @@ -1001,7 +1001,7 @@ bool WalletModel::updateAddressBookPurpose(const QString &addressStr, const std: options.isStaking = false; options.isShielded = false; options.isExchange = false; - CTxDestination address = DecodeDestination(addressStr.toStdString(), options.isStaking); + CTxDestination address = DecodeDestination(addressStr.toStdString(), options.isStaking, options.isExchange); if (options.isStaking) return error("Invalid PIVX address, cold staking address"); CKeyID keyID; diff --git a/src/rpc/misc.cpp b/src/rpc/misc.cpp index 4029b9f8605f1..a84634b276e41 100644 --- a/src/rpc/misc.cpp +++ b/src/rpc/misc.cpp @@ -431,11 +431,9 @@ UniValue validateaddress(const JSONRPCRequest& request) std::string strAddress = request.params[0].get_str(); // First check if it's a regular address - Standard::DecodeOptions options; - options.isStaking = false; - options.isShielded = false; - options.isExchange = false; - CTxDestination dest = DecodeDestination(strAddress, options); + bool isStaking = false; + bool isExchange = false; + CTxDestination dest = DecodeDestination(strAddress, isStaking, isExchange); bool isValid = IsValidDestination(dest); PPaymentAddress finalAddress; @@ -450,7 +448,7 @@ UniValue validateaddress(const JSONRPCRequest& request) ret.pushKV("isvalid", isValid); if (isValid) { ret.pushKV("address", strAddress); - UniValue detail = boost::apply_visitor(DescribePaymentAddressVisitor(pwallet, options.isStaking), finalAddress); + UniValue detail = boost::apply_visitor(DescribePaymentAddressVisitor(pwallet, isStaking), finalAddress); ret.pushKVs(detail); } diff --git a/src/rpc/rpcevo.cpp b/src/rpc/rpcevo.cpp index 3ac027208fb86..c98d4475da0cb 100644 --- a/src/rpc/rpcevo.cpp +++ b/src/rpc/rpcevo.cpp @@ -173,10 +173,10 @@ static CKey ParsePrivKey(CWallet* pwallet, const std::string &strKeyOrAddress, b options.isShielded = false; options.isExchange = false; const CWDestination& cwdest = Standard::DecodeDestination(strKeyOrAddress, options); - if (isStaking) { + if (options.isStaking) { throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "cold staking addresses not supported"); } - if (isShield) { + if (options.isShielded) { throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "shield addresses not supported"); } const CTxDestination* dest = Standard::GetTransparentDestination(cwdest); @@ -206,10 +206,10 @@ static CKeyID ParsePubKeyIDFromAddress(const std::string& strAddress) options.isShielded = false; options.isExchange = false; const CWDestination& cwdest = Standard::DecodeDestination(strAddress, options); - if (isStaking) { + if (options.isStaking) { throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "cold staking addresses not supported"); } - if (isShield) { + if (options.isShielded) { throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "shield addresses not supported"); } const CKeyID* keyID = boost::get(Standard::GetTransparentDestination(cwdest)); diff --git a/src/wallet/rpcdump.cpp b/src/wallet/rpcdump.cpp index 1e6438a9a3ae1..b94430614eee2 100644 --- a/src/wallet/rpcdump.cpp +++ b/src/wallet/rpcdump.cpp @@ -244,16 +244,14 @@ UniValue importaddress(const JSONRPCRequest& request) { LOCK2(cs_main, pwallet->cs_wallet); - Standard::DecodeOptions options; - options.isStaking = false; - options.isShielded = false; - options.isExchange = false; - CTxDestination dest = DecodeDestination(request.params[0].get_str(), options); + bool isStaking = false; + bool isExchange = false; + CTxDestination dest = DecodeDestination(request.params[0].get_str(), isStaking, isExchange); if (IsValidDestination(dest)) { if (fP2SH) throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Cannot use the p2sh flag with an address - use a script instead"); - ImportAddress(pwallet, dest, strLabel, options.isStaking ? + ImportAddress(pwallet, dest, strLabel, isStaking ? AddressBook::AddressBookPurpose::COLD_STAKING : AddressBook::AddressBookPurpose::RECEIVE); diff --git a/src/wallet/rpcwallet.cpp b/src/wallet/rpcwallet.cpp index 82495b485c8b0..4e270e63c0798 100644 --- a/src/wallet/rpcwallet.cpp +++ b/src/wallet/rpcwallet.cpp @@ -191,7 +191,11 @@ UniValue getaddressinfo(const JSONRPCRequest& request) UniValue ret(UniValue::VOBJ); ret.pushKV("address", strAdd); - const CWDestination& dest = Standard::DecodeDestination(strAdd); + Standard::DecodeOptions options; + options.isStaking = false; + options.isShielded = false; + options.isExchange = false; + const CWDestination& dest = Standard::DecodeDestination(strAdd, options); // Make sure the destination is valid if (!Standard::IsValidDestination(dest)) { throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid address"); @@ -781,13 +785,10 @@ UniValue delegatoradd(const JSONRPCRequest& request) HelpExampleRpc("delegatoradd", "\"DMJRSsuU9zfyrvxVaAEFQqK4MxZg6vgeS6\"") + HelpExampleRpc("delegatoradd", "\"DMJRSsuU9zfyrvxVaAEFQqK4MxZg6vgeS6\" \"myPaperWallet\"")); - - Standard::DecodeOptions options; - options.isStaking = false; - options.isShielded = false; - options.isExchange = false; - CTxDestination dest = DecodeDestination(request.params[0].get_str(), options.isStaking); - if (!IsValidDestination(dest) || options.isStaking) + bool isStaking = false; + bool isExchange = false; + CTxDestination dest = DecodeDestination(request.params[0].get_str(), isStaking, isExchange); + if (!IsValidDestination(dest) || isStaking) throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid PIVX address"); const std::string strLabel = (request.params.size() > 1 ? request.params[1].get_str() : ""); @@ -822,12 +823,10 @@ UniValue delegatorremove(const JSONRPCRequest& request) HelpExampleCli("delegatorremove", "DMJRSsuU9zfyrvxVaAEFQqK4MxZg6vgeS6") + HelpExampleRpc("delegatorremove", "\"DMJRSsuU9zfyrvxVaAEFQqK4MxZg6vgeS6\"")); - Standard::DecodeOptions options; - options.isStaking = false; - options.isShielded = false; - options.isExchange = false; - CTxDestination dest = DecodeDestination(request.params[0].get_str(), options.isStaking; - if (!IsValidDestination(dest) || options.isStaking) + bool isStaking = false; + bool isExchange = false; + CTxDestination dest = DecodeDestination(request.params[0].get_str(), isStaking, isExchange); + if (!IsValidDestination(dest) || isStaking) throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid PIVX address"); const CKeyID* keyID = boost::get(&dest); @@ -1042,7 +1041,11 @@ UniValue setlabel(const JSONRPCRequest& request) LOCK2(cs_main, pwallet->cs_wallet); - const CWDestination& dest = Standard::DecodeDestination(request.params[0].get_str()); + Standard::DecodeOptions options; + options.isStaking = false; + options.isShielded = false; + options.isExchange = false; + const CWDestination& dest = Standard::DecodeDestination(request.params[0].get_str(), options); // Make sure the destination is valid if (!Standard::IsValidDestination(dest)) { throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid address"); @@ -1191,7 +1194,7 @@ UniValue sendtoaddress(const JSONRPCRequest& request) options.isExchange = false; const std::string addrStr = request.params[0].get_str(); const CWDestination& destination = Standard::DecodeDestination(addrStr, options); - if (!Standard::IsValidDestination(destination) || isStaking) + if (!Standard::IsValidDestination(destination) || options.isStaking) throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid PIVX address"); const std::string commentStr = (request.params.size() > 2 && !request.params[2].isNull()) ? request.params[2].get_str() : ""; @@ -1199,7 +1202,7 @@ UniValue sendtoaddress(const JSONRPCRequest& request) request.params[3].get_str() : ""; bool fSubtractFeeFromAmount = request.params.size() > 4 && request.params[4].get_bool(); - if (isShielded) { + if (options.isShielded) { UniValue sendTo(UniValue::VOBJ); sendTo.pushKV(addrStr, request.params[1]); UniValue subtractFeeFromAmount(UniValue::VARR); @@ -1244,12 +1247,9 @@ static UniValue CreateColdStakeDelegation(CWallet* const pwallet, const UniValue } // Get Staking Address - Standard::DecodeOptions options; - options.isStaking = false; - options.isShielded = false; - options.isExchange = false; - CTxDestination stakeAddr = DecodeDestination(params[0].get_str(), options); - if (!IsValidDestination(stakeAddr) || !options.isStaking) + bool isStaking{false}; + CTxDestination stakeAddr = DecodeDestination(params[0].get_str()); + if (!IsValidDestination(stakeAddr) || isStaking) throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid PIVX staking address"); CKeyID* stakeKey = boost::get(&stakeAddr); @@ -1277,10 +1277,12 @@ static UniValue CreateColdStakeDelegation(CWallet* const pwallet, const UniValue // Get Owner Address std::string ownerAddressStr; CKeyID ownerKey; + bool isStakingAddress = false; + bool isExchange = false; if (params.size() > 2 && !params[2].isNull() && !params[2].get_str().empty()) { // Address provided - CTxDestination dest = DecodeDestination(params[2].get_str(), options.isStaking); - if (!IsValidDestination(dest) || options.isStaking) + CTxDestination dest = DecodeDestination(params[2].get_str(), isStakingAddress, isExchange); + if (!IsValidDestination(dest) || isStakingAddress) throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid PIVX spending address"); ownerKey = *boost::get(&dest); // Check that the owner address belongs to this wallet, or fForceExternalAddr is true @@ -2367,12 +2369,10 @@ static UniValue legacy_sendmany(CWallet* const pwallet, const UniValue& sendTo, CAmount totalAmount = 0; std::vector keys = sendTo.getKeys(); for (const std::string& name_ : keys) { - Standard::DecodeOptions options; - options.isStaking = false; - options.isShielded = false; - options.isExchange = false; - CTxDestination dest = DecodeDestination(name_, options); - if (!IsValidDestination(dest) || options.isStaking) + bool isStaking = false; + bool isExchange = false; + CTxDestination dest = DecodeDestination(name_, isStaking, isExchange); + if (!IsValidDestination(dest) || isStaking) throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, std::string("Invalid PIVX address: ")+name_); if (setAddress.count(dest)) @@ -2509,7 +2509,7 @@ UniValue sendmany(const JSONRPCRequest& request) options.isShielded = false; options.isExchange = false; Standard::DecodeDestination(key, options); - if (isShielded) { + if (options.isShielded) { fShieldSend = true; break; } From 770418f53ce3ce11da00e06a8c0adaa8fb4834e7 Mon Sep 17 00:00:00 2001 From: Liquid Date: Mon, 8 Jan 2024 13:28:18 -0600 Subject: [PATCH 03/18] Add tests --- src/test/base58_tests.cpp | 4 ++++ src/test/data/script_tests.json | 2 -- src/test/script_standard_tests.cpp | 30 ++++++++++++++++++++++++ test/functional/test_framework/script.py | 1 + 4 files changed, 35 insertions(+), 2 deletions(-) diff --git a/src/test/base58_tests.cpp b/src/test/base58_tests.cpp index 31bcab0a6244e..631032e09460d 100644 --- a/src/test/base58_tests.cpp +++ b/src/test/base58_tests.cpp @@ -96,6 +96,10 @@ class TestAddrTypeVisitor : public boost::static_visitor { return (exp_addrType == "pubkey"); } + bool operator()(const CExchangeKeyID &id) const + { + return (exp_addrType == "exchangepubkey"); + } bool operator()(const CScriptID &id) const { return (exp_addrType == "script"); diff --git a/src/test/data/script_tests.json b/src/test/data/script_tests.json index 846980c4739f6..1b984c350a79a 100644 --- a/src/test/data/script_tests.json +++ b/src/test/data/script_tests.json @@ -276,7 +276,6 @@ ["0", "IF 0xdd ELSE 1 ENDIF", "P2SH,STRICTENC", "OK"], ["0", "IF 0xde ELSE 1 ENDIF", "P2SH,STRICTENC", "OK"], ["0", "IF 0xdf ELSE 1 ENDIF", "P2SH,STRICTENC", "OK"], -["0", "IF 0xe0 ELSE 1 ENDIF", "P2SH,STRICTENC", "OK"], ["0", "IF 0xe1 ELSE 1 ENDIF", "P2SH,STRICTENC", "OK"], ["0", "IF 0xe2 ELSE 1 ENDIF", "P2SH,STRICTENC", "OK"], ["0", "IF 0xe3 ELSE 1 ENDIF", "P2SH,STRICTENC", "OK"], @@ -910,7 +909,6 @@ ["1", "IF 0xdd ELSE 1 ENDIF", "P2SH,STRICTENC", "BAD_OPCODE"], ["1", "IF 0xde ELSE 1 ENDIF", "P2SH,STRICTENC", "BAD_OPCODE"], ["1", "IF 0xdf ELSE 1 ENDIF", "P2SH,STRICTENC", "BAD_OPCODE"], -["1", "IF 0xe0 ELSE 1 ENDIF", "P2SH,STRICTENC", "BAD_OPCODE"], ["1", "IF 0xe1 ELSE 1 ENDIF", "P2SH,STRICTENC", "BAD_OPCODE"], ["1", "IF 0xe2 ELSE 1 ENDIF", "P2SH,STRICTENC", "BAD_OPCODE"], ["1", "IF 0xe3 ELSE 1 ENDIF", "P2SH,STRICTENC", "BAD_OPCODE"], diff --git a/src/test/script_standard_tests.cpp b/src/test/script_standard_tests.cpp index a25fe99908daf..2875242a423d2 100644 --- a/src/test/script_standard_tests.cpp +++ b/src/test/script_standard_tests.cpp @@ -43,6 +43,14 @@ BOOST_AUTO_TEST_CASE(script_standard_Solver_success) BOOST_CHECK_EQUAL(solutions.size(), 1); BOOST_CHECK(solutions[0] == ToByteVector(pubkeys[0].GetID())); + // TX_EXCHANGEADDR + s.clear(); + s << OP_EXCHANGEADDR << OP_DUP << OP_HASH160 << ToByteVector(pubkeys[0].GetID()) << OP_EQUALVERIFY << OP_CHECKSIG; + BOOST_CHECK(Solver(s, whichType, solutions)); + BOOST_CHECK_EQUAL(whichType, TX_EXCHANGEADDR); + BOOST_CHECK_EQUAL(solutions.size(), 1); + BOOST_CHECK(solutions[0] == ToByteVector(pubkeys[0].GetID())); + // TX_SCRIPTHASH CScript redeemScript(s); // initialize with leftover P2PKH script s.clear(); @@ -119,6 +127,11 @@ BOOST_AUTO_TEST_CASE(script_standard_Solver_failure) s << OP_DUP << OP_HASH160 << ToByteVector(pubkey) << OP_EQUALVERIFY << OP_CHECKSIG; BOOST_CHECK(!Solver(s, whichType, solutions)); + // TX_EXCHANGEADDR with incorrectly sized key hash + s.clear(); + s << OP_EXCHANGEADDR << OP_DUP << OP_HASH160 << ToByteVector(pubkey) << OP_EQUALVERIFY << OP_CHECKSIG; + BOOST_CHECK(!Solver(s, whichType, solutions)); + // TX_SCRIPTHASH with incorrectly sized script hash s.clear(); s << OP_HASH160 << std::vector(21, 0x01) << OP_EQUAL; @@ -179,6 +192,13 @@ BOOST_AUTO_TEST_CASE(script_standard_ExtractDestination) BOOST_CHECK(boost::get(&address) && *boost::get(&address) == pubkey.GetID()); + // TX_EXCHANGEADDR + s.clear(); + s << OP_EXCHANGEADDR << OP_DUP << OP_HASH160 << ToByteVector(pubkey.GetID()) << OP_EQUALVERIFY << OP_CHECKSIG; + BOOST_CHECK(ExtractDestination(s, address)); + BOOST_CHECK(boost::get(&address) && + *boost::get(&address) == pubkey.GetID()); + // TX_SCRIPTHASH CScript redeemScript(s); // initialize with leftover P2PKH script s.clear(); @@ -232,6 +252,16 @@ BOOST_AUTO_TEST_CASE(script_standard_ExtractDestinations) BOOST_CHECK(boost::get(&addresses[0]) && *boost::get(&addresses[0]) == pubkeys[0].GetID()); + // TX_EXCHANGEADDR + s.clear(); + s << OP_EXCHANGEADDR << OP_DUP << OP_HASH160 << ToByteVector(pubkeys[0].GetID()) << OP_EQUALVERIFY << OP_CHECKSIG; + BOOST_CHECK(ExtractDestinations(s, whichType, addresses, nRequired)); + BOOST_CHECK_EQUAL(whichType, TX_EXCHANGEADDR); + BOOST_CHECK_EQUAL(addresses.size(), 1); + BOOST_CHECK_EQUAL(nRequired, 1); + BOOST_CHECK(boost::get(&addresses[0]) && + *boost::get(&addresses[0]) == pubkeys[0].GetID()); + // TX_SCRIPTHASH CScript redeemScript(s); // initialize with leftover P2PKH script s.clear(); diff --git a/test/functional/test_framework/script.py b/test/functional/test_framework/script.py index 8935e99c6867a..3459541a4d923 100644 --- a/test/functional/test_framework/script.py +++ b/test/functional/test_framework/script.py @@ -229,6 +229,7 @@ def __new__(cls, n): OP_SMALLINTEGER = CScriptOp(0xfa) OP_PUBKEYS = CScriptOp(0xfb) OP_PUBKEYHASH = CScriptOp(0xfd) +OP_EXCHANGEADDR = CScriptOp(0xe0) OP_PUBKEY = CScriptOp(0xfe) OP_INVALIDOPCODE = CScriptOp(0xff) From fdad4b23a0c25cb063042b0b0126e6895924063b Mon Sep 17 00:00:00 2001 From: Liquid Date: Thu, 18 Jan 2024 18:39:25 -0600 Subject: [PATCH 04/18] Review adjustments Fix review changes minus tests Remove other struct Missed fixes Fix redefinition Remove struct Adjust decode length --- src/chainparams.cpp | 8 ++--- src/consensus/tx_verify.cpp | 15 ++------ src/destination_io.cpp | 20 ++++++----- src/destination_io.h | 10 ++---- src/key_io.cpp | 2 +- src/primitives/transaction.cpp | 7 +--- src/qt/pivx/addresseswidget.cpp | 9 ++--- src/qt/pivx/send.cpp | 9 ++--- src/qt/pivx/sendchangeaddressdialog.cpp | 11 +++--- src/qt/walletmodel.cpp | 47 +++++++++---------------- src/rpc/rpcevo.cpp | 22 +++++------- src/script/interpreter.cpp | 7 ---- src/script/script.h | 2 +- src/script/standard.cpp | 1 + src/wallet/rpcwallet.cpp | 36 +++++++------------ 15 files changed, 70 insertions(+), 136 deletions(-) diff --git a/src/chainparams.cpp b/src/chainparams.cpp index 356f92b013ac0..153cdc96d52be 100644 --- a/src/chainparams.cpp +++ b/src/chainparams.cpp @@ -246,7 +246,7 @@ class CMainParams : public CChainParams consensus.nTimeSlotLength = 15; consensus.nMaxProposalPayments = 6; - consensus.nExchangeAddrStart = 4204000; + consensus.nExchangeAddrStart = INT_MAX; // spork keys consensus.strSporkPubKey = "0410050aa740d280b134b40b40658781fc1116ba7700764e0ce27af3e1737586b3257d19232e0cb5084947f5107e44bcd577f126c9eb4a30ea2807b271d2145298"; @@ -330,7 +330,7 @@ class CMainParams : public CChainParams base58Prefixes[PUBKEY_ADDRESS] = std::vector(1, 30); base58Prefixes[SCRIPT_ADDRESS] = std::vector(1, 13); base58Prefixes[STAKING_ADDRESS] = std::vector(1, 63); // starting with 'S' - base58Prefixes[EXCHANGE_ADDRESS] = {0x01, 0xb9, 0xa2}; // starts with EXM + base58Prefixes[EXCHANGE_ADDRESS] = {0x01, 0xb9}; // starts with EX base58Prefixes[SECRET_KEY] = std::vector(1, 212); base58Prefixes[EXT_PUBLIC_KEY] = {0x02, 0x2D, 0x25, 0x33}; base58Prefixes[EXT_SECRET_KEY] = {0x02, 0x21, 0x31, 0x2B}; @@ -411,7 +411,7 @@ class CTestNetParams : public CChainParams consensus.nTimeSlotLength = 15; consensus.nMaxProposalPayments = 20; - consensus.nExchangeAddrStart = 1559000; + consensus.nExchangeAddrStart = INT_MAX; // spork keys consensus.strSporkPubKey = "04677c34726c491117265f4b1c83cef085684f36c8df5a97a3a42fc499316d0c4e63959c9eca0dba239d9aaaf72011afffeb3ef9f51b9017811dec686e412eb504"; @@ -626,7 +626,7 @@ class CRegTestParams : public CChainParams base58Prefixes[PUBKEY_ADDRESS] = std::vector(1, 139); // Testnet pivx addresses start with 'x' or 'y' base58Prefixes[SCRIPT_ADDRESS] = std::vector(1, 19); // Testnet pivx script addresses start with '8' or '9' base58Prefixes[STAKING_ADDRESS] = std::vector(1, 73); // starting with 'W' - base58Prefixes[EXCHANGE_ADDRESS] = {0x01, 0xb9, 0xac}; // EXR prefix for the address + base58Prefixes[EXCHANGE_ADDRESS] = {0x01, 0xb9, 0xb1}; // EXT prefix for the address base58Prefixes[SECRET_KEY] = std::vector(1, 239); // Testnet private keys start with '9' or 'c' (Bitcoin defaults) // Testnet pivx BIP32 pubkeys start with 'DRKV' base58Prefixes[EXT_PUBLIC_KEY] = {0x3a, 0x80, 0x61, 0xa0}; diff --git a/src/consensus/tx_verify.cpp b/src/consensus/tx_verify.cpp index b4677f44507ae..74775cead4198 100644 --- a/src/consensus/tx_verify.cpp +++ b/src/consensus/tx_verify.cpp @@ -115,20 +115,9 @@ bool CheckTransaction(const CTransaction& tx, CValidationState& state, bool fCol } } - // input scripts cannot have OP_EXCHANGEADDR at all - for (const auto &vin: tx.vin) { - if (vin.scriptSig.size() >= 1 && vin.scriptSig[0] == OP_EXCHANGEADDR) { - return state.DoS(100, false, REJECT_INVALID, "bad-exchange-address-vin"); - } - } - - bool hasExchangeUTXOs = false; - if (tx.HasExchangeAddr()) { - hasExchangeUTXOs = true; - } - bool isIDB = IsInitialBlockDownload(); + bool hasExchangeUTXOs = tx.HasExchangeAddr(); int nTxHeight = chainActive.Height(); - if (hasExchangeUTXOs && !isIDB && nTxHeight < ::Params().GetConsensus().nExchangeAddrStart) + if (hasExchangeUTXOs && nTxHeight < ::Params().GetConsensus().nExchangeAddrStart) return state.DoS(100, false, REJECT_INVALID, "bad-exchange-address-not-started"); if (tx.IsCoinBase()) { diff --git a/src/destination_io.cpp b/src/destination_io.cpp index 0b30293ab2128..6ce6eb07312ba 100644 --- a/src/destination_io.cpp +++ b/src/destination_io.cpp @@ -17,22 +17,26 @@ namespace Standard { CWDestination DecodeDestination(const std::string& strAddress) { - Standard::DecodeOptions options; - options.isStaking = false; - options.isShielded = false; - options.isExchange = false; - return DecodeDestination(strAddress, options); + bool isStaking = false; + bool isExchange = false; + return DecodeDestination(strAddress, isStaking, isExchange); + } + + CWDestination DecodeDestination(const std::string& strAddress, bool& isStaking, bool& isExchange) + { + bool isShielded = false; + return DecodeDestination(strAddress, isStaking, isExchange, isShielded); } // agregar isShielded - CWDestination DecodeDestination(const std::string& strAddress, DecodeOptions options) + CWDestination DecodeDestination(const std::string& strAddress, bool& isStaking, bool& isExchange, bool& isShielded) { CWDestination dest; - CTxDestination regDest = ::DecodeDestination(strAddress, options.isStaking, options.isExchange); + CTxDestination regDest = ::DecodeDestination(strAddress, isStaking, isExchange); if (!IsValidDestination(regDest)) { const auto sapDest = KeyIO::DecodeSaplingPaymentAddress(strAddress); if (sapDest) { - options.isShielded = true; + isShielded = true; return *sapDest; } } diff --git a/src/destination_io.h b/src/destination_io.h index 44fb9c38290c9..96b185e6c5bf9 100644 --- a/src/destination_io.h +++ b/src/destination_io.h @@ -12,17 +12,11 @@ typedef boost::variant CWDestination; namespace Standard { - - struct DecodeOptions { - bool isStaking = false; - bool isShielded = false; - bool isExchange = false; - }; - std::string EncodeDestination(const CWDestination &address, const CChainParams::Base58Type addrType = CChainParams::PUBKEY_ADDRESS); CWDestination DecodeDestination(const std::string& strAddress); - CWDestination DecodeDestination(const std::string& strAddress, DecodeOptions options); + CWDestination DecodeDestination(const std::string& strAddress, bool& isStaking, bool& isExchange); + CWDestination DecodeDestination(const std::string& strAddress, bool& isStaking, bool& isExchange, bool& isShielded); bool IsValidDestination(const CWDestination& dest); diff --git a/src/key_io.cpp b/src/key_io.cpp index a7a2adce49688..58e589c2dde0d 100644 --- a/src/key_io.cpp +++ b/src/key_io.cpp @@ -52,7 +52,7 @@ namespace { std::vector data; uint160 hash; - if (DecodeBase58Check(str, data, 21)) { + if (DecodeBase58Check(str, data, 23)) { // base58-encoded PIVX addresses. // Public-key-hash-addresses have version 30 (or 139 testnet). // The data vector contains RIPEMD160(SHA256(pubkey)), where pubkey is the serialized public key. diff --git a/src/primitives/transaction.cpp b/src/primitives/transaction.cpp index 76a1620863e9b..96e73f61e8281 100644 --- a/src/primitives/transaction.cpp +++ b/src/primitives/transaction.cpp @@ -145,12 +145,7 @@ bool CTransaction::IsCoinStake() const bool CTransaction::HasExchangeAddr() const { - for(const CTxOut& txout : vout) { - if (txout.scriptPubKey.size() >= 1 && txout.scriptPubKey.IsPayToExchangeAddress()) { - return true; - } - } - return false; + return std::any_of(vout.begin(), vout.end(), [](const auto& txout) { return txout.scriptPubKey.IsPayToExchangeAddress(); }); } bool CTransaction::HasP2CSOutputs() const diff --git a/src/qt/pivx/addresseswidget.cpp b/src/qt/pivx/addresseswidget.cpp index a7505befacf37..6057302cff5e9 100644 --- a/src/qt/pivx/addresseswidget.cpp +++ b/src/qt/pivx/addresseswidget.cpp @@ -184,11 +184,8 @@ void AddressesWidget::onStoreContactClicked() QString label = ui->lineEditName->text(); QString address = ui->lineEditAddress->text(); - Standard::DecodeOptions options; - options.isStaking = false; - options.isShielded = false; - options.isExchange = false; - auto pivAdd = Standard::DecodeDestination(address.toUtf8().constData(), options); + bool isStaking, isExchange, isShield = false; + auto pivAdd = Standard::DecodeDestination(address.toUtf8().constData(), isStaking, isExchange, isShield); if (!Standard::IsValidDestination(pivAdd)) { setCssEditLine(ui->lineEditAddress, false, true); @@ -212,7 +209,7 @@ void AddressesWidget::onStoreContactClicked() bool isShielded = walletModel->IsShieldedDestination(pivAdd); if (walletModel->updateAddressBookLabels(pivAdd, label.toUtf8().constData(), isShielded ? AddressBook::AddressBookPurpose::SHIELDED_SEND : - options.isStaking ? AddressBook::AddressBookPurpose::COLD_STAKING_SEND : AddressBook::AddressBookPurpose::SEND) + isStaking ? AddressBook::AddressBookPurpose::COLD_STAKING_SEND : AddressBook::AddressBookPurpose::SEND) ) { ui->lineEditAddress->setText(""); ui->lineEditName->setText(""); diff --git a/src/qt/pivx/send.cpp b/src/qt/pivx/send.cpp index 6d2f8aea9cb9f..ed13d6a053170 100644 --- a/src/qt/pivx/send.cpp +++ b/src/qt/pivx/send.cpp @@ -906,13 +906,10 @@ void SendWidget::onContactMultiClicked() return; } - Standard::DecodeOptions options; - options.isStaking = false; - options.isShielded = false; - options.isExchange = false; - auto pivAdd = Standard::DecodeDestination(address.toStdString(), options); + bool isStaking, isExchange, isShielded = false; + auto pivAdd = Standard::DecodeDestination(address.toStdString(), isStaking, isExchange, isShielded); - if (!Standard::IsValidDestination(pivAdd) || options.isStaking) { + if (!Standard::IsValidDestination(pivAdd) || isStaking) { inform(tr("Invalid address")); return; } diff --git a/src/qt/pivx/sendchangeaddressdialog.cpp b/src/qt/pivx/sendchangeaddressdialog.cpp index 7d7602988e76b..7225e159e3abb 100644 --- a/src/qt/pivx/sendchangeaddressdialog.cpp +++ b/src/qt/pivx/sendchangeaddressdialog.cpp @@ -73,17 +73,14 @@ void SendChangeAddressDialog::accept() QDialog::accept(); } else { // validate address - Standard::DecodeOptions options; - options.isStaking = false; - options.isShielded = false; - options.isExchange = false; - dest = Standard::DecodeDestination(ui->lineEditAddress->text().toStdString(), options); + bool isStaking, isExchange, isShielded = false; + dest = Standard::DecodeDestination(ui->lineEditAddress->text().toStdString(), isStaking, isExchange, isShielded); if (!Standard::IsValidDestination(dest)) { inform(tr("Invalid address")); - } else if (options.isStaking) { + } else if (isStaking) { inform(tr("Cannot use cold staking addresses for change")); - } else if (!options.isShielded && !isTransparent) { + } else if (!isShielded && !isTransparent) { inform(tr("Cannot use a transparent change for a shield transaction")); } else { QDialog::accept(); diff --git a/src/qt/walletmodel.cpp b/src/qt/walletmodel.cpp index e4f020f5dfa02..635685fb98637 100644 --- a/src/qt/walletmodel.cpp +++ b/src/qt/walletmodel.cpp @@ -398,14 +398,11 @@ void WalletModel::updateWatchOnlyFlag(bool fHaveWatchonly) bool WalletModel::validateAddress(const QString& address) { - // Only regular base58 addresses, shielded addresses and exchange addresses accepted here - Standard::DecodeOptions options; - options.isStaking = false; - options.isShielded = false; - options.isExchange = false; - CWDestination dest = Standard::DecodeDestination(address.toStdString(), options); + // Only regular base58 addresses and shielded addresses accepted here + bool isStaking, isExchange = false; + CWDestination dest = Standard::DecodeDestination(address.toStdString(), isStaking, isExchange); const auto regDest = boost::get(&dest); - if (regDest && IsValidDestination(*regDest) && options.isStaking) return false; + if (regDest && IsValidDestination(*regDest) && isStaking) return false; return Standard::IsValidDestination(dest); } @@ -416,16 +413,13 @@ bool WalletModel::validateAddress(const QString& address, bool fStaking) bool WalletModel::validateAddress(const QString& address, bool fStaking, bool& isShielded) { - Standard::DecodeOptions options; - options.isStaking = false; - options.isShielded = isShielded; - options.isExchange = false; - CWDestination dest = Standard::DecodeDestination(address.toStdString(), options); + bool isStaking, isExchange = false; + CWDestination dest = Standard::DecodeDestination(address.toStdString(), isStaking, isExchange); if (IsShieldedDestination(dest)) { isShielded = true; return true; } - return Standard::IsValidDestination(dest) && (options.isStaking == fStaking); + return Standard::IsValidDestination(dest) && (isStaking == fStaking); } bool WalletModel::updateAddressBookLabels(const CWDestination& dest, const std::string& strName, const std::string& strPurpose) @@ -602,13 +596,10 @@ WalletModel::SendCoinsReturn WalletModel::sendCoins(WalletModelTransaction& tran for (const SendCoinsRecipient& rcp : transaction.getRecipients()) { // Don't touch the address book when we have a payment request { - Standard::DecodeOptions options; - options.isStaking = false; - options.isShielded = false; - options.isExchange = false; - auto address = Standard::DecodeDestination(rcp.address.toStdString(), options); - std::string purpose = options.isShielded ? AddressBook::AddressBookPurpose::SHIELDED_SEND : - options.isStaking ? AddressBook::AddressBookPurpose::COLD_STAKING_SEND : AddressBook::AddressBookPurpose::SEND; + bool isStaking, isExchange, isShielded = false; + auto address = Standard::DecodeDestination(rcp.address.toStdString(), isStaking, isExchange, isShielded); + std::string purpose = isShielded ? AddressBook::AddressBookPurpose::SHIELDED_SEND : + isStaking ? AddressBook::AddressBookPurpose::COLD_STAKING_SEND : AddressBook::AddressBookPurpose::SEND; std::string strLabel = rcp.label.toStdString(); updateAddressBookLabels(address, strLabel, purpose); } @@ -951,11 +942,8 @@ int64_t WalletModel::getKeyCreationTime(const CTxDestination& address) int64_t WalletModel::getKeyCreationTime(const std::string& address) { - Standard::DecodeOptions options; - options.isStaking = false; - options.isShielded = false; - options.isExchange = false; - return wallet->GetKeyCreationTime(Standard::DecodeDestination(address, options)); + bool isStaking, isExchange, isShielded = false; + return wallet->GetKeyCreationTime(Standard::DecodeDestination(address, isStaking, isExchange, isShielded)); } int64_t WalletModel::getKeyCreationTime(const libzcash::SaplingPaymentAddress& address) @@ -997,12 +985,9 @@ bool WalletModel::blacklistAddressFromColdStaking(const QString &addressStr) bool WalletModel::updateAddressBookPurpose(const QString &addressStr, const std::string& purpose) { - Standard::DecodeOptions options; - options.isStaking = false; - options.isShielded = false; - options.isExchange = false; - CTxDestination address = DecodeDestination(addressStr.toStdString(), options.isStaking, options.isExchange); - if (options.isStaking) + bool isStaking, isExchange = false; + CTxDestination address = DecodeDestination(addressStr.toStdString(), isStaking, isExchange); + if (isStaking) return error("Invalid PIVX address, cold staking address"); CKeyID keyID; if (!getKeyId(address, keyID)) diff --git a/src/rpc/rpcevo.cpp b/src/rpc/rpcevo.cpp index c98d4475da0cb..7676ffee93c0e 100644 --- a/src/rpc/rpcevo.cpp +++ b/src/rpc/rpcevo.cpp @@ -168,15 +168,12 @@ static void CheckEvoUpgradeEnforcement() // Allows to specify PIVX address or priv key (as strings). In case of PIVX address, the priv key is taken from the wallet static CKey ParsePrivKey(CWallet* pwallet, const std::string &strKeyOrAddress, bool allowAddresses = true) { - Standard::DecodeOptions options; - options.isStaking = false; - options.isShielded = false; - options.isExchange = false; - const CWDestination& cwdest = Standard::DecodeDestination(strKeyOrAddress, options); - if (options.isStaking) { + bool isStaking{false}, isShield{false}, isExchange{false}; + const CWDestination& cwdest = Standard::DecodeDestination(strKeyOrAddress, isStaking, isExchange, isShield); + if (isStaking) { throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "cold staking addresses not supported"); } - if (options.isShielded) { + if (isShield) { throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "shield addresses not supported"); } const CTxDestination* dest = Standard::GetTransparentDestination(cwdest); @@ -201,15 +198,12 @@ static CKey ParsePrivKey(CWallet* pwallet, const std::string &strKeyOrAddress, b static CKeyID ParsePubKeyIDFromAddress(const std::string& strAddress) { - Standard::DecodeOptions options; - options.isStaking = false; - options.isShielded = false; - options.isExchange = false; - const CWDestination& cwdest = Standard::DecodeDestination(strAddress, options); - if (options.isStaking) { + bool isStaking{false}, isShield{false}, isExchange{false}; + const CWDestination& cwdest = Standard::DecodeDestination(strAddress, isStaking, isExchange, isShield); + if (isStaking) { throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "cold staking addresses not supported"); } - if (options.isShielded) { + if (isShield) { throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "shield addresses not supported"); } const CKeyID* keyID = boost::get(Standard::GetTransparentDestination(cwdest)); diff --git a/src/script/interpreter.cpp b/src/script/interpreter.cpp index 8f6e06e2c45ef..a54135bbbb63d 100644 --- a/src/script/interpreter.cpp +++ b/src/script/interpreter.cpp @@ -256,7 +256,6 @@ bool EvalScript(std::vector >& stack, const CScript& try { - bool fExchangeAddr = true; while (pc < pend) { bool fExec = !count(vfExec.begin(), vfExec.end(), false); @@ -334,11 +333,6 @@ bool EvalScript(std::vector >& stack, const CScript& case OP_NOP: break; - case OP_EXCHANGEADDR: - if (!fExchangeAddr) - return set_error(serror, SCRIPT_ERR_BAD_OPCODE); - break; - case OP_CHECKLOCKTIMEVERIFY: { if (!(flags & SCRIPT_VERIFY_CHECKLOCKTIMEVERIFY)) { @@ -988,7 +982,6 @@ bool EvalScript(std::vector >& stack, const CScript& if (stack.size() + altstack.size() > 1000) return set_error(serror, SCRIPT_ERR_STACK_SIZE); - fExchangeAddr = false; } } catch (...) diff --git a/src/script/script.h b/src/script/script.h index 894d03b57f4db..35d4c855be6b2 100644 --- a/src/script/script.h +++ b/src/script/script.h @@ -187,7 +187,7 @@ enum opcodetype OP_CHECKCOLDSTAKEVERIFY_LOF = 0xd1, // last output free for masternode/budget payments OP_CHECKCOLDSTAKEVERIFY = 0xd2, - // exchange address, NOP but identifies as a address not allowing private outputs + // exchange address, NOP but identifies as an address not allowing private outputs OP_EXCHANGEADDR = 0xe0, OP_INVALIDOPCODE = 0xff, diff --git a/src/script/standard.cpp b/src/script/standard.cpp index 9079382b5b4d3..559ca76009646 100644 --- a/src/script/standard.cpp +++ b/src/script/standard.cpp @@ -177,6 +177,7 @@ bool ExtractDestination(const CScript& scriptPubKey, CTxDestination& addressRet, } else if (whichType == TX_PUBKEYHASH) { addressRet = CKeyID(uint160(vSolutions[0])); return true; + } else if (whichType == TX_SCRIPTHASH) { addressRet = CScriptID(uint160(vSolutions[0])); return true; diff --git a/src/wallet/rpcwallet.cpp b/src/wallet/rpcwallet.cpp index 4e270e63c0798..d4718b013dc10 100644 --- a/src/wallet/rpcwallet.cpp +++ b/src/wallet/rpcwallet.cpp @@ -191,11 +191,8 @@ UniValue getaddressinfo(const JSONRPCRequest& request) UniValue ret(UniValue::VOBJ); ret.pushKV("address", strAdd); - Standard::DecodeOptions options; - options.isStaking = false; - options.isShielded = false; - options.isExchange = false; - const CWDestination& dest = Standard::DecodeDestination(strAdd, options); + bool isStaking, isExchange, isShielded = false; + const CWDestination& dest = Standard::DecodeDestination(strAdd, isStaking, isExchange, isShielded); // Make sure the destination is valid if (!Standard::IsValidDestination(dest)) { throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid address"); @@ -1041,11 +1038,8 @@ UniValue setlabel(const JSONRPCRequest& request) LOCK2(cs_main, pwallet->cs_wallet); - Standard::DecodeOptions options; - options.isStaking = false; - options.isShielded = false; - options.isExchange = false; - const CWDestination& dest = Standard::DecodeDestination(request.params[0].get_str(), options); + bool isStaking, isExchange, isShielded = false; + const CWDestination& dest = Standard::DecodeDestination(request.params[0].get_str(), isStaking, isExchange, isShielded); // Make sure the destination is valid if (!Standard::IsValidDestination(dest)) { throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid address"); @@ -1188,13 +1182,10 @@ UniValue sendtoaddress(const JSONRPCRequest& request) // the user could have gotten from another RPC command prior to now pwallet->BlockUntilSyncedToCurrentChain(); - Standard::DecodeOptions options; - options.isStaking = false; - options.isShielded = false; - options.isExchange = false; + bool isStaking, isExchange, isShielded = false; const std::string addrStr = request.params[0].get_str(); - const CWDestination& destination = Standard::DecodeDestination(addrStr, options); - if (!Standard::IsValidDestination(destination) || options.isStaking) + const CWDestination& destination = Standard::DecodeDestination(addrStr, isStaking, isExchange, isShielded); + if (!Standard::IsValidDestination(destination) || isStaking) throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid PIVX address"); const std::string commentStr = (request.params.size() > 2 && !request.params[2].isNull()) ? request.params[2].get_str() : ""; @@ -1202,7 +1193,7 @@ UniValue sendtoaddress(const JSONRPCRequest& request) request.params[3].get_str() : ""; bool fSubtractFeeFromAmount = request.params.size() > 4 && request.params[4].get_bool(); - if (options.isShielded) { + if (isShielded) { UniValue sendTo(UniValue::VOBJ); sendTo.pushKV(addrStr, request.params[1]); UniValue subtractFeeFromAmount(UniValue::VARR); @@ -2504,12 +2495,9 @@ UniValue sendmany(const JSONRPCRequest& request) // Check if any recipient address is shield bool fShieldSend = false; for (const std::string& key : sendTo.getKeys()) { - Standard::DecodeOptions options; - options.isStaking = false; - options.isShielded = false; - options.isExchange = false; - Standard::DecodeDestination(key, options); - if (options.isShielded) { + bool isStaking, isExchange, isShielded = false; + Standard::DecodeDestination(key, isStaking, isExchange, isShielded); + if (isShielded) { fShieldSend = true; break; } @@ -4798,7 +4786,7 @@ static const CRPCCommand commands[] = { "wallet", "upgradewallet", &upgradewallet, true, {} }, { "wallet", "sethdseed", &sethdseed, true, {"newkeypool","seed"} }, { "wallet", "getnewaddress", &getnewaddress, true, {"label"} }, - { "hidden", "getnewexchangeaddress", &getnewexchangeaddress, true, {"label"} }, + { "wallet", "getnewexchangeaddress", &getnewexchangeaddress, true, {"label"} }, { "wallet", "getnewstakingaddress", &getnewstakingaddress, true, {"label"} }, { "wallet", "getrawchangeaddress", &getrawchangeaddress, true, {} }, { "wallet", "getreceivedbyaddress", &getreceivedbyaddress, false, {"address","minconf"} }, From c74d76ec0d6c5f8d4b0941f44ae66b9cd29487de Mon Sep 17 00:00:00 2001 From: Liquid Date: Thu, 18 Jan 2024 18:52:52 -0600 Subject: [PATCH 05/18] Functional test lint Lint Lint --- test/functional/feature_exchangeaddr.py | 88 +++++++++++++++++++++++++ test/functional/test_runner.py | 1 + 2 files changed, 89 insertions(+) create mode 100755 test/functional/feature_exchangeaddr.py diff --git a/test/functional/feature_exchangeaddr.py b/test/functional/feature_exchangeaddr.py new file mode 100755 index 0000000000000..eab71be92b5e2 --- /dev/null +++ b/test/functional/feature_exchangeaddr.py @@ -0,0 +1,88 @@ +#!/usr/bin/env python3 +# Copyright (c) 2024 The PIVX Core developers +# Distributed under the MIT software license, see the accompanying +# file COPYING or http://www.opensource.org/licenses/mit-license.php. +"""Test OP_EXCHANGEADDR. + +Test that the OP_EXCHANGEADDR soft-fork activates at (regtest) block height +1001, and that the following is capable + t > e + e > t +and not capable + t > s > e + e > s +""" + +from decimal import Decimal + +from test_framework.test_framework import PivxTestFramework +from test_framework.util import assert_equal, assert_raises_rpc_error + +class ExchangeAddrTest(PivxTestFramework): + def set_test_params(self): + self.num_nodes = 2 + self.extra_args = [['-whitelist=127.0.0.1','-regtest'], ['-regtest']] + self.setup_clean_chain = True + + def run_test(self): + # Mine and activate exchange addresses + self.nodes[0].generate(1000) + assert_equal(self.nodes[0].getblockcount(), 1000) + self.nodes[0].generate(1) + + # Get addresses for testing + ex_addr = self.nodes[1].getnewexchangeaddress() + sapling_addr = self.nodes[0].getnewshieldaddress() + t_addr = self.nodes[0].getnewaddress() + + # Attempt to send funds from transparent address to exchange address + ex_addr_validation_result = self.nodes[0].validateaddress(ex_addr) + assert_equal(ex_addr_validation_result['isvalid'], True) + self.nodes[0].sendtoaddress(ex_addr, 2.0) + self.nodes[0].generate(6) + self.sync_all() + + # Verify balance + node_bal = self.nodes[1].getbalance() + assert_equal(node_bal, 2) + + # Attempt to send funds from exchange address back to transparent address + tx2 = self.nodes[0].sendtoaddress(t_addr, 1.0) + self.nodes[0].generate(6) + self.sync_all() + ex_result = self.nodes[0].gettransaction(tx2) + + # Assert that the transaction ID in the result matches tx2 + assert_equal(ex_result['txid'], tx2) + + # Transparent to Shield to Exchange should fail + self.nodes[0].sendtoaddress(sapling_addr, 2.0) + self.nodes[0].generate(6) + sap_to_ex = [{"address": ex_addr, "amount": Decimal('1')}] + + # Expect shieldsendmany to fail with bad-txns-invalid-sapling + expected_error_code = -4 + expected_error_message = "Failed to accept tx in the memory pool (reason: bad-txns-invalid-sapling)" + assert_raises_rpc_error( + expected_error_code, + expected_error_message, + self.nodes[0].shieldsendmany, + sapling_addr, sap_to_ex, 1, 0, sapling_addr + ) + + # Generate a new shielded address + new_shielded_addr = self.nodes[1].getnewshieldaddress() + + # Attempt to send funds from exchange address to shielded address + expected_error_message = "Failed to build transaction: Failed to sign transaction" + + assert_raises_rpc_error( + expected_error_code, + expected_error_message, + self.nodes[1].sendtoaddress, + new_shielded_addr, 1.0 + ) + + +if __name__ == "__main__": + ExchangeAddrTest().main() diff --git a/test/functional/test_runner.py b/test/functional/test_runner.py index bcec57836aecb..5b8bfac082351 100755 --- a/test/functional/test_runner.py +++ b/test/functional/test_runner.py @@ -122,6 +122,7 @@ 'feature_filelock.py', # ~ 71 sec 'mempool_packages.py', # ~ 63 sec 'sapling_wallet_encryption.py', + 'feature_exchangeaddr.py', # vv Tests less than 60s vv 'rpc_users.py', From 035a4b8320c338e961c1b0fdc03b9906686bc40a Mon Sep 17 00:00:00 2001 From: Liquid Date: Thu, 25 Jan 2024 07:00:26 -0600 Subject: [PATCH 06/18] Apply patch --- src/wallet/rpcwallet.cpp | 9 ++++----- test/functional/feature_exchangeaddr.py | 1 + 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/wallet/rpcwallet.cpp b/src/wallet/rpcwallet.cpp index d4718b013dc10..c14d06046a7be 100644 --- a/src/wallet/rpcwallet.cpp +++ b/src/wallet/rpcwallet.cpp @@ -191,8 +191,7 @@ UniValue getaddressinfo(const JSONRPCRequest& request) UniValue ret(UniValue::VOBJ); ret.pushKV("address", strAdd); - bool isStaking, isExchange, isShielded = false; - const CWDestination& dest = Standard::DecodeDestination(strAdd, isStaking, isExchange, isShielded); + const CWDestination& dest = Standard::DecodeDestination(strAdd); // Make sure the destination is valid if (!Standard::IsValidDestination(dest)) { throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid address"); @@ -1038,7 +1037,7 @@ UniValue setlabel(const JSONRPCRequest& request) LOCK2(cs_main, pwallet->cs_wallet); - bool isStaking, isExchange, isShielded = false; + bool isStaking = false, isExchange = false, isShielded = false; const CWDestination& dest = Standard::DecodeDestination(request.params[0].get_str(), isStaking, isExchange, isShielded); // Make sure the destination is valid if (!Standard::IsValidDestination(dest)) { @@ -1182,7 +1181,7 @@ UniValue sendtoaddress(const JSONRPCRequest& request) // the user could have gotten from another RPC command prior to now pwallet->BlockUntilSyncedToCurrentChain(); - bool isStaking, isExchange, isShielded = false; + bool isStaking = false, isExchange = false, isShielded = false; const std::string addrStr = request.params[0].get_str(); const CWDestination& destination = Standard::DecodeDestination(addrStr, isStaking, isExchange, isShielded); if (!Standard::IsValidDestination(destination) || isStaking) @@ -2495,7 +2494,7 @@ UniValue sendmany(const JSONRPCRequest& request) // Check if any recipient address is shield bool fShieldSend = false; for (const std::string& key : sendTo.getKeys()) { - bool isStaking, isExchange, isShielded = false; + bool isStaking = false, isExchange = false, isShielded = false; Standard::DecodeDestination(key, isStaking, isExchange, isShielded); if (isShielded) { fShieldSend = true; diff --git a/test/functional/feature_exchangeaddr.py b/test/functional/feature_exchangeaddr.py index eab71be92b5e2..8113a236d3574 100755 --- a/test/functional/feature_exchangeaddr.py +++ b/test/functional/feature_exchangeaddr.py @@ -86,3 +86,4 @@ def run_test(self): if __name__ == "__main__": ExchangeAddrTest().main() + From 146b81394a1be93eb68a821bc3c90d301d747d43 Mon Sep 17 00:00:00 2001 From: Liquid Date: Thu, 25 Jan 2024 09:22:19 -0600 Subject: [PATCH 07/18] Handle legacy pre-hd wallets --- test/functional/feature_exchangeaddr.py | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/test/functional/feature_exchangeaddr.py b/test/functional/feature_exchangeaddr.py index 8113a236d3574..9f40a67e1531b 100755 --- a/test/functional/feature_exchangeaddr.py +++ b/test/functional/feature_exchangeaddr.py @@ -18,6 +18,8 @@ from test_framework.test_framework import PivxTestFramework from test_framework.util import assert_equal, assert_raises_rpc_error +FEATURE_PRE_SPLIT_KEYPOOL = 169900 + class ExchangeAddrTest(PivxTestFramework): def set_test_params(self): self.num_nodes = 2 @@ -32,7 +34,6 @@ def run_test(self): # Get addresses for testing ex_addr = self.nodes[1].getnewexchangeaddress() - sapling_addr = self.nodes[0].getnewshieldaddress() t_addr = self.nodes[0].getnewaddress() # Attempt to send funds from transparent address to exchange address @@ -56,6 +57,12 @@ def run_test(self): assert_equal(ex_result['txid'], tx2) # Transparent to Shield to Exchange should fail + # Check wallet version + wallet_info = self.nodes[0].getwalletinfo() + if wallet_info['walletversion'] < FEATURE_PRE_SPLIT_KEYPOOL: + self.log.info("Pre-HD wallet version detected. Skipping Shield tests.") + return + sapling_addr = self.nodes[0].getnewshieldaddress() self.nodes[0].sendtoaddress(sapling_addr, 2.0) self.nodes[0].generate(6) sap_to_ex = [{"address": ex_addr, "amount": Decimal('1')}] From e6a26bfde6724bc5cc0fb6ae8407836194f49d42 Mon Sep 17 00:00:00 2001 From: Liquid Date: Wed, 31 Jan 2024 16:20:53 -0600 Subject: [PATCH 08/18] Use Consensus::UPGRADE_V5_6 --- src/consensus/params.h | 2 +- src/consensus/tx_verify.cpp | 2 +- src/consensus/upgrades.cpp | 4 ++++ 3 files changed, 6 insertions(+), 2 deletions(-) diff --git a/src/consensus/params.h b/src/consensus/params.h index 7f5b9e4f4b10d..7e89ada6ae197 100644 --- a/src/consensus/params.h +++ b/src/consensus/params.h @@ -37,6 +37,7 @@ enum UpgradeIndex : uint32_t { UPGRADE_V5_2, UPGRADE_V5_3, UPGRADE_V5_5, + UPGRADE_V5_6, UPGRADE_V6_0, UPGRADE_TESTDUMMY, // NOTE: Also add new upgrades to NetworkUpgradeInfo in upgrades.cpp @@ -192,7 +193,6 @@ struct Params { int64_t nTargetSpacing; int nTimeSlotLength; int nMaxProposalPayments; - int nExchangeAddrStart; // spork keys std::string strSporkPubKey; diff --git a/src/consensus/tx_verify.cpp b/src/consensus/tx_verify.cpp index 74775cead4198..66776216ad5c4 100644 --- a/src/consensus/tx_verify.cpp +++ b/src/consensus/tx_verify.cpp @@ -117,7 +117,7 @@ bool CheckTransaction(const CTransaction& tx, CValidationState& state, bool fCol bool hasExchangeUTXOs = tx.HasExchangeAddr(); int nTxHeight = chainActive.Height(); - if (hasExchangeUTXOs && nTxHeight < ::Params().GetConsensus().nExchangeAddrStart) + if (hasExchangeUTXOs && !Params().GetConsensus().NetworkUpgradeActive(nTxHeight, Consensus::UPGRADE_V5_6)) return state.DoS(100, false, REJECT_INVALID, "bad-exchange-address-not-started"); if (tx.IsCoinBase()) { diff --git a/src/consensus/upgrades.cpp b/src/consensus/upgrades.cpp index e313d831c9478..17490d592af3b 100644 --- a/src/consensus/upgrades.cpp +++ b/src/consensus/upgrades.cpp @@ -65,6 +65,10 @@ const struct NUInfo NetworkUpgradeInfo[Consensus::MAX_NETWORK_UPGRADES] = { /*.strName =*/ "PIVX_v5.5", /*.strInfo =*/ "New rewards structure", }, + { + /*.strName =*/ "PIVX_v5.6", + /*.strInfo =*/ "Exchange address", + }, { /*.strName =*/ "v6_evo", /*.strInfo =*/ "Deterministic Masternodes", From 3c914672a3cf98306021656dd6d38a5fb4e61a73 Mon Sep 17 00:00:00 2001 From: Liquid Date: Wed, 31 Jan 2024 16:21:05 -0600 Subject: [PATCH 09/18] Add draft release notes --- doc/release-notes/release-notes-5.6.0.md | 85 ++++++++++++++++++++++++ 1 file changed, 85 insertions(+) create mode 100644 doc/release-notes/release-notes-5.6.0.md diff --git a/doc/release-notes/release-notes-5.6.0.md b/doc/release-notes/release-notes-5.6.0.md new file mode 100644 index 0000000000000..f268531f6e677 --- /dev/null +++ b/doc/release-notes/release-notes-5.6.0.md @@ -0,0 +1,85 @@ +PIVX Core version *v5.6.0* is now available from: + +This is a new major version release, including various bug fixes and performance improvements, as well as updated translations. + +Please report bugs using the issue tracker at github: + + +How to Upgrade +============== + +If you are running an older version, shut it down. Wait until it has completely shut down (which might take a few minutes for older versions), then run the installer (on Windows) or just copy over /Applications/PIVX-Qt (on Mac) or pivxd/pivx-qt (on Linux). + +Notable Changes +============== + +(Developers: add your notes here as part of your pull requests whenever possible) + + +### Deprecated autocombinerewards RPC Command + +The `autocombinerewards` RPC command was soft-deprecated in v5.3.0 and replaced with explicit setter/getter commands `setautocombinethreshold`/`getautocombinethreshold`. PIVX Core, by default, will no longer accept the `autocombinerewards` command, returning a deprecation error, unless the `pivxd`/`pivx-qt` is started with the `-deprecatedrpc=autocombinerewards` option. + +This command will be fully removed in v6.0.0. + +### Shield address support for RPC label commands + +The `setlabel` RPC command now supports a shield address input argument to allow users to set labels for shield addresses. Additionally, the `getaddressesbylabel` RPC command will also now return shield addresses with a matching label. + +### Specify optional label for getnewshieldaddress + +The `getnewshieldaddress` RPC command now takes an optional argument `label (string)` to denote the desired label for the generated address. + +### New getnewexchangeaddress RPC Command + +The `getnewexchangeaddress` RPC command has been introduced to create an essentially fully Transparent address to disallow deshielding to in compliance for exchanges. It takes an optional `label (string)` to set a label for the address if necessary. Functionality is the same comparitively to `getnewaddress`, `getnewstakingaddress` and `getnewshieldaddress` + +| Command Name | Purpose | Requires Unlocked Wallet? | +| ------------ | ------- | ------------------------- | +| `getnewexchangeaddress` | Creates a new exchange address | Yes | + +Command is detailed below: + +* `getnewexchangeaddress` + ``` + Returns a new exchange address for receiving payments. + + Result: + "address" (string) The new exchange address. + ``` + +P2P connection management +-------------------------- + +- Peers manually added through the addnode option or addnode RPC now have their own + limit of sixteen connections which does not compete with other inbound or outbound + connection usage and is not subject to the maxconnections limitation. + +- New connections to manually added peers are much faster. + +*version* Change log +============== + +Detailed release notes follow. This overview includes changes that affect behavior, not code moves, refactors and string updates. For convenience in locating the code changes and accompanying discussion, both the pull request and git merge commit are mentioned. + +### Core Features + +### Build System + +### P2P Protocol and Network Code + +### GUI + +### RPC/REST +- #2895 Exchange Address `getnewexchangeaddress` (Liquid369) + +### Wallet + +### Miscellaneous + +## Credits + +Thanks to everyone who directly contributed to this release: + + +As well as everyone that helped translating on [Transifex](https://www.transifex.com/projects/p/pivx-project-translations/), the QA team during Testing and the Node hosts supporting our Testnet. From 814205969eb6f8506b8773177dec76e705343af0 Mon Sep 17 00:00:00 2001 From: Liquid Date: Wed, 31 Jan 2024 16:21:26 -0600 Subject: [PATCH 10/18] Remove nExchangeAddrStart --- src/chainparams.cpp | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/src/chainparams.cpp b/src/chainparams.cpp index 153cdc96d52be..ad6e910a8d0b7 100644 --- a/src/chainparams.cpp +++ b/src/chainparams.cpp @@ -246,8 +246,6 @@ class CMainParams : public CChainParams consensus.nTimeSlotLength = 15; consensus.nMaxProposalPayments = 6; - consensus.nExchangeAddrStart = INT_MAX; - // spork keys consensus.strSporkPubKey = "0410050aa740d280b134b40b40658781fc1116ba7700764e0ce27af3e1737586b3257d19232e0cb5084947f5107e44bcd577f126c9eb4a30ea2807b271d2145298"; consensus.strSporkPubKeyOld = "040F129DE6546FE405995329A887329BED4321325B1A73B0A257423C05C1FCFE9E40EF0678AEF59036A22C42E61DFD29DF7EFB09F56CC73CADF64E05741880E3E7"; @@ -295,6 +293,7 @@ class CMainParams : public CChainParams consensus.vUpgrades[Consensus::UPGRADE_V5_2].nActivationHeight = 2927000; consensus.vUpgrades[Consensus::UPGRADE_V5_3].nActivationHeight = 3014000; consensus.vUpgrades[Consensus::UPGRADE_V5_5].nActivationHeight = 3715200; + consensus.vUpgrades[Consensus::UPGRADE_V5_6].nActivationHeight = 9999999; consensus.vUpgrades[Consensus::UPGRADE_V6_0].nActivationHeight = Consensus::NetworkUpgrade::NO_ACTIVATION_HEIGHT; @@ -411,8 +410,6 @@ class CTestNetParams : public CChainParams consensus.nTimeSlotLength = 15; consensus.nMaxProposalPayments = 20; - consensus.nExchangeAddrStart = INT_MAX; - // spork keys consensus.strSporkPubKey = "04677c34726c491117265f4b1c83cef085684f36c8df5a97a3a42fc499316d0c4e63959c9eca0dba239d9aaaf72011afffeb3ef9f51b9017811dec686e412eb504"; consensus.strSporkPubKeyOld = "04E88BB455E2A04E65FCC41D88CD367E9CCE1F5A409BE94D8C2B4B35D223DED9C8E2F4E061349BA3A38839282508066B6DC4DB72DD432AC4067991E6BF20176127"; @@ -456,6 +453,7 @@ class CTestNetParams : public CChainParams consensus.vUpgrades[Consensus::UPGRADE_V5_2].nActivationHeight = 262525; consensus.vUpgrades[Consensus::UPGRADE_V5_3].nActivationHeight = 332300; consensus.vUpgrades[Consensus::UPGRADE_V5_5].nActivationHeight = 925056; + consensus.vUpgrades[Consensus::UPGRADE_V5_6].nActivationHeight = INT_MAX; consensus.vUpgrades[Consensus::UPGRADE_V6_0].nActivationHeight = Consensus::NetworkUpgrade::NO_ACTIVATION_HEIGHT; @@ -558,8 +556,6 @@ class CRegTestParams : public CChainParams consensus.nTimeSlotLength = 15; consensus.nMaxProposalPayments = 20; - consensus.nExchangeAddrStart = 1000; - /* Spork Key for RegTest: WIF private key: 932HEevBSujW2ud7RfB1YF91AFygbBRQj3de3LyaCRqNzKKgWXi private key hex: bd4960dcbd9e7f2223f24e7164ecb6f1fe96fc3a416f5d3a830ba5720c84b8ca @@ -609,6 +605,7 @@ class CRegTestParams : public CChainParams consensus.vUpgrades[Consensus::UPGRADE_V5_2].nActivationHeight = 300; consensus.vUpgrades[Consensus::UPGRADE_V5_3].nActivationHeight = 251; consensus.vUpgrades[Consensus::UPGRADE_V5_5].nActivationHeight = 576; + consensus.vUpgrades[Consensus::UPGRADE_V5_6].nActivationHeight = 1000; consensus.vUpgrades[Consensus::UPGRADE_V6_0].nActivationHeight = Consensus::NetworkUpgrade::NO_ACTIVATION_HEIGHT; From 4bef41eb1e482bd0af4465416dbdb1ef0d31687e Mon Sep 17 00:00:00 2001 From: Liquid Date: Wed, 7 Feb 2024 17:03:59 -0600 Subject: [PATCH 11/18] Review cleanup --- src/addressbook.cpp | 1 + src/destination_io.cpp | 1 + src/destination_io.h | 1 + src/script/interpreter.cpp | 4 +++- 4 files changed, 6 insertions(+), 1 deletion(-) diff --git a/src/addressbook.cpp b/src/addressbook.cpp index 13b042b873cb4..2f8c93b9e4120 100644 --- a/src/addressbook.cpp +++ b/src/addressbook.cpp @@ -53,5 +53,6 @@ namespace AddressBook { return IsShieldedPurpose(purpose); } + } diff --git a/src/destination_io.cpp b/src/destination_io.cpp index 6ce6eb07312ba..f7c0f5a1302aa 100644 --- a/src/destination_io.cpp +++ b/src/destination_io.cpp @@ -7,6 +7,7 @@ #include "sapling/key_io_sapling.h" namespace Standard { + std::string EncodeDestination(const CWDestination &address, const CChainParams::Base58Type addrType) { const CTxDestination *dest = boost::get(&address); if (!dest) { diff --git a/src/destination_io.h b/src/destination_io.h index 96b185e6c5bf9..9d9260bd1c2c7 100644 --- a/src/destination_io.h +++ b/src/destination_io.h @@ -12,6 +12,7 @@ typedef boost::variant CWDestination; namespace Standard { + std::string EncodeDestination(const CWDestination &address, const CChainParams::Base58Type addrType = CChainParams::PUBKEY_ADDRESS); CWDestination DecodeDestination(const std::string& strAddress); diff --git a/src/script/interpreter.cpp b/src/script/interpreter.cpp index a54135bbbb63d..f165ce75109b7 100644 --- a/src/script/interpreter.cpp +++ b/src/script/interpreter.cpp @@ -333,6 +333,9 @@ bool EvalScript(std::vector >& stack, const CScript& case OP_NOP: break; + case OP_EXCHANGEADDR: + break; + case OP_CHECKLOCKTIMEVERIFY: { if (!(flags & SCRIPT_VERIFY_CHECKLOCKTIMEVERIFY)) { @@ -981,7 +984,6 @@ bool EvalScript(std::vector >& stack, const CScript& // Size limits if (stack.size() + altstack.size() > 1000) return set_error(serror, SCRIPT_ERR_STACK_SIZE); - } } catch (...) From 19a17dd19857c4ad805256c2c7516655fe831e8c Mon Sep 17 00:00:00 2001 From: Liquid Date: Fri, 9 Feb 2024 12:43:37 -0600 Subject: [PATCH 12/18] Duddino review changes Adjust wallet model --- src/destination_io.h | 2 +- src/key_io.cpp | 6 +----- src/qt/walletmodel.cpp | 6 +++--- 3 files changed, 5 insertions(+), 9 deletions(-) diff --git a/src/destination_io.h b/src/destination_io.h index 9d9260bd1c2c7..63a95d6862203 100644 --- a/src/destination_io.h +++ b/src/destination_io.h @@ -34,7 +34,7 @@ class Destination { public: explicit Destination() {} explicit Destination(const CTxDestination& _dest, bool _isP2CS) : dest(_dest), isP2CS(_isP2CS) {} - explicit Destination(const CTxDestination& _dest, bool _isP2CS, bool _isEXCHANGE) : dest(_dest), isP2CS(_isP2CS), isExchange(_isEXCHANGE) {} + explicit Destination(const CTxDestination& _dest, bool _isP2CS, bool _isEXCHANGE = false) : dest(_dest), isP2CS(_isP2CS), isExchange(_isEXCHANGE) {} explicit Destination(const libzcash::SaplingPaymentAddress& _dest) : dest(_dest) {} CWDestination dest{CNoDestination()}; diff --git a/src/key_io.cpp b/src/key_io.cpp index 58e589c2dde0d..664e52f63fcda 100644 --- a/src/key_io.cpp +++ b/src/key_io.cpp @@ -90,11 +90,7 @@ namespace std::string EncodeDestination(const CTxDestination& dest, bool isStaking, bool isExchange) { - if (isExchange) { - return EncodeDestination(dest, CChainParams::EXCHANGE_ADDRESS); - } else { - return EncodeDestination(dest, isStaking ? CChainParams::STAKING_ADDRESS : CChainParams::PUBKEY_ADDRESS); - } + return isExchange ? EncodeDestination(dest, CChainParams::EXCHANGE_ADDRESS) : (isStaking ? EncodeDestination(dest, CChainParams::STAKING_ADDRESS) : EncodeDestination(dest, CChainParams::PUBKEY_ADDRESS)); } std::string EncodeDestination(const CTxDestination& dest, const CChainParams::Base58Type addrType) diff --git a/src/qt/walletmodel.cpp b/src/qt/walletmodel.cpp index 635685fb98637..3c77b23ef5cf0 100644 --- a/src/qt/walletmodel.cpp +++ b/src/qt/walletmodel.cpp @@ -492,7 +492,7 @@ WalletModel::SendCoinsReturn WalletModel::prepareTransaction(WalletModelTransact if (!res) return CannotCreateInternalAddress; ownerAdd = *res.getObjResult(); } else { - ownerAdd = Destination(DecodeDestination(rcp.ownerAddress.toStdString()), false); + ownerAdd = Destination(DecodeDestination(rcp.ownerAddress.toStdString()), false, false); } const CKeyID* stakerId = boost::get(&out); @@ -957,14 +957,14 @@ int64_t WalletModel::getKeyCreationTime(const libzcash::SaplingPaymentAddress& a CallResult WalletModel::getNewAddress(const std::string& label) const { auto res = wallet->getNewAddress(label); - return res ? CallResult(Destination(*res.getObjResult(), false)) : + return res ? CallResult(Destination(*res.getObjResult(), false, false)) : CallResult(res.getError()); } CallResult WalletModel::getNewStakingAddress(const std::string& label) const { auto res = wallet->getNewStakingAddress(label); - return res ? CallResult(Destination(*res.getObjResult(), true)) : + return res ? CallResult(Destination(*res.getObjResult(), true, false)) : CallResult(res.getError()); } From d9274a6f56eea0fec4f14f5d63cfc422add99d17 Mon Sep 17 00:00:00 2001 From: Liquid369 <45834289+Liquid369@users.noreply.github.com> Date: Fri, 9 Feb 2024 15:25:40 -0600 Subject: [PATCH 13/18] Reorder test and logic --- test/functional/feature_exchangeaddr.py | 17 ++++++----------- 1 file changed, 6 insertions(+), 11 deletions(-) diff --git a/test/functional/feature_exchangeaddr.py b/test/functional/feature_exchangeaddr.py index 9f40a67e1531b..62b6fc2a94b71 100755 --- a/test/functional/feature_exchangeaddr.py +++ b/test/functional/feature_exchangeaddr.py @@ -8,9 +8,9 @@ 1001, and that the following is capable t > e e > t -and not capable - t > s > e e > s +and not capable + s > e """ from decimal import Decimal @@ -80,15 +80,10 @@ def run_test(self): # Generate a new shielded address new_shielded_addr = self.nodes[1].getnewshieldaddress() - # Attempt to send funds from exchange address to shielded address - expected_error_message = "Failed to build transaction: Failed to sign transaction" - - assert_raises_rpc_error( - expected_error_code, - expected_error_message, - self.nodes[1].sendtoaddress, - new_shielded_addr, 1.0 - ) + # Send to Shield from Exchange Addr + tx3 = self.nodes[1].sendtoaddress(new_shielded_addr, 1.0) + ex_result2 = self.nodes[1].gettransaction(tx3) + assert_equal(ex_result2['txid'], tx3) if __name__ == "__main__": From 63a192ca151655237b38df68dd6fc50405345385 Mon Sep 17 00:00:00 2001 From: Liquid Date: Sat, 10 Feb 2024 17:06:24 -0600 Subject: [PATCH 14/18] More review changes --- src/script/interpreter.cpp | 2 ++ src/script/script_error.cpp | 2 ++ src/script/script_error.h | 1 + 3 files changed, 5 insertions(+) diff --git a/src/script/interpreter.cpp b/src/script/interpreter.cpp index f165ce75109b7..633638b992fd7 100644 --- a/src/script/interpreter.cpp +++ b/src/script/interpreter.cpp @@ -334,6 +334,8 @@ bool EvalScript(std::vector >& stack, const CScript& break; case OP_EXCHANGEADDR: + if (!script.IsPayToExchangeAddress()) + return set_error(serror, SCRIPT_ERR_EXCHANGEADDRVERIFY); break; case OP_CHECKLOCKTIMEVERIFY: diff --git a/src/script/script_error.cpp b/src/script/script_error.cpp index a7a4a64e9067f..e3e33209bfde9 100644 --- a/src/script/script_error.cpp +++ b/src/script/script_error.cpp @@ -18,6 +18,8 @@ const char* ScriptErrorString(const ScriptError serror) return "Script failed an OP_VERIFY operation"; case SCRIPT_ERR_EQUALVERIFY: return "Script failed an OP_EQUALVERIFY operation"; + case SCRIPT_ERR_EXCHANGEADDRVERIFY: + return "Script failed OP_EXCHANGEADDR operation"; case SCRIPT_ERR_CHECKCOLDSTAKEVERIFY: return "Script failed an OP_CHECKCOLDSTAKEVERIFY operation"; case SCRIPT_ERR_CHECKMULTISIGVERIFY: diff --git a/src/script/script_error.h b/src/script/script_error.h index b06874874dc1e..a9a76d3de3824 100644 --- a/src/script/script_error.h +++ b/src/script/script_error.h @@ -25,6 +25,7 @@ typedef enum ScriptError_t /* Failed verify operations */ SCRIPT_ERR_VERIFY, SCRIPT_ERR_EQUALVERIFY, + SCRIPT_ERR_EXCHANGEADDRVERIFY, SCRIPT_ERR_CHECKCOLDSTAKEVERIFY, SCRIPT_ERR_CHECKMULTISIGVERIFY, SCRIPT_ERR_CHECKSIGVERIFY, From ce86ae7e1ac466962f76f5247603f722756c6ebe Mon Sep 17 00:00:00 2001 From: Liquid Date: Mon, 12 Feb 2024 07:50:00 -0600 Subject: [PATCH 15/18] Add invalid OP_CODE test --- test/functional/feature_exchangeaddr.py | 24 +++++++++++++++++++++++- 1 file changed, 23 insertions(+), 1 deletion(-) diff --git a/test/functional/feature_exchangeaddr.py b/test/functional/feature_exchangeaddr.py index 62b6fc2a94b71..ddfce0a8db34b 100755 --- a/test/functional/feature_exchangeaddr.py +++ b/test/functional/feature_exchangeaddr.py @@ -17,6 +17,8 @@ from test_framework.test_framework import PivxTestFramework from test_framework.util import assert_equal, assert_raises_rpc_error +from test_framework.script import CScript, OP_NOP, OP_CHECKSIG +from test_framework.messages import CTransaction, CTxIn, CTxOut, COutPoint, ToHex FEATURE_PRE_SPLIT_KEYPOOL = 169900 @@ -27,8 +29,28 @@ def set_test_params(self): self.setup_clean_chain = True def run_test(self): + # Mine and test Pre v5.6 bad OP_EXCHANGEADDR + self.nodes[0].generate(900) + address = self.nodes[0].getnewaddress() + self.nodes[0].sendtoaddress(address, 10) + self.nodes[0].generate(6) + utxo = self.nodes[0].listunspent()[0] + # Create a raw transaction that attempts to spend the UTXO with a custom opcode + tx = CTransaction() + tx.vin.append(CTxIn(COutPoint(int(utxo["txid"], 16), utxo["vout"]), b"")) + tx.vout.append(CTxOut(int(utxo["amount"] * 100000000) - 10000, CScript([OP_NOP, OP_CHECKSIG]))) + tx.vin[0].scriptSig = CScript([b'\xe0', b'\x51']) # OP_EXCHANGEADDR (0xe0) followed by OP_1 (0x51) + + # Sign the transaction + signed_tx = self.nodes[0].signrawtransaction(ToHex(tx)) + + # Send the raw transaction + # Before the upgrade, this should fail if OP_EXCHANGEADDR is disallowed + error_code = -26 + error_message = "scriptpubkey" + assert_raises_rpc_error(error_code, error_message, self.nodes[0].sendrawtransaction, signed_tx["hex"]) # Mine and activate exchange addresses - self.nodes[0].generate(1000) + self.nodes[0].generate(94) assert_equal(self.nodes[0].getblockcount(), 1000) self.nodes[0].generate(1) From b7affc71fb21cbcf0d01415e04dcf39b918feeba Mon Sep 17 00:00:00 2001 From: Liquid Date: Mon, 12 Feb 2024 09:08:46 -0600 Subject: [PATCH 16/18] Check pre-upgrade ex addr fails --- test/functional/feature_exchangeaddr.py | 34 ++++++++++++++++++------- 1 file changed, 25 insertions(+), 9 deletions(-) diff --git a/test/functional/feature_exchangeaddr.py b/test/functional/feature_exchangeaddr.py index ddfce0a8db34b..c16e23bf46eba 100755 --- a/test/functional/feature_exchangeaddr.py +++ b/test/functional/feature_exchangeaddr.py @@ -30,7 +30,7 @@ def set_test_params(self): def run_test(self): # Mine and test Pre v5.6 bad OP_EXCHANGEADDR - self.nodes[0].generate(900) + self.nodes[0].generate(800) address = self.nodes[0].getnewaddress() self.nodes[0].sendtoaddress(address, 10) self.nodes[0].generate(6) @@ -49,28 +49,44 @@ def run_test(self): error_code = -26 error_message = "scriptpubkey" assert_raises_rpc_error(error_code, error_message, self.nodes[0].sendrawtransaction, signed_tx["hex"]) - # Mine and activate exchange addresses - self.nodes[0].generate(94) - assert_equal(self.nodes[0].getblockcount(), 1000) - self.nodes[0].generate(1) - # Get addresses for testing + # Test that proper Exchange Address is not allowed before upgrade ex_addr = self.nodes[1].getnewexchangeaddress() - t_addr = self.nodes[0].getnewaddress() # Attempt to send funds from transparent address to exchange address ex_addr_validation_result = self.nodes[0].validateaddress(ex_addr) assert_equal(ex_addr_validation_result['isvalid'], True) - self.nodes[0].sendtoaddress(ex_addr, 2.0) + # This should fail to be sent + error_code = -4 + error_message = "bad-exchange-address-not-started" + assert_raises_rpc_error( + error_code, + error_message, + self.nodes[0].sendtoaddress, + ex_addr, 1.0 + ) + + # Mine and activate exchange addresses + self.nodes[0].generate(194) + assert_equal(self.nodes[0].getblockcount(), 1000) + self.nodes[0].generate(1) + + # Get addresses for testing + ex_addr_2 = self.nodes[1].getnewexchangeaddress() + t_addr_2 = self.nodes[0].getnewaddress() + + # Attempt to send funds from transparent address to exchange address pre-upgrade activation + self.nodes[0].sendtoaddress(ex_addr_2, 2.0) self.nodes[0].generate(6) self.sync_all() + # Verify balance node_bal = self.nodes[1].getbalance() assert_equal(node_bal, 2) # Attempt to send funds from exchange address back to transparent address - tx2 = self.nodes[0].sendtoaddress(t_addr, 1.0) + tx2 = self.nodes[0].sendtoaddress(t_addr_2, 1.0) self.nodes[0].generate(6) self.sync_all() ex_result = self.nodes[0].gettransaction(tx2) From 7a7cc717c9988ee8893b8a6e4e6f6e07d2b6bee6 Mon Sep 17 00:00:00 2001 From: Liquid369 <45834289+Liquid369@users.noreply.github.com> Date: Mon, 12 Feb 2024 09:11:45 -0600 Subject: [PATCH 17/18] Set main net and testnet activation heights --- src/chainparams.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/chainparams.cpp b/src/chainparams.cpp index ad6e910a8d0b7..de55381dad583 100644 --- a/src/chainparams.cpp +++ b/src/chainparams.cpp @@ -293,7 +293,7 @@ class CMainParams : public CChainParams consensus.vUpgrades[Consensus::UPGRADE_V5_2].nActivationHeight = 2927000; consensus.vUpgrades[Consensus::UPGRADE_V5_3].nActivationHeight = 3014000; consensus.vUpgrades[Consensus::UPGRADE_V5_5].nActivationHeight = 3715200; - consensus.vUpgrades[Consensus::UPGRADE_V5_6].nActivationHeight = 9999999; + consensus.vUpgrades[Consensus::UPGRADE_V5_6].nActivationHeight = 4281680; // Estimate Feb 29th 12:00 UTC consensus.vUpgrades[Consensus::UPGRADE_V6_0].nActivationHeight = Consensus::NetworkUpgrade::NO_ACTIVATION_HEIGHT; @@ -453,7 +453,7 @@ class CTestNetParams : public CChainParams consensus.vUpgrades[Consensus::UPGRADE_V5_2].nActivationHeight = 262525; consensus.vUpgrades[Consensus::UPGRADE_V5_3].nActivationHeight = 332300; consensus.vUpgrades[Consensus::UPGRADE_V5_5].nActivationHeight = 925056; - consensus.vUpgrades[Consensus::UPGRADE_V5_6].nActivationHeight = INT_MAX; + consensus.vUpgrades[Consensus::UPGRADE_V5_6].nActivationHeight = 1624280; // Estimate Feb 23 Midnight UTC consensus.vUpgrades[Consensus::UPGRADE_V6_0].nActivationHeight = Consensus::NetworkUpgrade::NO_ACTIVATION_HEIGHT; From 1444336794301f4affe0871ffec67691dd974f38 Mon Sep 17 00:00:00 2001 From: Liquid Date: Mon, 12 Feb 2024 11:10:33 -0600 Subject: [PATCH 18/18] Fix lint --- test/functional/feature_exchangeaddr.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/functional/feature_exchangeaddr.py b/test/functional/feature_exchangeaddr.py index c16e23bf46eba..7699474e4fc4d 100755 --- a/test/functional/feature_exchangeaddr.py +++ b/test/functional/feature_exchangeaddr.py @@ -40,7 +40,7 @@ def run_test(self): tx.vin.append(CTxIn(COutPoint(int(utxo["txid"], 16), utxo["vout"]), b"")) tx.vout.append(CTxOut(int(utxo["amount"] * 100000000) - 10000, CScript([OP_NOP, OP_CHECKSIG]))) tx.vin[0].scriptSig = CScript([b'\xe0', b'\x51']) # OP_EXCHANGEADDR (0xe0) followed by OP_1 (0x51) - + # Sign the transaction signed_tx = self.nodes[0].signrawtransaction(ToHex(tx))