updated to support using bech32 addresses with claim ops

This commit is contained in:
Brannon King 2019-07-09 14:32:08 -06:00 committed by Anthony Fieroni
parent ff0f8d8059
commit a01614f5f5
9 changed files with 221 additions and 161 deletions

View file

@ -3,6 +3,7 @@
// file COPYING or http://www.opensource.org/licenses/mit-license.php. // file COPYING or http://www.opensource.org/licenses/mit-license.php.
#include <bloom.h> #include <bloom.h>
#include <nameclaim.h>
#include <primitives/transaction.h> #include <primitives/transaction.h>
#include <hash.h> #include <hash.h>
@ -155,7 +156,8 @@ bool CBloomFilter::IsRelevantAndUpdate(const CTransaction& tx)
else if ((nFlags & BLOOM_UPDATE_MASK) == BLOOM_UPDATE_P2PUBKEY_ONLY) else if ((nFlags & BLOOM_UPDATE_MASK) == BLOOM_UPDATE_P2PUBKEY_ONLY)
{ {
std::vector<std::vector<unsigned char> > vSolutions; std::vector<std::vector<unsigned char> > vSolutions;
txnouttype type = Solver(txout.scriptPubKey, vSolutions); const CScript& scriptPubKey = StripClaimScriptPrefix(txout.scriptPubKey);
txnouttype type = Solver(scriptPubKey, vSolutions);
if (type == TX_PUBKEY || type == TX_MULTISIG) { if (type == TX_PUBKEY || type == TX_MULTISIG) {
insert(COutPoint(hash, i)); 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); const Coin& coin = inputs.AccessCoin(tx.vin[i].prevout);
assert(!coin.IsSpent()); assert(!coin.IsSpent());
const CTxOut &prevout = coin.out; 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; return nSigOps;
} }

View file

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

View file

@ -169,6 +169,9 @@ static const CRPCConvertParam vRPCConvertParams[] =
{ "createwallet", 2, "blank"}, { "createwallet", 2, "blank"},
{ "createwallet", 4, "avoid_reuse"}, { "createwallet", 4, "avoid_reuse"},
{ "getnodeaddresses", 0, "count"}, { "getnodeaddresses", 0, "count"},
{ "listnameclaims", 0, "includesupports"},
{ "listnameclaims", 1, "activeonly"},
{ "listnameclaims", 2, "minconf"},
{ "stop", 0, "wait" }, { "stop", 0, "wait" },
}; };
// clang-format on // clang-format on

View file

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

View file

@ -40,8 +40,6 @@ enum class IsMineResult
WATCH_ONLY = 1, //!< Included in watch-only balance WATCH_ONLY = 1, //!< Included in watch-only balance
SPENDABLE = 2, //!< Included in all balances SPENDABLE = 2, //!< Included in all balances
INVALID = 3, //!< Not spendable by anyone (uncompressed pubkey in segwit, P2SH inside P2SH or witness, witness inside witness) 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) bool PermitsUncompressed(IsMineSigVersion sigversion)
@ -60,16 +58,10 @@ bool HaveKeys(const std::vector<valtype>& pubkeys, const CWallet& keystore)
IsMineResult IsMineInner(const CWallet& keystore, const CScript& scriptPubKey, IsMineSigVersion sigversion) IsMineResult IsMineInner(const CWallet& keystore, const CScript& scriptPubKey, IsMineSigVersion sigversion)
{ {
int op = 0;
IsMineResult ret = IsMineResult::NO; 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; std::vector<valtype> vSolutions;
txnouttype whichType = Solver(strippedScriptPubKey, vSolutions); txnouttype whichType = Solver(scriptPubKey, vSolutions);
CKeyID keyID; CKeyID keyID;
switch (whichType) switch (whichType)
@ -84,7 +76,7 @@ IsMineResult IsMineInner(const CWallet& keystore, const CScript& scriptPubKey, I
return IsMineResult::INVALID; return IsMineResult::INVALID;
} }
if (keystore.HaveKey(keyID)) { if (keystore.HaveKey(keyID)) {
ret = std::max(claim_ret, IsMineResult::SPENDABLE); ret = std::max(ret, IsMineResult::SPENDABLE);
} }
break; break;
case TX_WITNESS_V0_KEYHASH: case TX_WITNESS_V0_KEYHASH:
@ -99,10 +91,6 @@ IsMineResult IsMineInner(const CWallet& keystore, const CScript& scriptPubKey, I
// This also applies to the P2WSH case. // This also applies to the P2WSH case.
break; 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(PKHash(uint160(vSolutions[0]))), IsMineSigVersion::WITNESS_V0)); ret = std::max(ret, IsMineInner(keystore, GetScriptForDestination(PKHash(uint160(vSolutions[0]))), IsMineSigVersion::WITNESS_V0));
break; break;
} }
@ -115,7 +103,7 @@ IsMineResult IsMineInner(const CWallet& keystore, const CScript& scriptPubKey, I
} }
} }
if (keystore.HaveKey(keyID)) { if (keystore.HaveKey(keyID)) {
ret = std::max(claim_ret, IsMineResult::SPENDABLE); ret = std::max(ret, IsMineResult::SPENDABLE);
} }
break; break;
case TX_SCRIPTHASH: case TX_SCRIPTHASH:
@ -127,7 +115,7 @@ IsMineResult IsMineInner(const CWallet& keystore, const CScript& scriptPubKey, I
CScriptID scriptID = CScriptID(uint160(vSolutions[0])); CScriptID scriptID = CScriptID(uint160(vSolutions[0]));
CScript subscript; CScript subscript;
if (keystore.GetCScript(scriptID, 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; break;
} }
@ -145,10 +133,6 @@ IsMineResult IsMineInner(const CWallet& keystore, const CScript& scriptPubKey, I
CScriptID scriptID = CScriptID(hash); CScriptID scriptID = CScriptID(hash);
CScript subscript; CScript subscript;
if (keystore.GetCScript(scriptID, 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)); ret = std::max(ret, IsMineInner(keystore, subscript, IsMineSigVersion::WITNESS_V0));
} }
break; break;
@ -175,14 +159,14 @@ IsMineResult IsMineInner(const CWallet& keystore, const CScript& scriptPubKey, I
} }
} }
if (HaveKeys(keys, keystore)) { if (HaveKeys(keys, keystore)) {
ret = std::max(claim_ret, IsMineResult::SPENDABLE); ret = std::max(ret, IsMineResult::SPENDABLE);
} }
break; break;
} }
} }
if (ret == IsMineResult::NO && keystore.HaveWatchOnly(scriptPubKey)) { if (ret == IsMineResult::NO && keystore.HaveWatchOnly(scriptPubKey)) {
ret = std::max(claim_ret, IsMineResult::WATCH_ONLY); ret = std::max(ret, IsMineResult::WATCH_ONLY);
} }
return ret; return ret;
} }
@ -191,18 +175,22 @@ IsMineResult IsMineInner(const CWallet& keystore, const CScript& scriptPubKey, I
isminetype IsMine(const CWallet& keystore, const CScript& scriptPubKey) isminetype IsMine(const CWallet& 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::INVALID:
case IsMineResult::NO: case IsMineResult::NO:
return ISMINE_NO; return ISMINE_NO;
case IsMineResult::WATCH_ONLY: 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: case IsMineResult::SPENDABLE:
return ISMINE_SPENDABLE; return isminetype(ISMINE_SPENDABLE | flags);
case IsMineResult::CLAIM:
return ISMINE_CLAIM;
case IsMineResult::SUPPORT:
return ISMINE_SUPPORT;
} }
assert(false); assert(false);
} }

View file

@ -359,16 +359,17 @@ UniValue claimname(const JSONRPCRequest& request)
return NullUniValue; return NullUniValue;
} }
if (request.fHelp || request.params.size() != 3) if (request.fHelp || (request.params.size() != 3 && request.params.size() != 4))
throw std::runtime_error( throw std::runtime_error(
"claimname \"name\" \"value\" amount\n" "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\ "\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" 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) + + HelpRequiringPassphrase(pwallet) +
"\nArguments:\n" "\nArguments:\n"
"1. \"name\" (string, required) The name to be assigned the value.\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" "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" "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" "\nResult:\n"
"\"transactionid\" (string) The transaction id.\n"); "\"transactionid\" (string) The transaction id.\n");
auto sName = request.params[0].get_str(); auto sName = request.params[0].get_str();
@ -391,9 +392,20 @@ thoritative as long as it remains unspent and there are no other greater unspent
if (!pwallet->GetKeyFromPool(newKey)) if (!pwallet->GetKeyFromPool(newKey))
throw JSONRPCError(RPC_WALLET_KEYPOOL_RAN_OUT, "Error: Keypool ran out, please call keypoolrefill first"); 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; CCoinControl cc;
cc.m_change_type = DEFAULT_ADDRESS_TYPE; cc.m_change_type = pwallet->m_default_change_type;
auto tx = SendMoney(pwallet, CTxDestination(newKey.GetID()), nAmount, false, cc, {}, {}, claimScript); auto tx = SendMoney(pwallet, dest, nAmount, false, cc, {}, {}, claimScript);
return tx->GetHash().GetHex(); return tx->GetHash().GetHex();
} }
@ -406,15 +418,16 @@ UniValue updateclaim(const JSONRPCRequest& request)
return NullUniValue; return NullUniValue;
} }
if (request.fHelp || request.params.size() != 3) if (request.fHelp || (request.params.size() != 3 && request.params.size() != 4))
throw std::runtime_error( throw std::runtime_error(
"updateclaim \"txid\" \"value\" amount\n" "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" "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" + HelpRequiringPassphrase(pwallet) + "\n"
"Arguments:\n" "Arguments:\n"
"1. \"txid\" (string, required) The transaction containing the unspent txout which should be spent.\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" "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" "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" "\nResult:\n"
"\"transactionid\" (string) The new transaction id.\n"); "\"transactionid\" (string) The new transaction id.\n");
@ -467,11 +480,20 @@ UniValue updateclaim(const JSONRPCRequest& request)
if (!pwallet->GetKeyFromPool(newKey)) if (!pwallet->GetKeyFromPool(newKey))
throw JSONRPCError(RPC_WALLET_KEYPOOL_RAN_OUT, "Error: Keypool ran out, please call keypoolrefill first"); 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; 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.Select(COutPoint(wtx.tx->GetHash(), i));
cc.fAllowOtherInputs = true; // the doc comment on this parameter says it should be false; experience says otherwise 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, CTxDestination(newKey.GetID()), nAmount, false, cc, {}, {}, updateScript); wtxNew = SendMoney(pwallet, dest, nAmount, false, cc, {}, {}, updateScript);
break; break;
} }
} }
@ -523,7 +545,7 @@ UniValue abandonclaim(const JSONRPCRequest& request)
{ {
EnsureWalletIsUnlocked(pwallet); EnsureWalletIsUnlocked(pwallet);
CCoinControl cc; 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.Select(COutPoint(wtx.tx->GetHash(), i));
wtxNew = SendMoney(pwallet, address, wtx.tx->vout[i].nValue, true, cc, {}, {}); wtxNew = SendMoney(pwallet, address, wtx.tx->vout[i].nValue, true, cc, {}, {});
break; break;
@ -546,92 +568,93 @@ void ListNameClaims(const CWalletTx& wtx, CWallet* const pwallet, const std::str
std::list<COutputEntry> listSent; std::list<COutputEntry> listSent;
std::list<COutputEntry> listReceived; std::list<COutputEntry> listReceived;
isminefilter filter = isminetype::ISMINE_CLAIM; wtx.GetAmounts(listReceived, listSent, nFee, strSentAccount, isminetype::ISMINE_ALL);
if (include_supports)
filter |= isminetype::ISMINE_SUPPORT;
wtx.GetAmounts(listReceived, listSent, nFee, strSentAccount, filter);
bool fAllAccounts = (strAccount == std::string("*")); 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); UniValue entry(UniValue::VOBJ);
const CScript& scriptPubKey = wtx.tx->vout[s.vout].scriptPubKey; const CScript& scriptPubKey = wtx.tx->vout[s.vout].scriptPubKey;
int op; int op;
std::vector<std::vector<unsigned char>> vvchParams; std::vector<std::vector<unsigned char>> vvchParams;
if (!DecodeClaimScript(scriptPubKey, op, vvchParams)) { if (!DecodeClaimScript(scriptPubKey, op, vvchParams)) {
continue; continue;
} }
std::string sName (vvchParams[0].begin(), vvchParams[0].end()); if (!include_supports && op == OP_SUPPORT_CLAIM)
entry.pushKV("name", escapeNonUtf8(sName)); continue;
if (op == OP_CLAIM_NAME)
{ std::string sName (vvchParams[0].begin(), vvchParams[0].end());
uint160 claimId = ClaimIdHash(wtx.GetHash(), s.vout); entry.pushKV("name", escapeNonUtf8(sName));
entry.pushKV("claimtype", "CLAIM"); if (op == OP_CLAIM_NAME)
entry.pushKV("claimId", claimId.GetHex()); {
entry.pushKV("value", HexStr(vvchParams[1].begin(), vvchParams[1].end())); uint160 claimId = ClaimIdHash(wtx.GetHash(), s.vout);
} entry.pushKV("claimtype", "CLAIM");
else if (op == OP_UPDATE_CLAIM) entry.pushKV("claimId", claimId.GetHex());
{ entry.pushKV("value", HexStr(vvchParams[1].begin(), vvchParams[1].end()));
uint160 claimId(vvchParams[1]); }
entry.pushKV("claimtype", "CLAIM"); else if (op == OP_UPDATE_CLAIM)
entry.pushKV("claimId", claimId.GetHex()); {
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())); 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);
} }
} }
@ -650,8 +673,7 @@ UniValue listnameclaims(const JSONRPCRequest& request)
"Return a list of all transactions claiming names.\n" "Return a list of all transactions claiming names.\n"
"\nArguments\n" "\nArguments\n"
"1. includesupports (bool, optional) Whether to also include claim supports. Default is true.\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\ "2. activeonly (bool, optional) Whether to only include transactions which are still active, i.e. have not been spent. Default is true.\n"
ot been spent. Default is false.\n"
"3. minconf (numeric, optional, default=1) Only include transactions confirmed at least this many times.\n" "3. minconf (numeric, optional, default=1) Only include transactions confirmed at least this many times.\n"
"\nResult:\n" "\nResult:\n"
"[\n" "[\n"
@ -681,7 +703,7 @@ ot been spent. Default is false.\n"
std::string strAccount = "*"; std::string strAccount = "*";
auto include_supports = request.params.size() < 1 || request.params[0].get_bool(); 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 // Minimum confirmations
int nMinDepth = 1; int nMinDepth = 1;
@ -719,7 +741,7 @@ UniValue supportclaim(const JSONRPCRequest& request)
return NullUniValue; 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( throw std::runtime_error(
"supportclaim \"name\" \"claimid\" \"amount\" \"value\"\n" "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\ "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\
@ -727,10 +749,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" . 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) + + HelpRequiringPassphrase(pwallet) +
"\nArguments:\n" "\nArguments:\n"
"1. \"name\" (string, required) The name claimed by the claim to support.\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" "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" "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" "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" "\nResult:\n"
"\"transactionid\" (string) The transaction id of the support.\n"); "\"transactionid\" (string) The transaction id of the support.\n");
@ -757,10 +780,14 @@ UniValue supportclaim(const JSONRPCRequest& request)
auto lastOp = OP_DROP; auto lastOp = OP_DROP;
if (request.params.size() > 3) { if (request.params.size() > 3) {
auto hex = request.params[3].get_str(); auto hex = request.params[3].get_str();
if (!IsHex(hex)) if (!hex.empty()) {
throw JSONRPCError(RPC_INVALID_PARAMETER, "value/metadata must be of hexadecimal data"); if (!IsHex(hex))
supportScript = supportScript << ParseHex(hex); throw JSONRPCError(RPC_INVALID_PARAMETER, "value/metadata must be of hexadecimal data");
lastOp = OP_2DROP; 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; supportScript = supportScript << OP_2DROP << lastOp;
@ -769,9 +796,18 @@ UniValue supportclaim(const JSONRPCRequest& request)
if (!pwallet->GetKeyFromPool(newKey)) if (!pwallet->GetKeyFromPool(newKey))
throw JSONRPCError(RPC_WALLET_KEYPOOL_RAN_OUT, "Error: Keypool ran out, please call keypoolrefill first"); 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; CCoinControl cc;
cc.m_change_type = DEFAULT_ADDRESS_TYPE; cc.m_change_type = pwallet->m_default_change_type;
auto tx = SendMoney(pwallet, CTxDestination(newKey.GetID()), nAmount, false, cc, {}, {}, supportScript); auto tx = SendMoney(pwallet, dest, nAmount, false, cc, {}, {}, supportScript);
return tx->GetHash().GetHex(); return tx->GetHash().GetHex();
} }
@ -817,7 +853,7 @@ UniValue abandonsupport(const JSONRPCRequest& request)
{ {
EnsureWalletIsUnlocked(pwallet); EnsureWalletIsUnlocked(pwallet);
CCoinControl cc; 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.Select(COutPoint(wtx.tx->GetHash(), i));
wtxNew = SendMoney(pwallet, address, wtx.tx->vout[i].nValue, true, cc, {}, {}); wtxNew = SendMoney(pwallet, address, wtx.tx->vout[i].nValue, true, cc, {}, {});
break; break;
@ -4237,6 +4273,8 @@ UniValue getaddressinfo(const JSONRPCRequest& request)
ret.pushKV("desc", InferDescriptor(scriptPubKey, *pwallet)->ToString()); ret.pushKV("desc", InferDescriptor(scriptPubKey, *pwallet)->ToString());
} }
ret.pushKV("iswatchonly", bool(mine & ISMINE_WATCH_ONLY)); 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); UniValue detail = DescribeWalletAddress(pwallet, dest);
ret.pushKVs(detail); ret.pushKVs(detail);
if (pwallet->mapAddressBook.count(dest)) { if (pwallet->mapAddressBook.count(dest)) {
@ -4746,11 +4784,11 @@ static const CRPCCommand commands[] =
{ "wallet", "walletpassphrasechange", &walletpassphrasechange, {"oldpassphrase","newpassphrase"} }, { "wallet", "walletpassphrasechange", &walletpassphrasechange, {"oldpassphrase","newpassphrase"} },
{ "wallet", "walletprocesspsbt", &walletprocesspsbt, {"psbt","sign","sighashtype","bip32derivs"} }, { "wallet", "walletprocesspsbt", &walletprocesspsbt, {"psbt","sign","sighashtype","bip32derivs"} },
{ "Claimtrie", "claimname", &claimname, {"name","value","amount"} }, { "Claimtrie", "claimname", &claimname, {"name","value","amount","address_type"} },
{ "Claimtrie", "updateclaim", &updateclaim, {"txid","value","amount"} }, { "Claimtrie", "updateclaim", &updateclaim, {"txid","value","amount","address_type"} },
{ "Claimtrie", "abandonclaim", &abandonclaim, {"txid","address"} }, { "Claimtrie", "abandonclaim", &abandonclaim, {"txid","address"} },
{ "Claimtrie", "listnameclaims", &listnameclaims, {"includesuppports","activeonly","minconf"} }, { "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"} }, { "Claimtrie", "abandonsupport", &abandonsupport, {"txid","address"} },
}; };
// clang-format on // clang-format on

View file

@ -149,11 +149,10 @@ uint256 AbandonAClaim(const uint256& txid, bool isSupport = false) {
} }
} }
BOOST_AUTO_TEST_CASE(claim_op_runthrough) void AddClaimSupportThenRemove() {
{
generateBlock(105); generateBlock(105);
BOOST_CHECK_GE(AvailableBalance(), 2.0); BOOST_CHECK_EQUAL(AvailableBalance(), 5.0);
// ops for test: claimname, updateclaim, abandonclaim, listnameclaims, supportclaim, abandonsupport // ops for test: claimname, updateclaim, abandonclaim, listnameclaims, supportclaim, abandonsupport
// order of ops: // order of ops:
@ -252,4 +251,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() BOOST_AUTO_TEST_SUITE_END()

View file

@ -1566,16 +1566,15 @@ bool CWallet::IsChange(const CScript& script) const
// a better way of identifying which outputs are 'the send' and which are // 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 // 'the change' will need to be implemented (maybe extend CWalletTx to remember
// which output, if any, was change). // which output, if any, was change).
if (::IsMine(*this, script)) auto isMine = ::IsMine(*this, script);
if (isMine && !(isMine & ISMINE_STAKE)) // stakes are never change
{ {
CTxDestination address; CTxDestination address;
const CScript& scriptPubKey = StripClaimScriptPrefix(script); if (!ExtractDestination(script, address))
if (!ExtractDestination(scriptPubKey, address))
return true; return true;
LOCK(cs_wallet); LOCK(cs_wallet);
if (!mapAddressBook.count(address) && (scriptPubKey == txout.scriptPubKey)) if (!mapAddressBook.count(address))
return true; return true;
} }
return false; return false;
@ -1999,7 +1998,7 @@ void CWalletTx::GetAmounts(std::list<COutputEntry>& listReceived,
if (!ExtractDestination(scriptPubKey, address) && !scriptPubKey.IsUnspendable()) 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()); this->GetHash().ToString());
address = CNoDestination(); address = CNoDestination();
} }
@ -2007,7 +2006,7 @@ void CWalletTx::GetAmounts(std::list<COutputEntry>& listReceived,
COutputEntry output = {address, txout.nValue, (int)i}; COutputEntry output = {address, txout.nValue, (int)i};
// If we are debited by the transaction, add the output as a "sent" entry // 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); listSent.push_back(output);
// If we are receiving the output, add it as a "received" entry // If we are receiving the output, add it as a "received" entry
@ -2377,7 +2376,7 @@ bool CWalletTx::IsTrusted(interfaces::Chain::Lock& locked_chain) const
if (parent == nullptr) if (parent == nullptr)
return false; return false;
const CTxOut& parentOut = parent->tx->vout[txin.prevout.n]; const CTxOut& parentOut = parent->tx->vout[txin.prevout.n];
if (pwallet->IsMine(parentOut) != ISMINE_SPENDABLE) if (!(pwallet->IsMine(parentOut) & ISMINE_SPENDABLE))
return false; return false;
} }
return true; return true;
@ -2598,15 +2597,15 @@ void CWallet::AvailableCoins(interfaces::Chain::Lock& locked_chain, std::vector<
if (!allow_used_addresses && IsUsedDestination(wtxid, i)) { if (!allow_used_addresses && IsUsedDestination(wtxid, i)) {
continue; continue;
} }
bool solvable = IsSolvable(*this, wtx.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: // spending claims or supports requires specific selection:
auto isClaimCoin = (mine & ISMINE_CLAIM) || (mine & ISMINE_SUPPORT); auto isClaimCoin = (mine & ISMINE_CLAIM) || (mine & ISMINE_SUPPORT);
auto claimSpendRequested = isClaimCoin && 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 spendable = claimSpendRequested || ((mine & ISMINE_SPENDABLE) != ISMINE_NO) || (((mine & ISMINE_WATCH_ONLY) != ISMINE_NO) && (coinControl && coinControl->fAllowWatchOnly && solvable));
if (spendable && isClaimCoin && !claimSpendRequested) if (spendable && isClaimCoin && !claimSpendRequested)
continue; // double check continue;
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)));
@ -2651,7 +2650,7 @@ std::map<CTxDestination, std::vector<COutput>> CWallet::ListCoins(interfaces::Ch
if (it != mapWallet.end()) { if (it != mapWallet.end()) {
int depth = it->second.GetDepthInMainChain(locked_chain); int depth = it->second.GetDepthInMainChain(locked_chain);
if (depth >= 0 && output.n < it->second.tx->vout.size() && 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; CTxDestination address;
if (ExtractDestination(FindNonChangeParentOutput(*it->second.tx, output.n).scriptPubKey, address)) { if (ExtractDestination(FindNonChangeParentOutput(*it->second.tx, output.n).scriptPubKey, address)) {
result[address].emplace_back( result[address].emplace_back(
@ -2975,7 +2974,8 @@ OutputType CWallet::TransactionChangeType(OutputType change_type, const std::vec
// Check if any destination contains a witness program: // Check if any destination contains a witness program:
int witnessversion = 0; int witnessversion = 0;
std::vector<unsigned char> witnessprogram; 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; return OutputType::BECH32;
} }
} }