RPC: z_viewtransaction
This commit is contained in:
parent
8770f88663
commit
32ade1edb1
|
@ -3515,6 +3515,172 @@ UniValue z_gettotalbalance(const UniValue& params, bool fHelp)
|
|||
return result;
|
||||
}
|
||||
|
||||
UniValue z_viewtransaction(const UniValue& params, bool fHelp)
|
||||
{
|
||||
if (!EnsureWalletIsAvailable(fHelp))
|
||||
return NullUniValue;
|
||||
|
||||
if (fHelp || params.size() != 1)
|
||||
throw runtime_error(
|
||||
"z_viewtransaction \"txid\"\n"
|
||||
"\nGet detailed shielded information about in-wallet transaction <txid>\n"
|
||||
"\nArguments:\n"
|
||||
"1. \"txid\" (string, required) The transaction id\n"
|
||||
"\nResult:\n"
|
||||
"{\n"
|
||||
" \"txid\" : \"transactionid\", (string) The transaction id\n"
|
||||
" \"spends\" : [\n"
|
||||
" {\n"
|
||||
" \"type\" : \"sprout|sapling\", (string) The type of address\n"
|
||||
" \"js\" : n, (numeric, sprout) the index of the JSDescription within vJoinSplit\n"
|
||||
" \"jsSpend\" : n, (numeric, sprout) the index of the spend within the JSDescription\n"
|
||||
" \"spend\" : n, (numeric, sapling) the index of the spend within vShieldedSpend\n"
|
||||
" \"txidPrev\" : \"transactionid\", (string) The id for the transaction this note was created in\n"
|
||||
" \"jsPrev\" : n, (numeric, sprout) the index of the JSDescription within vJoinSplit\n"
|
||||
" \"jsOutputPrev\" : n, (numeric, sprout) the index of the output within the JSDescription\n"
|
||||
" \"outputPrev\" : n, (numeric, sapling) the index of the output within the vShieldedOutput\n"
|
||||
" \"address\" : \"zcashaddress\", (string) The Zcash address involved in the transaction\n"
|
||||
" \"value\" : x.xxx (numeric) The amount in " + CURRENCY_UNIT + "\n"
|
||||
" \"valueZat\" : xxxx (numeric) The amount in zatoshis\n"
|
||||
" }\n"
|
||||
" ,...\n"
|
||||
" ],\n"
|
||||
" \"outputs\" : [\n"
|
||||
" {\n"
|
||||
" \"type\" : \"sprout|sapling\", (string) The type of address\n"
|
||||
" \"js\" : n, (numeric, sprout) the index of the JSDescription within vJoinSplit\n"
|
||||
" \"jsOutput\" : n, (numeric, sprout) the index of the output within the JSDescription\n"
|
||||
" \"output\" : n, (numeric, sapling) the index of the output within the vShieldedOutput\n"
|
||||
" \"address\" : \"zcashaddress\", (string) The Zcash address involved in the transaction\n"
|
||||
" \"value\" : x.xxx (numeric) The amount in " + CURRENCY_UNIT + "\n"
|
||||
" \"valueZat\" : xxxx (numeric) The amount in zatoshis\n"
|
||||
" \"memo\" : \"hexmemo\", (string) Hexademical string representation of the memo field\n"
|
||||
" }\n"
|
||||
" ,...\n"
|
||||
" ],\n"
|
||||
"}\n"
|
||||
|
||||
"\nExamples:\n"
|
||||
+ HelpExampleCli("z_viewtransaction", "\"1075db55d416d3ca199f55b6084e2115b9345e16c5cf302fc80e9d5fbf5d48d\"")
|
||||
+ HelpExampleRpc("z_viewtransaction", "\"1075db55d416d3ca199f55b6084e2115b9345e16c5cf302fc80e9d5fbf5d48d\"")
|
||||
);
|
||||
|
||||
LOCK2(cs_main, pwalletMain->cs_wallet);
|
||||
|
||||
uint256 hash;
|
||||
hash.SetHex(params[0].get_str());
|
||||
|
||||
UniValue entry(UniValue::VOBJ);
|
||||
if (!pwalletMain->mapWallet.count(hash))
|
||||
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid or non-wallet transaction id");
|
||||
const CWalletTx& wtx = pwalletMain->mapWallet[hash];
|
||||
|
||||
entry.push_back(Pair("txid", hash.GetHex()));
|
||||
|
||||
UniValue spends(UniValue::VARR);
|
||||
UniValue outputs(UniValue::VARR);
|
||||
|
||||
// Sprout spends
|
||||
for (size_t i = 0; i < wtx.vJoinSplit.size(); ++i) {
|
||||
for (size_t j = 0; j < wtx.vJoinSplit[i].nullifiers.size(); ++j) {
|
||||
auto nullifier = wtx.vJoinSplit[i].nullifiers[j];
|
||||
|
||||
// Fetch the note that is being spent, if ours
|
||||
auto res = pwalletMain->mapSproutNullifiersToNotes.find(nullifier);
|
||||
if (res == pwalletMain->mapSproutNullifiersToNotes.end()) {
|
||||
continue;
|
||||
}
|
||||
auto jsop = res->second;
|
||||
auto wtxPrev = pwalletMain->mapWallet.at(jsop.hash);
|
||||
|
||||
auto decrypted = wtxPrev.DecryptSproutNote(jsop);
|
||||
auto notePt = decrypted.first;
|
||||
auto pa = decrypted.second;
|
||||
|
||||
UniValue entry(UniValue::VOBJ);
|
||||
entry.push_back(Pair("type", ADDR_TYPE_SPROUT));
|
||||
entry.push_back(Pair("js", (int)i));
|
||||
entry.push_back(Pair("jsSpend", (int)j));
|
||||
entry.push_back(Pair("txidPrev", jsop.hash.GetHex()));
|
||||
entry.push_back(Pair("jsPrev", (int)jsop.js));
|
||||
entry.push_back(Pair("jsOutputPrev", (int)jsop.n));
|
||||
entry.push_back(Pair("address", EncodePaymentAddress(pa)));
|
||||
entry.push_back(Pair("value", ValueFromAmount(notePt.value())));
|
||||
entry.push_back(Pair("valueZat", notePt.value()));
|
||||
spends.push_back(entry);
|
||||
}
|
||||
}
|
||||
|
||||
// Sprout outputs
|
||||
for (auto & pair : wtx.mapSproutNoteData) {
|
||||
JSOutPoint jsop = pair.first;
|
||||
|
||||
auto decrypted = wtx.DecryptSproutNote(jsop);
|
||||
auto notePt = decrypted.first;
|
||||
auto pa = decrypted.second;
|
||||
|
||||
UniValue entry(UniValue::VOBJ);
|
||||
entry.push_back(Pair("type", ADDR_TYPE_SPROUT));
|
||||
entry.push_back(Pair("js", (int)jsop.js));
|
||||
entry.push_back(Pair("jsOutput", (int)jsop.n));
|
||||
entry.push_back(Pair("address", EncodePaymentAddress(pa)));
|
||||
entry.push_back(Pair("value", ValueFromAmount(notePt.value())));
|
||||
entry.push_back(Pair("valueZat", notePt.value()));
|
||||
entry.push_back(Pair("memo", HexStr(notePt.memo())));
|
||||
outputs.push_back(entry);
|
||||
}
|
||||
|
||||
// Sapling spends
|
||||
for (size_t i = 0; i < wtx.vShieldedSpend.size(); ++i) {
|
||||
auto spend = wtx.vShieldedSpend[i];
|
||||
|
||||
// Fetch the note that is being spent
|
||||
auto res = pwalletMain->mapSaplingNullifiersToNotes.find(spend.nullifier);
|
||||
if (res == pwalletMain->mapSaplingNullifiersToNotes.end()) {
|
||||
continue;
|
||||
}
|
||||
auto op = res->second;
|
||||
auto wtxPrev = pwalletMain->mapWallet.at(op.hash);
|
||||
|
||||
auto decrypted = wtxPrev.DecryptSaplingNote(op);
|
||||
auto notePt = decrypted.first;
|
||||
auto pa = decrypted.second;
|
||||
|
||||
UniValue entry(UniValue::VOBJ);
|
||||
entry.push_back(Pair("type", ADDR_TYPE_SAPLING));
|
||||
entry.push_back(Pair("spend", (int)i));
|
||||
entry.push_back(Pair("txidPrev", op.hash.GetHex()));
|
||||
entry.push_back(Pair("outputPrev", (int)op.n));
|
||||
entry.push_back(Pair("address", EncodePaymentAddress(pa)));
|
||||
entry.push_back(Pair("value", ValueFromAmount(notePt.value())));
|
||||
entry.push_back(Pair("valueZat", notePt.value()));
|
||||
spends.push_back(entry);
|
||||
}
|
||||
|
||||
// Sapling outputs
|
||||
for (auto & pair : wtx.mapSaplingNoteData) {
|
||||
SaplingOutPoint op = pair.first;
|
||||
|
||||
auto decrypted = wtx.DecryptSaplingNote(op);
|
||||
auto notePt = decrypted.first;
|
||||
auto pa = decrypted.second;
|
||||
|
||||
UniValue entry(UniValue::VOBJ);
|
||||
entry.push_back(Pair("type", ADDR_TYPE_SAPLING));
|
||||
entry.push_back(Pair("output", (int)op.n));
|
||||
entry.push_back(Pair("address", EncodePaymentAddress(pa)));
|
||||
entry.push_back(Pair("value", ValueFromAmount(notePt.value())));
|
||||
entry.push_back(Pair("valueZat", notePt.value()));
|
||||
entry.push_back(Pair("memo", HexStr(notePt.memo())));
|
||||
outputs.push_back(entry);
|
||||
}
|
||||
|
||||
entry.push_back(Pair("spends", spends));
|
||||
entry.push_back(Pair("outputs", outputs));
|
||||
|
||||
return entry;
|
||||
}
|
||||
|
||||
UniValue z_getoperationresult(const UniValue& params, bool fHelp)
|
||||
{
|
||||
if (!EnsureWalletIsAvailable(fHelp))
|
||||
|
@ -4816,6 +4982,7 @@ static const CRPCCommand commands[] =
|
|||
{ "wallet", "z_importviewingkey", &z_importviewingkey, true },
|
||||
{ "wallet", "z_exportwallet", &z_exportwallet, true },
|
||||
{ "wallet", "z_importwallet", &z_importwallet, true },
|
||||
{ "wallet", "z_viewtransaction", &z_viewtransaction, false },
|
||||
// TODO: rearrange into another category
|
||||
{ "disclosure", "z_getpaymentdisclosure", &z_getpaymentdisclosure, true },
|
||||
{ "disclosure", "z_validatepaymentdisclosure", &z_validatepaymentdisclosure, true }
|
||||
|
|
|
@ -2218,6 +2218,67 @@ void CWalletTx::SetSaplingNoteData(mapSaplingNoteData_t ¬eData)
|
|||
}
|
||||
}
|
||||
|
||||
std::pair<SproutNotePlaintext, SproutPaymentAddress> CWalletTx::DecryptSproutNote(
|
||||
JSOutPoint jsop) const
|
||||
{
|
||||
LOCK(pwallet->cs_wallet);
|
||||
|
||||
auto nd = this->mapSproutNoteData.at(jsop);
|
||||
SproutPaymentAddress pa = nd.address;
|
||||
|
||||
// Get cached decryptor
|
||||
ZCNoteDecryption decryptor;
|
||||
if (!pwallet->GetNoteDecryptor(pa, decryptor)) {
|
||||
// Note decryptors are created when the wallet is loaded, so it should always exist
|
||||
throw std::runtime_error(strprintf(
|
||||
"Could not find note decryptor for payment address %s",
|
||||
EncodePaymentAddress(pa)));
|
||||
}
|
||||
|
||||
auto hSig = this->vJoinSplit[jsop.js].h_sig(*pzcashParams, this->joinSplitPubKey);
|
||||
try {
|
||||
SproutNotePlaintext plaintext = SproutNotePlaintext::decrypt(
|
||||
decryptor,
|
||||
this->vJoinSplit[jsop.js].ciphertexts[jsop.n],
|
||||
this->vJoinSplit[jsop.js].ephemeralKey,
|
||||
hSig,
|
||||
(unsigned char) jsop.n);
|
||||
|
||||
return std::make_pair(plaintext, pa);
|
||||
} catch (const note_decryption_failed &err) {
|
||||
// Couldn't decrypt with this spending key
|
||||
throw std::runtime_error(strprintf(
|
||||
"Could not decrypt note for payment address %s",
|
||||
EncodePaymentAddress(pa)));
|
||||
} catch (const std::exception &exc) {
|
||||
// Unexpected failure
|
||||
throw std::runtime_error(strprintf(
|
||||
"Error while decrypting note for payment address %s: %s",
|
||||
EncodePaymentAddress(pa), exc.what()));
|
||||
}
|
||||
}
|
||||
|
||||
std::pair<SaplingNotePlaintext, SaplingPaymentAddress> CWalletTx::DecryptSaplingNote(
|
||||
SaplingOutPoint op) const
|
||||
{
|
||||
auto output = this->vShieldedOutput[op.n];
|
||||
auto nd = this->mapSaplingNoteData.at(op);
|
||||
|
||||
auto maybe_pt = SaplingNotePlaintext::decrypt(
|
||||
output.encCiphertext,
|
||||
nd.ivk,
|
||||
output.ephemeralKey,
|
||||
output.cm);
|
||||
assert(static_cast<bool>(maybe_pt));
|
||||
auto notePt = maybe_pt.get();
|
||||
|
||||
auto maybe_pa = nd.ivk.address(notePt.d);
|
||||
assert(static_cast<bool>(maybe_pa));
|
||||
auto pa = maybe_pa.get();
|
||||
|
||||
return std::make_pair(notePt, pa);
|
||||
}
|
||||
|
||||
int64_t CWalletTx::GetTxTime() const
|
||||
{
|
||||
int64_t n = nTimeSmart;
|
||||
|
|
|
@ -555,6 +555,11 @@ public:
|
|||
void SetSproutNoteData(mapSproutNoteData_t ¬eData);
|
||||
void SetSaplingNoteData(mapSaplingNoteData_t ¬eData);
|
||||
|
||||
std::pair<libzcash::SproutNotePlaintext, libzcash::SproutPaymentAddress> DecryptSproutNote(
|
||||
JSOutPoint jsop) const;
|
||||
std::pair<libzcash::SaplingNotePlaintext, libzcash::SaplingPaymentAddress> DecryptSaplingNote(
|
||||
SaplingOutPoint op) const;
|
||||
|
||||
//! filter decides which addresses will count towards the debit
|
||||
CAmount GetDebit(const isminefilter& filter) const;
|
||||
CAmount GetCredit(const isminefilter& filter) const;
|
||||
|
|
Loading…
Reference in New Issue