Undo compatibility (#281)

* added test for claimname RPC
This commit is contained in:
Brannon King 2019-05-23 11:46:37 -06:00 committed by Anthony Fieroni
parent 3340fcb85a
commit d3f29be779
9 changed files with 328 additions and 126 deletions

View file

@ -51,6 +51,6 @@ add_executable(lbrycrd-cli src/bitcoin-cli.cpp ${sources})
add_executable(lbrycrd-tx src/bitcoin-tx.cpp ${sources}) add_executable(lbrycrd-tx src/bitcoin-tx.cpp ${sources})
add_executable(lbrycrdd src/bitcoind.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}) add_executable(test_lbrycrd ${tests} ${sources})
target_include_directories(test_lbrycrd PRIVATE src/test) target_include_directories(test_lbrycrd PRIVATE src/test)

View file

@ -167,6 +167,7 @@ endif
if ENABLE_WALLET if ENABLE_WALLET
BITCOIN_TESTS += \ BITCOIN_TESTS += \
wallet/test/db_tests.cpp \ wallet/test/db_tests.cpp \
wallet/test/claim_rpc_tests.cpp \
wallet/test/db_tests.cpp \ wallet/test/db_tests.cpp \
wallet/test/psbt_wallet_tests.cpp \ wallet/test/psbt_wallet_tests.cpp \
wallet/test/wallet_tests.cpp \ wallet/test/wallet_tests.cpp \

View file

@ -410,11 +410,8 @@ BOOST_AUTO_TEST_CASE(updatecoins_simulation_test)
// restore inputs // restore inputs
if (!tx.IsCoinBase()) { if (!tx.IsCoinBase()) {
const COutPoint &out = tx.vin[0].prevout; const COutPoint &out = tx.vin[0].prevout;
Coin coin = undo.vprevout[0];
CClaimTrieCache trieCache(pclaimTrie); CClaimTrieCache trieCache(pclaimTrie);
ApplyTxInUndo(0, undo, *(stack.back()), trieCache, out); ApplyTxInUndo(0, undo, *(stack.back()), trieCache, out);
// return coin
undo.vprevout[0] = coin;
} }
// Store as a candidate for reconnection // Store as a candidate for reconnection
disconnected_coins.insert(utxod->first); disconnected_coins.insert(utxod->first);

View file

@ -1,5 +1,5 @@
// Copyright (c) 2009-2010 Satoshi Nakamoto // 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 // Distributed under the MIT software license, see the accompanying
// file COPYING or http://www.opensource.org/licenses/mit-license.php. // file COPYING or http://www.opensource.org/licenses/mit-license.php.
@ -7,115 +7,67 @@
#define BITCOIN_UNDO_H #define BITCOIN_UNDO_H
#include <coins.h> #include <coins.h>
#include <claimtrie.h>
#include <compressor.h> #include <compressor.h>
#include <consensus/consensus.h> #include <claimtrie.h>
#include <primitives/transaction.h> #include <primitives/transaction.h>
#include <serialize.h> #include <serialize.h>
#include <version.h> #include <version.h>
/** Undo information for a CTxIn /** Undo information for a CTxIn
* *
* Contains the prevout's CTxOut being spent, and its metadata as well * Contains the prevout's CTxOut being spent, and if this was the
* (coinbase or not, height). The serialization contains a dummy value of * last output of the affected transaction, its metadata as well
* zero. This is compatible with older versions which expect to see * (coinbase or not, height, transaction version)
* the transaction version there.
*/ */
class TxInUndoSerializer class CTxInUndo
{ {
const Coin* txout; uint32_t nDeprecated1 = 0; // if the outpoint was the last unspent: its version
// whether the outpoint was the last unspent bool fDeprecated2 = false; // 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: public:
template<typename Stream> CTxOut txout; // the txout data before being spent
void Serialize(Stream &s) const { bool fCoinBase; // if the outpoint was the last unspent: whether it belonged to a coinbase
::Serialize(s, VARINT(txout->nHeight * 4 + (txout->fCoinBase ? 2u : 0u) + (fLastUnspent ? 1u : 0u))); uint32_t nHeight; // if the outpoint was the last unspent: its height
if (fLastUnspent) 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
::Serialize(s, VARINT(this->nVersion)); bool fIsClaim; // if the outpoint was a claim or support
::Serialize(s, CTxOutCompressor(REF(txout->out)));
::Serialize(s, VARINT(nClaimValidHeight)); CTxInUndo() : txout(), fCoinBase(false), nHeight(0), nClaimValidHeight(0), fIsClaim(false) {}
::Serialize(s, fIsClaim); 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 <typename Stream, typename Operation>
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<typename Stream>
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(), PROTOCOL_VERSION);
static const size_t MAX_INPUTS_PER_BLOCK = MAX_BLOCK_WEIGHT / MIN_TRANSACTION_INPUT_WEIGHT;
/** Undo information for a CTransaction */ /** Undo information for a CTransaction */
class CTxUndo class CTxUndo
{ {
struct claimState
{
ADD_SERIALIZE_METHODS;
template <typename Stream, typename Operation>
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<unsigned int, claimState>;
public: public:
// undo information for all txins // undo information for all txins
std::vector<Coin> vprevout; std::vector<CTxInUndo> vprevout;
claimStateMap claimInfo;
CTxUndo() {
}
ADD_SERIALIZE_METHODS; ADD_SERIALIZE_METHODS;
template <typename Stream, typename Operation> template <typename Stream, typename Operation>
inline void SerializationOp(Stream& s, Operation ser_action) { inline void SerializationOp(Stream& s, Operation ser_action) {
READWRITE(vprevout); READWRITE(vprevout);
READWRITE(claimInfo);
} }
}; };

View file

@ -1482,12 +1482,11 @@ void UpdateCoins(const CTransaction& tx, CCoinsViewCache& inputs, CTxUndo &txund
// mark inputs spent // mark inputs spent
if (!tx.IsCoinBase()) { if (!tx.IsCoinBase()) {
txundo.vprevout.reserve(tx.vin.size()); txundo.vprevout.reserve(tx.vin.size());
for(size_t i = 0; i < tx.vin.size(); i++) { Coin coin;
const CTxIn& txin = tx.vin[i]; for (const CTxIn &txin : tx.vin) {
Coin coin{};
bool is_spent = inputs.SpendCoin(txin.prevout, &coin); bool is_spent = inputs.SpendCoin(txin.prevout, &coin);
assert(is_spent); assert(is_spent);
txundo.vprevout.push_back(coin); txundo.vprevout.emplace_back(coin.out, coin.IsCoinBase(), int(coin.nHeight));
} }
} }
// add outputs // add outputs
@ -1707,24 +1706,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) int ApplyTxInUndo(unsigned int index, CTxUndo& txUndo, CCoinsViewCache& view, CClaimTrieCache& trieCache, const COutPoint& out)
{ {
Coin& undo = txUndo.vprevout[index]; auto& 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;
}
bool fClean = true; bool fClean = true;
if (view.HaveCoin(out)) fClean = false; // overwriting transaction output if (view.HaveCoin(out)) fClean = false; // overwriting transaction output
if (undo.nHeight == 0) { 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' // 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. // outputs. This implies that it must be present for some other output of the same tx.
const Coin& alternate = AccessByTxid(view, out.hash); const Coin& alternate = AccessByTxid(view, out.hash);
if (!alternate.IsSpent()) { if (!alternate.IsSpent()) {
assert(!undo.fIsClaim);
undo.nHeight = alternate.nHeight; undo.nHeight = alternate.nHeight;
undo.fCoinBase = alternate.fCoinBase; undo.fCoinBase = alternate.fCoinBase;
} else { } else {
@ -1733,11 +1725,11 @@ int ApplyTxInUndo(unsigned int index, CTxUndo& txUndo, CCoinsViewCache& view, CC
} }
// restore claim if applicable // restore claim if applicable
if (fIsClaim && !undo.out.scriptPubKey.empty()) { if (undo.fIsClaim && !undo.txout.scriptPubKey.empty()) {
int nValidHeight = static_cast<int>(nClaimValidHeight); int nValidHeight = static_cast<int>(undo.nClaimValidHeight);
if (nValidHeight > 0 && nValidHeight >= undo.nHeight) { if (nValidHeight > 0 && nValidHeight >= undo.nHeight) {
CClaimScriptUndoSpendOp undoSpend(COutPoint(out.hash, out.n), undo.out.nValue, undo.nHeight, nValidHeight); CClaimScriptUndoSpendOp undoSpend(COutPoint(out.hash, out.n), undo.txout.nValue, undo.nHeight, nValidHeight);
ProcessClaim(undoSpend, trieCache, undo.out.scriptPubKey); ProcessClaim(undoSpend, trieCache, undo.txout.scriptPubKey);
} else { } 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: (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()); LogPrintf("%s: nValidHeight = %d, undo.nHeight = %d, nCurrentHeight = %d\n", __func__, nValidHeight, undo.nHeight, chainActive.Height());
@ -1748,7 +1740,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 // 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 // using HaveCoin, we don't need to guess. When fClean is false, a coin already existed and
// it is an overwrite. // 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; return fClean ? DISCONNECT_OK : DISCONNECT_UNCLEAN;
} }
@ -2319,15 +2312,14 @@ bool CChainState::ConnectBlock(const CBlock& block, CValidationState& state, CBl
UpdateCoins(tx, view, i == 0 ? undoDummy : blockundo.vtxundo.back(), pindex->nHeight); UpdateCoins(tx, view, i == 0 ? undoDummy : blockundo.vtxundo.back(), pindex->nHeight);
if (i > 0 && !mClaimUndoHeights.empty()) if (i > 0 && !mClaimUndoHeights.empty())
{ {
/* std::vector<CTxInUndo>& txinUndos = blockUndo.vtxundo.back().vprevout */ auto& txinUndos = blockundo.vtxundo.back().vprevout;
/* CTxUndo &txundo = blockUndo.vtxundo[i-1]; */
CTxUndo& txUndo = blockundo.vtxundo.back();
for (std::map<unsigned int, unsigned int>::iterator itHeight = mClaimUndoHeights.begin(); itHeight != mClaimUndoHeights.end(); ++itHeight) for (std::map<unsigned int, unsigned int>::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 txinUndos[itHeight->first].nClaimValidHeight = itHeight->second;
txUndo.claimInfo[itHeight->first] = { itHeight->second, true }; txinUndos[itHeight->first].fIsClaim = true;
} }
} }
// The CTxUndo vector contains the heights at which claims should be put into the trie. // 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 // 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 // others are inserted after a delay, depending on the state of the claim trie at the time

View file

@ -62,16 +62,14 @@ IsMineResult IsMineInner(const CWallet& keystore, const CScript& scriptPubKey, I
{ {
int op = 0; int op = 0;
IsMineResult ret = IsMineResult::NO; IsMineResult ret = IsMineResult::NO;
IsMineResult claim_ret = IsMineResult::NO;
CScript strippedScriptPubKey = StripClaimScriptPrefix(scriptPubKey, op); CScript strippedScriptPubKey = StripClaimScriptPrefix(scriptPubKey, op);
if (strippedScriptPubKey != scriptPubKey) IsMineResult claim_ret = ((op == OP_CLAIM_NAME || op == OP_UPDATE_CLAIM) ? IsMineResult::CLAIM :
claim_ret = ((op == OP_CLAIM_NAME || op == OP_UPDATE_CLAIM) ? IsMineResult::CLAIM : ((op == OP_SUPPORT_CLAIM) ? IsMineResult::SUPPORT :
((op == OP_SUPPORT_CLAIM) ? IsMineResult::SUPPORT : IsMineResult::NO));
IsMineResult::NO));
std::vector<valtype> vSolutions; std::vector<valtype> vSolutions;
txnouttype whichType = Solver(scriptPubKey, vSolutions); txnouttype whichType = Solver(strippedScriptPubKey, vSolutions);
CKeyID keyID; CKeyID keyID;
switch (whichType) switch (whichType)

View file

@ -381,9 +381,11 @@ thoritative as long as it remains unspent and there are no other greater unspent
std::vector<unsigned char> vchValue(ParseHex(sValue)); std::vector<unsigned char> vchValue(ParseHex(sValue));
CAmount nAmount = AmountFromValue(request.params[2]); CAmount nAmount = AmountFromValue(request.params[2]);
EnsureWalletIsUnlocked(pwallet);
CScript claimScript = CScript() << OP_CLAIM_NAME << vchName << vchValue << OP_2DROP << OP_DROP; CScript claimScript = CScript() << OP_CLAIM_NAME << vchName << vchValue << OP_2DROP << OP_DROP;
pwallet->BlockUntilSyncedToCurrentChain();
EnsureWalletIsUnlocked(pwallet);
//Get new address //Get new address
CPubKey newKey; CPubKey newKey;
if (!pwallet->GetKeyFromPool(newKey)) if (!pwallet->GetKeyFromPool(newKey))
@ -426,6 +428,7 @@ UniValue updateclaim(const JSONRPCRequest& request)
std::vector<unsigned char> vchValue(ParseHex(sValue)); std::vector<unsigned char> vchValue(ParseHex(sValue));
CAmount nAmount = AmountFromValue(request.params[2]); CAmount nAmount = AmountFromValue(request.params[2]);
pwallet->BlockUntilSyncedToCurrentChain();
LOCK2(cs_main, pwallet->cs_wallet); LOCK2(cs_main, pwallet->cs_wallet);
auto it = pwallet->mapWallet.find(hash); auto it = pwallet->mapWallet.find(hash);
if (it == pwallet->mapWallet.end()) { if (it == pwallet->mapWallet.end()) {
@ -436,7 +439,7 @@ UniValue updateclaim(const JSONRPCRequest& request)
for (uint32_t i = 0; i < wtx.tx->vout.size(); ++i) for (uint32_t i = 0; i < wtx.tx->vout.size(); ++i)
{ {
const auto& out = wtx.tx->vout[i]; const auto& out = wtx.tx->vout[i];
if (pwallet->IsMine(out) & isminetype::ISMINE_SPENDABLE) if (pwallet->IsMine(out) & isminetype::ISMINE_CLAIM)
{ {
std::vector<std::vector<unsigned char>> vvchParams; std::vector<std::vector<unsigned char>> vvchParams;
int op; int op;
@ -466,6 +469,8 @@ UniValue updateclaim(const JSONRPCRequest& request)
CCoinControl cc; CCoinControl cc;
cc.m_change_type = DEFAULT_ADDRESS_TYPE; 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); wtxNew = SendMoney(pwallet, CTxDestination(newKey.GetID()), nAmount, false, cc, {}, {}, updateScript);
break; break;
} }
@ -502,6 +507,7 @@ UniValue abandonclaim(const JSONRPCRequest& request)
CKeyID address; CKeyID address;
address.SetHex(request.params[1].get_str()); address.SetHex(request.params[1].get_str());
pwallet->BlockUntilSyncedToCurrentChain();
LOCK2(cs_main, pwallet->cs_wallet); LOCK2(cs_main, pwallet->cs_wallet);
auto it = pwallet->mapWallet.find(hash); auto it = pwallet->mapWallet.find(hash);
if (it == pwallet->mapWallet.end()) { if (it == pwallet->mapWallet.end()) {
@ -546,9 +552,9 @@ void ListNameClaims(const CWalletTx& wtx, CWallet* const pwallet, const std::str
bool fAllAccounts = (strAccount == std::string("*")); 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)) if (!list_spent && pwallet->IsSpent(wtx.GetHash(), s.vout))
continue; continue;
@ -565,18 +571,21 @@ void ListNameClaims(const CWalletTx& wtx, CWallet* const pwallet, const std::str
if (op == OP_CLAIM_NAME) if (op == OP_CLAIM_NAME)
{ {
uint160 claimId = ClaimIdHash(wtx.GetHash(), s.vout); uint160 claimId = ClaimIdHash(wtx.GetHash(), s.vout);
entry.pushKV("claimtype", "CLAIM");
entry.pushKV("claimId", claimId.GetHex()); entry.pushKV("claimId", claimId.GetHex());
entry.pushKV("value", HexStr(vvchParams[1].begin(), vvchParams[1].end())); entry.pushKV("value", HexStr(vvchParams[1].begin(), vvchParams[1].end()));
} }
else if (op == OP_UPDATE_CLAIM) else if (op == OP_UPDATE_CLAIM)
{ {
uint160 claimId(vvchParams[1]); uint160 claimId(vvchParams[1]);
entry.pushKV("claimtype", "CLAIM");
entry.pushKV("claimId", claimId.GetHex()); entry.pushKV("claimId", claimId.GetHex());
entry.pushKV("value", HexStr(vvchParams[2].begin(), vvchParams[2].end())); entry.pushKV("value", HexStr(vvchParams[2].begin(), vvchParams[2].end()));
} }
else if (op == OP_SUPPORT_CLAIM) else if (op == OP_SUPPORT_CLAIM)
{ {
uint160 claimId(vvchParams[1]); uint160 claimId(vvchParams[1]);
entry.pushKV("claimtype", "SUPPORT");
entry.pushKV("supported_claimid", claimId.GetHex()); entry.pushKV("supported_claimid", claimId.GetHex());
if (vvchParams.size() > 2) { if (vvchParams.size() > 2) {
entry.pushKV("value", HexStr(vvchParams[2].begin(), vvchParams[2].end())); entry.pushKV("value", HexStr(vvchParams[2].begin(), vvchParams[2].end()));
@ -676,6 +685,7 @@ ot been spent. Default is false.\n"
nMinDepth = request.params[2].get_int(); nMinDepth = request.params[2].get_int();
UniValue ret(UniValue::VARR); UniValue ret(UniValue::VARR);
pwallet->BlockUntilSyncedToCurrentChain();
LOCK2(cs_main, pwallet->cs_wallet); LOCK2(cs_main, pwallet->cs_wallet);
const auto& txOrdered = pwallet->wtxOrdered; const auto& txOrdered = pwallet->wtxOrdered;
@ -736,6 +746,7 @@ UniValue supportclaim(const JSONRPCRequest& request)
std::vector<unsigned char> vchClaimId (claimId.begin(), claimId.end()); std::vector<unsigned char> vchClaimId (claimId.begin(), claimId.end());
CAmount nAmount = AmountFromValue(request.params[2]); CAmount nAmount = AmountFromValue(request.params[2]);
pwallet->BlockUntilSyncedToCurrentChain();
LOCK2(cs_main, pwallet->cs_wallet); LOCK2(cs_main, pwallet->cs_wallet);
EnsureWalletIsUnlocked(pwallet); EnsureWalletIsUnlocked(pwallet);
CScript supportScript = CScript() << OP_SUPPORT_CLAIM << vchName << vchClaimId; CScript supportScript = CScript() << OP_SUPPORT_CLAIM << vchName << vchClaimId;
@ -786,6 +797,7 @@ UniValue abandonsupport(const JSONRPCRequest& request)
CKeyID address; CKeyID address;
address.SetHex(request.params[1].get_str()); address.SetHex(request.params[1].get_str());
pwallet->BlockUntilSyncedToCurrentChain();
LOCK2(cs_main, pwallet->cs_wallet); LOCK2(cs_main, pwallet->cs_wallet);
auto it = pwallet->mapWallet.find(hash); auto it = pwallet->mapWallet.find(hash);
if (it == pwallet->mapWallet.end()) { if (it == pwallet->mapWallet.end()) {

View file

@ -0,0 +1,247 @@
#include <memory>
#include <set>
#include <utility>
#include <vector>
#include <wallet/rpcwallet.h>
#include <wallet/wallet.h>
#include <consensus/validation.h>
#include <rpc/server.h>
#include <test/test_bitcoin.h>
#include <validation.h>
#include <wallet/coincontrol.h>
#include <wallet/test/wallet_test_fixture.h>
#include <policy/policy.h>
#include <boost/test/unit_test.hpp>
#include <univalue.h>
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<uint256> 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<uint256> 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<uint256>& 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()

View file

@ -2599,12 +2599,15 @@ void CWallet::AvailableCoins(interfaces::Chain::Lock& locked_chain, std::vector<
continue; continue;
} }
// spending claims or supports requires specific selection: // spending claims or supports requires specific selection:
auto claimSpendRequested = (mine & ISMINE_CLAIM) || (mine & ISMINE_SUPPORT); auto isClaimCoin = (mine & ISMINE_CLAIM) || (mine & ISMINE_SUPPORT);
claimSpendRequested &= coinControl && coinControl->IsSelected(COutPoint(entry.first, i)); auto claimSpendRequested = isClaimCoin && coinControl && coinControl->IsSelected(COutPoint(entry.first, i));
bool solvable = IsSolvable(*this, wtx.tx->vout[i].scriptPubKey); bool solvable = IsSolvable(*this, wtx.tx->vout[i].scriptPubKey);
bool spendable = claimSpendRequested || ((mine & ISMINE_SPENDABLE) != ISMINE_NO) || (((mine & ISMINE_WATCH_ONLY) != ISMINE_NO) && (coinControl && coinControl->fAllowWatchOnly && solvable)); 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(&wtx, i, nDepth, spendable, solvable, safeTx, (coinControl && coinControl->fAllowWatchOnly))); vCoins.push_back(COutput(&wtx, i, nDepth, spendable, solvable, safeTx, (coinControl && coinControl->fAllowWatchOnly)));
// Checks the sum amount of all UTXO's. // Checks the sum amount of all UTXO's.