diff --git a/src/consensus/tx_verify.cpp b/src/consensus/tx_verify.cpp index 66776216ad5c4..f43f270554183 100644 --- a/src/consensus/tx_verify.cpp +++ b/src/consensus/tx_verify.cpp @@ -116,9 +116,6 @@ bool CheckTransaction(const CTransaction& tx, CValidationState& state, bool fCol } bool hasExchangeUTXOs = tx.HasExchangeAddr(); - int nTxHeight = chainActive.Height(); - 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()) { if (tx.vin[0].scriptSig.size() < 2 || tx.vin[0].scriptSig.size() > 150) diff --git a/src/policy/policy.h b/src/policy/policy.h index 2568d298178eb..860a890a75176 100644 --- a/src/policy/policy.h +++ b/src/policy/policy.h @@ -45,7 +45,8 @@ static constexpr unsigned int STANDARD_SCRIPT_VERIFY_FLAGS = MANDATORY_SCRIPT_VE SCRIPT_VERIFY_NULLDUMMY | SCRIPT_VERIFY_DISCOURAGE_UPGRADABLE_NOPS | SCRIPT_VERIFY_CLEANSTACK | - SCRIPT_VERIFY_LOW_S; + SCRIPT_VERIFY_LOW_S | + SCRIPT_VERIFY_EXCHANGEADDR; /** For convenience, standard but not mandatory verify flags. */ static constexpr unsigned int STANDARD_NOT_MANDATORY_VERIFY_FLAGS = STANDARD_SCRIPT_VERIFY_FLAGS & ~MANDATORY_SCRIPT_VERIFY_FLAGS; diff --git a/src/sapling/sapling_validation.cpp b/src/sapling/sapling_validation.cpp index 383e61394f429..059628da8ec11 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 or exchange address - if (tx.IsCoinStake() || tx.IsCoinBase() || tx.HasZerocoinSpendInputs() || tx.HasZerocoinMintOutputs() || tx.HasExchangeAddr()) + // if the tx has shielded data, cannot be a coinstake, coinbase, zcspend and zcmint + if (tx.IsCoinStake() || tx.IsCoinBase() || tx.HasZerocoinSpendInputs() || tx.HasZerocoinMintOutputs()) return state.DoS(100, error("%s: Sapling version with invalid data", __func__), REJECT_INVALID, "bad-txns-invalid-sapling"); @@ -160,6 +160,12 @@ bool ContextualCheckTransaction( if (hasShieldedData) { uint256 dataToBeSigned; + + if (tx.HasExchangeAddr() && Params().GetConsensus().NetworkUpgradeActive(nHeight, Consensus::UPGRADE_V5_6)) { + return state.DoS(100, error("%s: Sapling version with invalid data", __func__), + REJECT_INVALID, "bad-txns-exchange-addr-has-sapling"); + } + // Empty output script. CScript scriptCode; try { diff --git a/src/script/interpreter.cpp b/src/script/interpreter.cpp index 633638b992fd7..1fd448180bb67 100644 --- a/src/script/interpreter.cpp +++ b/src/script/interpreter.cpp @@ -334,6 +334,10 @@ bool EvalScript(std::vector >& stack, const CScript& break; case OP_EXCHANGEADDR: + // Not enabled, treat as OP_UNKNOWN + if (!(flags & SCRIPT_VERIFY_EXCHANGEADDR)) { + return set_error(serror, SCRIPT_ERR_BAD_OPCODE); + } if (!script.IsPayToExchangeAddress()) return set_error(serror, SCRIPT_ERR_EXCHANGEADDRVERIFY); break; diff --git a/src/script/interpreter.h b/src/script/interpreter.h index b69b8ea9471fc..0d92ff7c11dec 100644 --- a/src/script/interpreter.h +++ b/src/script/interpreter.h @@ -80,7 +80,11 @@ enum // Verify CHECKLOCKTIMEVERIFY // // See BIP65 for details. - SCRIPT_VERIFY_CHECKLOCKTIMEVERIFY = (1U << 9) + SCRIPT_VERIFY_CHECKLOCKTIMEVERIFY = (1U << 9), + + // Verify OP_EXCHANGEADDR. + // Treat as UNKNOWN before 5.6 activation + SCRIPT_VERIFY_EXCHANGEADDR = (1U << 10), }; bool CheckSignatureEncoding(const std::vector &vchSig, unsigned int flags, ScriptError* serror); diff --git a/src/test/transaction_tests.cpp b/src/test/transaction_tests.cpp index d0b259f5fef43..6898ef63151a7 100644 --- a/src/test/transaction_tests.cpp +++ b/src/test/transaction_tests.cpp @@ -47,6 +47,7 @@ static std::map mapFlagNames = { {std::string("DISCOURAGE_UPGRADABLE_NOPS"), (unsigned int)SCRIPT_VERIFY_DISCOURAGE_UPGRADABLE_NOPS}, {std::string("CLEANSTACK"), (unsigned int)SCRIPT_VERIFY_CLEANSTACK}, {std::string("CHECKLOCKTIMEVERIFY"), (unsigned int)SCRIPT_VERIFY_CHECKLOCKTIMEVERIFY}, + {std::string("EXCHANGEADDRVERIFY"), (unsigned int)SCRIPT_VERIFY_EXCHANGEADDR}, }; unsigned int ParseScriptFlags(std::string strFlags) diff --git a/src/validation.cpp b/src/validation.cpp index f087d59551f08..9c02ceecce5a4 100644 --- a/src/validation.cpp +++ b/src/validation.cpp @@ -569,12 +569,15 @@ static bool AcceptToMemoryPoolWorker(CTxMemPool& pool, CValidationState &state, } bool fCLTVIsActivated = consensus.NetworkUpgradeActive(chainHeight, Consensus::UPGRADE_BIP65); - + bool exchangeAddrActivated = consensus.NetworkUpgradeActive(chainHeight, Consensus::UPGRADE_V5_6); // Check against previous transactions // This is done last to help prevent CPU exhaustion denial-of-service attacks. int flags = STANDARD_SCRIPT_VERIFY_FLAGS; if (fCLTVIsActivated) flags |= SCRIPT_VERIFY_CHECKLOCKTIMEVERIFY; + if (exchangeAddrActivated) + flags |= SCRIPT_VERIFY_EXCHANGEADDR; + PrecomputedTransactionData precomTxData(tx); if (!CheckInputs(tx, state, view, true, flags, true, precomTxData)) { @@ -593,6 +596,8 @@ static bool AcceptToMemoryPoolWorker(CTxMemPool& pool, CValidationState &state, flags = MANDATORY_SCRIPT_VERIFY_FLAGS; if (fCLTVIsActivated) flags |= SCRIPT_VERIFY_CHECKLOCKTIMEVERIFY; + if (exchangeAddrActivated) + flags |= SCRIPT_VERIFY_EXCHANGEADDR; if (!CheckInputs(tx, state, view, true, flags, true, precomTxData)) { return error("%s: BUG! PLEASE REPORT THIS! ConnectInputs failed against MANDATORY but not STANDARD flags %s, %s", __func__, hash.ToString(), FormatStateMessage(state)); @@ -1496,8 +1501,10 @@ static bool ConnectBlock(const CBlock& block, CValidationState& state, CBlockInd // If scripts won't be checked anyways, don't bother seeing if CLTV is activated bool fCLTVIsActivated = false; + bool exchangeAddrActivated = false; if (fScriptChecks && pindex->pprev) { fCLTVIsActivated = consensus.NetworkUpgradeActive(pindex->pprev->nHeight, Consensus::UPGRADE_BIP65); + exchangeAddrActivated = consensus.NetworkUpgradeActive(pindex->pprev->nHeight, Consensus::UPGRADE_V5_6); } CCheckQueueControl control(fScriptChecks && nScriptCheckThreads ? &scriptcheckqueue : nullptr); @@ -1575,6 +1582,8 @@ static bool ConnectBlock(const CBlock& block, CValidationState& state, CBlockInd unsigned int flags = SCRIPT_VERIFY_P2SH | SCRIPT_VERIFY_DERSIG; if (fCLTVIsActivated) flags |= SCRIPT_VERIFY_CHECKLOCKTIMEVERIFY; + if (exchangeAddrActivated) + flags |= SCRIPT_VERIFY_EXCHANGEADDR; bool fCacheResults = fJustCheck; /* Don't cache results if we're actually connecting blocks (still consult the cache, though) */ if (!CheckInputs(tx, state, view, fScriptChecks, flags, fCacheResults, precomTxData[i], nScriptCheckThreads ? &vChecks : nullptr)) diff --git a/test/functional/feature_exchangeaddr.py b/test/functional/feature_exchangeaddr.py index 7699474e4fc4d..33dc28e8ffab8 100755 --- a/test/functional/feature_exchangeaddr.py +++ b/test/functional/feature_exchangeaddr.py @@ -44,7 +44,6 @@ def run_test(self): # 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" @@ -56,18 +55,22 @@ def run_test(self): # 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) - # 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 - ) + # This should succeed even before the upgrade + self.nodes[0].sendtoaddress(ex_addr, 1.0) + # Check wallet version + wallet_info = self.nodes[0].getwalletinfo() + if wallet_info['walletversion'] >= FEATURE_PRE_SPLIT_KEYPOOL: + sapling_addr = self.nodes[0].getnewshieldaddress() + self.nodes[0].sendtoaddress(sapling_addr, 2.0) + self.nodes[0].generate(1) + sap_to_ex = [{"address": ex_addr, "amount": Decimal('1')}] + # Shield data should be allowed before activation + self.nodes[0].shieldsendmany(sapling_addr, sap_to_ex) + else: + self.nodes[0].generate(1) # Mine and activate exchange addresses - self.nodes[0].generate(194) + self.nodes[0].generate(193) assert_equal(self.nodes[0].getblockcount(), 1000) self.nodes[0].generate(1) @@ -83,8 +86,10 @@ def run_test(self): # Verify balance node_bal = self.nodes[1].getbalance() - assert_equal(node_bal, 2) - + if wallet_info['walletversion'] >= FEATURE_PRE_SPLIT_KEYPOOL: + assert_equal(node_bal, 4) + else: + assert_equal(node_bal, 3) # Attempt to send funds from exchange address back to transparent address tx2 = self.nodes[0].sendtoaddress(t_addr_2, 1.0) self.nodes[0].generate(6) @@ -96,7 +101,6 @@ def run_test(self): # 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 @@ -107,7 +111,7 @@ def run_test(self): # 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)" + expected_error_message = "Failed to accept tx in the memory pool (reason: bad-txns-exchange-addr-has-sapling)" assert_raises_rpc_error( expected_error_code, expected_error_message,