parent
3340fcb85a
commit
d3f29be779
9 changed files with 328 additions and 126 deletions
|
@ -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)
|
|
@ -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 \
|
||||||
|
|
|
@ -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);
|
||||||
|
|
124
src/undo.h
124
src/undo.h
|
@ -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);
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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()) {
|
||||||
|
|
247
src/wallet/test/claim_rpc_tests.cpp
Normal file
247
src/wallet/test/claim_rpc_tests.cpp
Normal 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()
|
|
@ -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.
|
||||||
|
|
Loading…
Add table
Reference in a new issue