diff --git a/CMakeLists.txt b/CMakeLists.txt index e42d7ef13..68569f94b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -51,6 +51,6 @@ add_executable(lbrycrd-cli src/bitcoin-cli.cpp ${sources}) add_executable(lbrycrd-tx src/bitcoin-tx.cpp ${sources}) add_executable(lbrycrdd src/bitcoind.cpp ${sources}) -file(GLOB tests src/test/*.cpp) +file(GLOB tests src/test/*.cpp src/wallet/test/*.cpp) add_executable(test_lbrycrd ${tests} ${sources}) target_include_directories(test_lbrycrd PRIVATE src/test) \ No newline at end of file diff --git a/src/Makefile.test.include b/src/Makefile.test.include index b13c3de9c..c267b8435 100644 --- a/src/Makefile.test.include +++ b/src/Makefile.test.include @@ -100,6 +100,7 @@ BITCOIN_TESTS =\ if ENABLE_WALLET BITCOIN_TESTS += \ wallet/test/accounting_tests.cpp \ + wallet/test/claim_rpc_tests.cpp \ wallet/test/db_tests.cpp \ wallet/test/psbt_wallet_tests.cpp \ wallet/test/wallet_tests.cpp \ diff --git a/src/script/ismine.cpp b/src/script/ismine.cpp index 00dcc9727..99718d0ac 100644 --- a/src/script/ismine.cpp +++ b/src/script/ismine.cpp @@ -62,17 +62,15 @@ IsMineResult IsMineInner(const CKeyStore& keystore, const CScript& scriptPubKey, { int op = 0; IsMineResult ret = IsMineResult::NO; - IsMineResult claim_ret = IsMineResult::NO; CScript strippedScriptPubKey = StripClaimScriptPrefix(scriptPubKey, op); - if (strippedScriptPubKey != scriptPubKey) - claim_ret = ((op == OP_CLAIM_NAME || op == OP_UPDATE_CLAIM) ? IsMineResult::CLAIM : - ((op == OP_SUPPORT_CLAIM) ? IsMineResult::SUPPORT : - IsMineResult::NO)); + IsMineResult claim_ret = ((op == OP_CLAIM_NAME || op == OP_UPDATE_CLAIM) ? IsMineResult::CLAIM : + ((op == OP_SUPPORT_CLAIM) ? IsMineResult::SUPPORT : + IsMineResult::NO)); std::vector vSolutions; txnouttype whichType; - Solver(scriptPubKey, whichType, vSolutions); + Solver(strippedScriptPubKey, whichType, vSolutions); CKeyID keyID; switch (whichType) diff --git a/src/test/coins_tests.cpp b/src/test/coins_tests.cpp index a11454046..728cf1ec6 100644 --- a/src/test/coins_tests.cpp +++ b/src/test/coins_tests.cpp @@ -406,11 +406,8 @@ BOOST_AUTO_TEST_CASE(updatecoins_simulation_test) // restore inputs if (!tx.IsCoinBase()) { const COutPoint &out = tx.vin[0].prevout; - Coin coin = undo.vprevout[0]; CClaimTrieCache trieCache(pclaimTrie); ApplyTxInUndo(0, undo, *(stack.back()), trieCache, out); - // return coin - undo.vprevout[0] = coin; } // Store as a candidate for reconnection disconnected_coins.insert(utxod->first); diff --git a/src/undo.h b/src/undo.h index 3470b6114..0fee635f1 100644 --- a/src/undo.h +++ b/src/undo.h @@ -1,120 +1,71 @@ // Copyright (c) 2009-2010 Satoshi Nakamoto -// Copyright (c) 2009-2018 The Bitcoin Core developers +// Copyright (c) 2009-2014 The Bitcoin Core developers // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. #ifndef BITCOIN_UNDO_H #define BITCOIN_UNDO_H -#include -#include #include -#include +#include #include #include /** Undo information for a CTxIn * - * Contains the prevout's CTxOut being spent, and its metadata as well - * (coinbase or not, height). The serialization contains a dummy value of - * zero. This is compatible with older versions which expect to see - * the transaction version there. + * Contains the prevout's CTxOut being spent, and if this was the + * last output of the affected transaction, its metadata as well + * (coinbase or not, height, transaction version) */ -class TxInUndoSerializer +class CTxInUndo { - const Coin* txout; - // whether the outpoint was the last unspent - bool fLastUnspent; - // if the outpoint was the last unspent: its version - unsigned int nVersion; - // If the outpoint was a claim or support, the height at which the claim or support should be inserted into the trie - unsigned int nClaimValidHeight; - // if the outpoint was a claim or support - bool fIsClaim; - + uint32_t nDeprecated1 = 0; // if the outpoint was the last unspent: its version + bool fDeprecated2 = false; // whether the outpoint was the last unspent public: - template - void Serialize(Stream &s) const { - ::Serialize(s, VARINT(txout->nHeight * 4 + (txout->fCoinBase ? 2u : 0u) + (fLastUnspent ? 1u : 0u))); - if (fLastUnspent) - ::Serialize(s, VARINT(this->nVersion)); - ::Serialize(s, CTxOutCompressor(REF(txout->out))); - ::Serialize(s, VARINT(nClaimValidHeight)); - ::Serialize(s, fIsClaim); + CTxOut txout; // the txout data before being spent + bool fCoinBase; // if the outpoint was the last unspent: whether it belonged to a coinbase + uint32_t nHeight; // if the outpoint was the last unspent: its height + uint32_t nClaimValidHeight; // If the outpoint was a claim or support, the height at which the claim or support should be inserted into the trie + bool fIsClaim; // if the outpoint was a claim or support + + CTxInUndo() : txout(), fCoinBase(false), nHeight(0), nClaimValidHeight(0), fIsClaim(false) {} + CTxInUndo(const CTxOut &txoutIn, bool fCoinBaseIn = false, uint32_t nHeightIn = 0) : + txout(txoutIn), fCoinBase(fCoinBaseIn), nHeight(nHeightIn), nClaimValidHeight(0), fIsClaim(false) {} + + ADD_SERIALIZE_METHODS; + + template + inline void SerializationOp(Stream& s, Operation ser_action) { + if (ser_action.ForRead()) { + uint32_t nCode = 0; + READWRITE(VARINT(nCode)); // VARINT in this method is for legacy compatibility + nHeight = nCode >> 2U; + fCoinBase = nCode & 2U; + fDeprecated2 = nCode & 1U; + } else { + uint32_t nCode = (nHeight << 2U) | (fCoinBase ? 2U : 0U) | (fDeprecated2 ? 1U: 0U); + READWRITE(VARINT(nCode)); + } + if (fDeprecated2) + READWRITE(VARINT(nDeprecated1)); + READWRITE(REF(CTxOutCompressor(REF(txout)))); + READWRITE(VARINT(nClaimValidHeight)); + READWRITE(fIsClaim); } - - explicit TxInUndoSerializer(const Coin* coin) : txout(coin) {} }; -class TxInUndoDeserializer -{ - Coin* txout; - // whether the outpoint was the last unspent - bool fLastUnspent; - // if the outpoint was the last unspent: its version - unsigned int nVersion; - // If the outpoint was a claim or support, the height at which the claim or support should be inserted into the trie - unsigned int nClaimValidHeight; - // if the outpoint was a claim or support - bool fIsClaim; - -public: - template - void Unserialize(Stream &s) { - unsigned int nCode = 0; - ::Unserialize(s, VARINT(nCode)); - txout->nHeight = nCode / 4; // >> 2? - txout->fCoinBase = (nCode & 2) ? 1: 0; - fLastUnspent = (nCode & 1) > 0; - if (fLastUnspent) - ::Unserialize(s, VARINT(this->nVersion)); - ::Unserialize(s, CTxOutCompressor(REF(txout->out))); - ::Unserialize(s, VARINT(nClaimValidHeight)); - ::Unserialize(s, fIsClaim); - } - - explicit TxInUndoDeserializer(Coin* coin) : txout(coin) {} -}; - -static const size_t MIN_TRANSACTION_INPUT_WEIGHT = WITNESS_SCALE_FACTOR * ::GetSerializeSize(CTxIn(), SER_NETWORK, PROTOCOL_VERSION); -static const size_t MAX_INPUTS_PER_BLOCK = MAX_BLOCK_WEIGHT / MIN_TRANSACTION_INPUT_WEIGHT; - /** Undo information for a CTransaction */ class CTxUndo { - struct claimState - { - ADD_SERIALIZE_METHODS; - - template - inline void SerializationOp(Stream& s, Operation ser_action) { - READWRITE(nClaimValidHeight); - READWRITE(fIsClaim); - }; - - // If the outpoint was a claim or support, the height at which the - // claim or support should be inserted into the trie; indexed by Coin index - unsigned int nClaimValidHeight; - // if the outpoint was a claim or support; indexed by Coin index - bool fIsClaim; - }; - - using claimStateMap = std::map; - public: // undo information for all txins - std::vector vprevout; - claimStateMap claimInfo; - - CTxUndo() { - } + std::vector vprevout; ADD_SERIALIZE_METHODS; template inline void SerializationOp(Stream& s, Operation ser_action) { READWRITE(vprevout); - READWRITE(claimInfo); } }; diff --git a/src/validation.cpp b/src/validation.cpp index acd57c108..997d02dd0 100644 --- a/src/validation.cpp +++ b/src/validation.cpp @@ -1211,12 +1211,11 @@ void UpdateCoins(const CTransaction& tx, CCoinsViewCache& inputs, CTxUndo &txund // mark inputs spent if (!tx.IsCoinBase()) { txundo.vprevout.reserve(tx.vin.size()); - for(size_t i = 0; i < tx.vin.size(); i++) { - const CTxIn& txin = tx.vin[i]; - Coin coin{}; + Coin coin; + for (const CTxIn &txin : tx.vin) { bool is_spent = inputs.SpendCoin(txin.prevout, &coin); assert(is_spent); - txundo.vprevout.push_back(coin); + txundo.vprevout.emplace_back(coin.out, coin.IsCoinBase(), int(coin.nHeight)); } } // add outputs @@ -1443,24 +1442,17 @@ static bool AbortNode(CValidationState& state, const std::string& strMessage, co */ int ApplyTxInUndo(unsigned int index, CTxUndo& txUndo, CCoinsViewCache& view, CClaimTrieCache& trieCache, const COutPoint& out) { - Coin& undo = txUndo.vprevout[index]; - unsigned int nClaimValidHeight = 0; - bool fIsClaim = false; - const auto claimIt = txUndo.claimInfo.find(index); - if (claimIt != txUndo.claimInfo.end()) { - nClaimValidHeight = claimIt->second.nClaimValidHeight; - fIsClaim = claimIt->second.fIsClaim; - } - + auto& undo = txUndo.vprevout[index]; bool fClean = true; if (view.HaveCoin(out)) fClean = false; // overwriting transaction output if (undo.nHeight == 0) { - // Missing undo metadata (height and coinbase). Older versions included this + // Missing undo metadata (height and coinbase, not txout). Older versions included this // information only in undo records for the last spend of a transactions' // outputs. This implies that it must be present for some other output of the same tx. const Coin& alternate = AccessByTxid(view, out.hash); if (!alternate.IsSpent()) { + assert(!undo.fIsClaim); undo.nHeight = alternate.nHeight; undo.fCoinBase = alternate.fCoinBase; } else { @@ -1469,11 +1461,11 @@ int ApplyTxInUndo(unsigned int index, CTxUndo& txUndo, CCoinsViewCache& view, CC } // restore claim if applicable - if (fIsClaim && !undo.out.scriptPubKey.empty()) { - int nValidHeight = static_cast(nClaimValidHeight); + if (undo.fIsClaim && !undo.txout.scriptPubKey.empty()) { + int nValidHeight = static_cast(undo.nClaimValidHeight); if (nValidHeight > 0 && nValidHeight >= undo.nHeight) { - CClaimScriptUndoSpendOp undoSpend(COutPoint(out.hash, out.n), undo.out.nValue, undo.nHeight, nValidHeight); - ProcessClaim(undoSpend, trieCache, undo.out.scriptPubKey); + CClaimScriptUndoSpendOp undoSpend(COutPoint(out.hash, out.n), undo.txout.nValue, undo.nHeight, nValidHeight); + ProcessClaim(undoSpend, trieCache, undo.txout.scriptPubKey); } else { LogPrintf("%s: (txid: %s, nOut: %d) Not restoring claim/support to the claim trie because it expired before it was spent\n", __func__, out.hash.ToString(), out.n); LogPrintf("%s: nValidHeight = %d, undo.nHeight = %d, nCurrentHeight = %d\n", __func__, nValidHeight, undo.nHeight, chainActive.Height()); @@ -1484,7 +1476,8 @@ int ApplyTxInUndo(unsigned int index, CTxUndo& txUndo, CCoinsViewCache& view, CC // sure that the coin did not already exist in the cache. As we have queried for that above // using HaveCoin, we don't need to guess. When fClean is false, a coin already existed and // it is an overwrite. - view.AddCoin(out, std::move(undo), !fClean); + Coin coin(undo.txout, int(undo.nHeight), undo.fCoinBase); + view.AddCoin(out, std::move(coin), !fClean); return fClean ? DISCONNECT_OK : DISCONNECT_UNCLEAN; } @@ -2044,15 +2037,14 @@ bool CChainState::ConnectBlock(const CBlock& block, CValidationState& state, CBl UpdateCoins(tx, view, i == 0 ? undoDummy : blockundo.vtxundo.back(), pindex->nHeight); if (i > 0 && !mClaimUndoHeights.empty()) { - /* std::vector& txinUndos = blockUndo.vtxundo.back().vprevout */ - /* CTxUndo &txundo = blockUndo.vtxundo[i-1]; */ - CTxUndo& txUndo = blockundo.vtxundo.back(); + auto& txinUndos = blockundo.vtxundo.back().vprevout; for (std::map::iterator itHeight = mClaimUndoHeights.begin(); itHeight != mClaimUndoHeights.end(); ++itHeight) { - // Note: by appearing in this map, we know it's a claim so the bool is redundant - txUndo.claimInfo[itHeight->first] = { itHeight->second, true }; + txinUndos[itHeight->first].nClaimValidHeight = itHeight->second; + txinUndos[itHeight->first].fIsClaim = true; } } + // The CTxUndo vector contains the heights at which claims should be put into the trie. // This is necessary because some claims are inserted immediately into the trie, and // others are inserted after a delay, depending on the state of the claim trie at the time diff --git a/src/wallet/rpcwallet.cpp b/src/wallet/rpcwallet.cpp index 28e0767cc..95e9b534a 100644 --- a/src/wallet/rpcwallet.cpp +++ b/src/wallet/rpcwallet.cpp @@ -534,9 +534,11 @@ thoritative as long as it remains unspent and there are no other greater unspent std::vector vchValue(ParseHex(sValue)); CAmount nAmount = AmountFromValue(request.params[2]); - EnsureWalletIsUnlocked(pwallet); CScript claimScript = CScript() << OP_CLAIM_NAME << vchName << vchValue << OP_2DROP << OP_DROP; + pwallet->BlockUntilSyncedToCurrentChain(); + EnsureWalletIsUnlocked(pwallet); + //Get new address CPubKey newKey; if (!pwallet->GetKeyFromPool(newKey)) @@ -579,6 +581,7 @@ UniValue updateclaim(const JSONRPCRequest& request) std::vector vchValue(ParseHex(sValue)); CAmount nAmount = AmountFromValue(request.params[2]); + pwallet->BlockUntilSyncedToCurrentChain(); LOCK2(cs_main, pwallet->cs_wallet); auto it = pwallet->mapWallet.find(hash); if (it == pwallet->mapWallet.end()) { @@ -589,7 +592,7 @@ UniValue updateclaim(const JSONRPCRequest& request) for (uint32_t i = 0; i < wtx.tx->vout.size(); ++i) { const auto& out = wtx.tx->vout[i]; - if (pwallet->IsMine(out) & isminetype::ISMINE_SPENDABLE) + if (pwallet->IsMine(out) & isminetype::ISMINE_CLAIM) { std::vector> vvchParams; int op; @@ -619,6 +622,8 @@ UniValue updateclaim(const JSONRPCRequest& request) CCoinControl cc; cc.m_change_type = DEFAULT_ADDRESS_TYPE; + cc.Select(COutPoint(wtx.tx->GetHash(), i)); + cc.fAllowOtherInputs = true; // the doc comment on this parameter says it should be false; experience says otherwise wtxNew = SendMoney(pwallet, CTxDestination(newKey.GetID()), nAmount, false, cc, {}, {}, updateScript); break; } @@ -655,6 +660,7 @@ UniValue abandonclaim(const JSONRPCRequest& request) CKeyID address; address.SetHex(request.params[1].get_str()); + pwallet->BlockUntilSyncedToCurrentChain(); LOCK2(cs_main, pwallet->cs_wallet); auto it = pwallet->mapWallet.find(hash); if (it == pwallet->mapWallet.end()) { @@ -699,9 +705,9 @@ void ListNameClaims(const CWalletTx& wtx, CWallet* const pwallet, const std::str bool fAllAccounts = (strAccount == std::string("*")); - if ((!listReceived.empty() || nFee != 0) && (fAllAccounts || strAccount == strSentAccount)) + if ((!listSent.empty() || nFee != 0) && (fAllAccounts || strAccount == strSentAccount)) { - for (const COutputEntry& s: listReceived) + for (const COutputEntry& s: listSent) { if (!list_spent && pwallet->IsSpent(wtx.GetHash(), s.vout)) continue; @@ -718,18 +724,21 @@ void ListNameClaims(const CWalletTx& wtx, CWallet* const pwallet, const std::str if (op == OP_CLAIM_NAME) { uint160 claimId = ClaimIdHash(wtx.GetHash(), s.vout); + entry.pushKV("claimtype", "CLAIM"); entry.pushKV("claimId", claimId.GetHex()); entry.pushKV("value", HexStr(vvchParams[1].begin(), vvchParams[1].end())); } else if (op == OP_UPDATE_CLAIM) { uint160 claimId(vvchParams[1]); + entry.pushKV("claimtype", "CLAIM"); entry.pushKV("claimId", claimId.GetHex()); entry.pushKV("value", HexStr(vvchParams[2].begin(), vvchParams[2].end())); } else if (op == OP_SUPPORT_CLAIM) { uint160 claimId(vvchParams[1]); + entry.pushKV("claimtype", "SUPPORT"); entry.pushKV("supported_claimid", claimId.GetHex()); if (vvchParams.size() > 2) { entry.pushKV("value", HexStr(vvchParams[2].begin(), vvchParams[2].end())); @@ -829,6 +838,7 @@ ot been spent. Default is false.\n" nMinDepth = request.params[2].get_int(); UniValue ret(UniValue::VARR); + pwallet->BlockUntilSyncedToCurrentChain(); LOCK2(cs_main, pwallet->cs_wallet); const auto& txOrdered = pwallet->wtxOrdered; @@ -889,6 +899,7 @@ UniValue supportclaim(const JSONRPCRequest& request) std::vector vchClaimId (claimId.begin(), claimId.end()); CAmount nAmount = AmountFromValue(request.params[2]); + pwallet->BlockUntilSyncedToCurrentChain(); LOCK2(cs_main, pwallet->cs_wallet); EnsureWalletIsUnlocked(pwallet); CScript supportScript = CScript() << OP_SUPPORT_CLAIM << vchName << vchClaimId; @@ -939,6 +950,7 @@ UniValue abandonsupport(const JSONRPCRequest& request) CKeyID address; address.SetHex(request.params[1].get_str()); + pwallet->BlockUntilSyncedToCurrentChain(); LOCK2(cs_main, pwallet->cs_wallet); auto it = pwallet->mapWallet.find(hash); if (it == pwallet->mapWallet.end()) { diff --git a/src/wallet/test/claim_rpc_tests.cpp b/src/wallet/test/claim_rpc_tests.cpp new file mode 100644 index 000000000..263371cba --- /dev/null +++ b/src/wallet/test/claim_rpc_tests.cpp @@ -0,0 +1,247 @@ +#include +#include +#include +#include + +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +struct CatchWalletTestSetup: public TestingSetup { + CatchWalletTestSetup() : TestingSetup(CBaseChainParams::REGTEST) { + RegisterWalletRPCCommands(tableRPC); + + rpcfn_type rpc_method = tableRPC["createwallet"]->actor; + JSONRPCRequest req; + req.params = UniValue(UniValue::VARR); + req.params.push_back("tester_wallet"); + UniValue results = rpc_method(req); + BOOST_CHECK_EQUAL(results["name"].get_str(), "tester_wallet"); + } + ~CatchWalletTestSetup() { + rpcfn_type rpc_method = tableRPC["unloadwallet"]->actor; + JSONRPCRequest req; + req.params = UniValue(UniValue::VARR); + req.params.push_back("tester_wallet"); + rpc_method(req); + } +}; + +BOOST_FIXTURE_TEST_SUITE(claim_rpc_tests, CatchWalletTestSetup) + +double AvailableBalance() { + rpcfn_type rpc_method = tableRPC["getbalance"]->actor; + JSONRPCRequest req; + req.params = UniValue(UniValue::VARR); + UniValue results = rpc_method(req); + return results.get_real(); +} + +uint256 ClaimAName(const std::string& name, const std::string& data, const std::string& price, bool isUpdate = false) { + // pass a txid as name for update + rpcfn_type rpc_method = tableRPC[isUpdate ? "updateclaim" : "claimname"]->actor; + JSONRPCRequest req; + req.params = UniValue(UniValue::VARR); + req.params.push_back(name); + req.params.push_back(data); + req.params.push_back(price); + + UniValue results = rpc_method(req); + auto txid = results.get_str(); + uint256 ret; + ret.SetHex(txid); + return ret; +} + +uint256 SupportAName(const std::string& name, const std::string& claimId, const std::string& price) { + // pass a txid as name for update + rpcfn_type rpc_method = tableRPC["supportclaim"]->actor; + JSONRPCRequest req; + req.params = UniValue(UniValue::VARR); + req.params.push_back(name); + req.params.push_back(claimId); + req.params.push_back(price); + + UniValue results = rpc_method(req); + auto txid = results.get_str(); + uint256 ret; + ret.SetHex(txid); + return ret; +} + +UniValue LookupAllNames() { + rpcfn_type rpc_method = tableRPC["listnameclaims"]->actor; + JSONRPCRequest req; + req.params = UniValue(UniValue::VARR); + return rpc_method(req); +} + +std::vector generateBlock(int blocks = 1) { + rpcfn_type rpc_method = tableRPC["generate"]->actor; + JSONRPCRequest req; + req.params = UniValue(UniValue::VARR); + req.params.push_back(blocks); + UniValue results = rpc_method(req); + + std::vector ret; + for (auto i = 0U; i < results.size(); ++i) { + auto txid = results[i].get_str(); + uint256 id; + id.SetHex(txid); + ret.push_back(id); + } + return ret; +} + +void rollbackBlock(const std::vector& ids) { + rpcfn_type rpc_method = tableRPC["invalidateblock"]->actor; + for (auto it = ids.rbegin(); it != ids.rend(); ++it) { + JSONRPCRequest req; + req.params = UniValue(UniValue::VARR); + req.params.push_back(it->GetHex()); + rpc_method(req); + } + // totally weird that invalidateblock is async + while (GetMainSignals().CallbacksPending()) + usleep(5000); +} + +uint256 AbandonAClaim(const uint256& txid, bool isSupport = false) { + rpcfn_type pre_rpc_method = tableRPC["getrawchangeaddress"]->actor; + JSONRPCRequest pre_req; + pre_req.params = UniValue(UniValue::VARR); + pre_req.params.push_back("legacy"); + UniValue adr_hash = pre_rpc_method(pre_req); + + // pass a txid as name for update + rpcfn_type rpc_method = tableRPC[isSupport ? "abandonsupport" : "abandonclaim"]->actor; + JSONRPCRequest req; + req.params = UniValue(UniValue::VARR); + req.params.push_back(txid.GetHex()); + req.params.push_back(adr_hash.get_str()); + + try { + UniValue results = rpc_method(req); + uint256 ret; + ret.SetHex(results.get_str()); + return ret; + } + catch(...) { + return {}; + } +} + +BOOST_AUTO_TEST_CASE(claim_op_runthrough) +{ + generateBlock(105); + + BOOST_CHECK_GE(AvailableBalance(), 2.0); + + // ops for test: claimname, updateclaim, abandonclaim, listnameclaims, supportclaim, abandonsupport + // order of ops: + // claim a name + // udpate it + // support it + // abandon support + // abandon claim + // all above in reverse + + // claim a name + auto txid = ClaimAName("tester", "deadbeef", "1.0"); + auto g1 = generateBlock(); + auto looked = LookupAllNames().get_array(); + BOOST_CHECK_EQUAL(looked[0]["value"].get_str(), "deadbeef"); + BOOST_CHECK_EQUAL(looked[0]["txid"].get_str(), txid.GetHex()); + + // udpate it + auto txid2 = ClaimAName(txid.GetHex(), "deadbeef02", "1.0", true); + auto g2 = generateBlock(); + looked = LookupAllNames().get_array(); + BOOST_CHECK_EQUAL(looked[0]["value"].get_str(), "deadbeef02"); + + // support it + auto clid = looked[0]["claimId"].get_str(); + auto spid = SupportAName("tester", clid, "0.5"); + auto g3 = generateBlock(); + looked = LookupAllNames().get_array(); + BOOST_CHECK_EQUAL(looked[0]["value"].get_str(), "deadbeef02"); + BOOST_CHECK_EQUAL(looked[0]["amount"].get_real(), 1.0); + BOOST_CHECK_EQUAL(looked[1]["claimtype"].get_str(), "SUPPORT"); + BOOST_CHECK_EQUAL(looked[1]["name"].get_str(), "tester"); + BOOST_CHECK_EQUAL(looked[1]["amount"].get_real(), 0.5); + BOOST_CHECK_EQUAL(looked[1]["txid"].get_str(), spid.GetHex()); + BOOST_CHECK_EQUAL(looked[1]["supported_claimid"].get_str(), clid); + + // abandon support + auto aid1 = AbandonAClaim(spid, true); + BOOST_CHECK(!aid1.IsNull()); + auto g4 = generateBlock(); + looked = LookupAllNames().get_array(); + BOOST_CHECK_EQUAL(looked.size(), 1); + BOOST_CHECK_EQUAL(looked[0]["value"].get_str(), "deadbeef02"); + BOOST_CHECK_EQUAL(looked[0]["claimtype"].get_str(), "CLAIM"); + + // abandon claim + auto aid2 = AbandonAClaim(txid); + BOOST_CHECK(aid2.IsNull()); + aid2 = AbandonAClaim(txid2); + BOOST_CHECK(!aid2.IsNull()); + auto g5 = generateBlock(); + looked = LookupAllNames().get_array(); + BOOST_CHECK_EQUAL(looked.size(), 0); + + // all above in reverse + // except that it doesn't work + // TODO: understand why it doesn't work + + /* + // re-add claim + rollbackBlock(g5); + looked = LookupAllNames().get_array(); + BOOST_CHECK_EQUAL(looked.size(), 1); + BOOST_CHECK_EQUAL(looked[0]["name"].get_str(), "tester"); + BOOST_CHECK_EQUAL(looked[0]["value"].get_str(), "deadbeef02"); + + // re-add support + rollbackBlock(g4); + looked = LookupAllNames().get_array(); + BOOST_CHECK_EQUAL(looked[0]["value"].get_str(), "deadbeef02"); + BOOST_CHECK_EQUAL(looked[0]["amount"].get_real(), 1.0); + BOOST_CHECK_EQUAL(looked[1]["claimtype"].get_str(), "SUPPORT"); + BOOST_CHECK_EQUAL(looked[1]["name"].get_str(), "tester"); + BOOST_CHECK_EQUAL(looked[1]["amount"].get_real(), 0.5); + BOOST_CHECK_EQUAL(looked[1]["txid"].get_str(), spid.GetHex()); + BOOST_CHECK_EQUAL(looked[1]["supported_claimid"].get_str(), clid); + + // remove support + rollbackBlock(g3); + looked = LookupAllNames().get_array(); + BOOST_CHECK_EQUAL(looked[0]["value"].get_str(), "deadbeef02"); + + // remove update + rollbackBlock(g2); + looked = LookupAllNames().get_array(); + BOOST_CHECK_EQUAL(looked[0]["value"].get_str(), "deadbeef"); + + // remove claim + rollbackBlock(g1); + looked = LookupAllNames().get_array(); + BOOST_CHECK_EQUAL(looked.size(), 0); + + generateBlock(); + looked = LookupAllNames().get_array(); + BOOST_CHECK_EQUAL(looked.size(), 0); + */ +} + +BOOST_AUTO_TEST_SUITE_END() \ No newline at end of file diff --git a/src/wallet/wallet.cpp b/src/wallet/wallet.cpp index 4ff0b2094..ec9dc87ad 100644 --- a/src/wallet/wallet.cpp +++ b/src/wallet/wallet.cpp @@ -2376,12 +2376,15 @@ void CWallet::AvailableCoins(std::vector &vCoins, bool fOnlySafe, const } // spending claims or supports requires specific selection: - auto claimSpendRequested = (mine & ISMINE_CLAIM) || (mine & ISMINE_SUPPORT); - claimSpendRequested &= coinControl && coinControl->IsSelected(COutPoint(entry.first, i)); + auto isClaimCoin = (mine & ISMINE_CLAIM) || (mine & ISMINE_SUPPORT); + auto claimSpendRequested = isClaimCoin && coinControl && coinControl->IsSelected(COutPoint(entry.first, i)); bool solvable = IsSolvable(*this, pcoin->tx->vout[i].scriptPubKey); bool spendable = claimSpendRequested || ((mine & ISMINE_SPENDABLE) != ISMINE_NO) || (((mine & ISMINE_WATCH_ONLY) != ISMINE_NO) && (coinControl && coinControl->fAllowWatchOnly && solvable)); + if (spendable && isClaimCoin && !claimSpendRequested) + continue; // double check + vCoins.push_back(COutput(pcoin, i, nDepth, spendable, solvable, safeTx, (coinControl && coinControl->fAllowWatchOnly))); // Checks the sum amount of all UTXO's.