Support segwit addresses in combination with claim and support ops #295

Closed
BrannonKing wants to merge 1 commit from support_segwit_tails_on_ops into fix_hdr
11 changed files with 229 additions and 167 deletions

View file

@ -3,6 +3,7 @@
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
#include <bloom.h>
#include <nameclaim.h>
#include <primitives/transaction.h>
#include <hash.h>
@ -166,7 +167,8 @@ bool CBloomFilter::IsRelevantAndUpdate(const CTransaction& tx)
{
txnouttype type;
std::vector<std::vector<unsigned char> > vSolutions;
if (Solver(txout.scriptPubKey, type, vSolutions) &&
const CScript& scriptPubKey = StripClaimScriptPrefix(txout.scriptPubKey);
if (Solver(scriptPubKey, type, vSolutions) &&
(type == TX_PUBKEY || type == TX_MULTISIG))
insert(COutPoint(hash, i));
}

View file

@ -154,7 +154,8 @@ int64_t GetTransactionSigOpCost(const CTransaction& tx, const CCoinsViewCache& i
const Coin& coin = inputs.AccessCoin(tx.vin[i].prevout);
assert(!coin.IsSpent());
const CTxOut &prevout = coin.out;
nSigOps += CountWitnessSigOps(tx.vin[i].scriptSig, prevout.scriptPubKey, &tx.vin[i].scriptWitness, flags);
const CScript& scriptPubKey = StripClaimScriptPrefix(prevout.scriptPubKey);
nSigOps += CountWitnessSigOps(tx.vin[i].scriptSig, scriptPubKey, &tx.vin[i].scriptWitness, flags);
}
return nSigOps;
}

View file

@ -208,7 +208,7 @@ bool IsWitnessStandard(const CTransaction& tx, const CCoinsViewCache& mapInputs)
const CTxOut &prev = mapInputs.AccessCoin(tx.vin[i].prevout).out;
// get the scriptPubKey corresponding to this input:
CScript prevScript = prev.scriptPubKey;
CScript prevScript = StripClaimScriptPrefix(prev.scriptPubKey);
if (prevScript.IsPayToScriptHash()) {
std::vector <std::vector<unsigned char> > stack;

View file

@ -171,6 +171,9 @@ static const CRPCConvertParam vRPCConvertParams[] =
{ "rescanblockchain", 0, "start_height"},
{ "rescanblockchain", 1, "stop_height"},
{ "createwallet", 1, "disable_private_keys"},
{ "listnameclaims", 0, "includesupports"},
{ "listnameclaims", 1, "activeonly"},
{ "listnameclaims", 2, "minconf"},
};
class CRPCConvertTable

View file

@ -4,6 +4,7 @@
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
#include <script/interpreter.h>
#include <nameclaim.h>
#include <crypto/ripemd160.h>
#include <crypto/sha1.h>
@ -1505,10 +1506,13 @@ bool VerifyScript(const CScript& scriptSig, const CScript& scriptPubKey, const C
return set_error(serror, SCRIPT_ERR_EVAL_FALSE);
// Bare witness programs
const CScript& strippedScriptPubKey = StripClaimScriptPrefix(scriptPubKey);
int witnessversion;
std::vector<unsigned char> witnessprogram;
if (flags & SCRIPT_VERIFY_WITNESS) {
if (scriptPubKey.IsWitnessProgram(witnessversion, witnessprogram)) {
if (strippedScriptPubKey.IsWitnessProgram(witnessversion, witnessprogram)) {
hadWitness = true;
if (scriptSig.size() != 0) {
// The scriptSig must be _exactly_ CScript(), otherwise we reintroduce malleability.
@ -1524,7 +1528,7 @@ bool VerifyScript(const CScript& scriptSig, const CScript& scriptPubKey, const C
}
// Additional validation for spend-to-script-hash transactions:
if ((flags & SCRIPT_VERIFY_P2SH) && scriptPubKey.IsPayToScriptHash())
if ((flags & SCRIPT_VERIFY_P2SH) && strippedScriptPubKey.IsPayToScriptHash())
{
// scriptSig must be literals-only or validation fails
if (!scriptSig.IsPushOnly())

View file

@ -40,8 +40,6 @@ enum class IsMineResult
WATCH_ONLY = 1, //! Included in watch-only balance
SPENDABLE = 2, //! Included in all balances
INVALID = 3, //! Not spendable by anyone (uncompressed pubkey in segwit, P2SH inside P2SH or witness, witness inside witness)
CLAIM = 4,
SUPPORT = 5,
};
bool PermitsUncompressed(IsMineSigVersion sigversion)
@ -60,17 +58,11 @@ bool HaveKeys(const std::vector<valtype>& pubkeys, const CKeyStore& keystore)
IsMineResult IsMineInner(const CKeyStore& keystore, const CScript& scriptPubKey, IsMineSigVersion sigversion)
{
int op = 0;
IsMineResult ret = IsMineResult::NO;
CScript strippedScriptPubKey = StripClaimScriptPrefix(scriptPubKey, op);
IsMineResult claim_ret = ((op == OP_CLAIM_NAME || op == OP_UPDATE_CLAIM) ? IsMineResult::CLAIM :
((op == OP_SUPPORT_CLAIM) ? IsMineResult::SUPPORT :
IsMineResult::NO));
std::vector<valtype> vSolutions;
txnouttype whichType;
Solver(strippedScriptPubKey, whichType, vSolutions);
Solver(scriptPubKey, whichType, vSolutions);
CKeyID keyID;
switch (whichType)
@ -85,7 +77,7 @@ IsMineResult IsMineInner(const CKeyStore& keystore, const CScript& scriptPubKey,
return IsMineResult::INVALID;
}
if (keystore.HaveKey(keyID)) {
ret = std::max(claim_ret, IsMineResult::SPENDABLE);
ret = std::max(ret, IsMineResult::SPENDABLE);
}
break;
case TX_WITNESS_V0_KEYHASH:
@ -100,10 +92,6 @@ IsMineResult IsMineInner(const CKeyStore& keystore, const CScript& scriptPubKey,
// This also applies to the P2WSH case.
break;
}
// Claims are not explicitly supported on Witness v0
// Transactions, and instead of supporting the wrapped inner
// tx, we are ignoring this type at this time (consistent with
// previous releases).
ret = std::max(ret, IsMineInner(keystore, GetScriptForDestination(CKeyID(uint160(vSolutions[0]))), IsMineSigVersion::WITNESS_V0));
break;
}
@ -116,7 +104,7 @@ IsMineResult IsMineInner(const CKeyStore& keystore, const CScript& scriptPubKey,
}
}
if (keystore.HaveKey(keyID)) {
ret = std::max(claim_ret, IsMineResult::SPENDABLE);
ret = std::max(ret, IsMineResult::SPENDABLE);
}
break;
case TX_SCRIPTHASH:
@ -128,7 +116,7 @@ IsMineResult IsMineInner(const CKeyStore& keystore, const CScript& scriptPubKey,
CScriptID scriptID = CScriptID(uint160(vSolutions[0]));
CScript subscript;
if (keystore.GetCScript(scriptID, subscript)) {
ret = std::max(claim_ret, IsMineInner(keystore, subscript, IsMineSigVersion::P2SH));
ret = std::max(ret, IsMineInner(keystore, subscript, IsMineSigVersion::P2SH));
}
break;
}
@ -146,10 +134,6 @@ IsMineResult IsMineInner(const CKeyStore& keystore, const CScript& scriptPubKey,
CScriptID scriptID = CScriptID(hash);
CScript subscript;
if (keystore.GetCScript(scriptID, subscript)) {
// Claims are not explicitly supported on Witness v0
// Transactions, and instead of supporting the wrapped inner
// tx, we are ignoring this type at this time (consistent with
// previous releases).
ret = std::max(ret, IsMineInner(keystore, subscript, IsMineSigVersion::WITNESS_V0));
}
break;
@ -176,14 +160,14 @@ IsMineResult IsMineInner(const CKeyStore& keystore, const CScript& scriptPubKey,
}
}
if (HaveKeys(keys, keystore)) {
ret = std::max(claim_ret, IsMineResult::SPENDABLE);
ret = std::max(ret, IsMineResult::SPENDABLE);
}
break;
}
}
if (ret == IsMineResult::NO && keystore.HaveWatchOnly(scriptPubKey)) {
ret = std::max(claim_ret, IsMineResult::WATCH_ONLY);
ret = std::max(ret, IsMineResult::WATCH_ONLY);
}
return ret;
}
@ -192,18 +176,22 @@ IsMineResult IsMineInner(const CKeyStore& keystore, const CScript& scriptPubKey,
isminetype IsMine(const CKeyStore& keystore, const CScript& scriptPubKey)
{
switch (IsMineInner(keystore, scriptPubKey, IsMineSigVersion::TOP)) {
isminetype flags = ISMINE_NO;
int op;
CScript strippedScriptPubKey = StripClaimScriptPrefix(scriptPubKey, op);
if (op == OP_CLAIM_NAME || op == OP_UPDATE_CLAIM)
flags = ISMINE_CLAIM;
else if (op == OP_SUPPORT_CLAIM)
flags = ISMINE_SUPPORT;
switch (IsMineInner(keystore, strippedScriptPubKey, IsMineSigVersion::TOP)) {
case IsMineResult::INVALID:
case IsMineResult::NO:
return ISMINE_NO;
case IsMineResult::WATCH_ONLY:
return ISMINE_WATCH_ONLY;
return ISMINE_WATCH_ONLY; // addresses we're watching are never considered our claim or support -- but should they be?
case IsMineResult::SPENDABLE:
return ISMINE_SPENDABLE;
case IsMineResult::CLAIM:
return ISMINE_CLAIM;
case IsMineResult::SUPPORT:
return ISMINE_SUPPORT;
return isminetype(ISMINE_SPENDABLE | flags);
}
assert(false);
}

View file

@ -21,7 +21,9 @@ enum isminetype
ISMINE_SPENDABLE = 2,
ISMINE_CLAIM = 4,
ISMINE_SUPPORT = 8,
ISMINE_ALL = ISMINE_WATCH_ONLY | ISMINE_SPENDABLE
ISMINE_ALL = ISMINE_WATCH_ONLY | ISMINE_SPENDABLE,
ISMINE_STAKE = ISMINE_CLAIM | ISMINE_SUPPORT,
ISMINE_SPENDABLE_OR_STAKE = ISMINE_SPENDABLE | ISMINE_STAKE
};
/** used for bitflags of isminetype */
typedef uint8_t isminefilter;

View file

@ -218,7 +218,7 @@ UniValue abortrescan(const JSONRPCRequest& request)
static void ImportAddress(CWallet*, const CTxDestination& dest, const std::string& strLabel);
static void ImportScript(CWallet* const pwallet, const CScript& script, const std::string& strLabel, bool isRedeemScript) EXCLUSIVE_LOCKS_REQUIRED(pwallet->cs_wallet)
{
if (!isRedeemScript && ::IsMine(*pwallet, script) == ISMINE_SPENDABLE) {
if (!isRedeemScript && (::IsMine(*pwallet, script) & ISMINE_SPENDABLE)) {
throw JSONRPCError(RPC_WALLET_ERROR, "The wallet already contains the private key for this address or script");
}
@ -902,7 +902,7 @@ static UniValue ProcessImport(CWallet * const pwallet, const UniValue& data, con
CScript redeemDestination = GetScriptForDestination(redeem_id);
if (::IsMine(*pwallet, redeemDestination) == ISMINE_SPENDABLE) {
if (::IsMine(*pwallet, redeemDestination) & ISMINE_SPENDABLE) {
throw JSONRPCError(RPC_WALLET_ERROR, "The wallet already contains the private key for this address or script");
}
@ -986,7 +986,7 @@ static UniValue ProcessImport(CWallet * const pwallet, const UniValue& data, con
CScript pubKeyScript = GetScriptForDestination(pubkey_dest);
if (::IsMine(*pwallet, pubKeyScript) == ISMINE_SPENDABLE) {
if (::IsMine(*pwallet, pubKeyScript) & ISMINE_SPENDABLE) {
throw JSONRPCError(RPC_WALLET_ERROR, "The wallet already contains the private key for this address or script");
}
@ -1005,7 +1005,7 @@ static UniValue ProcessImport(CWallet * const pwallet, const UniValue& data, con
// TODO Is this necessary?
CScript scriptRawPubKey = GetScriptForRawPubKey(pubKey);
if (::IsMine(*pwallet, scriptRawPubKey) == ISMINE_SPENDABLE) {
if (::IsMine(*pwallet, scriptRawPubKey) & ISMINE_SPENDABLE) {
throw JSONRPCError(RPC_WALLET_ERROR, "The wallet already contains the private key for this address or script");
}
@ -1071,7 +1071,7 @@ static UniValue ProcessImport(CWallet * const pwallet, const UniValue& data, con
// Import scriptPubKey only.
if (pubKeys.size() == 0 && keys.size() == 0) {
if (::IsMine(*pwallet, script) == ISMINE_SPENDABLE) {
if (::IsMine(*pwallet, script) & ISMINE_SPENDABLE) {
throw JSONRPCError(RPC_WALLET_ERROR, "The wallet already contains the private key for this address or script");
}

View file

@ -513,16 +513,17 @@ UniValue claimname(const JSONRPCRequest& request)
return NullUniValue;
}
if (request.fHelp || request.params.size() != 3)
if (request.fHelp || (request.params.size() != 3 && request.params.size() != 4))
throw std::runtime_error(
"claimname \"name\" \"value\" amount\n"
"\nCreate a transaction which issues a claim assigning a value to a name. The claim will be authoritative if the transaction amount is greater than the transaction amount of all other unspent transactions which issue a claim over the same name, and it will remain au\
thoritative as long as it remains unspent and there are no other greater unspent transactions issuing a claim over the same name. The amount is a real and is rounded to the nearest 0.00000001\n"
+ HelpRequiringPassphrase(pwallet) +
"\nArguments:\n"
"1. \"name\" (string, required) The name to be assigned the value.\n"
"2. \"value\" (string, required) The value to assign to the name encoded in hexadecimal.\n"
"3. \"amount\" (numeric, required) The amount in LBRYcrd to send. eg 0.1\n"
"1. \"name\" (string, required) The name to be assigned the value.\n"
"2. \"value\" (string, required) The value to assign to the name encoded in hexadecimal.\n"
"3. \"amount\" (numeric, required) The amount in LBRYcrd to send. eg 0.1\n"
"4. \"address_type\" (string, optional) The address type to use. Options are \"legacy\", \"p2sh-segwit\", and \"bech32\". Default is set by -addresstype.\n"
"\nResult:\n"
"\"transactionid\" (string) The transaction id.\n");
auto sName = request.params[0].get_str();
@ -545,9 +546,20 @@ thoritative as long as it remains unspent and there are no other greater unspent
if (!pwallet->GetKeyFromPool(newKey))
throw JSONRPCError(RPC_WALLET_KEYPOOL_RAN_OUT, "Error: Keypool ran out, please call keypoolrefill first");
OutputType output_type = pwallet->m_default_address_type;
if (request.params.size() > 3 && !request.params[3].isNull()) {
if (!ParseOutputType(request.params[3].get_str(), output_type)) {
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, strprintf("Unknown address type '%s'", request.params[3].get_str()));
}
}
// pwallet->m_default_address_type == 0 (LEGACY), pwallet->m_default_change_type == 3 (AUTO)
pwallet->LearnRelatedScripts(newKey, output_type);
CTxDestination dest = GetDestinationForKey(newKey, output_type);
CCoinControl cc;
cc.m_change_type = DEFAULT_ADDRESS_TYPE;
auto tx = SendMoney(pwallet, CTxDestination(newKey.GetID()), nAmount, false, cc, {}, {}, claimScript);
cc.m_change_type = pwallet->m_default_change_type;
auto tx = SendMoney(pwallet, dest, nAmount, false, cc, {}, {}, claimScript);
return tx->GetHash().GetHex();
}
@ -560,15 +572,16 @@ UniValue updateclaim(const JSONRPCRequest& request)
return NullUniValue;
}
if (request.fHelp || request.params.size() != 3)
if (request.fHelp || (request.params.size() != 3 && request.params.size() != 4))
throw std::runtime_error(
"updateclaim \"txid\" \"value\" amount\n"
"Create a transaction which issues a claim assigning a value to a name, spending the previous txout which issued a claim over the same name and therefore superseding that claim. The claim will be authoritative if the transaction amount is greater than the transaction amount of all other unspent transactions which issue a claim over the same name, and it will remain authoritative as long as it remains unspent and there are no greater unspent transactions issuing a claim over the same name.\n"
+ HelpRequiringPassphrase(pwallet) + "\n"
"Arguments:\n"
"1. \"txid\" (string, required) The transaction containing the unspent txout which should be spent.\n"
"2. \"value\" (string, required) The value to assign to the name encoded in hexadecimal.\n"
"3. \"amount\" (numeric, required) The amount in LBRYcrd to use to bid for the name. eg 0.1\n"
"1. \"txid\" (string, required) The transaction containing the unspent txout which should be spent.\n"
"2. \"value\" (string, required) The value to assign to the name encoded in hexadecimal.\n"
"3. \"amount\" (numeric, required) The amount in LBRYcrd to use to bid for the name. eg 0.1\n"
"4. \"address_type\" (string, optional) The address type to use. Options are \"legacy\", \"p2sh-segwit\", and \"bech32\". Default is set by -addresstype.\n"
"\nResult:\n"
"\"transactionid\" (string) The new transaction id.\n");
@ -621,11 +634,20 @@ UniValue updateclaim(const JSONRPCRequest& request)
if (!pwallet->GetKeyFromPool(newKey))
throw JSONRPCError(RPC_WALLET_KEYPOOL_RAN_OUT, "Error: Keypool ran out, please call keypoolrefill first");
OutputType output_type = pwallet->m_default_address_type;
if (request.params.size() > 3 && !request.params[3].isNull()) {
if (!ParseOutputType(request.params[3].get_str(), output_type)) {
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, strprintf("Unknown address type '%s'", request.params[3].get_str()));
}
}
pwallet->LearnRelatedScripts(newKey, output_type);
CTxDestination dest = GetDestinationForKey(newKey, output_type);
CCoinControl cc;
cc.m_change_type = DEFAULT_ADDRESS_TYPE;
cc.m_change_type = pwallet->m_default_change_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);
cc.fAllowOtherInputs = true; // when selecting a coin, that's the only one used without this flag (and we need to cover the fee)
wtxNew = SendMoney(pwallet, dest, nAmount, false, cc, {}, {}, updateScript);
break;
}
}
@ -677,7 +699,7 @@ UniValue abandonclaim(const JSONRPCRequest& request)
{
EnsureWalletIsUnlocked(pwallet);
CCoinControl cc;
cc.m_change_type = DEFAULT_ADDRESS_TYPE;
cc.m_change_type = pwallet->m_default_change_type;
cc.Select(COutPoint(wtx.tx->GetHash(), i));
wtxNew = SendMoney(pwallet, address, wtx.tx->vout[i].nValue, true, cc, {}, {});
break;
@ -700,92 +722,93 @@ void ListNameClaims(const CWalletTx& wtx, CWallet* const pwallet, const std::str
std::list<COutputEntry> listSent;
std::list<COutputEntry> listReceived;
isminefilter filter = isminetype::ISMINE_CLAIM;
if (include_supports)
filter |= isminetype::ISMINE_SUPPORT;
wtx.GetAmounts(listReceived, listSent, nFee, strSentAccount, filter);
wtx.GetAmounts(listReceived, listSent, nFee, strSentAccount, isminetype::ISMINE_ALL);
bool fAllAccounts = (strAccount == std::string("*"));
if (!fAllAccounts && wtx.strFromAccount != strAccount)
return;
if ((!listSent.empty() || nFee != 0) && (fAllAccounts || strAccount == strSentAccount))
for (const auto& s: listSent)
{
for (const COutputEntry& s: listSent)
{
if (!list_spent && pwallet->IsSpent(wtx.GetHash(), s.vout))
continue;
if (!list_spent && pwallet->IsSpent(wtx.GetHash(), s.vout))
continue;
UniValue entry(UniValue::VOBJ);
const CScript& scriptPubKey = wtx.tx->vout[s.vout].scriptPubKey;
int op;
std::vector<std::vector<unsigned char>> vvchParams;
if (!DecodeClaimScript(scriptPubKey, op, vvchParams)) {
continue;
}
std::string sName (vvchParams[0].begin(), vvchParams[0].end());
entry.pushKV("name", escapeNonUtf8(sName));
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());
UniValue entry(UniValue::VOBJ);
const CScript& scriptPubKey = wtx.tx->vout[s.vout].scriptPubKey;
int op;
std::vector<std::vector<unsigned char>> vvchParams;
if (!DecodeClaimScript(scriptPubKey, op, vvchParams)) {
continue;
}
if (!include_supports && op == OP_SUPPORT_CLAIM)
continue;
std::string sName (vvchParams[0].begin(), vvchParams[0].end());
entry.pushKV("name", escapeNonUtf8(sName));
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()));
}
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()));
}
}
entry.pushKV("txid", wtx.GetHash().ToString());
entry.pushKV("account", strSentAccount);
MaybePushAddress(entry, s.destination);
entry.pushKV("amount", ValueFromAmount(s.amount));
entry.pushKV("vout", s.vout);
entry.pushKV("fee", ValueFromAmount(nFee));
CClaimTrieCache trieCache(pclaimTrie);
auto it = mapBlockIndex.find(wtx.hashBlock);
if (it != mapBlockIndex.end())
{
CBlockIndex* pindex = it->second;
if (pindex)
{
entry.pushKV("height", pindex->nHeight);
entry.pushKV("expiration height", pindex->nHeight + trieCache.expirationTime());
if (pindex->nHeight + trieCache.expirationTime() > chainActive.Height())
{
entry.pushKV("expired", false);
entry.pushKV("blocks to expiration", pindex->nHeight + trieCache.expirationTime() - chainActive.Height());
}
else
{
entry.pushKV("expired", true);
}
}
}
entry.pushKV("confirmations", wtx.GetDepthInMainChain());
entry.pushKV("is spent", pwallet->IsSpent(wtx.GetHash(), s.vout));
if (op == OP_CLAIM_NAME)
{
entry.pushKV("is in name trie", trieCache.haveClaim(sName, COutPoint(wtx.GetHash(), s.vout)));
}
else if (op == OP_SUPPORT_CLAIM)
{
entry.pushKV("is in support map", trieCache.haveSupport(sName, COutPoint(wtx.GetHash(), s.vout)));
}
ret.push_back(entry);
}
else
throw JSONRPCError(RPC_INTERNAL_ERROR, "Undefined claim operator.");
entry.pushKV("txid", wtx.GetHash().ToString());
entry.pushKV("account", strSentAccount);
MaybePushAddress(entry, s.destination);
entry.pushKV("amount", ValueFromAmount(s.amount));
entry.pushKV("vout", s.vout);
entry.pushKV("fee", ValueFromAmount(nFee));
CClaimTrieCache trieCache(pclaimTrie);
auto it = mapBlockIndex.find(wtx.hashBlock);
if (it != mapBlockIndex.end())
{
CBlockIndex* pindex = it->second;
if (pindex)
{
entry.pushKV("height", pindex->nHeight);
entry.pushKV("expiration height", pindex->nHeight + trieCache.expirationTime());
if (pindex->nHeight + trieCache.expirationTime() > chainActive.Height())
{
entry.pushKV("expired", false);
entry.pushKV("blocks to expiration", pindex->nHeight + trieCache.expirationTime() - chainActive.Height());
}
else
{
entry.pushKV("expired", true);
}
}
}
entry.pushKV("confirmations", wtx.GetDepthInMainChain());
entry.pushKV("is spent", pwallet->IsSpent(wtx.GetHash(), s.vout));
if (op == OP_CLAIM_NAME)
{
entry.pushKV("is in name trie", trieCache.haveClaim(sName, COutPoint(wtx.GetHash(), s.vout)));
}
else if (op == OP_SUPPORT_CLAIM)
{
entry.pushKV("is in support map", trieCache.haveSupport(sName, COutPoint(wtx.GetHash(), s.vout)));
}
ret.push_back(entry);
}
}
@ -804,8 +827,7 @@ UniValue listnameclaims(const JSONRPCRequest& request)
"Return a list of all transactions claiming names.\n"
"\nArguments\n"
"1. includesupports (bool, optional) Whether to also include claim supports. Default is true.\n"
"2. activeonly (bool, optional, not implemented) Whether to only include transactions which are still active, i.e. have n\
ot been spent. Default is false.\n"
"2. activeonly (bool, optional) Whether to only include transactions which are still active, i.e. have not been spent. Default is true.\n"
"3. minconf (numeric, optional, default=1) Only include transactions confirmed at least this many times.\n"
"\nResult:\n"
"[\n"
@ -835,7 +857,7 @@ ot been spent. Default is false.\n"
std::string strAccount = "*";
auto include_supports = request.params.size() < 1 || request.params[0].get_bool();
bool fListSpent = request.params.size() > 1 && request.params[1].get_bool();
bool fListSpent = request.params.size() > 1 && !request.params[1].get_bool();
// Minimum confirmations
int nMinDepth = 1;
@ -873,7 +895,7 @@ UniValue supportclaim(const JSONRPCRequest& request)
return NullUniValue;
}
if (request.fHelp || (request.params.size() != 3 && request.params.size() != 4))
if (request.fHelp || request.params.size() < 3 || request.params.size() > 5)
throw std::runtime_error(
"supportclaim \"name\" \"claimid\" \"amount\" \"value\"\n"
"Increase the value of a claim. Whichever claim has the greatest value, including all support values, will be the authoritative claim, according to the rest of the rules. The name is the name which is claimed by the claim that will be supported, the txid is the txid\
@ -881,10 +903,11 @@ UniValue supportclaim(const JSONRPCRequest& request)
. Otherwise, it will go into effect after 100 blocks. The support will be in effect until it is spent, and will lose its effect when the claim is spent or expires. The amount is a real and is rounded to the nearest .00000001\n"
+ HelpRequiringPassphrase(pwallet) +
"\nArguments:\n"
"1. \"name\" (string, required) The name claimed by the claim to support.\n"
"2. \"claimid\" (string, required) The claimid of the claim to support. This can be obtained by TODO PUT IN PLACE THAT SHOWS THIS.\n"
"3. \"amount\" (numeric, required) The amount in LBC to use to support the claim.\n"
"4. \"value\" (string, optional) The metadata of the support encoded in hexadecimal.\n"
"1. \"name\" (string, required) The name claimed by the claim to support.\n"
"2. \"claimid\" (string, required) The claimid of the claim to support.\n"
"3. \"amount\" (numeric, required) The amount in LBC to use to support the claim.\n"
"4. \"value\" (string, optional) The metadata of the support encoded in hexadecimal.\n"
"5. \"address_type\" (string, optional) The address type to use. Options are \"legacy\", \"p2sh-segwit\", and \"bech32\". Default is set by -addresstype.\n"
"\nResult:\n"
"\"transactionid\" (string) The transaction id of the support.\n");
@ -911,10 +934,14 @@ UniValue supportclaim(const JSONRPCRequest& request)
auto lastOp = OP_DROP;
if (request.params.size() > 3) {
auto hex = request.params[3].get_str();
if (!IsHex(hex))
throw JSONRPCError(RPC_INVALID_PARAMETER, "value/metadata must be of hexadecimal data");
supportScript = supportScript << ParseHex(hex);
lastOp = OP_2DROP;
if (!hex.empty()) {
if (!IsHex(hex))
throw JSONRPCError(RPC_INVALID_PARAMETER, "value/metadata must be of hexadecimal data");
if (!IsWitnessEnabled(chainActive.Tip(), Params().GetConsensus()))
throw JSONRPCError(RPC_INVALID_PARAMETER, "value/metadata on supports is not enabled yet");
supportScript = supportScript << ParseHex(hex);
lastOp = OP_2DROP;
}
}
supportScript = supportScript << OP_2DROP << lastOp;
@ -923,9 +950,18 @@ UniValue supportclaim(const JSONRPCRequest& request)
if (!pwallet->GetKeyFromPool(newKey))
throw JSONRPCError(RPC_WALLET_KEYPOOL_RAN_OUT, "Error: Keypool ran out, please call keypoolrefill first");
OutputType output_type = pwallet->m_default_address_type;
if (request.params.size() > 4 && !request.params[4].isNull()) {
if (!ParseOutputType(request.params[4].get_str(), output_type)) {
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, strprintf("Unknown address type '%s'", request.params[4].get_str()));
}
}
pwallet->LearnRelatedScripts(newKey, output_type);
CTxDestination dest = GetDestinationForKey(newKey, output_type);
CCoinControl cc;
cc.m_change_type = DEFAULT_ADDRESS_TYPE;
auto tx = SendMoney(pwallet, CTxDestination(newKey.GetID()), nAmount, false, cc, {}, {}, supportScript);
cc.m_change_type = pwallet->m_default_change_type;
auto tx = SendMoney(pwallet, dest, nAmount, false, cc, {}, {}, supportScript);
return tx->GetHash().GetHex();
}
@ -971,7 +1007,7 @@ UniValue abandonsupport(const JSONRPCRequest& request)
{
EnsureWalletIsUnlocked(pwallet);
CCoinControl cc;
cc.m_change_type = DEFAULT_ADDRESS_TYPE;
cc.m_change_type = pwallet->m_default_change_type;
cc.Select(COutPoint(wtx.tx->GetHash(), i));
wtxNew = SendMoney(pwallet, address, wtx.tx->vout[i].nValue, true, cc, {}, {});
break;
@ -4688,6 +4724,8 @@ UniValue getaddressinfo(const JSONRPCRequest& request)
isminetype mine = IsMine(*pwallet, dest);
ret.pushKV("ismine", bool(mine & ISMINE_SPENDABLE));
ret.pushKV("iswatchonly", bool(mine & ISMINE_WATCH_ONLY));
ret.pushKV("isclaim", bool(mine & ISMINE_CLAIM));
ret.pushKV("issupport", bool(mine & ISMINE_SUPPORT));
UniValue detail = DescribeWalletAddress(pwallet, dest);
ret.pushKVs(detail);
if (pwallet->mapAddressBook.count(dest)) {
@ -5311,11 +5349,11 @@ static const CRPCCommand commands[] =
{ "generating", "generate", &generate, {"nblocks","maxtries"} },
{ "Claimtrie", "claimname", &claimname, {"name","value","amount"} },
{ "Claimtrie", "updateclaim", &updateclaim, {"txid","value","amount"} },
{ "Claimtrie", "claimname", &claimname, {"name","value","amount","address_type"} },
{ "Claimtrie", "updateclaim", &updateclaim, {"txid","value","amount","address_type"} },
{ "Claimtrie", "abandonclaim", &abandonclaim, {"txid","address"} },
{ "Claimtrie", "listnameclaims", &listnameclaims, {"includesuppports","activeonly","minconf"} },
{ "Claimtrie", "supportclaim", &supportclaim, {"name","claimid","amount","value"} },
{ "Claimtrie", "supportclaim", &supportclaim, {"name","claimid","amount","value","address_type"} },
{ "Claimtrie", "abandonsupport", &abandonsupport, {"txid","address"} },
};

View file

@ -141,11 +141,10 @@ uint256 AbandonAClaim(const uint256& txid, bool isSupport = false) {
}
}
BOOST_AUTO_TEST_CASE(claim_op_runthrough)
{
void AddClaimSupportThenRemove() {
generateBlock(105);
BOOST_CHECK_GE(AvailableBalance(), 2.0);
BOOST_CHECK_EQUAL(AvailableBalance(), 5.0);
// ops for test: claimname, updateclaim, abandonclaim, listnameclaims, supportclaim, abandonsupport
// order of ops:
@ -244,4 +243,29 @@ BOOST_AUTO_TEST_CASE(claim_op_runthrough)
*/
}
BOOST_AUTO_TEST_CASE(claim_op_runthrough_legacy)
{
for (auto& wallet: GetWallets())
wallet->m_default_address_type = OutputType::LEGACY;
AddClaimSupportThenRemove();
}
BOOST_AUTO_TEST_CASE(claim_op_runthrough_p2sh)
{
for (auto& wallet: GetWallets()) {
BOOST_CHECK(DEFAULT_ADDRESS_TYPE == wallet->m_default_address_type); // BOOST_CHECK_EQUAL no compile here
wallet->m_default_address_type = OutputType::P2SH_SEGWIT;
}
AddClaimSupportThenRemove();
}
BOOST_AUTO_TEST_CASE(claim_op_runthrough_bech32)
{
for (auto& wallet: GetWallets()) {
BOOST_CHECK(DEFAULT_ADDRESS_TYPE == wallet->m_default_address_type); // BOOST_CHECK_EQUAL no compile here
wallet->m_default_address_type = OutputType::BECH32;
}
AddClaimSupportThenRemove();
}
BOOST_AUTO_TEST_SUITE_END()

View file

@ -1401,16 +1401,15 @@ bool CWallet::IsChange(const CTxOut& txout) const
// a better way of identifying which outputs are 'the send' and which are
// 'the change' will need to be implemented (maybe extend CWalletTx to remember
// which output, if any, was change).
if (::IsMine(*this, txout.scriptPubKey))
auto isMine = ::IsMine(*this, txout.scriptPubKey);
if (isMine && !(isMine & ISMINE_STAKE)) // stakes are never change
{
CTxDestination address;
const CScript& scriptPubKey = StripClaimScriptPrefix(txout.scriptPubKey);
if (!ExtractDestination(scriptPubKey, address))
if (!ExtractDestination(txout.scriptPubKey, address))
return true;
LOCK(cs_wallet);
if (!mapAddressBook.count(address) && (scriptPubKey == txout.scriptPubKey))
if (!mapAddressBook.count(address))
return true;
}
return false;
@ -1697,7 +1696,7 @@ void CWalletTx::GetAmounts(std::list<COutputEntry>& listReceived,
if (!ExtractDestination(scriptPubKey, address) && !scriptPubKey.IsUnspendable())
{
LogPrintf("CWalletTx::GetAmounts: Unknown transaction type found, txid %s\n",
pwallet->WalletLogPrintf("CWalletTx::GetAmounts: Unknown transaction type found, txid %s\n",
this->GetHash().ToString());
address = CNoDestination();
}
@ -1705,7 +1704,7 @@ void CWalletTx::GetAmounts(std::list<COutputEntry>& listReceived,
COutputEntry output = {address, txout.nValue, (int)i};
// If we are debited by the transaction, add the output as a "sent" entry
if (nDebit > 0 || filter & ISMINE_CLAIM || filter & ISMINE_SUPPORT)
if (nDebit > 0)
listSent.push_back(output);
// If we are receiving the output, add it as a "received" entry
@ -2081,7 +2080,7 @@ bool CWalletTx::IsTrusted() const
if (parent == nullptr)
return false;
const CTxOut& parentOut = parent->tx->vout[txin.prevout.n];
if (pwallet->IsMine(parentOut) != ISMINE_SPENDABLE)
if (!(pwallet->IsMine(parentOut) & ISMINE_SPENDABLE))
return false;
}
return true;
@ -2375,15 +2374,15 @@ void CWallet::AvailableCoins(std::vector<COutput> &vCoins, bool fOnlySafe, const
continue;
}
bool solvable = IsSolvable(*this, pcoin->tx->vout[i].scriptPubKey);
bool spendable = (mine & ISMINE_SPENDABLE) || (((mine & ISMINE_WATCH_ONLY) != ISMINE_NO) && (coinControl && coinControl->fAllowWatchOnly && solvable));
// spending claims or supports requires specific selection:
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
continue;
vCoins.push_back(COutput(pcoin, i, nDepth, spendable, solvable, safeTx, (coinControl && coinControl->fAllowWatchOnly)));
@ -2437,7 +2436,7 @@ std::map<CTxDestination, std::vector<COutput>> CWallet::ListCoins() const
if (it != mapWallet.end()) {
int depth = it->second.GetDepthInMainChain();
if (depth >= 0 && output.n < it->second.tx->vout.size() &&
IsMine(it->second.tx->vout[output.n]) == ISMINE_SPENDABLE) {
IsMine(it->second.tx->vout[output.n]) & ISMINE_SPENDABLE) {
CTxDestination address;
if (ExtractDestination(FindNonChangeParentOutput(*it->second.tx, output.n).scriptPubKey, address)) {
result[address].emplace_back(
@ -2704,7 +2703,8 @@ OutputType CWallet::TransactionChangeType(OutputType change_type, const std::vec
// Check if any destination contains a witness program:
int witnessversion = 0;
std::vector<unsigned char> witnessprogram;
if (recipient.scriptPubKey.IsWitnessProgram(witnessversion, witnessprogram)) {
const auto stripped = StripClaimScriptPrefix(recipient.scriptPubKey);
if (stripped.IsWitnessProgram(witnessversion, witnessprogram)) {
return OutputType::BECH32;
}
}