z_listsentbyaddress, getalldata, z_getbalances and z_listreceivedaddress deep-copied every CWalletTx (witness lists included) into an ordered map, and the JSON helpers re-derived keys and scanned the whole wallet for every output: zsTxReceivedToJSON trial-decrypted each output with every spending key, and each change check called GetNullifiersForAddresses, a full mapWallet note scan, once per decrypted output. - ZsTxJSONContext memoises per call the spending keys with their ivk/ovk, the HD-seed ovk and the wallet's nullifier set. - Wallet txs are ordered by pointer. stable_sort + unique on nOrderPos keeps the first entry, exactly like the old map::insert. - A received output is first decrypted with the ivk the wallet recorded for it when that ivk belongs to one of the usable keys, then with the remaining keys in the old order. - "size" is measured without a slicing copy; the result is reversed in one pass; getalldata's note loop reads note data by reference and reuses the memoised keys. Output is unchanged. New opt-in 6th argument sendsOnly (default false): true omits each entry's "spends" and "received" sections and the trial decryption they need. Older daemons reject a 6th argument with the help text, so a client can fall back. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
582 lines
22 KiB
C++
582 lines
22 KiB
C++
// Copyright (c) 2016-2024 The Hush developers
|
|
// Copyright (c) 2019 Cryptoforge
|
|
// Distributed under the GPLv3 software license, see the accompanying
|
|
// file COPYING or https://www.gnu.org/licenses/gpl-3.0.en.html
|
|
|
|
#include "init.h"
|
|
#include "key_io.h"
|
|
#include "rpc/server.h"
|
|
#include "wallet.h"
|
|
#include "rpchushwallet.h"
|
|
#include "utilmoneystr.h"
|
|
#include <utf8.h>
|
|
|
|
#include <map>
|
|
#include <set>
|
|
#include <utility>
|
|
#include <vector>
|
|
|
|
using namespace std;
|
|
using namespace libzcash;
|
|
|
|
bool EnsureWalletIsAvailable(bool avoidException);
|
|
|
|
const std::vector<ZsTxJSONContext::Key>& ZsTxJSONContext::keys()
|
|
{
|
|
if (!keysBuilt) {
|
|
// The calls, order and filter of the per-output key loops this replaces.
|
|
std::set<libzcash::SaplingPaymentAddress> addresses;
|
|
pwalletMain->GetSaplingPaymentAddresses(addresses);
|
|
for (std::set<libzcash::SaplingPaymentAddress>::const_iterator it = addresses.begin(); it != addresses.end(); ++it) {
|
|
libzcash::SaplingExtendedSpendingKey extsk;
|
|
if (!pwalletMain->GetSaplingExtendedSpendingKey(*it, extsk))
|
|
continue; // viewing-key-only, or encrypted and locked: skipped, as before
|
|
libzcash::SaplingFullViewingKey fvk = extsk.expsk.full_viewing_key();
|
|
Key key;
|
|
key.addr = *it;
|
|
key.ivk = fvk.in_viewing_key();
|
|
key.ovk = fvk.ovk;
|
|
vKeys.push_back(key);
|
|
}
|
|
keysBuilt = true;
|
|
}
|
|
return vKeys;
|
|
}
|
|
|
|
bool ZsTxJSONContext::hasNullifier(const libzcash::SaplingPaymentAddress& addr, const uint256& nullifier)
|
|
{
|
|
if (!nullifiersBuilt) {
|
|
// The population GetNullifiersForAddresses() scans: every mapWallet note that has a nullifier.
|
|
for (std::map<uint256, CWalletTx>::const_iterator it = pwalletMain->mapWallet.begin(); it != pwalletMain->mapWallet.end(); ++it) {
|
|
for (mapSaplingNoteData_t::const_iterator nit = it->second.mapSaplingNoteData.begin(); nit != it->second.mapSaplingNoteData.end(); ++nit) {
|
|
if (nit->second.nullifier)
|
|
nfByIvk.insert(std::make_pair(nit->second.ivk, nit->second.nullifier.get()));
|
|
}
|
|
}
|
|
nullifiersBuilt = true;
|
|
}
|
|
// GetNullifiersForAddresses() ignores the result of this lookup, so an address the wallet does not
|
|
// know is matched against the default (null) ivk. Keep that.
|
|
libzcash::SaplingIncomingViewingKey ivk;
|
|
pwalletMain->GetSaplingIncomingViewingKey(addr, ivk);
|
|
return nfByIvk.count(std::make_pair(ivk, nullifier)) > 0;
|
|
}
|
|
|
|
bool ZsTxJSONContext::seedOvk(uint256& ovkOut)
|
|
{
|
|
if (!seedOvkTried) {
|
|
HDSeed seed;
|
|
if (pwalletMain->GetHDSeedForDerivation(seed)) {
|
|
seedOvkValue = ovkForShieldingFromTaddr(seed);
|
|
seedOvkOk = true;
|
|
}
|
|
seedOvkTried = true;
|
|
}
|
|
if (seedOvkOk)
|
|
ovkOut = seedOvkValue;
|
|
return seedOvkOk;
|
|
}
|
|
|
|
void zsTxSpendsToJSON(const CWalletTx& wtx, UniValue& spends, CAmount& totalSpends, CAmount& filteredSpends, const std::string& strAddress, bool filterByAddress) {
|
|
|
|
LOCK2(cs_main, pwalletMain->cs_wallet);
|
|
|
|
//Used to identify incomplete key sets
|
|
int vinCount = 0;
|
|
int vinSpendCount = 0;
|
|
int shieldedSpendCount = 0;
|
|
int shieldedSpendsSpentCount = 0;
|
|
|
|
//Check address
|
|
bool isTAddress = false;
|
|
bool isZsAddress = false;
|
|
|
|
CTxDestination tAddress = DecodeDestination(strAddress);
|
|
auto zAddress = DecodePaymentAddress(strAddress);
|
|
SaplingPaymentAddress zsAddress;
|
|
|
|
if (filterByAddress) {
|
|
|
|
if (IsValidDestination(tAddress))
|
|
isTAddress = true;
|
|
|
|
if (IsValidPaymentAddress(zAddress)) {
|
|
if (boost::get<libzcash::SaplingPaymentAddress>(&zAddress) != nullptr) {
|
|
zsAddress = boost::get<libzcash::SaplingPaymentAddress>(zAddress);
|
|
isZsAddress = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
// Transparent Inputs belonging to the wallet
|
|
UniValue tSpends(UniValue::VARR);
|
|
if (isTAddress || !filterByAddress) {
|
|
for (int i = 0; i < wtx.vin.size(); i++) {
|
|
vinCount++;
|
|
const CTxIn& txin = wtx.vin[i];
|
|
UniValue obj(UniValue::VOBJ);
|
|
CTxDestination address;
|
|
const CWalletTx* parent = pwalletMain->GetWalletTx(txin.prevout.hash);
|
|
if (parent != NULL) {
|
|
const CTxOut& parentOut = parent->vout[txin.prevout.n];
|
|
ExtractDestination(parentOut.scriptPubKey, address);
|
|
if(IsMine(*pwalletMain, address)){
|
|
vinSpendCount++;
|
|
totalSpends += CAmount(-parentOut.nValue);
|
|
obj.push_back(Pair("address",EncodeDestination(address)));
|
|
obj.push_back(Pair("scriptPubKey",HexStr(parentOut.scriptPubKey.begin(), parentOut.scriptPubKey.end())));
|
|
obj.push_back(Pair("amount",ValueFromAmount(-parentOut.nValue)));
|
|
obj.push_back(Pair("spendTxid",parent->GetHash().ToString()));
|
|
obj.push_back(Pair("spendVout",(int)txin.prevout.n));
|
|
CTxDestination filterAddress = DecodeDestination(strAddress);
|
|
if (address == tAddress || !filterByAddress) {
|
|
filteredSpends += CAmount(-parentOut.nValue);
|
|
tSpends.push_back(obj);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
spends.push_back(Pair("transparentSpends",tSpends));
|
|
|
|
// Sapling Inputs belonging to the wallet
|
|
UniValue zsSpends(UniValue::VARR);
|
|
if (isZsAddress || !filterByAddress) {
|
|
for (int i = 0; i < wtx.vShieldedSpend.size(); i++) {
|
|
shieldedSpendCount++;
|
|
const SpendDescription& spendDesc = wtx.vShieldedSpend[i];
|
|
UniValue obj(UniValue::VOBJ);
|
|
|
|
SaplingOutPoint op = pwalletMain->mapSaplingNullifiersToNotes[spendDesc.nullifier];
|
|
|
|
if (pwalletMain->IsSaplingNullifierFromMe(spendDesc.nullifier)) {
|
|
const CWalletTx* parent = pwalletMain->GetWalletTx(pwalletMain->mapSaplingNullifiersToNotes[spendDesc.nullifier].hash);
|
|
const OutputDescription& output = parent->vShieldedOutput[op.n];
|
|
auto nd = pwalletMain->mapWallet[pwalletMain->mapSaplingNullifiersToNotes[spendDesc.nullifier].hash].mapSaplingNoteData[op];
|
|
auto pt = libzcash::SaplingNotePlaintext::decrypt(output.encCiphertext,nd.ivk,output.ephemeralKey,output.cm);
|
|
|
|
if (pt) {
|
|
auto note = pt.get();
|
|
auto pa = nd.ivk.address(note.d);
|
|
auto address = pa.get();
|
|
shieldedSpendsSpentCount++;
|
|
totalSpends += CAmount(-note.value());
|
|
obj.push_back(Pair("address",EncodePaymentAddress(address)));
|
|
obj.push_back(Pair("amount", ValueFromAmount(CAmount(-note.value()))));
|
|
obj.push_back(Pair("spendTxid",parent->GetHash().ToString()));
|
|
obj.push_back(Pair("spendshieldedOutputIndex",(int)op.n));
|
|
if (address == zsAddress || !filterByAddress) {
|
|
filteredSpends += CAmount(-note.value());
|
|
zsSpends.push_back(obj);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
spends.push_back(Pair("saplingSpends",zsSpends));
|
|
|
|
|
|
spends.push_back(Pair("totalSpends",ValueFromAmount(filteredSpends)));
|
|
|
|
if (!filterByAddress) {
|
|
if (filteredSpends != 0 && (vinCount != vinSpendCount || shieldedSpendCount != shieldedSpendsSpentCount)) {
|
|
spends.push_back(Pair("missingSpendingKeys", true));
|
|
spends.push_back(Pair("vinCount", vinCount));
|
|
spends.push_back(Pair("vinSpendCount", vinSpendCount));
|
|
spends.push_back(Pair("shieldedSpendCount", shieldedSpendCount));
|
|
spends.push_back(Pair("shieldedSpendsSpentCount", shieldedSpendsSpentCount));
|
|
} else {
|
|
spends.push_back(Pair("missingSpendingKeys", false));
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
void zsTxReceivedToJSON(const CWalletTx& wtx, UniValue& received, CAmount& totalReceived, const std::string& strAddress, bool filterByAddress, ZsTxJSONContext* ctx) {
|
|
|
|
if(fZdebug)
|
|
fprintf(stderr,"%s: txid=%s\n", __func__, wtx.GetHash().ToString().c_str() );
|
|
|
|
LOCK2(cs_main, pwalletMain->cs_wallet);
|
|
|
|
ZsTxJSONContext localCtx; // used only when the caller passed no per-call memo
|
|
ZsTxJSONContext& zctx = ctx ? *ctx : localCtx;
|
|
|
|
//Check address
|
|
bool isTAddress = false;
|
|
bool isZsAddress = false;
|
|
|
|
CTxDestination tAddress = DecodeDestination(strAddress);
|
|
auto zAddress = DecodePaymentAddress(strAddress);
|
|
SaplingPaymentAddress zsAddress;
|
|
|
|
if (filterByAddress) {
|
|
|
|
if (IsValidDestination(tAddress))
|
|
isTAddress = true;
|
|
|
|
if (IsValidPaymentAddress(zAddress)) {
|
|
if (boost::get<libzcash::SaplingPaymentAddress>(&zAddress) != nullptr) {
|
|
zsAddress = boost::get<libzcash::SaplingPaymentAddress>(zAddress);
|
|
isZsAddress = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
//Transparent Received txos belonging to the wallet
|
|
UniValue tReceived(UniValue::VARR);
|
|
if (isTAddress || !filterByAddress) {
|
|
for (int i = 0; i < wtx.vout.size(); i++) {
|
|
const CTxOut& txout = wtx.vout[i];
|
|
UniValue obj(UniValue::VOBJ);
|
|
CTxDestination address;
|
|
ExtractDestination(txout.scriptPubKey, address);
|
|
if(IsMine(*pwalletMain, address)){
|
|
obj.push_back(Pair("address",EncodeDestination(address)));
|
|
obj.push_back(Pair("scriptPubKey",HexStr(txout.scriptPubKey.begin(), txout.scriptPubKey.end())));
|
|
obj.push_back(Pair("amount",ValueFromAmount(txout.nValue)));
|
|
obj.push_back(Pair("vout", i));
|
|
if (address == tAddress || !filterByAddress) {
|
|
totalReceived += CAmount(txout.nValue);
|
|
tReceived.push_back(obj);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
received.push_back(Pair("transparentReceived",tReceived));
|
|
|
|
|
|
//Sapling Sends belonging to the wallet
|
|
UniValue zsReceived(UniValue::VARR);
|
|
if (isZsAddress || !filterByAddress) {
|
|
for (int i = 0; i < wtx.vShieldedOutput.size(); i++) {
|
|
const OutputDescription& outputDesc = wtx.vShieldedOutput[i];
|
|
UniValue obj(UniValue::VOBJ);
|
|
bool changeTx = false;
|
|
//Decrypt sapling incoming commitments using IVK
|
|
const std::vector<ZsTxJSONContext::Key>& keys = zctx.keys();
|
|
// An output opens under at most one ivk (decrypt() checks the note commitment, which binds
|
|
// pk_d = [ivk]g_d), and FindMySaplingNotes records that ivk in mapSaplingNoteData. When the recorded
|
|
// ivk is one of keys(), pass 0 tries only the keys with that ivk: every other key would fail, as in
|
|
// the old single loop. If pass 0 opens nothing (stale note data), pass 1 tries the remaining keys in
|
|
// the same order. Without a usable record only pass 1 runs, over every key, exactly as before.
|
|
libzcash::SaplingIncomingViewingKey recordedIvk;
|
|
bool narrow = false;
|
|
{
|
|
mapSaplingNoteData_t::const_iterator ndIt = wtx.mapSaplingNoteData.find(SaplingOutPoint(wtx.GetHash(), i));
|
|
if (ndIt != wtx.mapSaplingNoteData.end()) {
|
|
for (size_t k = 0; k < keys.size() && !narrow; k++) {
|
|
if (keys[k].ivk == ndIt->second.ivk) {
|
|
recordedIvk = ndIt->second.ivk;
|
|
narrow = true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
bool openedByRecordedIvk = false;
|
|
for (int pass = narrow ? 0 : 1; pass < 2 && !openedByRecordedIvk; pass++) {
|
|
for (size_t k = 0; k < keys.size(); k++) {
|
|
if (narrow && ((pass == 0) != (keys[k].ivk == recordedIvk)))
|
|
continue; // pass 0: only the recorded ivk; pass 1: only the others
|
|
const libzcash::SaplingPaymentAddress& addr = keys[k].addr;
|
|
{ // spending-key availability is resolved once per call, in ZsTxJSONContext::keys()
|
|
auto pt = libzcash::SaplingNotePlaintext::decrypt(
|
|
outputDesc.encCiphertext, keys[k].ivk, outputDesc.ephemeralKey, outputDesc.cm);
|
|
if (pt && pass == 0)
|
|
openedByRecordedIvk = true;
|
|
|
|
if (pt) {
|
|
auto note = pt.get();
|
|
auto pt_unwrapped = pt.get();
|
|
auto memo = pt_unwrapped.memo();
|
|
obj.push_back(Pair("address",EncodePaymentAddress(addr)));
|
|
obj.push_back(Pair("amount", ValueFromAmount(CAmount(note.value()))));
|
|
obj.push_back(Pair("memo", HexStr(memo)));
|
|
|
|
if (memo[0] <= 0xf4) {
|
|
auto end = std::find_if(memo.rbegin(), memo.rend(), [](unsigned char v) { return v != 0; });
|
|
std::string memoStr(memo.begin(), end.base());
|
|
if (utf8::is_valid(memoStr)) {
|
|
obj.push_back(Pair("memoStr", memoStr));
|
|
}
|
|
}
|
|
obj.push_back(Pair("shieldedOutputIndex",i));
|
|
|
|
//Check Change Status
|
|
if (wtx.vShieldedSpend.size()!=0) {
|
|
BOOST_FOREACH(const SpendDescription& spendDesc, wtx.vShieldedSpend) {
|
|
if (zctx.hasNullifier(addr, spendDesc.nullifier)) {
|
|
changeTx = true;
|
|
}
|
|
}
|
|
}
|
|
obj.push_back(Pair("change",changeTx));
|
|
if (addr == zsAddress || !filterByAddress) {
|
|
totalReceived += CAmount(note.value());
|
|
zsReceived.push_back(obj);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
} // pass
|
|
}
|
|
}
|
|
received.push_back(Pair("saplingReceived",zsReceived));
|
|
|
|
received.push_back(Pair("totalReceived",ValueFromAmount(totalReceived)));
|
|
}
|
|
|
|
|
|
void zsTxSendsToJSON(const CWalletTx& wtx, UniValue& sends, CAmount& totalSends, const std::string& strAddress, bool filterByAddress, ZsTxJSONContext* ctx) {
|
|
|
|
LOCK2(cs_main, pwalletMain->cs_wallet);
|
|
|
|
ZsTxJSONContext localCtx; // used only when the caller passed no per-call memo
|
|
ZsTxJSONContext& zctx = ctx ? *ctx : localCtx;
|
|
|
|
//Used to identify incomplete key sets
|
|
int shieldedOutputCount = 0;
|
|
int shieldedOutputDecryptedCount = 0;
|
|
|
|
//Check address
|
|
bool isTAddress = false;
|
|
bool isZsAddress = false;
|
|
|
|
CTxDestination tAddress = DecodeDestination(strAddress);
|
|
auto zAddress = DecodePaymentAddress(strAddress);
|
|
SaplingPaymentAddress zsAddress;
|
|
|
|
if (filterByAddress) {
|
|
|
|
if (IsValidDestination(tAddress))
|
|
isTAddress = true;
|
|
|
|
if (IsValidPaymentAddress(zAddress)) {
|
|
if (boost::get<libzcash::SaplingPaymentAddress>(&zAddress) != nullptr) {
|
|
zsAddress = boost::get<libzcash::SaplingPaymentAddress>(zAddress);
|
|
isZsAddress = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
//All Transparent Sends in the transaction
|
|
|
|
UniValue tSends(UniValue::VARR);
|
|
if (isTAddress || !filterByAddress) {
|
|
for (int i = 0; i < wtx.vout.size(); i++) {
|
|
const CTxOut& txout = wtx.vout[i];
|
|
UniValue obj(UniValue::VOBJ);
|
|
CTxDestination address;
|
|
ExtractDestination(txout.scriptPubKey, address);
|
|
obj.push_back(Pair("address",EncodeDestination(address)));
|
|
obj.push_back(Pair("scriptPubKey",HexStr(txout.scriptPubKey.begin(), txout.scriptPubKey.end())));
|
|
obj.push_back(Pair("amount",ValueFromAmount(-txout.nValue)));
|
|
obj.push_back(Pair("vout", i));
|
|
if (address == tAddress || !filterByAddress) {
|
|
totalSends += CAmount(-txout.nValue);
|
|
tSends.push_back(obj);
|
|
}
|
|
}
|
|
}
|
|
sends.push_back(Pair("transparentSends",tSends));
|
|
|
|
//All Shielded Sends in the transaction
|
|
UniValue zsSends(UniValue::VARR);
|
|
if (isZsAddress || !filterByAddress) {
|
|
for (int i = 0; i < wtx.vShieldedOutput.size(); i++) {
|
|
const OutputDescription& outputDesc = wtx.vShieldedOutput[i];
|
|
shieldedOutputCount++;
|
|
UniValue obj(UniValue::VOBJ);
|
|
bool changeTx = false;
|
|
|
|
//Decrypt sapling outgoing t to z transaction using HDseed
|
|
if (wtx.vShieldedSpend.size()==0) {
|
|
uint256 taddrOvk;
|
|
if (zctx.seedOvk(taddrOvk)) {
|
|
auto opt = libzcash::SaplingOutgoingPlaintext::decrypt(
|
|
outputDesc.outCiphertext,taddrOvk,outputDesc.cv,outputDesc.cm,outputDesc.ephemeralKey);
|
|
|
|
if (opt) {
|
|
auto opt_unwrapped = opt.get();
|
|
auto pt = libzcash::SaplingNotePlaintext::decrypt(
|
|
outputDesc.encCiphertext,outputDesc.ephemeralKey,opt_unwrapped.esk,opt_unwrapped.pk_d,outputDesc.cm);
|
|
|
|
if (pt) {
|
|
shieldedOutputDecryptedCount++;
|
|
auto pt_unwrapped = pt.get();
|
|
libzcash::SaplingPaymentAddress sentAddr(pt_unwrapped.d, opt_unwrapped.pk_d);
|
|
obj.push_back(Pair("address",EncodePaymentAddress(sentAddr)));
|
|
obj.push_back(Pair("amount", ValueFromAmount(CAmount(pt_unwrapped.value()))));
|
|
obj.push_back(Pair("shieldedOutputIndex",i));
|
|
obj.push_back(Pair("change",false));
|
|
if (sentAddr == zsAddress || !filterByAddress) {
|
|
totalSends += CAmount(-pt_unwrapped.value());
|
|
zsSends.push_back(obj);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
//attempt Decryption of Outgoing Sapling using wallet extended spending keys
|
|
} else {
|
|
const std::vector<ZsTxJSONContext::Key>& keys = zctx.keys();
|
|
for (size_t k = 0; k < keys.size(); k++) {
|
|
{ // spending-key availability is resolved once per call, in ZsTxJSONContext::keys()
|
|
auto opt = libzcash::SaplingOutgoingPlaintext::decrypt(
|
|
outputDesc.outCiphertext,keys[k].ovk,outputDesc.cv,outputDesc.cm,outputDesc.ephemeralKey);
|
|
|
|
if (opt) {
|
|
auto opt_unwrapped = opt.get();
|
|
auto pt = libzcash::SaplingNotePlaintext::decrypt(
|
|
outputDesc.encCiphertext,outputDesc.ephemeralKey,opt_unwrapped.esk,opt_unwrapped.pk_d,outputDesc.cm);
|
|
|
|
if (pt) {
|
|
auto pt_unwrapped = pt.get();
|
|
auto memo = pt_unwrapped.memo();
|
|
shieldedOutputDecryptedCount++;
|
|
libzcash::SaplingPaymentAddress sentAddr(pt_unwrapped.d, opt_unwrapped.pk_d);
|
|
obj.push_back(Pair("address",EncodePaymentAddress(sentAddr)));
|
|
obj.push_back(Pair("amount", ValueFromAmount(CAmount(-pt_unwrapped.value()))));
|
|
obj.push_back(Pair("memo", HexStr(memo)));
|
|
|
|
if (memo[0] <= 0xf4) {
|
|
auto end = std::find_if(memo.rbegin(), memo.rend(), [](unsigned char v) { return v != 0; });
|
|
std::string memoStr(memo.begin(), end.base());
|
|
if (utf8::is_valid(memoStr)) {
|
|
obj.push_back(Pair("memoStr", memoStr));
|
|
}
|
|
}
|
|
obj.push_back(Pair("shieldedOutputIndex",i));
|
|
|
|
//Check Change Status
|
|
if (wtx.vShieldedSpend.size()!=0) {
|
|
BOOST_FOREACH(const SpendDescription& spendDesc, wtx.vShieldedSpend) {
|
|
if (zctx.hasNullifier(sentAddr, spendDesc.nullifier)) {
|
|
changeTx = true;
|
|
}
|
|
}
|
|
}
|
|
obj.push_back(Pair("change",changeTx));
|
|
if (sentAddr == zsAddress || !filterByAddress) {
|
|
totalSends += CAmount(-pt_unwrapped.value());
|
|
zsSends.push_back(obj);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
sends.push_back(Pair("saplingSends",zsSends));
|
|
|
|
if (shieldedOutputCount != shieldedOutputDecryptedCount) {
|
|
sends.push_back(Pair("missingSaplingOVK", true));
|
|
} else {
|
|
sends.push_back(Pair("missingSaplingOVK", false));
|
|
}
|
|
|
|
sends.push_back(Pair("totalSends",ValueFromAmount(totalSends)));
|
|
}
|
|
|
|
|
|
void zsWalletTxJSON(const CWalletTx& wtx, UniValue& ret, const std::string strAddress, bool fBool, const int returnType, ZsTxJSONContext* ctx) {
|
|
|
|
LOCK2(cs_main, pwalletMain->cs_wallet);
|
|
|
|
//Track total wallet spend and received
|
|
CAmount totalSpends = 0;
|
|
CAmount filteredSpends = 0;
|
|
CAmount totalReceived = 0;
|
|
CAmount totalSends = 0;
|
|
|
|
//Various Univalue to be added to the final transaction
|
|
UniValue spends(UniValue::VOBJ);
|
|
UniValue received(UniValue::VOBJ);
|
|
UniValue sends(UniValue::VOBJ);
|
|
UniValue tx(UniValue::VOBJ);
|
|
|
|
//Begin Compiling the Decrypted Transaction
|
|
tx.push_back(Pair("txid", wtx.GetHash().ToString()));
|
|
|
|
if(fZdebug)
|
|
fprintf(stderr,"%s: txid=%s\n", __func__, wtx.GetHash().ToString().c_str() );
|
|
|
|
if (wtx.IsCoinBase())
|
|
{
|
|
tx.push_back(Pair("coinbase", true));
|
|
if (wtx.GetDepthInMainChain() < 1)
|
|
tx.push_back(Pair("category", "orphan"));
|
|
else if (wtx.GetBlocksToMaturity() > 0)
|
|
tx.push_back(Pair("category", "immature"));
|
|
else
|
|
tx.push_back(Pair("category", "generate"));
|
|
} else {
|
|
tx.push_back(Pair("coinbase", false));
|
|
tx.push_back(Pair("category", "standard"));
|
|
}
|
|
|
|
tx.push_back(Pair("blockhash", wtx.hashBlock.GetHex()));
|
|
tx.push_back(Pair("blockindex", wtx.nIndex));
|
|
int confirms = wtx.GetDepthInMainChain();
|
|
if(confirms > 0)
|
|
{
|
|
tx.push_back(Pair("blocktime", mapBlockIndex[wtx.hashBlock]->GetBlockTime()));
|
|
} else {
|
|
tx.push_back(Pair("blocktime", 0));
|
|
}
|
|
tx.push_back(Pair("expiryheight", (int64_t)wtx.nExpiryHeight));
|
|
tx.push_back(Pair("confirmations", confirms));
|
|
tx.push_back(Pair("time", wtx.GetTxTime()));
|
|
tx.push_back(Pair("size", static_cast<uint64_t>(GetSerializeSize(static_cast<const CTransaction&>(wtx), SER_NETWORK, PROTOCOL_VERSION))));
|
|
|
|
//Wallet Conflicts
|
|
UniValue conflicts(UniValue::VARR);
|
|
BOOST_FOREACH(const uint256& conflict, wtx.GetConflicts())
|
|
conflicts.push_back(conflict.GetHex());
|
|
tx.push_back(Pair("walletconflicts", conflicts));
|
|
|
|
// Return Type used to determine what is included in the transaction
|
|
// 0 Spends, Received and spends
|
|
// 1 Sends
|
|
// 2 Received
|
|
// 3 Spends
|
|
// TODO - Setup Enum, maybe...
|
|
|
|
// Add Spends retrieved earlier
|
|
if (returnType != 2) {
|
|
zsTxSpendsToJSON(wtx, spends, totalSpends, filteredSpends, strAddress, fBool);
|
|
if ((!fBool || filteredSpends != 0) && (returnType == 0 || returnType == 1)) {
|
|
tx.push_back(Pair("spends",spends));
|
|
}
|
|
}
|
|
// Get Received
|
|
if (returnType == 0 || returnType == 2) {
|
|
zsTxReceivedToJSON(wtx, received, totalReceived, strAddress, fBool, ctx);
|
|
if (!fBool || totalReceived != 0) {
|
|
tx.push_back(Pair("received",received));
|
|
}
|
|
}
|
|
|
|
// Get Sends
|
|
if (returnType == 0 || returnType == 3) {
|
|
//Only include sends if there are spends that belong to the wallet.
|
|
if (totalSpends != 0 || fBool) {
|
|
zsTxSendsToJSON(wtx, sends, totalSends, strAddress, fBool, ctx);
|
|
}
|
|
if (!fBool || totalSends != 0) {
|
|
tx.push_back(Pair("sends",sends));
|
|
}
|
|
}
|
|
|
|
if ((returnType == 0 && (!fBool || filteredSpends != 0 || totalReceived != 0 || totalSends != 0))
|
|
|| (returnType == 1 && (!fBool || filteredSpends != 0))
|
|
|| (returnType == 2 && (!fBool || totalReceived != 0))
|
|
|| (returnType == 3 && (!fBool || totalSends != 0))) {
|
|
ret.push_back(tx);
|
|
}
|
|
|
|
}
|