RPC: z_viewtransaction

This commit is contained in:
Jack Grigg 2019-09-27 11:06:11 +01:00
parent 8770f88663
commit 32ade1edb1
3 changed files with 233 additions and 0 deletions

View File

@ -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 }

View File

@ -2218,6 +2218,67 @@ void CWalletTx::SetSaplingNoteData(mapSaplingNoteData_t &noteData)
}
}
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;

View File

@ -555,6 +555,11 @@ public:
void SetSproutNoteData(mapSproutNoteData_t &noteData);
void SetSaplingNoteData(mapSaplingNoteData_t &noteData);
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;