Merge branch 'danger' into sietch_dynamic
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
// Copyright (c) 2017 The Zcash developers
|
||||
// Copyright (c) 2019-2020 The Hush developers
|
||||
// Distributed under the MIT software license, see the accompanying
|
||||
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
|
||||
// file COPYING or https://www.opensource.org/licenses/mit-license.php .
|
||||
|
||||
/******************************************************************************
|
||||
* Copyright © 2014-2019 The SuperNET Developers. *
|
||||
@@ -45,7 +46,6 @@
|
||||
#include <string>
|
||||
#include <thread>
|
||||
|
||||
#include "paymentdisclosuredb.h"
|
||||
int32_t komodo_blockheight(uint256 hash);
|
||||
|
||||
using namespace libzcash;
|
||||
@@ -74,19 +74,17 @@ AsyncRPCOperation_mergetoaddress::AsyncRPCOperation_mergetoaddress(
|
||||
boost::optional<TransactionBuilder> builder,
|
||||
CMutableTransaction contextualTx,
|
||||
std::vector<MergeToAddressInputUTXO> utxoInputs,
|
||||
std::vector<MergeToAddressInputSproutNote> sproutNoteInputs,
|
||||
std::vector<MergeToAddressInputSaplingNote> saplingNoteInputs,
|
||||
MergeToAddressRecipient recipient,
|
||||
CAmount fee,
|
||||
UniValue contextInfo) :
|
||||
tx_(contextualTx), utxoInputs_(utxoInputs), sproutNoteInputs_(sproutNoteInputs),
|
||||
saplingNoteInputs_(saplingNoteInputs), recipient_(recipient), fee_(fee), contextinfo_(contextInfo)
|
||||
tx_(contextualTx), utxoInputs_(utxoInputs), saplingNoteInputs_(saplingNoteInputs), recipient_(recipient), fee_(fee), contextinfo_(contextInfo)
|
||||
{
|
||||
if (fee < 0 || fee > MAX_MONEY) {
|
||||
throw JSONRPCError(RPC_INVALID_PARAMETER, "Fee is out of range");
|
||||
}
|
||||
|
||||
if (utxoInputs.empty() && sproutNoteInputs.empty() && saplingNoteInputs.empty()) {
|
||||
if (utxoInputs.empty() && saplingNoteInputs.empty()) {
|
||||
throw JSONRPCError(RPC_INVALID_PARAMETER, "No inputs");
|
||||
}
|
||||
|
||||
@@ -94,14 +92,6 @@ saplingNoteInputs_(saplingNoteInputs), recipient_(recipient), fee_(fee), context
|
||||
throw JSONRPCError(RPC_INVALID_PARAMETER, "Recipient parameter missing");
|
||||
}
|
||||
|
||||
if (sproutNoteInputs.size() > 0 && saplingNoteInputs.size() > 0) {
|
||||
throw JSONRPCError(RPC_INVALID_PARAMETER, "Cannot send from both Sprout and Sapling addresses using z_mergetoaddress");
|
||||
}
|
||||
|
||||
if (sproutNoteInputs.size() > 0 && builder) {
|
||||
throw JSONRPCError(RPC_INVALID_PARAMETER, "Sprout notes are not supported by the TransactionBuilder");
|
||||
}
|
||||
|
||||
isUsingBuilder_ = false;
|
||||
if (builder) {
|
||||
isUsingBuilder_ = true;
|
||||
@@ -132,9 +122,6 @@ saplingNoteInputs_(saplingNoteInputs), recipient_(recipient), fee_(fee), context
|
||||
// Lock UTXOs
|
||||
lock_utxos();
|
||||
lock_notes();
|
||||
|
||||
// Enable payment disclosure if requested
|
||||
paymentDisclosureMode = fExperimentalMode && GetBoolArg("-paymentdisclosure", true);
|
||||
}
|
||||
|
||||
AsyncRPCOperation_mergetoaddress::~AsyncRPCOperation_mergetoaddress()
|
||||
@@ -209,31 +196,16 @@ void AsyncRPCOperation_mergetoaddress::main()
|
||||
|
||||
unlock_utxos(); // clean up
|
||||
unlock_notes(); // clean up
|
||||
|
||||
// !!! Payment disclosure START
|
||||
if (success && paymentDisclosureMode && paymentDisclosureData_.size() > 0) {
|
||||
uint256 txidhash = tx_.GetHash();
|
||||
std::shared_ptr<PaymentDisclosureDB> db = PaymentDisclosureDB::sharedInstance();
|
||||
for (PaymentDisclosureKeyInfo p : paymentDisclosureData_) {
|
||||
p.first.hash = txidhash;
|
||||
if (!db->Put(p.first, p.second)) {
|
||||
LogPrint("paymentdisclosure", "%s: Payment Disclosure: Error writing entry to database for key %s\n", getId(), p.first.ToString());
|
||||
} else {
|
||||
LogPrint("paymentdisclosure", "%s: Payment Disclosure: Successfully added entry to database for key %s\n", getId(), p.first.ToString());
|
||||
}
|
||||
}
|
||||
}
|
||||
// !!! Payment disclosure END
|
||||
}
|
||||
|
||||
// Notes:
|
||||
// 1. #1359 Currently there is no limit set on the number of joinsplits, so size of tx could be invalid.
|
||||
// 1. #1359 Currently there is no limit set on the number of inputs+outputs, so size of tx could be invalid.
|
||||
// 2. #1277 Spendable notes are not locked, so an operation running in parallel could also try to use them.
|
||||
bool AsyncRPCOperation_mergetoaddress::main_impl()
|
||||
{
|
||||
assert(isToTaddr_ != isToZaddr_);
|
||||
|
||||
bool isPureTaddrOnlyTx = (sproutNoteInputs_.empty() && saplingNoteInputs_.empty() && isToTaddr_);
|
||||
bool isPureTaddrOnlyTx = (saplingNoteInputs_.empty() && isToTaddr_);
|
||||
CAmount minersFee = fee_;
|
||||
|
||||
size_t numInputs = utxoInputs_.size();
|
||||
@@ -258,9 +230,6 @@ bool AsyncRPCOperation_mergetoaddress::main_impl()
|
||||
}
|
||||
|
||||
CAmount z_inputs_total = 0;
|
||||
for (const MergeToAddressInputSproutNote& t : sproutNoteInputs_) {
|
||||
z_inputs_total += std::get<2>(t);
|
||||
}
|
||||
|
||||
for (const MergeToAddressInputSaplingNote& t : saplingNoteInputs_) {
|
||||
z_inputs_total += std::get<2>(t);
|
||||
@@ -311,7 +280,7 @@ bool AsyncRPCOperation_mergetoaddress::main_impl()
|
||||
/**
|
||||
* SCENARIO #0
|
||||
*
|
||||
* Sprout not involved, so we just use the TransactionBuilder and we're done.
|
||||
* Only sapling involved, so we just use the TransactionBuilder and we're done.
|
||||
*
|
||||
* This is based on code from AsyncRPCOperation_sendmany::main_impl() and should be refactored.
|
||||
*/
|
||||
@@ -428,7 +397,6 @@ bool AsyncRPCOperation_mergetoaddress::main_impl()
|
||||
* END SCENARIO #0
|
||||
*/
|
||||
|
||||
|
||||
/**
|
||||
* SCENARIO #1
|
||||
*
|
||||
@@ -446,337 +414,7 @@ bool AsyncRPCOperation_mergetoaddress::main_impl()
|
||||
* END SCENARIO #1
|
||||
*/
|
||||
|
||||
|
||||
// Prepare raw transaction to handle JoinSplits
|
||||
CMutableTransaction mtx(tx_);
|
||||
crypto_sign_keypair(joinSplitPubKey_.begin(), joinSplitPrivKey_);
|
||||
mtx.joinSplitPubKey = joinSplitPubKey_;
|
||||
tx_ = CTransaction(mtx);
|
||||
std::string hexMemo = std::get<1>(recipient_);
|
||||
|
||||
|
||||
/**
|
||||
* SCENARIO #2
|
||||
*
|
||||
* taddrs -> zaddr
|
||||
*
|
||||
* We only need a single JoinSplit.
|
||||
*/
|
||||
if (sproutNoteInputs_.empty() && isToZaddr_) {
|
||||
// Create JoinSplit to target z-addr.
|
||||
MergeToAddressJSInfo info;
|
||||
info.vpub_old = sendAmount;
|
||||
info.vpub_new = 0;
|
||||
|
||||
JSOutput jso = JSOutput(boost::get<libzcash::SproutPaymentAddress>(toPaymentAddress_), sendAmount);
|
||||
if (hexMemo.size() > 0) {
|
||||
jso.memo = get_memo_from_hex_string(hexMemo);
|
||||
}
|
||||
info.vjsout.push_back(jso);
|
||||
|
||||
UniValue obj(UniValue::VOBJ);
|
||||
obj = perform_joinsplit(info);
|
||||
sign_send_raw_transaction(obj);
|
||||
return true;
|
||||
}
|
||||
/**
|
||||
* END SCENARIO #2
|
||||
*/
|
||||
|
||||
|
||||
// Copy zinputs to more flexible containers
|
||||
std::deque<MergeToAddressInputSproutNote> zInputsDeque;
|
||||
for (const auto& o : sproutNoteInputs_) {
|
||||
zInputsDeque.push_back(o);
|
||||
}
|
||||
|
||||
// When spending notes, take a snapshot of note witnesses and anchors as the treestate will
|
||||
// change upon arrival of new blocks which contain joinsplit transactions. This is likely
|
||||
// to happen as creating a chained joinsplit transaction can take longer than the block interval.
|
||||
{
|
||||
LOCK2(cs_main, pwalletMain->cs_wallet);
|
||||
for (auto t : sproutNoteInputs_) {
|
||||
JSOutPoint jso = std::get<0>(t);
|
||||
std::vector<JSOutPoint> vOutPoints = {jso};
|
||||
uint256 inputAnchor;
|
||||
std::vector<boost::optional<SproutWitness>> vInputWitnesses;
|
||||
pwalletMain->GetSproutNoteWitnesses(vOutPoints, vInputWitnesses, inputAnchor);
|
||||
jsopWitnessAnchorMap[jso.ToString()] = MergeToAddressWitnessAnchorData{vInputWitnesses[0], inputAnchor};
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* SCENARIO #3
|
||||
*
|
||||
* zaddrs -> zaddr
|
||||
* taddrs ->
|
||||
*
|
||||
* zaddrs ->
|
||||
* taddrs -> taddr
|
||||
*
|
||||
* Send to zaddr by chaining JoinSplits together and immediately consuming any change
|
||||
* Send to taddr by creating dummy z outputs and accumulating value in a change note
|
||||
* which is used to set vpub_new in the last chained joinsplit.
|
||||
*/
|
||||
UniValue obj(UniValue::VOBJ);
|
||||
CAmount jsChange = 0; // this is updated after each joinsplit
|
||||
int changeOutputIndex = -1; // this is updated after each joinsplit if jsChange > 0
|
||||
bool vpubOldProcessed = false; // updated when vpub_old for taddr inputs is set in first joinsplit
|
||||
bool vpubNewProcessed = false; // updated when vpub_new for miner fee and taddr outputs is set in last joinsplit
|
||||
|
||||
// At this point, we are guaranteed to have at least one input note.
|
||||
// Use address of first input note as the temporary change address.
|
||||
SproutSpendingKey changeKey = std::get<3>(zInputsDeque.front());
|
||||
SproutPaymentAddress changeAddress = changeKey.address();
|
||||
|
||||
CAmount vpubOldTarget = 0;
|
||||
CAmount vpubNewTarget = 0;
|
||||
if (isToTaddr_) {
|
||||
vpubNewTarget = z_inputs_total;
|
||||
} else {
|
||||
if (utxoInputs_.empty()) {
|
||||
vpubNewTarget = minersFee;
|
||||
} else {
|
||||
vpubOldTarget = t_inputs_total - minersFee;
|
||||
}
|
||||
}
|
||||
|
||||
// Keep track of treestate within this transaction
|
||||
boost::unordered_map<uint256, SproutMerkleTree, CCoinsKeyHasher> intermediates;
|
||||
std::vector<uint256> previousCommitments;
|
||||
|
||||
while (!vpubNewProcessed) {
|
||||
MergeToAddressJSInfo info;
|
||||
info.vpub_old = 0;
|
||||
info.vpub_new = 0;
|
||||
|
||||
// Set vpub_old in the first joinsplit
|
||||
if (!vpubOldProcessed) {
|
||||
if (t_inputs_total < vpubOldTarget) {
|
||||
throw JSONRPCError(RPC_WALLET_ERROR,
|
||||
strprintf("Insufficient transparent funds for vpub_old %s (miners fee %s, taddr inputs %s)",
|
||||
FormatMoney(vpubOldTarget), FormatMoney(minersFee), FormatMoney(t_inputs_total)));
|
||||
}
|
||||
info.vpub_old += vpubOldTarget; // funds flowing from public pool
|
||||
vpubOldProcessed = true;
|
||||
}
|
||||
|
||||
CAmount jsInputValue = 0;
|
||||
uint256 jsAnchor;
|
||||
std::vector<boost::optional<SproutWitness>> witnesses;
|
||||
|
||||
JSDescription prevJoinSplit;
|
||||
|
||||
// Keep track of previous JoinSplit and its commitments
|
||||
if (tx_.vjoinsplit.size() > 0) {
|
||||
prevJoinSplit = tx_.vjoinsplit.back();
|
||||
}
|
||||
|
||||
// If there is no change, the chain has terminated so we can reset the tracked treestate.
|
||||
if (jsChange == 0 && tx_.vjoinsplit.size() > 0) {
|
||||
intermediates.clear();
|
||||
previousCommitments.clear();
|
||||
}
|
||||
|
||||
//
|
||||
// Consume change as the first input of the JoinSplit.
|
||||
//
|
||||
if (jsChange > 0) {
|
||||
LOCK2(cs_main, pwalletMain->cs_wallet);
|
||||
|
||||
// Update tree state with previous joinsplit
|
||||
SproutMerkleTree tree;
|
||||
auto it = intermediates.find(prevJoinSplit.anchor);
|
||||
if (it != intermediates.end()) {
|
||||
tree = it->second;
|
||||
} else if (!pcoinsTip->GetSproutAnchorAt(prevJoinSplit.anchor, tree)) {
|
||||
throw JSONRPCError(RPC_WALLET_ERROR, "Could not find previous JoinSplit anchor");
|
||||
}
|
||||
|
||||
assert(changeOutputIndex != -1);
|
||||
boost::optional<SproutWitness> changeWitness;
|
||||
int n = 0;
|
||||
for (const uint256& commitment : prevJoinSplit.commitments) {
|
||||
tree.append(commitment);
|
||||
previousCommitments.push_back(commitment);
|
||||
if (!changeWitness && changeOutputIndex == n++) {
|
||||
changeWitness = tree.witness();
|
||||
} else if (changeWitness) {
|
||||
changeWitness.get().append(commitment);
|
||||
}
|
||||
}
|
||||
if (changeWitness) {
|
||||
witnesses.push_back(changeWitness);
|
||||
}
|
||||
jsAnchor = tree.root();
|
||||
intermediates.insert(std::make_pair(tree.root(), tree)); // chained js are interstitial (found in between block boundaries)
|
||||
|
||||
// Decrypt the change note's ciphertext to retrieve some data we need
|
||||
ZCNoteDecryption decryptor(changeKey.receiving_key());
|
||||
auto hSig = prevJoinSplit.h_sig(*pzcashParams, tx_.joinSplitPubKey);
|
||||
try {
|
||||
SproutNotePlaintext plaintext = SproutNotePlaintext::decrypt(
|
||||
decryptor,
|
||||
prevJoinSplit.ciphertexts[changeOutputIndex],
|
||||
prevJoinSplit.ephemeralKey,
|
||||
hSig,
|
||||
(unsigned char)changeOutputIndex);
|
||||
|
||||
SproutNote note = plaintext.note(changeAddress);
|
||||
info.notes.push_back(note);
|
||||
info.zkeys.push_back(changeKey);
|
||||
|
||||
jsInputValue += plaintext.value();
|
||||
|
||||
LogPrint("zrpcunsafe", "%s: spending change (amount=%s)\n",
|
||||
getId(),
|
||||
FormatMoney(plaintext.value()));
|
||||
|
||||
} catch (const std::exception& e) {
|
||||
throw JSONRPCError(RPC_WALLET_ERROR, strprintf("Error decrypting output note of previous JoinSplit: %s", e.what()));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//
|
||||
// Consume spendable non-change notes
|
||||
//
|
||||
std::vector<SproutNote> vInputNotes;
|
||||
std::vector<SproutSpendingKey> vInputZKeys;
|
||||
std::vector<JSOutPoint> vOutPoints;
|
||||
std::vector<boost::optional<SproutWitness>> vInputWitnesses;
|
||||
uint256 inputAnchor;
|
||||
int numInputsNeeded = (jsChange > 0) ? 1 : 0;
|
||||
while (numInputsNeeded++ < ZC_NUM_JS_INPUTS && zInputsDeque.size() > 0) {
|
||||
MergeToAddressInputSproutNote t = zInputsDeque.front();
|
||||
JSOutPoint jso = std::get<0>(t);
|
||||
SproutNote note = std::get<1>(t);
|
||||
CAmount noteFunds = std::get<2>(t);
|
||||
SproutSpendingKey zkey = std::get<3>(t);
|
||||
zInputsDeque.pop_front();
|
||||
|
||||
MergeToAddressWitnessAnchorData wad = jsopWitnessAnchorMap[jso.ToString()];
|
||||
vInputWitnesses.push_back(wad.witness);
|
||||
if (inputAnchor.IsNull()) {
|
||||
inputAnchor = wad.anchor;
|
||||
} else if (inputAnchor != wad.anchor) {
|
||||
throw JSONRPCError(RPC_WALLET_ERROR, "Selected input notes do not share the same anchor");
|
||||
}
|
||||
|
||||
vOutPoints.push_back(jso);
|
||||
vInputNotes.push_back(note);
|
||||
vInputZKeys.push_back(zkey);
|
||||
|
||||
jsInputValue += noteFunds;
|
||||
|
||||
int wtxHeight = -1;
|
||||
int wtxDepth = -1;
|
||||
{
|
||||
LOCK2(cs_main, pwalletMain->cs_wallet);
|
||||
const CWalletTx& wtx = pwalletMain->mapWallet[jso.hash];
|
||||
// Zero confirmation notes belong to transactions which have not yet been mined
|
||||
if (mapBlockIndex.find(wtx.hashBlock) == mapBlockIndex.end()) {
|
||||
throw JSONRPCError(RPC_WALLET_ERROR, strprintf("mapBlockIndex does not contain block hash %s", wtx.hashBlock.ToString()));
|
||||
}
|
||||
wtxHeight = komodo_blockheight(wtx.hashBlock);
|
||||
wtxDepth = wtx.GetDepthInMainChain();
|
||||
}
|
||||
LogPrint("zrpcunsafe", "%s: spending note (txid=%s, vjoinsplit=%d, ciphertext=%d, amount=%s, height=%d, confirmations=%d)\n",
|
||||
getId(),
|
||||
jso.hash.ToString().substr(0, 10),
|
||||
jso.js,
|
||||
int(jso.n), // uint8_t
|
||||
FormatMoney(noteFunds),
|
||||
wtxHeight,
|
||||
wtxDepth);
|
||||
}
|
||||
|
||||
// Add history of previous commitments to witness
|
||||
if (vInputNotes.size() > 0) {
|
||||
if (vInputWitnesses.size() == 0) {
|
||||
throw JSONRPCError(RPC_WALLET_ERROR, "Could not find witness for note commitment");
|
||||
}
|
||||
|
||||
for (auto& optionalWitness : vInputWitnesses) {
|
||||
if (!optionalWitness) {
|
||||
throw JSONRPCError(RPC_WALLET_ERROR, "Witness for note commitment is null");
|
||||
}
|
||||
SproutWitness w = *optionalWitness; // could use .get();
|
||||
if (jsChange > 0) {
|
||||
for (const uint256& commitment : previousCommitments) {
|
||||
w.append(commitment);
|
||||
}
|
||||
if (jsAnchor != w.root()) {
|
||||
throw JSONRPCError(RPC_WALLET_ERROR, "Witness for spendable note does not have same anchor as change input");
|
||||
}
|
||||
}
|
||||
witnesses.push_back(w);
|
||||
}
|
||||
|
||||
// The jsAnchor is null if this JoinSplit is at the start of a new chain
|
||||
if (jsAnchor.IsNull()) {
|
||||
jsAnchor = inputAnchor;
|
||||
}
|
||||
|
||||
// Add spendable notes as inputs
|
||||
std::copy(vInputNotes.begin(), vInputNotes.end(), std::back_inserter(info.notes));
|
||||
std::copy(vInputZKeys.begin(), vInputZKeys.end(), std::back_inserter(info.zkeys));
|
||||
}
|
||||
|
||||
// Accumulate change
|
||||
jsChange = jsInputValue + info.vpub_old;
|
||||
|
||||
// Set vpub_new in the last joinsplit (when there are no more notes to spend)
|
||||
if (zInputsDeque.empty()) {
|
||||
assert(!vpubNewProcessed);
|
||||
if (jsInputValue < vpubNewTarget) {
|
||||
throw JSONRPCError(RPC_WALLET_ERROR,
|
||||
strprintf("Insufficient funds for vpub_new %s (miners fee %s, taddr inputs %s)",
|
||||
FormatMoney(vpubNewTarget), FormatMoney(minersFee), FormatMoney(t_inputs_total)));
|
||||
}
|
||||
info.vpub_new += vpubNewTarget; // funds flowing back to public pool
|
||||
vpubNewProcessed = true;
|
||||
jsChange -= vpubNewTarget;
|
||||
// If we are merging to a t-addr, there should be no change
|
||||
if (isToTaddr_) assert(jsChange == 0);
|
||||
}
|
||||
|
||||
// create dummy output
|
||||
info.vjsout.push_back(JSOutput()); // dummy output while we accumulate funds into a change note for vpub_new
|
||||
|
||||
// create output for any change
|
||||
if (jsChange > 0) {
|
||||
std::string outputType = "change";
|
||||
auto jso = JSOutput(changeAddress, jsChange);
|
||||
// If this is the final output, set the target and memo
|
||||
if (isToZaddr_ && vpubNewProcessed) {
|
||||
outputType = "target";
|
||||
jso.addr = boost::get<libzcash::SproutPaymentAddress>(toPaymentAddress_);
|
||||
if (!hexMemo.empty()) {
|
||||
jso.memo = get_memo_from_hex_string(hexMemo);
|
||||
}
|
||||
}
|
||||
info.vjsout.push_back(jso);
|
||||
|
||||
LogPrint("zrpcunsafe", "%s: generating note for %s (amount=%s)\n",
|
||||
getId(),
|
||||
outputType,
|
||||
FormatMoney(jsChange));
|
||||
}
|
||||
|
||||
obj = perform_joinsplit(info, witnesses, jsAnchor);
|
||||
|
||||
if (jsChange > 0) {
|
||||
changeOutputIndex = mta_find_output(obj, 1);
|
||||
}
|
||||
}
|
||||
|
||||
// Sanity check in case changes to code block above exits loop by invoking 'break'
|
||||
assert(zInputsDeque.size() == 0);
|
||||
assert(vpubNewProcessed);
|
||||
|
||||
sign_send_raw_transaction(obj);
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
@@ -848,191 +486,6 @@ void AsyncRPCOperation_mergetoaddress::sign_send_raw_transaction(UniValue obj)
|
||||
tx_ = tx;
|
||||
}
|
||||
|
||||
|
||||
UniValue AsyncRPCOperation_mergetoaddress::perform_joinsplit(MergeToAddressJSInfo& info)
|
||||
{
|
||||
std::vector<boost::optional<SproutWitness>> witnesses;
|
||||
uint256 anchor;
|
||||
{
|
||||
LOCK(cs_main);
|
||||
anchor = pcoinsTip->GetBestAnchor(SPROUT); // As there are no inputs, ask the wallet for the best anchor
|
||||
}
|
||||
return perform_joinsplit(info, witnesses, anchor);
|
||||
}
|
||||
|
||||
|
||||
UniValue AsyncRPCOperation_mergetoaddress::perform_joinsplit(MergeToAddressJSInfo& info, std::vector<JSOutPoint>& outPoints)
|
||||
{
|
||||
std::vector<boost::optional<SproutWitness>> witnesses;
|
||||
uint256 anchor;
|
||||
{
|
||||
LOCK(cs_main);
|
||||
pwalletMain->GetSproutNoteWitnesses(outPoints, witnesses, anchor);
|
||||
}
|
||||
return perform_joinsplit(info, witnesses, anchor);
|
||||
}
|
||||
|
||||
UniValue AsyncRPCOperation_mergetoaddress::perform_joinsplit(
|
||||
MergeToAddressJSInfo& info,
|
||||
std::vector<boost::optional<SproutWitness>> witnesses,
|
||||
uint256 anchor)
|
||||
{
|
||||
if (anchor.IsNull()) {
|
||||
throw std::runtime_error("anchor is null");
|
||||
}
|
||||
|
||||
if (witnesses.size() != info.notes.size()) {
|
||||
throw runtime_error("number of notes and witnesses do not match");
|
||||
}
|
||||
|
||||
if (info.notes.size() != info.zkeys.size()) {
|
||||
throw runtime_error("number of notes and spending keys do not match");
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < witnesses.size(); i++) {
|
||||
if (!witnesses[i]) {
|
||||
throw runtime_error("joinsplit input could not be found in tree");
|
||||
}
|
||||
info.vjsin.push_back(JSInput(*witnesses[i], info.notes[i], info.zkeys[i]));
|
||||
}
|
||||
|
||||
// Make sure there are two inputs and two outputs
|
||||
while (info.vjsin.size() < ZC_NUM_JS_INPUTS) {
|
||||
info.vjsin.push_back(JSInput());
|
||||
}
|
||||
|
||||
while (info.vjsout.size() < ZC_NUM_JS_OUTPUTS) {
|
||||
info.vjsout.push_back(JSOutput());
|
||||
}
|
||||
|
||||
if (info.vjsout.size() != ZC_NUM_JS_INPUTS || info.vjsin.size() != ZC_NUM_JS_OUTPUTS) {
|
||||
throw runtime_error("unsupported joinsplit input/output counts");
|
||||
}
|
||||
|
||||
CMutableTransaction mtx(tx_);
|
||||
|
||||
LogPrint("zrpcunsafe", "%s: creating joinsplit at index %d (vpub_old=%s, vpub_new=%s, in[0]=%s, in[1]=%s, out[0]=%s, out[1]=%s)\n",
|
||||
getId(),
|
||||
tx_.vjoinsplit.size(),
|
||||
FormatMoney(info.vpub_old), FormatMoney(info.vpub_new),
|
||||
FormatMoney(info.vjsin[0].note.value()), FormatMoney(info.vjsin[1].note.value()),
|
||||
FormatMoney(info.vjsout[0].value), FormatMoney(info.vjsout[1].value));
|
||||
|
||||
// Generate the proof, this can take over a minute.
|
||||
std::array<libzcash::JSInput, ZC_NUM_JS_INPUTS> inputs{info.vjsin[0], info.vjsin[1]};
|
||||
std::array<libzcash::JSOutput, ZC_NUM_JS_OUTPUTS> outputs{info.vjsout[0], info.vjsout[1]};
|
||||
std::array<size_t, ZC_NUM_JS_INPUTS> inputMap;
|
||||
std::array<size_t, ZC_NUM_JS_OUTPUTS> outputMap;
|
||||
|
||||
uint256 esk; // payment disclosure - secret
|
||||
|
||||
JSDescription jsdesc = JSDescription::Randomized(
|
||||
mtx.fOverwintered && (mtx.nVersion >= SAPLING_TX_VERSION),
|
||||
*pzcashParams,
|
||||
joinSplitPubKey_,
|
||||
anchor,
|
||||
inputs,
|
||||
outputs,
|
||||
inputMap,
|
||||
outputMap,
|
||||
info.vpub_old,
|
||||
info.vpub_new,
|
||||
!this->testmode,
|
||||
&esk); // parameter expects pointer to esk, so pass in address
|
||||
{
|
||||
auto verifier = libzcash::ProofVerifier::Strict();
|
||||
if (!(jsdesc.Verify(*pzcashParams, verifier, joinSplitPubKey_))) {
|
||||
throw std::runtime_error("error verifying joinsplit");
|
||||
}
|
||||
}
|
||||
|
||||
mtx.vjoinsplit.push_back(jsdesc);
|
||||
|
||||
// Empty output script.
|
||||
CScript scriptCode;
|
||||
CTransaction signTx(mtx);
|
||||
uint256 dataToBeSigned = SignatureHash(scriptCode, signTx, NOT_AN_INPUT, SIGHASH_ALL, 0, consensusBranchId_);
|
||||
|
||||
// Add the signature
|
||||
if (!(crypto_sign_detached(&mtx.joinSplitSig[0], NULL,
|
||||
dataToBeSigned.begin(), 32,
|
||||
joinSplitPrivKey_) == 0)) {
|
||||
throw std::runtime_error("crypto_sign_detached failed");
|
||||
}
|
||||
|
||||
// Sanity check
|
||||
if (!(crypto_sign_verify_detached(&mtx.joinSplitSig[0],
|
||||
dataToBeSigned.begin(), 32,
|
||||
mtx.joinSplitPubKey.begin()) == 0)) {
|
||||
throw std::runtime_error("crypto_sign_verify_detached failed");
|
||||
}
|
||||
|
||||
CTransaction rawTx(mtx);
|
||||
tx_ = rawTx;
|
||||
|
||||
CDataStream ss(SER_NETWORK, PROTOCOL_VERSION);
|
||||
ss << rawTx;
|
||||
|
||||
std::string encryptedNote1;
|
||||
std::string encryptedNote2;
|
||||
{
|
||||
CDataStream ss2(SER_NETWORK, PROTOCOL_VERSION);
|
||||
ss2 << ((unsigned char)0x00);
|
||||
ss2 << jsdesc.ephemeralKey;
|
||||
ss2 << jsdesc.ciphertexts[0];
|
||||
ss2 << jsdesc.h_sig(*pzcashParams, joinSplitPubKey_);
|
||||
|
||||
encryptedNote1 = HexStr(ss2.begin(), ss2.end());
|
||||
}
|
||||
{
|
||||
CDataStream ss2(SER_NETWORK, PROTOCOL_VERSION);
|
||||
ss2 << ((unsigned char)0x01);
|
||||
ss2 << jsdesc.ephemeralKey;
|
||||
ss2 << jsdesc.ciphertexts[1];
|
||||
ss2 << jsdesc.h_sig(*pzcashParams, joinSplitPubKey_);
|
||||
|
||||
encryptedNote2 = HexStr(ss2.begin(), ss2.end());
|
||||
}
|
||||
|
||||
UniValue arrInputMap(UniValue::VARR);
|
||||
UniValue arrOutputMap(UniValue::VARR);
|
||||
for (size_t i = 0; i < ZC_NUM_JS_INPUTS; i++) {
|
||||
arrInputMap.push_back(static_cast<uint64_t>(inputMap[i]));
|
||||
}
|
||||
for (size_t i = 0; i < ZC_NUM_JS_OUTPUTS; i++) {
|
||||
arrOutputMap.push_back(static_cast<uint64_t>(outputMap[i]));
|
||||
}
|
||||
|
||||
|
||||
// !!! Payment disclosure START
|
||||
unsigned char buffer[32] = {0};
|
||||
memcpy(&buffer[0], &joinSplitPrivKey_[0], 32); // private key in first half of 64 byte buffer
|
||||
std::vector<unsigned char> vch(&buffer[0], &buffer[0] + 32);
|
||||
uint256 joinSplitPrivKey = uint256(vch);
|
||||
size_t js_index = tx_.vjoinsplit.size() - 1;
|
||||
uint256 placeholder;
|
||||
for (int i = 0; i < ZC_NUM_JS_OUTPUTS; i++) {
|
||||
uint8_t mapped_index = outputMap[i];
|
||||
// placeholder for txid will be filled in later when tx has been finalized and signed.
|
||||
PaymentDisclosureKey pdKey = {placeholder, js_index, mapped_index};
|
||||
JSOutput output = outputs[mapped_index];
|
||||
libzcash::SproutPaymentAddress zaddr = output.addr; // randomized output
|
||||
PaymentDisclosureInfo pdInfo = {PAYMENT_DISCLOSURE_VERSION_EXPERIMENTAL, esk, joinSplitPrivKey, zaddr};
|
||||
paymentDisclosureData_.push_back(PaymentDisclosureKeyInfo(pdKey, pdInfo));
|
||||
|
||||
LogPrint("paymentdisclosure", "%s: Payment Disclosure: js=%d, n=%d, zaddr=%s\n", getId(), js_index, int(mapped_index), EncodePaymentAddress(zaddr));
|
||||
}
|
||||
// !!! Payment disclosure END
|
||||
|
||||
UniValue obj(UniValue::VOBJ);
|
||||
obj.push_back(Pair("encryptednote1", encryptedNote1));
|
||||
obj.push_back(Pair("encryptednote2", encryptedNote2));
|
||||
obj.push_back(Pair("rawtxn", HexStr(ss.begin(), ss.end())));
|
||||
obj.push_back(Pair("inputmap", arrInputMap));
|
||||
obj.push_back(Pair("outputmap", arrOutputMap));
|
||||
return obj;
|
||||
}
|
||||
|
||||
std::array<unsigned char, ZC_MEMO_SIZE> AsyncRPCOperation_mergetoaddress::get_memo_from_hex_string(std::string s)
|
||||
{
|
||||
std::array<unsigned char, ZC_MEMO_SIZE> memo = {{0x00}};
|
||||
@@ -1099,9 +552,6 @@ void AsyncRPCOperation_mergetoaddress::unlock_utxos() {
|
||||
*/
|
||||
void AsyncRPCOperation_mergetoaddress::lock_notes() {
|
||||
LOCK2(cs_main, pwalletMain->cs_wallet);
|
||||
for (auto note : sproutNoteInputs_) {
|
||||
pwalletMain->LockNote(std::get<0>(note));
|
||||
}
|
||||
for (auto note : saplingNoteInputs_) {
|
||||
pwalletMain->LockNote(std::get<0>(note));
|
||||
}
|
||||
@@ -1112,9 +562,6 @@ void AsyncRPCOperation_mergetoaddress::lock_notes() {
|
||||
*/
|
||||
void AsyncRPCOperation_mergetoaddress::unlock_notes() {
|
||||
LOCK2(cs_main, pwalletMain->cs_wallet);
|
||||
for (auto note : sproutNoteInputs_) {
|
||||
pwalletMain->UnlockNote(std::get<0>(note));
|
||||
}
|
||||
for (auto note : saplingNoteInputs_) {
|
||||
pwalletMain->UnlockNote(std::get<0>(note));
|
||||
}
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
// Copyright (c) 2017 The Zcash developers
|
||||
// Copyright (c) 2019-2020 The Hush developers
|
||||
|
||||
// Distributed under the MIT software license, see the accompanying
|
||||
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
|
||||
|
||||
@@ -22,7 +24,6 @@
|
||||
|
||||
#include "amount.h"
|
||||
#include "asyncrpcoperation.h"
|
||||
#include "paymentdisclosure.h"
|
||||
#include "primitives/transaction.h"
|
||||
#include "transaction_builder.h"
|
||||
#include "wallet.h"
|
||||
@@ -43,29 +44,11 @@ using namespace libzcash;
|
||||
// Input UTXO is a tuple of txid, vout, amount, script
|
||||
typedef std::tuple<COutPoint, CAmount, CScript> MergeToAddressInputUTXO;
|
||||
|
||||
// Input JSOP is a tuple of JSOutpoint, note, amount, spending key
|
||||
typedef std::tuple<JSOutPoint, SproutNote, CAmount, SproutSpendingKey> MergeToAddressInputSproutNote;
|
||||
|
||||
typedef std::tuple<SaplingOutPoint, SaplingNote, CAmount, SaplingExpandedSpendingKey> MergeToAddressInputSaplingNote;
|
||||
|
||||
// A recipient is a tuple of address, memo (optional if zaddr)
|
||||
typedef std::tuple<std::string, std::string> MergeToAddressRecipient;
|
||||
|
||||
// Package of info which is passed to perform_joinsplit methods.
|
||||
struct MergeToAddressJSInfo {
|
||||
std::vector<JSInput> vjsin;
|
||||
std::vector<JSOutput> vjsout;
|
||||
std::vector<SproutNote> notes;
|
||||
std::vector<SproutSpendingKey> zkeys;
|
||||
CAmount vpub_old = 0;
|
||||
CAmount vpub_new = 0;
|
||||
};
|
||||
|
||||
// A struct to help us track the witness and anchor for a given JSOutPoint
|
||||
struct MergeToAddressWitnessAnchorData {
|
||||
boost::optional<SproutWitness> witness;
|
||||
uint256 anchor;
|
||||
};
|
||||
|
||||
class AsyncRPCOperation_mergetoaddress : public AsyncRPCOperation
|
||||
{
|
||||
@@ -74,7 +57,6 @@ public:
|
||||
boost::optional<TransactionBuilder> builder,
|
||||
CMutableTransaction contextualTx,
|
||||
std::vector<MergeToAddressInputUTXO> utxoInputs,
|
||||
std::vector<MergeToAddressInputSproutNote> sproutNoteInputs,
|
||||
std::vector<MergeToAddressInputSaplingNote> saplingNoteInputs,
|
||||
MergeToAddressRecipient recipient,
|
||||
CAmount fee = MERGE_TO_ADDRESS_OPERATION_DEFAULT_MINERS_FEE,
|
||||
@@ -93,8 +75,6 @@ public:
|
||||
|
||||
bool testmode = false; // Set to true to disable sending txs and generating proofs
|
||||
|
||||
bool paymentDisclosureMode = true; // Set to true to save esk for encrypted notes in payment disclosure database.
|
||||
|
||||
private:
|
||||
friend class TEST_FRIEND_AsyncRPCOperation_mergetoaddress; // class for unit testing
|
||||
|
||||
@@ -113,11 +93,7 @@ private:
|
||||
uint256 joinSplitPubKey_;
|
||||
unsigned char joinSplitPrivKey_[crypto_sign_SECRETKEYBYTES];
|
||||
|
||||
// The key is the result string from calling JSOutPoint::ToString()
|
||||
std::unordered_map<std::string, MergeToAddressWitnessAnchorData> jsopWitnessAnchorMap;
|
||||
|
||||
std::vector<MergeToAddressInputUTXO> utxoInputs_;
|
||||
std::vector<MergeToAddressInputSproutNote> sproutNoteInputs_;
|
||||
std::vector<MergeToAddressInputSaplingNote> saplingNoteInputs_;
|
||||
|
||||
TransactionBuilder builder_;
|
||||
@@ -126,18 +102,6 @@ private:
|
||||
std::array<unsigned char, ZC_MEMO_SIZE> get_memo_from_hex_string(std::string s);
|
||||
bool main_impl();
|
||||
|
||||
// JoinSplit without any input notes to spend
|
||||
UniValue perform_joinsplit(MergeToAddressJSInfo&);
|
||||
|
||||
// JoinSplit with input notes to spend (JSOutPoints))
|
||||
UniValue perform_joinsplit(MergeToAddressJSInfo&, std::vector<JSOutPoint>&);
|
||||
|
||||
// JoinSplit where you have the witnesses and anchor
|
||||
UniValue perform_joinsplit(
|
||||
MergeToAddressJSInfo& info,
|
||||
std::vector<boost::optional<SproutWitness>> witnesses,
|
||||
uint256 anchor);
|
||||
|
||||
void sign_send_raw_transaction(UniValue obj); // throws exception if there was an error
|
||||
|
||||
void lock_utxos();
|
||||
@@ -148,8 +112,6 @@ private:
|
||||
|
||||
void unlock_notes();
|
||||
|
||||
// payment disclosure!
|
||||
std::vector<PaymentDisclosureKeyInfo> paymentDisclosureData_;
|
||||
};
|
||||
|
||||
|
||||
@@ -183,24 +145,6 @@ public:
|
||||
return delegate->main_impl();
|
||||
}
|
||||
|
||||
UniValue perform_joinsplit(MergeToAddressJSInfo& info)
|
||||
{
|
||||
return delegate->perform_joinsplit(info);
|
||||
}
|
||||
|
||||
UniValue perform_joinsplit(MergeToAddressJSInfo& info, std::vector<JSOutPoint>& v)
|
||||
{
|
||||
return delegate->perform_joinsplit(info, v);
|
||||
}
|
||||
|
||||
UniValue perform_joinsplit(
|
||||
MergeToAddressJSInfo& info,
|
||||
std::vector<boost::optional<SproutWitness>> witnesses,
|
||||
uint256 anchor)
|
||||
{
|
||||
return delegate->perform_joinsplit(info, witnesses, anchor);
|
||||
}
|
||||
|
||||
void sign_send_raw_transaction(UniValue obj)
|
||||
{
|
||||
delegate->sign_send_raw_transaction(obj);
|
||||
|
||||
279
src/wallet/asyncrpcoperation_saplingconsolidation.cpp
Normal file
279
src/wallet/asyncrpcoperation_saplingconsolidation.cpp
Normal file
@@ -0,0 +1,279 @@
|
||||
// Copyright (c) 2019-2020 The Hush developers
|
||||
// Copyright (c) 2019 CryptoForge
|
||||
// Distributed under the MIT software license, see the accompanying
|
||||
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
|
||||
|
||||
#include "assert.h"
|
||||
#include "boost/variant/static_visitor.hpp"
|
||||
#include "asyncrpcoperation_saplingconsolidation.h"
|
||||
#include "init.h"
|
||||
#include "key_io.h"
|
||||
#include "rpc/protocol.h"
|
||||
#include "random.h"
|
||||
#include "sync.h"
|
||||
#include "tinyformat.h"
|
||||
#include "transaction_builder.h"
|
||||
#include "util.h"
|
||||
#include "utilmoneystr.h"
|
||||
#include "wallet.h"
|
||||
|
||||
CAmount fConsolidationTxFee = DEFAULT_CONSOLIDATION_FEE;
|
||||
bool fConsolidationMapUsed = false;
|
||||
const int CONSOLIDATION_EXPIRY_DELTA = 15;
|
||||
|
||||
extern string randomSietchZaddr();
|
||||
|
||||
AsyncRPCOperation_saplingconsolidation::AsyncRPCOperation_saplingconsolidation(int targetHeight) : targetHeight_(targetHeight) {}
|
||||
|
||||
AsyncRPCOperation_saplingconsolidation::~AsyncRPCOperation_saplingconsolidation() {}
|
||||
|
||||
void AsyncRPCOperation_saplingconsolidation::main() {
|
||||
if (isCancelled())
|
||||
return;
|
||||
|
||||
set_state(OperationStatus::EXECUTING);
|
||||
start_execution_clock();
|
||||
|
||||
bool success = false;
|
||||
|
||||
try {
|
||||
success = main_impl();
|
||||
} catch (const UniValue& objError) {
|
||||
int code = find_value(objError, "code").get_int();
|
||||
std::string message = find_value(objError, "message").get_str();
|
||||
set_error_code(code);
|
||||
set_error_message(message);
|
||||
} catch (const runtime_error& e) {
|
||||
set_error_code(-1);
|
||||
set_error_message("runtime error: " + string(e.what()));
|
||||
} catch (const logic_error& e) {
|
||||
set_error_code(-1);
|
||||
set_error_message("logic error: " + string(e.what()));
|
||||
} catch (const exception& e) {
|
||||
set_error_code(-1);
|
||||
set_error_message("general exception: " + string(e.what()));
|
||||
} catch (...) {
|
||||
set_error_code(-2);
|
||||
set_error_message("unknown error");
|
||||
}
|
||||
|
||||
stop_execution_clock();
|
||||
|
||||
if (success) {
|
||||
set_state(OperationStatus::SUCCESS);
|
||||
} else {
|
||||
set_state(OperationStatus::FAILED);
|
||||
}
|
||||
|
||||
std::string s = strprintf("%s: Sapling Consolidation transaction created. (status=%s", getId(), getStateAsString());
|
||||
if (success) {
|
||||
s += strprintf(", success)\n");
|
||||
} else {
|
||||
s += strprintf(", error=%s)\n", getErrorMessage());
|
||||
}
|
||||
|
||||
LogPrintf("%s", s);
|
||||
}
|
||||
|
||||
bool AsyncRPCOperation_saplingconsolidation::main_impl() {
|
||||
bool status=true;
|
||||
auto opid=getId();
|
||||
LogPrint("zrpcunsafe", "%s: Beginning AsyncRPCOperation_saplingconsolidation.\n", opid);
|
||||
auto consensusParams = Params().GetConsensus();
|
||||
auto nextActivationHeight = NextActivationHeight(targetHeight_, consensusParams);
|
||||
if (nextActivationHeight && targetHeight_ + CONSOLIDATION_EXPIRY_DELTA >= nextActivationHeight.get()) {
|
||||
LogPrint("zrpcunsafe", "%s: Consolidation txs would be created before a NU activation but may expire after. Skipping this round.\n",opid);
|
||||
setConsolidationResult(0, 0, std::vector<std::string>());
|
||||
return status;
|
||||
}
|
||||
|
||||
std::vector<SaplingNoteEntry> saplingEntries;
|
||||
std::set<libzcash::SaplingPaymentAddress> addresses;
|
||||
{
|
||||
LOCK2(cs_main, pwalletMain->cs_wallet);
|
||||
// We set minDepth to 11 to avoid unconfirmed notes and in anticipation of specifying
|
||||
// an anchor at height N-10 for each SpendDescription
|
||||
// Consider, should notes be sorted?
|
||||
pwalletMain->GetFilteredNotes(saplingEntries, "", 11);
|
||||
|
||||
if(saplingEntries.size() == 0) {
|
||||
LogPrint("zrpcunsafe", "%s: Nothing to consolidate, done.\n",opid);
|
||||
return true;
|
||||
}
|
||||
|
||||
if (fConsolidationMapUsed) {
|
||||
const vector<string>& v = mapMultiArgs["-consolidatesaplingaddress"];
|
||||
for(int i = 0; i < v.size(); i++) {
|
||||
auto zAddress = DecodePaymentAddress(v[i]);
|
||||
if (boost::get<libzcash::SaplingPaymentAddress>(&zAddress) != nullptr) {
|
||||
libzcash::SaplingPaymentAddress saplingAddress = boost::get<libzcash::SaplingPaymentAddress>(zAddress);
|
||||
addresses.insert(saplingAddress);
|
||||
} else {
|
||||
LogPrint("zrpcunsafe", "%s: Invalid zaddr, exiting\n", opid);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
pwalletMain->GetSaplingPaymentAddresses(addresses);
|
||||
}
|
||||
}
|
||||
|
||||
int numTxCreated = 0;
|
||||
std::vector<std::string> consolidationTxIds;
|
||||
CAmount amountConsolidated = 0;
|
||||
CCoinsViewCache coinsView(pcoinsTip);
|
||||
|
||||
for (auto addr : addresses) {
|
||||
libzcash::SaplingExtendedSpendingKey extsk;
|
||||
if (pwalletMain->GetSaplingExtendedSpendingKey(addr, extsk)) {
|
||||
|
||||
std::vector<SaplingNoteEntry> fromNotes;
|
||||
CAmount amountToSend = 0;
|
||||
// max of 8 zins means the tx cannot reduce the anonset,
|
||||
// since there will be 8 zins and 8 zouts at worst case
|
||||
// This also helps reduce ztx creation time
|
||||
int maxQuantity = rand() % 8 + 1;
|
||||
for (const SaplingNoteEntry& saplingEntry : saplingEntries) {
|
||||
|
||||
libzcash::SaplingIncomingViewingKey ivk;
|
||||
pwalletMain->GetSaplingIncomingViewingKey(boost::get<libzcash::SaplingPaymentAddress>(saplingEntry.address), ivk);
|
||||
|
||||
//Select Notes from that same address we will be sending to.
|
||||
if (ivk == extsk.expsk.full_viewing_key().in_viewing_key()) {
|
||||
amountToSend += CAmount(saplingEntry.note.value());
|
||||
fromNotes.push_back(saplingEntry);
|
||||
}
|
||||
|
||||
//Only use a randomly determined number of notes
|
||||
if (fromNotes.size() >= maxQuantity)
|
||||
break;
|
||||
|
||||
}
|
||||
|
||||
// minimum required
|
||||
// We use 3 so that addresses can spent one zutxo and still have another zutxo to use while that
|
||||
// tx is confirming
|
||||
int minQuantity = 3;
|
||||
if (fromNotes.size() < minQuantity)
|
||||
continue;
|
||||
|
||||
amountConsolidated += amountToSend;
|
||||
auto builder = TransactionBuilder(consensusParams, targetHeight_, pwalletMain);
|
||||
//builder.SetExpiryHeight(targetHeight_ + CONSOLIDATION_EXPIRY_DELTA);
|
||||
auto actualAmountToSend = amountToSend < fConsolidationTxFee ? 0 : amountToSend - fConsolidationTxFee;
|
||||
LogPrint("zrpcunsafe", "%s: Beginning to create transaction with Sapling output amount=%s\n", opid, FormatMoney(actualAmountToSend));
|
||||
|
||||
// Select Sapling notes
|
||||
std::vector<SaplingOutPoint> ops;
|
||||
std::vector<libzcash::SaplingNote> notes;
|
||||
for (auto fromNote : fromNotes) {
|
||||
ops.push_back(fromNote.op);
|
||||
notes.push_back(fromNote.note);
|
||||
}
|
||||
|
||||
// Fetch Sapling anchor and witnesses
|
||||
uint256 anchor;
|
||||
std::vector<boost::optional<SaplingWitness>> witnesses;
|
||||
{
|
||||
LOCK2(cs_main, pwalletMain->cs_wallet);
|
||||
LogPrint("zrpcunsafe", "%s: Fetching note witnesses\n", opid);
|
||||
pwalletMain->GetSaplingNoteWitnesses(ops, witnesses, anchor);
|
||||
}
|
||||
|
||||
// Add Sapling spends
|
||||
for (size_t i = 0; i < notes.size(); i++) {
|
||||
if (!witnesses[i]) {
|
||||
LogPrint("zrpcunsafe", "%s: Missing Witnesses. Stopping.\n", opid);
|
||||
status=false;
|
||||
break;
|
||||
}
|
||||
builder.AddSaplingSpend(extsk.expsk, notes[i], anchor, witnesses[i].get());
|
||||
LogPrint("zrpcunsafe", "%s: Added consolidation input %d\n", opid, i);
|
||||
}
|
||||
|
||||
CAmount thisTxFee = amountToSend < fConsolidationTxFee ? 0 : fConsolidationTxFee;
|
||||
LogPrint("zrpcunsafe", "%s: Using fee=%d\n", opid, thisTxFee);
|
||||
builder.SetFee(thisTxFee);
|
||||
|
||||
// Add the actual consolidation tx
|
||||
builder.AddSaplingOutput(extsk.expsk.ovk, addr, actualAmountToSend);
|
||||
LogPrint("zrpcunsafe", "%s: Added consolidation output %s with amount=%li\n", opid, addr.GetHash().ToString().c_str(), actualAmountToSend);
|
||||
|
||||
// Add sietch zouts
|
||||
int MIN_ZOUTS = 7;
|
||||
for(size_t i = 0; i < MIN_ZOUTS; i++) {
|
||||
// In Privacy Zdust We Trust -- Duke
|
||||
string zdust = randomSietchZaddr();
|
||||
auto zaddr = DecodePaymentAddress(zdust);
|
||||
if (IsValidPaymentAddress(zaddr)) {
|
||||
CAmount amount=0;
|
||||
auto sietchZoutput = boost::get<libzcash::SaplingPaymentAddress>(zaddr);
|
||||
LogPrint("zrpcunsafe", "%s: Adding Sietch zdust output %d %s amount=%li\n", opid, i, zdust, amount);
|
||||
|
||||
// actually add our sietch zoutput, the new way
|
||||
builder.AddSaplingOutput(extsk.expsk.ovk, sietchZoutput, amount);
|
||||
} else {
|
||||
LogPrint("zrpcunsafe", "%s: Invalid payment address %s! Stopping.\n", opid, zdust);
|
||||
status = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
LogPrint("zrpcunsafe", "%s: Done adding %d sietch zouts\n", opid, MIN_ZOUTS);
|
||||
//CTransaction tx = builder.Build();
|
||||
|
||||
auto maybe_tx = builder.Build();
|
||||
if (!maybe_tx) {
|
||||
LogPrint("zrpcunsafe", "%s: Failed to build transaction.\n",opid);
|
||||
status=false;
|
||||
break;
|
||||
}
|
||||
CTransaction tx = maybe_tx.get();
|
||||
|
||||
if (isCancelled()) {
|
||||
LogPrint("zrpcunsafe", "%s: Canceled. Stopping.\n", opid);
|
||||
status=false;
|
||||
break;
|
||||
}
|
||||
|
||||
if(pwalletMain->CommitConsolidationTx(tx)) {
|
||||
LogPrint("zrpcunsafe", "%s: Committed consolidation transaction with txid=%s\n",opid, tx.GetHash().ToString());
|
||||
amountConsolidated += actualAmountToSend;
|
||||
consolidationTxIds.push_back(tx.GetHash().ToString());
|
||||
numTxCreated++;
|
||||
} else {
|
||||
LogPrint("zrpcunsafe", "%s: Consolidation transaction FAILED in CommitTransaction, txid=%s\n",opid , tx.GetHash().ToString());
|
||||
setConsolidationResult(numTxCreated, amountConsolidated, consolidationTxIds);
|
||||
status = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
LogPrint("zrpcunsafe", "%s: Created %d transactions with total Sapling output amount=%s,status=%d\n",opid , numTxCreated, FormatMoney(amountConsolidated), (int)status);
|
||||
setConsolidationResult(numTxCreated, amountConsolidated, consolidationTxIds);
|
||||
return status;
|
||||
}
|
||||
|
||||
void AsyncRPCOperation_saplingconsolidation::setConsolidationResult(int numTxCreated, const CAmount& amountConsolidated, const std::vector<std::string>& consolidationTxIds) {
|
||||
UniValue res(UniValue::VOBJ);
|
||||
res.push_back(Pair("num_tx_created", numTxCreated));
|
||||
res.push_back(Pair("amount_consolidated", FormatMoney(amountConsolidated)));
|
||||
UniValue txIds(UniValue::VARR);
|
||||
for (const std::string& txId : consolidationTxIds) {
|
||||
txIds.push_back(txId);
|
||||
}
|
||||
res.push_back(Pair("consolidation_txids", txIds));
|
||||
set_result(res);
|
||||
}
|
||||
|
||||
void AsyncRPCOperation_saplingconsolidation::cancel() {
|
||||
set_state(OperationStatus::CANCELLED);
|
||||
}
|
||||
|
||||
UniValue AsyncRPCOperation_saplingconsolidation::getStatus() const {
|
||||
UniValue v = AsyncRPCOperation::getStatus();
|
||||
UniValue obj = v.get_obj();
|
||||
obj.push_back(Pair("method", "saplingconsolidation"));
|
||||
obj.push_back(Pair("target_height", targetHeight_));
|
||||
return obj;
|
||||
}
|
||||
42
src/wallet/asyncrpcoperation_saplingconsolidation.h
Normal file
42
src/wallet/asyncrpcoperation_saplingconsolidation.h
Normal file
@@ -0,0 +1,42 @@
|
||||
// Copyright (c) 2020 The Hush developers
|
||||
// Copyright (c) 2019 CryptoForge
|
||||
// Distributed under the MIT software license, see the accompanying
|
||||
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
|
||||
|
||||
#include "amount.h"
|
||||
#include "asyncrpcoperation.h"
|
||||
#include "univalue.h"
|
||||
#include "zcash/Address.hpp"
|
||||
#include "zcash/zip32.h"
|
||||
|
||||
//Default fee used for consolidation transactions, in puposhis
|
||||
static const CAmount DEFAULT_CONSOLIDATION_FEE = 10000;
|
||||
extern CAmount fConsolidationTxFee;
|
||||
extern bool fConsolidationMapUsed;
|
||||
|
||||
class AsyncRPCOperation_saplingconsolidation : public AsyncRPCOperation
|
||||
{
|
||||
public:
|
||||
AsyncRPCOperation_saplingconsolidation(int targetHeight);
|
||||
virtual ~AsyncRPCOperation_saplingconsolidation();
|
||||
|
||||
// We don't want to be copied or moved around
|
||||
AsyncRPCOperation_saplingconsolidation(AsyncRPCOperation_saplingconsolidation const&) = delete; // Copy construct
|
||||
AsyncRPCOperation_saplingconsolidation(AsyncRPCOperation_saplingconsolidation&&) = delete; // Move construct
|
||||
AsyncRPCOperation_saplingconsolidation& operator=(AsyncRPCOperation_saplingconsolidation const&) = delete; // Copy assign
|
||||
AsyncRPCOperation_saplingconsolidation& operator=(AsyncRPCOperation_saplingconsolidation&&) = delete; // Move assign
|
||||
|
||||
virtual void main();
|
||||
|
||||
virtual void cancel();
|
||||
|
||||
virtual UniValue getStatus() const;
|
||||
|
||||
private:
|
||||
int targetHeight_;
|
||||
|
||||
bool main_impl();
|
||||
|
||||
void setConsolidationResult(int numTxCreated, const CAmount& amountConsolidated, const std::vector<std::string>& consolidationTxIds);
|
||||
|
||||
};
|
||||
@@ -1,7 +1,7 @@
|
||||
// Copyright (c) 2016 The Zcash developers
|
||||
// Copyright (c) 2019-2020 The Hush developers
|
||||
// Distributed under the MIT software license, see the accompanying
|
||||
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
|
||||
// file COPYING or https://www.opensource.org/licenses/mit-license.php
|
||||
|
||||
/******************************************************************************
|
||||
* Copyright © 2014-2019 The SuperNET Developers. *
|
||||
@@ -49,7 +49,6 @@
|
||||
#include <thread>
|
||||
#include <string>
|
||||
|
||||
#include "paymentdisclosuredb.h"
|
||||
#include <boost/optional/optional_io.hpp>
|
||||
|
||||
using namespace libzcash;
|
||||
@@ -141,10 +140,6 @@ AsyncRPCOperation_sendmany::AsyncRPCOperation_sendmany(
|
||||
} else {
|
||||
LogPrint("zrpc", "%s: z_sendmany initialized\n", getId());
|
||||
}
|
||||
|
||||
|
||||
// Enable payment disclosure if requested
|
||||
paymentDisclosureMode = fExperimentalMode && GetBoolArg("-paymentdisclosure", true);
|
||||
}
|
||||
|
||||
AsyncRPCOperation_sendmany::~AsyncRPCOperation_sendmany() {
|
||||
@@ -211,25 +206,10 @@ void AsyncRPCOperation_sendmany::main() {
|
||||
s += strprintf(", error=%s)\n", getErrorMessage());
|
||||
}
|
||||
LogPrintf("%s",s);
|
||||
|
||||
// !!! Payment disclosure START
|
||||
if (success && paymentDisclosureMode && paymentDisclosureData_.size()>0) {
|
||||
uint256 txidhash = tx_.GetHash();
|
||||
std::shared_ptr<PaymentDisclosureDB> db = PaymentDisclosureDB::sharedInstance();
|
||||
for (PaymentDisclosureKeyInfo p : paymentDisclosureData_) {
|
||||
p.first.hash = txidhash;
|
||||
if (!db->Put(p.first, p.second)) {
|
||||
LogPrint("paymentdisclosure", "%s: Payment Disclosure: Error writing entry to database for key %s\n", getId(), p.first.ToString());
|
||||
} else {
|
||||
LogPrint("paymentdisclosure", "%s: Payment Disclosure: Successfully added entry to database for key %s\n", getId(), p.first.ToString());
|
||||
}
|
||||
}
|
||||
}
|
||||
// !!! Payment disclosure END
|
||||
}
|
||||
|
||||
// Notes:
|
||||
// 1. #1159 Currently there is no limit set on the number of joinsplits, so size of tx could be invalid.
|
||||
// 1. #1159 Currently there is no limit set on the number of shielded spends, so size of tx could be invalid.
|
||||
// 2. #1360 Note selection is not optimal
|
||||
// 3. #1277 Spendable notes are not locked, so an operation running in parallel could also try to use them
|
||||
bool AsyncRPCOperation_sendmany::main_impl() {
|
||||
@@ -265,18 +245,12 @@ bool AsyncRPCOperation_sendmany::main_impl() {
|
||||
throw JSONRPCError(RPC_WALLET_INSUFFICIENT_FUNDS, "Insufficient funds, no unspent notes found for zaddr from address.");
|
||||
}
|
||||
|
||||
// At least one of z_sprout_inputs_ and z_sapling_inputs_ must be empty by design
|
||||
assert(z_sprout_inputs_.empty() || z_sapling_inputs_.empty());
|
||||
|
||||
CAmount t_inputs_total = 0;
|
||||
for (SendManyInputUTXO & t : t_inputs_) {
|
||||
t_inputs_total += std::get<2>(t);
|
||||
}
|
||||
|
||||
CAmount z_inputs_total = 0;
|
||||
for (SendManyInputJSOP & t : z_sprout_inputs_) {
|
||||
z_inputs_total += std::get<2>(t);
|
||||
}
|
||||
for (auto t : z_sapling_inputs_) {
|
||||
z_inputs_total += t.note.value();
|
||||
}
|
||||
@@ -566,430 +540,7 @@ bool AsyncRPCOperation_sendmany::main_impl() {
|
||||
* END SCENARIO #0
|
||||
*/
|
||||
|
||||
|
||||
// Grab the current consensus branch ID
|
||||
{
|
||||
LOCK(cs_main);
|
||||
consensusBranchId_ = CurrentEpochBranchId(chainActive.Height() + 1, Params().GetConsensus());
|
||||
}
|
||||
|
||||
/**
|
||||
* SCENARIO #1
|
||||
*
|
||||
* taddr -> taddrs
|
||||
*
|
||||
* There are no zaddrs or joinsplits involved.
|
||||
*/
|
||||
if (isPureTaddrOnlyTx) {
|
||||
add_taddr_outputs_to_tx();
|
||||
|
||||
CAmount funds = selectedUTXOAmount;
|
||||
CAmount fundsSpent = t_outputs_total + minersFee;
|
||||
CAmount change = funds - fundsSpent;
|
||||
|
||||
if (change > 0) {
|
||||
add_taddr_change_output_to_tx(0,change);
|
||||
|
||||
LogPrint("zrpc", "%s: transparent change in transaction output (amount=%s)\n",
|
||||
getId(),
|
||||
FormatMoney(change)
|
||||
);
|
||||
}
|
||||
|
||||
UniValue obj(UniValue::VOBJ);
|
||||
obj.push_back(Pair("rawtxn", EncodeHexTx(tx_)));
|
||||
sign_send_raw_transaction(obj);
|
||||
return true;
|
||||
}
|
||||
/**
|
||||
* END SCENARIO #1
|
||||
*/
|
||||
|
||||
|
||||
// Prepare raw transaction to handle JoinSplits
|
||||
CMutableTransaction mtx(tx_);
|
||||
crypto_sign_keypair(joinSplitPubKey_.begin(), joinSplitPrivKey_);
|
||||
mtx.joinSplitPubKey = joinSplitPubKey_;
|
||||
//if ((uint32_t)chainActive.LastTip()->nTime < ASSETCHAINS_STAKED_HF_TIMESTAMP)
|
||||
if ( !hush_hardfork_active((uint32_t)chainActive.LastTip()->nTime) )
|
||||
mtx.nLockTime = (uint32_t)time(NULL) - 60; // jl777
|
||||
else
|
||||
mtx.nLockTime = (uint32_t)chainActive.Tip()->GetMedianTimePast();
|
||||
|
||||
tx_ = CTransaction(mtx);
|
||||
|
||||
// Copy zinputs and zoutputs to more flexible containers
|
||||
std::deque<SendManyInputJSOP> zInputsDeque; // zInputsDeque stores minimum numbers of notes for target amount
|
||||
CAmount tmp = 0;
|
||||
for (auto o : z_sprout_inputs_) {
|
||||
zInputsDeque.push_back(o);
|
||||
tmp += std::get<2>(o);
|
||||
if (tmp >= targetAmount) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
std::deque<SendManyRecipient> zOutputsDeque;
|
||||
for (auto o : z_outputs_) {
|
||||
zOutputsDeque.push_back(o);
|
||||
}
|
||||
|
||||
// When spending notes, take a snapshot of note witnesses and anchors as the treestate will
|
||||
// change upon arrival of new blocks which contain joinsplit transactions. This is likely
|
||||
// to happen as creating a chained joinsplit transaction can take longer than the block interval.
|
||||
if (z_sprout_inputs_.size() > 0) {
|
||||
LOCK2(cs_main, pwalletMain->cs_wallet);
|
||||
for (auto t : z_sprout_inputs_) {
|
||||
JSOutPoint jso = std::get<0>(t);
|
||||
std::vector<JSOutPoint> vOutPoints = { jso };
|
||||
uint256 inputAnchor;
|
||||
std::vector<boost::optional<SproutWitness>> vInputWitnesses;
|
||||
pwalletMain->GetSproutNoteWitnesses(vOutPoints, vInputWitnesses, inputAnchor);
|
||||
jsopWitnessAnchorMap[ jso.ToString() ] = WitnessAnchorData{ vInputWitnesses[0], inputAnchor };
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* SCENARIO #2
|
||||
*
|
||||
* taddr -> taddrs
|
||||
* -> zaddrs
|
||||
*
|
||||
* Note: Consensus rule states that coinbase utxos can only be sent to a zaddr. TODO: Do they?
|
||||
* Local wallet rule does not allow any change when sending coinbase utxos
|
||||
* since there is currently no way to specify a change address and we don't
|
||||
* want users accidentally sending excess funds to a recipient.
|
||||
*/
|
||||
if (isfromtaddr_) {
|
||||
add_taddr_outputs_to_tx();
|
||||
|
||||
CAmount funds = selectedUTXOAmount;
|
||||
CAmount fundsSpent = t_outputs_total + minersFee + z_outputs_total;
|
||||
CAmount change = funds - fundsSpent;
|
||||
|
||||
if (change > 0) {
|
||||
if (selectedUTXOCoinbase) {
|
||||
assert(isSingleZaddrOutput);
|
||||
throw JSONRPCError(RPC_WALLET_ERROR, strprintf(
|
||||
"Change %s not allowed. When shielding coinbase funds, the wallet does not "
|
||||
"allow any change as there is currently no way to specify a change address "
|
||||
"in z_sendmany.", FormatMoney(change)));
|
||||
} else {
|
||||
CBitcoinAddress ba = CBitcoinAddress(fromtaddr_);
|
||||
add_taddr_change_output_to_tx(&ba,change);
|
||||
LogPrint("zrpc", "%s: transparent change in transaction output (amount=%s)\n",
|
||||
getId(),
|
||||
FormatMoney(change)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Create joinsplits, where each output represents a zaddr recipient.
|
||||
UniValue obj(UniValue::VOBJ);
|
||||
while (zOutputsDeque.size() > 0) {
|
||||
AsyncJoinSplitInfo info;
|
||||
info.vpub_old = 0;
|
||||
info.vpub_new = 0;
|
||||
int n = 0;
|
||||
while (n++<ZC_NUM_JS_OUTPUTS && zOutputsDeque.size() > 0) {
|
||||
SendManyRecipient smr = zOutputsDeque.front();
|
||||
std::string address = std::get<0>(smr);
|
||||
CAmount value = std::get<1>(smr);
|
||||
std::string hexMemo = std::get<2>(smr);
|
||||
zOutputsDeque.pop_front();
|
||||
|
||||
PaymentAddress pa = DecodePaymentAddress(address);
|
||||
JSOutput jso = JSOutput(boost::get<libzcash::SproutPaymentAddress>(pa), value);
|
||||
if (hexMemo.size() > 0) {
|
||||
jso.memo = get_memo_from_hex_string(hexMemo);
|
||||
}
|
||||
info.vjsout.push_back(jso);
|
||||
|
||||
// Funds are removed from the value pool and enter the private pool
|
||||
info.vpub_old += value;
|
||||
}
|
||||
obj = perform_joinsplit(info);
|
||||
}
|
||||
sign_send_raw_transaction(obj);
|
||||
return true;
|
||||
}
|
||||
/**
|
||||
* END SCENARIO #2
|
||||
*/
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* SCENARIO #3
|
||||
*
|
||||
* zaddr -> taddrs
|
||||
* -> zaddrs
|
||||
*
|
||||
* Send to zaddrs by chaining JoinSplits together and immediately consuming any change
|
||||
* Send to taddrs by creating dummy z outputs and accumulating value in a change note
|
||||
* which is used to set vpub_new in the last chained joinsplit.
|
||||
*/
|
||||
UniValue obj(UniValue::VOBJ);
|
||||
CAmount jsChange = 0; // this is updated after each joinsplit
|
||||
int changeOutputIndex = -1; // this is updated after each joinsplit if jsChange > 0
|
||||
bool vpubNewProcessed = false; // updated when vpub_new for miner fee and taddr outputs is set in last joinsplit
|
||||
CAmount vpubNewTarget = minersFee;
|
||||
if (t_outputs_total > 0) {
|
||||
add_taddr_outputs_to_tx();
|
||||
vpubNewTarget += t_outputs_total;
|
||||
}
|
||||
|
||||
// Keep track of treestate within this transaction
|
||||
boost::unordered_map<uint256, SproutMerkleTree, CCoinsKeyHasher> intermediates;
|
||||
std::vector<uint256> previousCommitments;
|
||||
|
||||
while (!vpubNewProcessed) {
|
||||
AsyncJoinSplitInfo info;
|
||||
info.vpub_old = 0;
|
||||
info.vpub_new = 0;
|
||||
|
||||
CAmount jsInputValue = 0;
|
||||
uint256 jsAnchor;
|
||||
std::vector<boost::optional<SproutWitness>> witnesses;
|
||||
|
||||
JSDescription prevJoinSplit;
|
||||
|
||||
// Keep track of previous JoinSplit and its commitments
|
||||
if (tx_.vjoinsplit.size() > 0) {
|
||||
prevJoinSplit = tx_.vjoinsplit.back();
|
||||
}
|
||||
|
||||
// If there is no change, the chain has terminated so we can reset the tracked treestate.
|
||||
if (jsChange==0 && tx_.vjoinsplit.size() > 0) {
|
||||
intermediates.clear();
|
||||
previousCommitments.clear();
|
||||
}
|
||||
|
||||
//
|
||||
// Consume change as the first input of the JoinSplit.
|
||||
//
|
||||
if (jsChange > 0) {
|
||||
LOCK2(cs_main, pwalletMain->cs_wallet);
|
||||
|
||||
// Update tree state with previous joinsplit
|
||||
SproutMerkleTree tree;
|
||||
auto it = intermediates.find(prevJoinSplit.anchor);
|
||||
if (it != intermediates.end()) {
|
||||
tree = it->second;
|
||||
} else if (!pcoinsTip->GetSproutAnchorAt(prevJoinSplit.anchor, tree)) {
|
||||
throw JSONRPCError(RPC_WALLET_ERROR, "Could not find previous JoinSplit anchor");
|
||||
}
|
||||
|
||||
assert(changeOutputIndex != -1);
|
||||
boost::optional<SproutWitness> changeWitness;
|
||||
int n = 0;
|
||||
for (const uint256& commitment : prevJoinSplit.commitments) {
|
||||
tree.append(commitment);
|
||||
previousCommitments.push_back(commitment);
|
||||
if (!changeWitness && changeOutputIndex == n++) {
|
||||
changeWitness = tree.witness();
|
||||
} else if (changeWitness) {
|
||||
changeWitness.get().append(commitment);
|
||||
}
|
||||
}
|
||||
if (changeWitness) {
|
||||
witnesses.push_back(changeWitness);
|
||||
}
|
||||
jsAnchor = tree.root();
|
||||
intermediates.insert(std::make_pair(tree.root(), tree)); // chained js are interstitial (found in between block boundaries)
|
||||
|
||||
// Decrypt the change note's ciphertext to retrieve some data we need
|
||||
ZCNoteDecryption decryptor(boost::get<libzcash::SproutSpendingKey>(spendingkey_).receiving_key());
|
||||
auto hSig = prevJoinSplit.h_sig(*pzcashParams, tx_.joinSplitPubKey);
|
||||
try {
|
||||
SproutNotePlaintext plaintext = SproutNotePlaintext::decrypt(
|
||||
decryptor,
|
||||
prevJoinSplit.ciphertexts[changeOutputIndex],
|
||||
prevJoinSplit.ephemeralKey,
|
||||
hSig,
|
||||
(unsigned char) changeOutputIndex);
|
||||
|
||||
SproutNote note = plaintext.note(boost::get<libzcash::SproutPaymentAddress>(frompaymentaddress_));
|
||||
info.notes.push_back(note);
|
||||
|
||||
jsInputValue += plaintext.value();
|
||||
|
||||
LogPrint("zrpcunsafe", "%s: spending change (amount=%s)\n",
|
||||
getId(),
|
||||
FormatMoney(plaintext.value())
|
||||
);
|
||||
|
||||
} catch (const std::exception& e) {
|
||||
throw JSONRPCError(RPC_WALLET_ERROR, strprintf("Error decrypting output note of previous JoinSplit: %s", e.what()));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//
|
||||
// Consume spendable non-change notes
|
||||
//
|
||||
std::vector<SproutNote> vInputNotes;
|
||||
std::vector<JSOutPoint> vOutPoints;
|
||||
std::vector<boost::optional<SproutWitness>> vInputWitnesses;
|
||||
uint256 inputAnchor;
|
||||
int numInputsNeeded = (jsChange>0) ? 1 : 0;
|
||||
while (numInputsNeeded++ < ZC_NUM_JS_INPUTS && zInputsDeque.size() > 0) {
|
||||
SendManyInputJSOP t = zInputsDeque.front();
|
||||
JSOutPoint jso = std::get<0>(t);
|
||||
SproutNote note = std::get<1>(t);
|
||||
CAmount noteFunds = std::get<2>(t);
|
||||
zInputsDeque.pop_front();
|
||||
|
||||
WitnessAnchorData wad = jsopWitnessAnchorMap[ jso.ToString() ];
|
||||
vInputWitnesses.push_back(wad.witness);
|
||||
if (inputAnchor.IsNull()) {
|
||||
inputAnchor = wad.anchor;
|
||||
} else if (inputAnchor != wad.anchor) {
|
||||
throw JSONRPCError(RPC_WALLET_ERROR, "Selected input notes do not share the same anchor");
|
||||
}
|
||||
|
||||
vOutPoints.push_back(jso);
|
||||
vInputNotes.push_back(note);
|
||||
|
||||
jsInputValue += noteFunds;
|
||||
|
||||
int wtxHeight = -1;
|
||||
int wtxDepth = -1;
|
||||
{
|
||||
LOCK2(cs_main, pwalletMain->cs_wallet);
|
||||
const CWalletTx& wtx = pwalletMain->mapWallet[jso.hash];
|
||||
// Zero-confirmation notes belong to transactions which have not yet been mined
|
||||
if (mapBlockIndex.find(wtx.hashBlock) == mapBlockIndex.end()) {
|
||||
throw JSONRPCError(RPC_WALLET_ERROR, strprintf("mapBlockIndex does not contain block hash %s", wtx.hashBlock.ToString()));
|
||||
}
|
||||
wtxHeight = komodo_blockheight(wtx.hashBlock);
|
||||
wtxDepth = wtx.GetDepthInMainChain();
|
||||
}
|
||||
LogPrint("zrpcunsafe", "%s: spending note (txid=%s, vjoinsplit=%d, ciphertext=%d, amount=%s, height=%d, confirmations=%d)\n",
|
||||
getId(),
|
||||
jso.hash.ToString().substr(0, 10),
|
||||
jso.js,
|
||||
int(jso.n), // uint8_t
|
||||
FormatMoney(noteFunds),
|
||||
wtxHeight,
|
||||
wtxDepth
|
||||
);
|
||||
}
|
||||
|
||||
// Add history of previous commitments to witness
|
||||
if (vInputNotes.size() > 0) {
|
||||
|
||||
if (vInputWitnesses.size()==0) {
|
||||
throw JSONRPCError(RPC_WALLET_ERROR, "Could not find witness for note commitment");
|
||||
}
|
||||
|
||||
for (auto & optionalWitness : vInputWitnesses) {
|
||||
if (!optionalWitness) {
|
||||
throw JSONRPCError(RPC_WALLET_ERROR, "Witness for note commitment is null");
|
||||
}
|
||||
SproutWitness w = *optionalWitness; // could use .get();
|
||||
if (jsChange > 0) {
|
||||
for (const uint256& commitment : previousCommitments) {
|
||||
w.append(commitment);
|
||||
}
|
||||
if (jsAnchor != w.root()) {
|
||||
throw JSONRPCError(RPC_WALLET_ERROR, "Witness for spendable note does not have same anchor as change input");
|
||||
}
|
||||
}
|
||||
witnesses.push_back(w);
|
||||
}
|
||||
|
||||
// The jsAnchor is null if this JoinSplit is at the start of a new chain
|
||||
if (jsAnchor.IsNull()) {
|
||||
jsAnchor = inputAnchor;
|
||||
}
|
||||
|
||||
// Add spendable notes as inputs
|
||||
std::copy(vInputNotes.begin(), vInputNotes.end(), std::back_inserter(info.notes));
|
||||
}
|
||||
|
||||
// Find recipient to transfer funds to
|
||||
std::string address, hexMemo;
|
||||
CAmount value = 0;
|
||||
if (zOutputsDeque.size() > 0) {
|
||||
SendManyRecipient smr = zOutputsDeque.front();
|
||||
address = std::get<0>(smr);
|
||||
value = std::get<1>(smr);
|
||||
hexMemo = std::get<2>(smr);
|
||||
zOutputsDeque.pop_front();
|
||||
}
|
||||
|
||||
// Reset change
|
||||
jsChange = 0;
|
||||
CAmount outAmount = value;
|
||||
|
||||
// Set vpub_new in the last joinsplit (when there are no more notes to spend or zaddr outputs to satisfy)
|
||||
if (zOutputsDeque.size() == 0 && zInputsDeque.size() == 0) {
|
||||
assert(!vpubNewProcessed);
|
||||
if (jsInputValue < vpubNewTarget) {
|
||||
throw JSONRPCError(RPC_WALLET_ERROR,
|
||||
strprintf("Insufficient funds for vpub_new %s (miners fee %s, taddr outputs %s)",
|
||||
FormatMoney(vpubNewTarget), FormatMoney(minersFee), FormatMoney(t_outputs_total)));
|
||||
}
|
||||
outAmount += vpubNewTarget;
|
||||
info.vpub_new += vpubNewTarget; // funds flowing back to public pool
|
||||
vpubNewProcessed = true;
|
||||
jsChange = jsInputValue - outAmount;
|
||||
assert(jsChange >= 0);
|
||||
}
|
||||
else {
|
||||
// This is not the last joinsplit, so compute change and any amount still due to the recipient
|
||||
if (jsInputValue > outAmount) {
|
||||
jsChange = jsInputValue - outAmount;
|
||||
} else if (outAmount > jsInputValue) {
|
||||
// Any amount due is owed to the recipient. Let the miners fee get paid first.
|
||||
CAmount due = outAmount - jsInputValue;
|
||||
SendManyRecipient r = SendManyRecipient(address, due, hexMemo);
|
||||
zOutputsDeque.push_front(r);
|
||||
|
||||
// reduce the amount being sent right now to the value of all inputs
|
||||
value = jsInputValue;
|
||||
}
|
||||
}
|
||||
|
||||
// create output for recipient
|
||||
if (address.empty()) {
|
||||
assert(value==0);
|
||||
info.vjsout.push_back(JSOutput()); // dummy output while we accumulate funds into a change note for vpub_new
|
||||
} else {
|
||||
PaymentAddress pa = DecodePaymentAddress(address);
|
||||
// If we are here, we know we have no Sapling outputs.
|
||||
JSOutput jso = JSOutput(boost::get<libzcash::SproutPaymentAddress>(pa), value);
|
||||
if (hexMemo.size() > 0) {
|
||||
jso.memo = get_memo_from_hex_string(hexMemo);
|
||||
}
|
||||
info.vjsout.push_back(jso);
|
||||
}
|
||||
|
||||
// create output for any change
|
||||
if (jsChange>0) {
|
||||
info.vjsout.push_back(JSOutput(boost::get<libzcash::SproutPaymentAddress>(frompaymentaddress_), jsChange));
|
||||
|
||||
LogPrint("zrpcunsafe", "%s: generating note for change (amount=%s)\n",
|
||||
getId(),
|
||||
FormatMoney(jsChange)
|
||||
);
|
||||
}
|
||||
|
||||
obj = perform_joinsplit(info, witnesses, jsAnchor);
|
||||
|
||||
if (jsChange > 0) {
|
||||
changeOutputIndex = find_output(obj, 1);
|
||||
}
|
||||
}
|
||||
|
||||
// Sanity check in case changes to code block above exits loop by invoking 'break'
|
||||
assert(zInputsDeque.size() == 0);
|
||||
assert(zOutputsDeque.size() == 0);
|
||||
assert(vpubNewProcessed);
|
||||
|
||||
sign_send_raw_transaction(obj);
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
@@ -1127,33 +678,10 @@ bool AsyncRPCOperation_sendmany::find_utxos(bool fAcceptCoinbase=false) {
|
||||
|
||||
|
||||
bool AsyncRPCOperation_sendmany::find_unspent_notes() {
|
||||
std::vector<CSproutNotePlaintextEntry> sproutEntries;
|
||||
std::vector<SaplingNoteEntry> saplingEntries;
|
||||
{
|
||||
LOCK2(cs_main, pwalletMain->cs_wallet);
|
||||
pwalletMain->GetFilteredNotes(sproutEntries, saplingEntries, fromaddress_, mindepth_);
|
||||
}
|
||||
|
||||
// If using the TransactionBuilder, we only want Sapling notes.
|
||||
// If not using it, we only want Sprout notes.
|
||||
// TODO: Refactor `GetFilteredNotes()` so we only fetch what we need.
|
||||
if (isUsingBuilder_) {
|
||||
sproutEntries.clear();
|
||||
} else {
|
||||
saplingEntries.clear();
|
||||
}
|
||||
|
||||
for (CSproutNotePlaintextEntry & entry : sproutEntries) {
|
||||
z_sprout_inputs_.push_back(SendManyInputJSOP(entry.jsop, entry.plaintext.note(boost::get<libzcash::SproutPaymentAddress>(frompaymentaddress_)), CAmount(entry.plaintext.value())));
|
||||
std::string data(entry.plaintext.memo().begin(), entry.plaintext.memo().end());
|
||||
LogPrint("zrpcunsafe", "%s: found unspent Sprout note (txid=%s, vjoinsplit=%d, ciphertext=%d, amount=%s, memo=%s)\n",
|
||||
getId(),
|
||||
entry.jsop.hash.ToString().substr(0, 10),
|
||||
entry.jsop.js,
|
||||
int(entry.jsop.n), // uint8_t
|
||||
FormatMoney(entry.plaintext.value()),
|
||||
HexStr(data).substr(0, 10)
|
||||
);
|
||||
pwalletMain->GetFilteredNotes(saplingEntries, fromaddress_, mindepth_);
|
||||
}
|
||||
|
||||
for (auto entry : saplingEntries) {
|
||||
@@ -1167,15 +695,7 @@ bool AsyncRPCOperation_sendmany::find_unspent_notes() {
|
||||
HexStr(data).substr(0, 10));
|
||||
}
|
||||
|
||||
if (z_sprout_inputs_.empty() && z_sapling_inputs_.empty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// sort in descending order, so big notes appear first
|
||||
std::sort(z_sprout_inputs_.begin(), z_sprout_inputs_.end(),
|
||||
[](SendManyInputJSOP i, SendManyInputJSOP j) -> bool {
|
||||
return std::get<2>(i) > std::get<2>(j);
|
||||
});
|
||||
std::sort(z_sapling_inputs_.begin(), z_sapling_inputs_.end(),
|
||||
[](SaplingNoteEntry i, SaplingNoteEntry j) -> bool {
|
||||
return i.note.value() > j.note.value();
|
||||
@@ -1184,190 +704,6 @@ bool AsyncRPCOperation_sendmany::find_unspent_notes() {
|
||||
return true;
|
||||
}
|
||||
|
||||
UniValue AsyncRPCOperation_sendmany::perform_joinsplit(AsyncJoinSplitInfo & info) {
|
||||
std::vector<boost::optional < SproutWitness>> witnesses;
|
||||
uint256 anchor;
|
||||
{
|
||||
LOCK(cs_main);
|
||||
anchor = pcoinsTip->GetBestAnchor(SPROUT); // As there are no inputs, ask the wallet for the best anchor
|
||||
}
|
||||
return perform_joinsplit(info, witnesses, anchor);
|
||||
}
|
||||
|
||||
|
||||
UniValue AsyncRPCOperation_sendmany::perform_joinsplit(AsyncJoinSplitInfo & info, std::vector<JSOutPoint> & outPoints) {
|
||||
std::vector<boost::optional < SproutWitness>> witnesses;
|
||||
uint256 anchor;
|
||||
{
|
||||
LOCK(cs_main);
|
||||
pwalletMain->GetSproutNoteWitnesses(outPoints, witnesses, anchor);
|
||||
}
|
||||
return perform_joinsplit(info, witnesses, anchor);
|
||||
}
|
||||
|
||||
UniValue AsyncRPCOperation_sendmany::perform_joinsplit(
|
||||
AsyncJoinSplitInfo & info,
|
||||
std::vector<boost::optional < SproutWitness>> witnesses,
|
||||
uint256 anchor)
|
||||
{
|
||||
if (anchor.IsNull()) {
|
||||
throw std::runtime_error("anchor is null");
|
||||
}
|
||||
|
||||
if (!(witnesses.size() == info.notes.size())) {
|
||||
throw runtime_error("number of notes and witnesses do not match");
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < witnesses.size(); i++) {
|
||||
if (!witnesses[i]) {
|
||||
throw runtime_error("joinsplit input could not be found in tree");
|
||||
}
|
||||
info.vjsin.push_back(JSInput(*witnesses[i], info.notes[i], boost::get<libzcash::SproutSpendingKey>(spendingkey_)));
|
||||
}
|
||||
|
||||
// Make sure there are two inputs and two outputs
|
||||
while (info.vjsin.size() < ZC_NUM_JS_INPUTS) {
|
||||
info.vjsin.push_back(JSInput());
|
||||
}
|
||||
|
||||
while (info.vjsout.size() < ZC_NUM_JS_OUTPUTS) {
|
||||
info.vjsout.push_back(JSOutput());
|
||||
}
|
||||
|
||||
if (info.vjsout.size() != ZC_NUM_JS_INPUTS || info.vjsin.size() != ZC_NUM_JS_OUTPUTS) {
|
||||
throw runtime_error("unsupported joinsplit input/output counts");
|
||||
}
|
||||
|
||||
CMutableTransaction mtx(tx_);
|
||||
|
||||
LogPrint("zrpcunsafe", "%s: creating joinsplit at index %d (vpub_old=%s, vpub_new=%s, in[0]=%s, in[1]=%s, out[0]=%s, out[1]=%s)\n",
|
||||
getId(),
|
||||
tx_.vjoinsplit.size(),
|
||||
FormatMoney(info.vpub_old), FormatMoney(info.vpub_new),
|
||||
FormatMoney(info.vjsin[0].note.value()), FormatMoney(info.vjsin[1].note.value()),
|
||||
FormatMoney(info.vjsout[0].value), FormatMoney(info.vjsout[1].value)
|
||||
);
|
||||
|
||||
// Generate the proof, this can take over a minute.
|
||||
std::array<libzcash::JSInput, ZC_NUM_JS_INPUTS> inputs
|
||||
{info.vjsin[0], info.vjsin[1]};
|
||||
std::array<libzcash::JSOutput, ZC_NUM_JS_OUTPUTS> outputs
|
||||
{info.vjsout[0], info.vjsout[1]};
|
||||
std::array<size_t, ZC_NUM_JS_INPUTS> inputMap;
|
||||
std::array<size_t, ZC_NUM_JS_OUTPUTS> outputMap;
|
||||
uint256 esk; // payment disclosure - secret
|
||||
|
||||
JSDescription jsdesc = JSDescription::Randomized(
|
||||
mtx.fOverwintered && (mtx.nVersion >= SAPLING_TX_VERSION),
|
||||
*pzcashParams,
|
||||
joinSplitPubKey_,
|
||||
anchor,
|
||||
inputs,
|
||||
outputs,
|
||||
inputMap,
|
||||
outputMap,
|
||||
info.vpub_old,
|
||||
info.vpub_new,
|
||||
!this->testmode,
|
||||
&esk); // parameter expects pointer to esk, so pass in address
|
||||
{
|
||||
auto verifier = libzcash::ProofVerifier::Strict();
|
||||
if (!(jsdesc.Verify(*pzcashParams, verifier, joinSplitPubKey_))) {
|
||||
throw std::runtime_error("error verifying joinsplit");
|
||||
}
|
||||
}
|
||||
|
||||
mtx.vjoinsplit.push_back(jsdesc);
|
||||
|
||||
// Empty output script.
|
||||
CScript scriptCode;
|
||||
CTransaction signTx(mtx);
|
||||
uint256 dataToBeSigned = SignatureHash(scriptCode, signTx, NOT_AN_INPUT, SIGHASH_ALL, 0, consensusBranchId_);
|
||||
|
||||
// Add the signature
|
||||
if (!(crypto_sign_detached(&mtx.joinSplitSig[0], NULL,
|
||||
dataToBeSigned.begin(), 32,
|
||||
joinSplitPrivKey_
|
||||
) == 0))
|
||||
{
|
||||
throw std::runtime_error("crypto_sign_detached failed");
|
||||
}
|
||||
|
||||
// Sanity check
|
||||
if (!(crypto_sign_verify_detached(&mtx.joinSplitSig[0],
|
||||
dataToBeSigned.begin(), 32,
|
||||
mtx.joinSplitPubKey.begin()
|
||||
) == 0))
|
||||
{
|
||||
throw std::runtime_error("crypto_sign_verify_detached failed");
|
||||
}
|
||||
|
||||
CTransaction rawTx(mtx);
|
||||
tx_ = rawTx;
|
||||
|
||||
CDataStream ss(SER_NETWORK, PROTOCOL_VERSION);
|
||||
ss << rawTx;
|
||||
|
||||
std::string encryptedNote1;
|
||||
std::string encryptedNote2;
|
||||
{
|
||||
CDataStream ss2(SER_NETWORK, PROTOCOL_VERSION);
|
||||
ss2 << ((unsigned char) 0x00);
|
||||
ss2 << jsdesc.ephemeralKey;
|
||||
ss2 << jsdesc.ciphertexts[0];
|
||||
ss2 << jsdesc.h_sig(*pzcashParams, joinSplitPubKey_);
|
||||
|
||||
encryptedNote1 = HexStr(ss2.begin(), ss2.end());
|
||||
}
|
||||
{
|
||||
CDataStream ss2(SER_NETWORK, PROTOCOL_VERSION);
|
||||
ss2 << ((unsigned char) 0x01);
|
||||
ss2 << jsdesc.ephemeralKey;
|
||||
ss2 << jsdesc.ciphertexts[1];
|
||||
ss2 << jsdesc.h_sig(*pzcashParams, joinSplitPubKey_);
|
||||
|
||||
encryptedNote2 = HexStr(ss2.begin(), ss2.end());
|
||||
}
|
||||
|
||||
UniValue arrInputMap(UniValue::VARR);
|
||||
UniValue arrOutputMap(UniValue::VARR);
|
||||
for (size_t i = 0; i < ZC_NUM_JS_INPUTS; i++) {
|
||||
arrInputMap.push_back(static_cast<uint64_t>(inputMap[i]));
|
||||
}
|
||||
for (size_t i = 0; i < ZC_NUM_JS_OUTPUTS; i++) {
|
||||
arrOutputMap.push_back(static_cast<uint64_t>(outputMap[i]));
|
||||
}
|
||||
|
||||
|
||||
// !!! Payment disclosure START
|
||||
unsigned char buffer[32] = {0};
|
||||
memcpy(&buffer[0], &joinSplitPrivKey_[0], 32); // private key in first half of 64 byte buffer
|
||||
std::vector<unsigned char> vch(&buffer[0], &buffer[0] + 32);
|
||||
uint256 joinSplitPrivKey = uint256(vch);
|
||||
size_t js_index = tx_.vjoinsplit.size() - 1;
|
||||
uint256 placeholder;
|
||||
for (int i = 0; i < ZC_NUM_JS_OUTPUTS; i++) {
|
||||
uint8_t mapped_index = outputMap[i];
|
||||
// placeholder for txid will be filled in later when tx has been finalized and signed.
|
||||
PaymentDisclosureKey pdKey = {placeholder, js_index, mapped_index};
|
||||
JSOutput output = outputs[mapped_index];
|
||||
libzcash::SproutPaymentAddress zaddr = output.addr; // randomized output
|
||||
PaymentDisclosureInfo pdInfo = {PAYMENT_DISCLOSURE_VERSION_EXPERIMENTAL, esk, joinSplitPrivKey, zaddr};
|
||||
paymentDisclosureData_.push_back(PaymentDisclosureKeyInfo(pdKey, pdInfo));
|
||||
|
||||
LogPrint("paymentdisclosure", "%s: Payment Disclosure: js=%d, n=%d, zaddr=%s\n", getId(), js_index, int(mapped_index), EncodePaymentAddress(zaddr));
|
||||
}
|
||||
// !!! Payment disclosure END
|
||||
|
||||
UniValue obj(UniValue::VOBJ);
|
||||
obj.push_back(Pair("encryptednote1", encryptedNote1));
|
||||
obj.push_back(Pair("encryptednote2", encryptedNote2));
|
||||
obj.push_back(Pair("rawtxn", HexStr(ss.begin(), ss.end())));
|
||||
obj.push_back(Pair("inputmap", arrInputMap));
|
||||
obj.push_back(Pair("outputmap", arrOutputMap));
|
||||
return obj;
|
||||
}
|
||||
|
||||
void AsyncRPCOperation_sendmany::add_taddr_outputs_to_tx() {
|
||||
|
||||
CMutableTransaction rawTx(tx_);
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
// Copyright (c) 2016 The Zcash developers
|
||||
// Copyright (c) 2019-2020 The Hush developers
|
||||
// Distributed under the MIT software license, see the accompanying
|
||||
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
|
||||
|
||||
@@ -27,7 +28,6 @@
|
||||
#include "zcash/JoinSplit.hpp"
|
||||
#include "zcash/Address.hpp"
|
||||
#include "wallet.h"
|
||||
#include "paymentdisclosure.h"
|
||||
|
||||
#include <array>
|
||||
#include <unordered_map>
|
||||
@@ -46,19 +46,6 @@ typedef std::tuple<std::string, CAmount, std::string> SendManyRecipient;
|
||||
// Input UTXO is a tuple (quadruple) of txid, vout, amount, coinbase)
|
||||
typedef std::tuple<uint256, int, CAmount, bool, CTxDestination> SendManyInputUTXO;
|
||||
|
||||
// Input JSOP is a tuple of JSOutpoint, note and amount
|
||||
typedef std::tuple<JSOutPoint, SproutNote, CAmount> SendManyInputJSOP;
|
||||
|
||||
// Package of info which is passed to perform_joinsplit methods.
|
||||
struct AsyncJoinSplitInfo
|
||||
{
|
||||
std::vector<JSInput> vjsin;
|
||||
std::vector<JSOutput> vjsout;
|
||||
std::vector<SproutNote> notes;
|
||||
CAmount vpub_old = 0;
|
||||
CAmount vpub_new = 0;
|
||||
};
|
||||
|
||||
// A struct to help us track the witness and anchor for a given JSOutPoint
|
||||
struct WitnessAnchorData {
|
||||
boost::optional<SproutWitness> witness;
|
||||
@@ -90,8 +77,6 @@ public:
|
||||
|
||||
bool testmode = false; // Set to true to disable sending txs and generating proofs
|
||||
|
||||
bool paymentDisclosureMode = true; // Set to true to save esk for encrypted notes in payment disclosure database.
|
||||
|
||||
private:
|
||||
friend class TEST_FRIEND_AsyncRPCOperation_sendmany; // class for unit testing
|
||||
|
||||
@@ -117,7 +102,7 @@ private:
|
||||
std::vector<SendManyRecipient> t_outputs_;
|
||||
std::vector<SendManyRecipient> z_outputs_;
|
||||
std::vector<SendManyInputUTXO> t_inputs_;
|
||||
std::vector<SendManyInputJSOP> z_sprout_inputs_;
|
||||
//std::vector<SendManyInputJSOP> z_sprout_inputs_;
|
||||
std::vector<SaplingNoteEntry> z_sapling_inputs_;
|
||||
|
||||
TransactionBuilder builder_;
|
||||
@@ -130,22 +115,7 @@ private:
|
||||
std::array<unsigned char, ZC_MEMO_SIZE> get_memo_from_hex_string(std::string s);
|
||||
bool main_impl();
|
||||
|
||||
// JoinSplit without any input notes to spend
|
||||
UniValue perform_joinsplit(AsyncJoinSplitInfo &);
|
||||
|
||||
// JoinSplit with input notes to spend (JSOutPoints))
|
||||
UniValue perform_joinsplit(AsyncJoinSplitInfo &, std::vector<JSOutPoint> & );
|
||||
|
||||
// JoinSplit where you have the witnesses and anchor
|
||||
UniValue perform_joinsplit(
|
||||
AsyncJoinSplitInfo & info,
|
||||
std::vector<boost::optional < SproutWitness>> witnesses,
|
||||
uint256 anchor);
|
||||
|
||||
void sign_send_raw_transaction(UniValue obj); // throws exception if there was an error
|
||||
|
||||
// payment disclosure!
|
||||
std::vector<PaymentDisclosureKeyInfo> paymentDisclosureData_;
|
||||
};
|
||||
|
||||
|
||||
@@ -190,22 +160,6 @@ public:
|
||||
return delegate->main_impl();
|
||||
}
|
||||
|
||||
UniValue perform_joinsplit(AsyncJoinSplitInfo &info) {
|
||||
return delegate->perform_joinsplit(info);
|
||||
}
|
||||
|
||||
UniValue perform_joinsplit(AsyncJoinSplitInfo &info, std::vector<JSOutPoint> &v ) {
|
||||
return delegate->perform_joinsplit(info, v);
|
||||
}
|
||||
|
||||
UniValue perform_joinsplit(
|
||||
AsyncJoinSplitInfo & info,
|
||||
std::vector<boost::optional < SproutWitness>> witnesses,
|
||||
uint256 anchor)
|
||||
{
|
||||
return delegate->perform_joinsplit(info, witnesses, anchor);
|
||||
}
|
||||
|
||||
void sign_send_raw_transaction(UniValue obj) {
|
||||
delegate->sign_send_raw_transaction(obj);
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
// Copyright (c) 2017 The Zcash developers
|
||||
// Copyright (c) 2019-2020 The Hush developers
|
||||
// Distributed under the MIT software license, see the accompanying
|
||||
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
|
||||
|
||||
@@ -47,9 +48,6 @@
|
||||
|
||||
#include "asyncrpcoperation_shieldcoinbase.h"
|
||||
|
||||
#include "paymentdisclosure.h"
|
||||
#include "paymentdisclosuredb.h"
|
||||
|
||||
using namespace libzcash;
|
||||
extern uint64_t ASSETCHAINS_TIMELOCKGTE;
|
||||
|
||||
@@ -107,8 +105,6 @@ AsyncRPCOperation_shieldcoinbase::AsyncRPCOperation_shieldcoinbase(
|
||||
// Lock UTXOs
|
||||
lock_utxos();
|
||||
|
||||
// Enable payment disclosure if requested
|
||||
paymentDisclosureMode = fExperimentalMode && GetBoolArg("-paymentdisclosure", true);
|
||||
}
|
||||
|
||||
AsyncRPCOperation_shieldcoinbase::~AsyncRPCOperation_shieldcoinbase() {
|
||||
@@ -180,20 +176,6 @@ void AsyncRPCOperation_shieldcoinbase::main() {
|
||||
|
||||
unlock_utxos(); // clean up
|
||||
|
||||
// !!! Payment disclosure START
|
||||
if (success && paymentDisclosureMode && paymentDisclosureData_.size()>0) {
|
||||
uint256 txidhash = tx_.GetHash();
|
||||
std::shared_ptr<PaymentDisclosureDB> db = PaymentDisclosureDB::sharedInstance();
|
||||
for (PaymentDisclosureKeyInfo p : paymentDisclosureData_) {
|
||||
p.first.hash = txidhash;
|
||||
if (!db->Put(p.first, p.second)) {
|
||||
LogPrint("paymentdisclosure", "%s: Payment Disclosure: Error writing entry to database for key %s\n", getId(), p.first.ToString());
|
||||
} else {
|
||||
LogPrint("paymentdisclosure", "%s: Payment Disclosure: Successfully added entry to database for key %s\n", getId(), p.first.ToString());
|
||||
}
|
||||
}
|
||||
}
|
||||
// !!! Payment disclosure END
|
||||
}
|
||||
|
||||
bool AsyncRPCOperation_shieldcoinbase::main_impl() {
|
||||
@@ -234,37 +216,6 @@ bool AsyncRPCOperation_shieldcoinbase::main_impl() {
|
||||
return boost::apply_visitor(ShieldToAddress(this, sendAmount), tozaddr_);
|
||||
}
|
||||
|
||||
bool ShieldToAddress::operator()(const libzcash::SproutPaymentAddress &zaddr) const {
|
||||
// update the transaction with these inputs
|
||||
CMutableTransaction rawTx(m_op->tx_);
|
||||
for (ShieldCoinbaseUTXO & t : m_op->inputs_) {
|
||||
CTxIn in(COutPoint(t.txid, t.vout));
|
||||
if (t.amount >= ASSETCHAINS_TIMELOCKGTE)
|
||||
in.nSequence = 0xfffffffe;
|
||||
rawTx.vin.push_back(in);
|
||||
}
|
||||
m_op->tx_ = CTransaction(rawTx);
|
||||
|
||||
// Prepare raw transaction to handle JoinSplits
|
||||
CMutableTransaction mtx(m_op->tx_);
|
||||
crypto_sign_keypair(m_op->joinSplitPubKey_.begin(), m_op->joinSplitPrivKey_);
|
||||
mtx.joinSplitPubKey = m_op->joinSplitPubKey_;
|
||||
m_op->tx_ = CTransaction(mtx);
|
||||
|
||||
// Create joinsplit
|
||||
UniValue obj(UniValue::VOBJ);
|
||||
ShieldCoinbaseJSInfo info;
|
||||
info.vpub_old = sendAmount;
|
||||
info.vpub_new = 0;
|
||||
JSOutput jso = JSOutput(zaddr, sendAmount);
|
||||
info.vjsout.push_back(jso);
|
||||
obj = m_op->perform_joinsplit(info);
|
||||
|
||||
m_op->sign_send_raw_transaction(obj);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
extern UniValue signrawtransaction(const UniValue& params, bool fHelp, const CPubKey& mypk);
|
||||
extern UniValue sendrawtransaction(const UniValue& params, bool fHelp, const CPubKey& mypk);
|
||||
|
||||
@@ -406,164 +357,6 @@ void AsyncRPCOperation_shieldcoinbase::sign_send_raw_transaction(UniValue obj)
|
||||
}
|
||||
|
||||
|
||||
UniValue AsyncRPCOperation_shieldcoinbase::perform_joinsplit(ShieldCoinbaseJSInfo & info) {
|
||||
uint32_t consensusBranchId;
|
||||
uint256 anchor;
|
||||
{
|
||||
LOCK(cs_main);
|
||||
consensusBranchId = CurrentEpochBranchId(chainActive.Height() + 1, Params().GetConsensus());
|
||||
anchor = pcoinsTip->GetBestAnchor(SPROUT);
|
||||
}
|
||||
|
||||
|
||||
if (anchor.IsNull()) {
|
||||
throw std::runtime_error("anchor is null");
|
||||
}
|
||||
|
||||
// Make sure there are two inputs and two outputs
|
||||
while (info.vjsin.size() < ZC_NUM_JS_INPUTS) {
|
||||
info.vjsin.push_back(JSInput());
|
||||
}
|
||||
|
||||
while (info.vjsout.size() < ZC_NUM_JS_OUTPUTS) {
|
||||
info.vjsout.push_back(JSOutput());
|
||||
}
|
||||
|
||||
if (info.vjsout.size() != ZC_NUM_JS_INPUTS || info.vjsin.size() != ZC_NUM_JS_OUTPUTS) {
|
||||
throw runtime_error("unsupported joinsplit input/output counts");
|
||||
}
|
||||
|
||||
CMutableTransaction mtx(tx_);
|
||||
|
||||
LogPrint("zrpcunsafe", "%s: creating joinsplit at index %d (vpub_old=%s, vpub_new=%s, in[0]=%s, in[1]=%s, out[0]=%s, out[1]=%s)\n",
|
||||
getId(),
|
||||
tx_.vjoinsplit.size(),
|
||||
FormatMoney(info.vpub_old), FormatMoney(info.vpub_new),
|
||||
FormatMoney(info.vjsin[0].note.value()), FormatMoney(info.vjsin[1].note.value()),
|
||||
FormatMoney(info.vjsout[0].value), FormatMoney(info.vjsout[1].value)
|
||||
);
|
||||
|
||||
// Generate the proof, this can take over a minute.
|
||||
std::array<libzcash::JSInput, ZC_NUM_JS_INPUTS> inputs
|
||||
{info.vjsin[0], info.vjsin[1]};
|
||||
std::array<libzcash::JSOutput, ZC_NUM_JS_OUTPUTS> outputs
|
||||
{info.vjsout[0], info.vjsout[1]};
|
||||
|
||||
std::array<size_t, ZC_NUM_JS_INPUTS> inputMap;
|
||||
std::array<size_t, ZC_NUM_JS_OUTPUTS> outputMap;
|
||||
|
||||
uint256 esk; // payment disclosure - secret
|
||||
|
||||
JSDescription jsdesc = JSDescription::Randomized(
|
||||
mtx.fOverwintered && (mtx.nVersion >= SAPLING_TX_VERSION),
|
||||
*pzcashParams,
|
||||
joinSplitPubKey_,
|
||||
anchor,
|
||||
inputs,
|
||||
outputs,
|
||||
inputMap,
|
||||
outputMap,
|
||||
info.vpub_old,
|
||||
info.vpub_new,
|
||||
!this->testmode,
|
||||
&esk); // parameter expects pointer to esk, so pass in address
|
||||
{
|
||||
auto verifier = libzcash::ProofVerifier::Strict();
|
||||
if (!(jsdesc.Verify(*pzcashParams, verifier, joinSplitPubKey_))) {
|
||||
throw std::runtime_error("error verifying joinsplit");
|
||||
}
|
||||
}
|
||||
|
||||
mtx.vjoinsplit.push_back(jsdesc);
|
||||
|
||||
// Empty output script.
|
||||
CScript scriptCode;
|
||||
CTransaction signTx(mtx);
|
||||
uint256 dataToBeSigned = SignatureHash(scriptCode, signTx, NOT_AN_INPUT, SIGHASH_ALL, 0, consensusBranchId);
|
||||
|
||||
// Add the signature
|
||||
if (!(crypto_sign_detached(&mtx.joinSplitSig[0], NULL,
|
||||
dataToBeSigned.begin(), 32,
|
||||
joinSplitPrivKey_
|
||||
) == 0))
|
||||
{
|
||||
throw std::runtime_error("crypto_sign_detached failed");
|
||||
}
|
||||
|
||||
// Sanity check
|
||||
if (!(crypto_sign_verify_detached(&mtx.joinSplitSig[0],
|
||||
dataToBeSigned.begin(), 32,
|
||||
mtx.joinSplitPubKey.begin()
|
||||
) == 0))
|
||||
{
|
||||
throw std::runtime_error("crypto_sign_verify_detached failed");
|
||||
}
|
||||
|
||||
CTransaction rawTx(mtx);
|
||||
tx_ = rawTx;
|
||||
|
||||
CDataStream ss(SER_NETWORK, PROTOCOL_VERSION);
|
||||
ss << rawTx;
|
||||
|
||||
std::string encryptedNote1;
|
||||
std::string encryptedNote2;
|
||||
{
|
||||
CDataStream ss2(SER_NETWORK, PROTOCOL_VERSION);
|
||||
ss2 << ((unsigned char) 0x00);
|
||||
ss2 << jsdesc.ephemeralKey;
|
||||
ss2 << jsdesc.ciphertexts[0];
|
||||
ss2 << jsdesc.h_sig(*pzcashParams, joinSplitPubKey_);
|
||||
|
||||
encryptedNote1 = HexStr(ss2.begin(), ss2.end());
|
||||
}
|
||||
{
|
||||
CDataStream ss2(SER_NETWORK, PROTOCOL_VERSION);
|
||||
ss2 << ((unsigned char) 0x01);
|
||||
ss2 << jsdesc.ephemeralKey;
|
||||
ss2 << jsdesc.ciphertexts[1];
|
||||
ss2 << jsdesc.h_sig(*pzcashParams, joinSplitPubKey_);
|
||||
|
||||
encryptedNote2 = HexStr(ss2.begin(), ss2.end());
|
||||
}
|
||||
|
||||
UniValue arrInputMap(UniValue::VARR);
|
||||
UniValue arrOutputMap(UniValue::VARR);
|
||||
for (size_t i = 0; i < ZC_NUM_JS_INPUTS; i++) {
|
||||
arrInputMap.push_back(static_cast<uint64_t>(inputMap[i]));
|
||||
}
|
||||
for (size_t i = 0; i < ZC_NUM_JS_OUTPUTS; i++) {
|
||||
arrOutputMap.push_back(static_cast<uint64_t>(outputMap[i]));
|
||||
}
|
||||
|
||||
// !!! Payment disclosure START
|
||||
unsigned char buffer[32] = {0};
|
||||
memcpy(&buffer[0], &joinSplitPrivKey_[0], 32); // private key in first half of 64 byte buffer
|
||||
std::vector<unsigned char> vch(&buffer[0], &buffer[0] + 32);
|
||||
uint256 joinSplitPrivKey = uint256(vch);
|
||||
size_t js_index = tx_.vjoinsplit.size() - 1;
|
||||
uint256 placeholder;
|
||||
for (int i = 0; i < ZC_NUM_JS_OUTPUTS; i++) {
|
||||
uint8_t mapped_index = outputMap[i];
|
||||
// placeholder for txid will be filled in later when tx has been finalized and signed.
|
||||
PaymentDisclosureKey pdKey = {placeholder, js_index, mapped_index};
|
||||
JSOutput output = outputs[mapped_index];
|
||||
libzcash::SproutPaymentAddress zaddr = output.addr; // randomized output
|
||||
PaymentDisclosureInfo pdInfo = {PAYMENT_DISCLOSURE_VERSION_EXPERIMENTAL, esk, joinSplitPrivKey, zaddr};
|
||||
paymentDisclosureData_.push_back(PaymentDisclosureKeyInfo(pdKey, pdInfo));
|
||||
|
||||
LogPrint("paymentdisclosure", "%s: Payment Disclosure: js=%d, n=%d, zaddr=%s\n", getId(), js_index, int(mapped_index), EncodePaymentAddress(zaddr));
|
||||
}
|
||||
// !!! Payment disclosure END
|
||||
|
||||
UniValue obj(UniValue::VOBJ);
|
||||
obj.push_back(Pair("encryptednote1", encryptedNote1));
|
||||
obj.push_back(Pair("encryptednote2", encryptedNote2));
|
||||
obj.push_back(Pair("rawtxn", HexStr(ss.begin(), ss.end())));
|
||||
obj.push_back(Pair("inputmap", arrInputMap));
|
||||
obj.push_back(Pair("outputmap", arrOutputMap));
|
||||
return obj;
|
||||
}
|
||||
|
||||
/**
|
||||
* Override getStatus() to append the operation's context object to the default status object.
|
||||
*/
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
// Copyright (c) 2017 The Zcash developers
|
||||
// Copyright (c) 2019-2020 The Hush developers
|
||||
// Distributed under the MIT software license, see the accompanying
|
||||
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
|
||||
|
||||
@@ -33,8 +34,6 @@
|
||||
|
||||
#include <univalue.h>
|
||||
|
||||
#include "paymentdisclosure.h"
|
||||
|
||||
// Default transaction fee if caller does not specify one.
|
||||
#define SHIELD_COINBASE_DEFAULT_MINERS_FEE 10000
|
||||
|
||||
@@ -80,8 +79,6 @@ public:
|
||||
bool testmode = false; // Set to true to disable sending txs and generating proofs
|
||||
bool cheatSpend = false; // set when this is shielding a cheating coinbase
|
||||
|
||||
bool paymentDisclosureMode = true; // Set to true to save esk for encrypted notes in payment disclosure database.
|
||||
|
||||
private:
|
||||
friend class ShieldToAddress;
|
||||
friend class TEST_FRIEND_AsyncRPCOperation_shieldcoinbase; // class for unit testing
|
||||
@@ -109,9 +106,6 @@ private:
|
||||
void lock_utxos();
|
||||
|
||||
void unlock_utxos();
|
||||
|
||||
// payment disclosure!
|
||||
std::vector<PaymentDisclosureKeyInfo> paymentDisclosureData_;
|
||||
};
|
||||
|
||||
class ShieldToAddress : public boost::static_visitor<bool>
|
||||
@@ -123,7 +117,6 @@ public:
|
||||
ShieldToAddress(AsyncRPCOperation_shieldcoinbase *op, CAmount sendAmount) :
|
||||
m_op(op), sendAmount(sendAmount) {}
|
||||
|
||||
bool operator()(const libzcash::SproutPaymentAddress &zaddr) const;
|
||||
bool operator()(const libzcash::SaplingPaymentAddress &zaddr) const;
|
||||
bool operator()(const libzcash::InvalidEncoding& no) const;
|
||||
};
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
// Copyright (c) 2009-2013 The Bitcoin Core developers
|
||||
// Copyright (c) 2019-2020 The Hush developers
|
||||
// Distributed under the MIT software license, see the accompanying
|
||||
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
|
||||
// file COPYING or https://www.opensource.org/licenses/mit-license.php
|
||||
|
||||
/******************************************************************************
|
||||
* Copyright © 2014-2019 The SuperNET Developers. *
|
||||
@@ -168,22 +169,6 @@ static bool DecryptKey(const CKeyingMaterial& vMasterKey, const std::vector<unsi
|
||||
return key.VerifyPubKey(vchPubKey);
|
||||
}
|
||||
|
||||
static bool DecryptSproutSpendingKey(const CKeyingMaterial& vMasterKey,
|
||||
const std::vector<unsigned char>& vchCryptedSecret,
|
||||
const libzcash::SproutPaymentAddress& address,
|
||||
libzcash::SproutSpendingKey& sk)
|
||||
{
|
||||
CKeyingMaterial vchSecret;
|
||||
if (!DecryptSecret(vMasterKey, vchCryptedSecret, address.GetHash(), vchSecret))
|
||||
return false;
|
||||
|
||||
if (vchSecret.size() != libzcash::SerializedSproutSpendingKeySize)
|
||||
return false;
|
||||
|
||||
CSecureDataStream ss(vchSecret, SER_NETWORK, PROTOCOL_VERSION);
|
||||
ss >> sk;
|
||||
return sk.address() == address;
|
||||
}
|
||||
|
||||
static bool DecryptSaplingSpendingKey(const CKeyingMaterial& vMasterKey,
|
||||
const std::vector<unsigned char>& vchCryptedSecret,
|
||||
@@ -207,7 +192,7 @@ bool CCryptoKeyStore::SetCrypted()
|
||||
LOCK2(cs_KeyStore, cs_SpendingKeyStore);
|
||||
if (fUseCrypto)
|
||||
return true;
|
||||
if (!(mapKeys.empty() && mapSproutSpendingKeys.empty() && mapSaplingSpendingKeys.empty()))
|
||||
if (!(mapKeys.empty() && mapSaplingSpendingKeys.empty()))
|
||||
return false;
|
||||
fUseCrypto = true;
|
||||
return true;
|
||||
@@ -260,21 +245,6 @@ bool CCryptoKeyStore::Unlock(const CKeyingMaterial& vMasterKeyIn)
|
||||
if (fDecryptionThoroughlyChecked)
|
||||
break;
|
||||
}
|
||||
CryptedSproutSpendingKeyMap::const_iterator miSprout = mapCryptedSproutSpendingKeys.begin();
|
||||
for (; miSprout != mapCryptedSproutSpendingKeys.end(); ++miSprout)
|
||||
{
|
||||
const libzcash::SproutPaymentAddress &address = (*miSprout).first;
|
||||
const std::vector<unsigned char> &vchCryptedSecret = (*miSprout).second;
|
||||
libzcash::SproutSpendingKey sk;
|
||||
if (!DecryptSproutSpendingKey(vMasterKeyIn, vchCryptedSecret, address, sk))
|
||||
{
|
||||
keyFail = true;
|
||||
break;
|
||||
}
|
||||
keyPass = true;
|
||||
if (fDecryptionThoroughlyChecked)
|
||||
break;
|
||||
}
|
||||
CryptedSaplingSpendingKeyMap::const_iterator miSapling = mapCryptedSaplingSpendingKeys.begin();
|
||||
for (; miSapling != mapCryptedSaplingSpendingKeys.end(); ++miSapling)
|
||||
{
|
||||
@@ -292,7 +262,7 @@ bool CCryptoKeyStore::Unlock(const CKeyingMaterial& vMasterKeyIn)
|
||||
}
|
||||
if (keyPass && keyFail)
|
||||
{
|
||||
LogPrintf("The wallet is probably corrupted: Some keys decrypt but not all.\n");
|
||||
LogPrintf("Oh shit! The wallet is probably corrupted: Some keys decrypt but not all.\n");
|
||||
assert(false);
|
||||
}
|
||||
if (keyFail || !keyPass)
|
||||
@@ -440,30 +410,6 @@ bool CCryptoKeyStore::GetPubKey(const CKeyID &address, CPubKey& vchPubKeyOut) co
|
||||
return false;
|
||||
}
|
||||
|
||||
bool CCryptoKeyStore::AddSproutSpendingKey(const libzcash::SproutSpendingKey &sk)
|
||||
{
|
||||
{
|
||||
LOCK(cs_SpendingKeyStore);
|
||||
if (!IsCrypted())
|
||||
return CBasicKeyStore::AddSproutSpendingKey(sk);
|
||||
|
||||
if (IsLocked())
|
||||
return false;
|
||||
|
||||
std::vector<unsigned char> vchCryptedSecret;
|
||||
CSecureDataStream ss(SER_NETWORK, PROTOCOL_VERSION);
|
||||
ss << sk;
|
||||
CKeyingMaterial vchSecret(ss.begin(), ss.end());
|
||||
auto address = sk.address();
|
||||
if (!EncryptSecret(vMasterKey, vchSecret, address.GetHash(), vchCryptedSecret))
|
||||
return false;
|
||||
|
||||
if (!AddCryptedSproutSpendingKey(address, sk.receiving_key(), vchCryptedSecret))
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool CCryptoKeyStore::AddSaplingSpendingKey(
|
||||
const libzcash::SaplingExtendedSpendingKey &sk,
|
||||
const libzcash::SaplingPaymentAddress &defaultAddr)
|
||||
@@ -494,22 +440,6 @@ bool CCryptoKeyStore::AddSaplingSpendingKey(
|
||||
return true;
|
||||
}
|
||||
|
||||
bool CCryptoKeyStore::AddCryptedSproutSpendingKey(
|
||||
const libzcash::SproutPaymentAddress &address,
|
||||
const libzcash::ReceivingKey &rk,
|
||||
const std::vector<unsigned char> &vchCryptedSecret)
|
||||
{
|
||||
{
|
||||
LOCK(cs_SpendingKeyStore);
|
||||
if (!SetCrypted())
|
||||
return false;
|
||||
|
||||
mapCryptedSproutSpendingKeys[address] = vchCryptedSecret;
|
||||
mapNoteDecryptors.insert(std::make_pair(address, ZCNoteDecryption(rk)));
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool CCryptoKeyStore::AddCryptedSaplingSpendingKey(
|
||||
const libzcash::SaplingExtendedFullViewingKey &extfvk,
|
||||
const std::vector<unsigned char> &vchCryptedSecret,
|
||||
@@ -531,23 +461,6 @@ bool CCryptoKeyStore::AddCryptedSaplingSpendingKey(
|
||||
return true;
|
||||
}
|
||||
|
||||
bool CCryptoKeyStore::GetSproutSpendingKey(const libzcash::SproutPaymentAddress &address, libzcash::SproutSpendingKey &skOut) const
|
||||
{
|
||||
{
|
||||
LOCK(cs_SpendingKeyStore);
|
||||
if (!IsCrypted())
|
||||
return CBasicKeyStore::GetSproutSpendingKey(address, skOut);
|
||||
|
||||
CryptedSproutSpendingKeyMap::const_iterator mi = mapCryptedSproutSpendingKeys.find(address);
|
||||
if (mi != mapCryptedSproutSpendingKeys.end())
|
||||
{
|
||||
const std::vector<unsigned char> &vchCryptedSecret = (*mi).second;
|
||||
return DecryptSproutSpendingKey(vMasterKey, vchCryptedSecret, address, skOut);
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool CCryptoKeyStore::GetSaplingSpendingKey(const libzcash::SaplingFullViewingKey &fvk, libzcash::SaplingExtendedSpendingKey &skOut) const
|
||||
{
|
||||
{
|
||||
@@ -603,22 +516,6 @@ bool CCryptoKeyStore::EncryptKeys(CKeyingMaterial& vMasterKeyIn)
|
||||
}
|
||||
}
|
||||
mapKeys.clear();
|
||||
BOOST_FOREACH(SproutSpendingKeyMap::value_type& mSproutSpendingKey, mapSproutSpendingKeys)
|
||||
{
|
||||
const libzcash::SproutSpendingKey &sk = mSproutSpendingKey.second;
|
||||
CSecureDataStream ss(SER_NETWORK, PROTOCOL_VERSION);
|
||||
ss << sk;
|
||||
CKeyingMaterial vchSecret(ss.begin(), ss.end());
|
||||
libzcash::SproutPaymentAddress address = sk.address();
|
||||
std::vector<unsigned char> vchCryptedSecret;
|
||||
if (!EncryptSecret(vMasterKeyIn, vchSecret, address.GetHash(), vchCryptedSecret)) {
|
||||
return false;
|
||||
}
|
||||
if (!AddCryptedSproutSpendingKey(address, sk.receiving_key(), vchCryptedSecret)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
mapSproutSpendingKeys.clear();
|
||||
//! Sapling key support
|
||||
BOOST_FOREACH(SaplingSpendingKeyMap::value_type& mSaplingSpendingKey, mapSaplingSpendingKeys)
|
||||
{
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
// Copyright (c) 2009-2014 The Bitcoin Core developers
|
||||
// Copyright (c) 2019-2020 The Hush developers
|
||||
// Distributed under the MIT software license, see the accompanying
|
||||
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
|
||||
|
||||
@@ -145,7 +146,7 @@ class CCryptoKeyStore : public CBasicKeyStore
|
||||
private:
|
||||
std::pair<uint256, std::vector<unsigned char>> cryptedHDSeed;
|
||||
CryptedKeyMap mapCryptedKeys;
|
||||
CryptedSproutSpendingKeyMap mapCryptedSproutSpendingKeys;
|
||||
//CryptedSproutSpendingKeyMap mapCryptedSproutSpendingKeys;
|
||||
CryptedSaplingSpendingKeyMap mapCryptedSaplingSpendingKeys;
|
||||
|
||||
CKeyingMaterial vMasterKey;
|
||||
@@ -223,37 +224,6 @@ public:
|
||||
mi++;
|
||||
}
|
||||
}
|
||||
virtual bool AddCryptedSproutSpendingKey(
|
||||
const libzcash::SproutPaymentAddress &address,
|
||||
const libzcash::ReceivingKey &rk,
|
||||
const std::vector<unsigned char> &vchCryptedSecret);
|
||||
bool AddSproutSpendingKey(const libzcash::SproutSpendingKey &sk);
|
||||
bool HaveSproutSpendingKey(const libzcash::SproutPaymentAddress &address) const
|
||||
{
|
||||
{
|
||||
LOCK(cs_SpendingKeyStore);
|
||||
if (!IsCrypted())
|
||||
return CBasicKeyStore::HaveSproutSpendingKey(address);
|
||||
return mapCryptedSproutSpendingKeys.count(address) > 0;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
bool GetSproutSpendingKey(const libzcash::SproutPaymentAddress &address, libzcash::SproutSpendingKey &skOut) const;
|
||||
void GetSproutPaymentAddresses(std::set<libzcash::SproutPaymentAddress> &setAddress) const
|
||||
{
|
||||
if (!IsCrypted())
|
||||
{
|
||||
CBasicKeyStore::GetSproutPaymentAddresses(setAddress);
|
||||
return;
|
||||
}
|
||||
setAddress.clear();
|
||||
CryptedSproutSpendingKeyMap::const_iterator mi = mapCryptedSproutSpendingKeys.begin();
|
||||
while (mi != mapCryptedSproutSpendingKeys.end())
|
||||
{
|
||||
setAddress.insert((*mi).first);
|
||||
mi++;
|
||||
}
|
||||
}
|
||||
//! Sapling
|
||||
virtual bool AddCryptedSaplingSpendingKey(
|
||||
const libzcash::SaplingExtendedFullViewingKey &extfvk,
|
||||
|
||||
@@ -105,7 +105,7 @@ bool CDBEnv::Open(const boost::filesystem::path& pathIn)
|
||||
nEnvFlags |= DB_PRIVATE;
|
||||
|
||||
dbenv->set_lg_dir(pathLogDir.string().c_str());
|
||||
dbenv->set_cachesize(0, 0x100000, 1); // 1 MiB should be enough for just the wallet
|
||||
dbenv->set_cachesize(1, 0x100000, 1); // 1 MiB should be enough for just the wallet, Increased by 1 GB
|
||||
dbenv->set_lg_bsize(0x10000);
|
||||
dbenv->set_lg_max(1048576);
|
||||
dbenv->set_lk_max_locks(40000);
|
||||
@@ -181,6 +181,55 @@ CDBEnv::VerifyResult CDBEnv::Verify(const std::string& strFile, bool (*recoverFu
|
||||
return (fRecovered ? RECOVER_OK : RECOVER_FAIL);
|
||||
}
|
||||
|
||||
bool CDBEnv::Compact(const std::string& strFile)
|
||||
{
|
||||
LOCK(cs_db);
|
||||
|
||||
DB_COMPACT dbcompact;
|
||||
dbcompact.compact_fillpercent = 80;
|
||||
dbcompact.compact_pages = DB_MAX_PAGES;
|
||||
dbcompact.compact_timeout = 0;
|
||||
|
||||
DB_COMPACT *pdbcompact;
|
||||
pdbcompact = &dbcompact;
|
||||
|
||||
int result = 1;
|
||||
if (mapDb[strFile] != NULL) {
|
||||
Db* pdb = mapDb[strFile];
|
||||
result = pdb->compact(NULL, NULL, NULL, pdbcompact, DB_FREE_SPACE, NULL);
|
||||
delete pdb;
|
||||
mapDb[strFile] = NULL;
|
||||
|
||||
switch (result)
|
||||
{
|
||||
case DB_LOCK_DEADLOCK:
|
||||
LogPrint("db","Deadlock %i\n", result);
|
||||
break;
|
||||
case DB_LOCK_NOTGRANTED:
|
||||
LogPrint("db","Lock Not Granted %i\n", result);
|
||||
break;
|
||||
case DB_REP_HANDLE_DEAD:
|
||||
LogPrint("db","Handle Dead %i\n", result);
|
||||
break;
|
||||
case DB_REP_LOCKOUT:
|
||||
LogPrint("db","Rep Lockout %i\n", result);
|
||||
break;
|
||||
case EACCES:
|
||||
LogPrint("db","Eacces %i\n", result);
|
||||
break;
|
||||
case EINVAL:
|
||||
LogPrint("db","Error Invalid %i\n", result);
|
||||
break;
|
||||
case 0:
|
||||
LogPrint("db","Wallet Compact Sucessful\n");
|
||||
break;
|
||||
default:
|
||||
LogPrint("db","Compact result int %i\n", result);
|
||||
}
|
||||
}
|
||||
return (result == 0);
|
||||
}
|
||||
|
||||
bool CDBEnv::Salvage(const std::string& strFile, bool fAggressive, std::vector<CDBEnv::KeyValPair>& vResult)
|
||||
{
|
||||
LOCK(cs_db);
|
||||
|
||||
@@ -88,6 +88,7 @@ public:
|
||||
* NOTE: reads the entire database into memory, so cannot be used
|
||||
* for huge databases.
|
||||
*/
|
||||
bool Compact(const std::string& strFile);
|
||||
typedef std::pair<std::vector<unsigned char>, std::vector<unsigned char> > KeyValPair;
|
||||
bool Salvage(const std::string& strFile, bool fAggressive, std::vector<KeyValPair>& vResult);
|
||||
|
||||
|
||||
@@ -53,12 +53,10 @@ public:
|
||||
return CCryptoKeyStore::Unlock(vMasterKeyIn);
|
||||
}
|
||||
|
||||
void IncrementNoteWitnesses(const CBlockIndex* pindex,
|
||||
const CBlock* pblock,
|
||||
SproutMerkleTree& sproutTree,
|
||||
SaplingMerkleTree& saplingTree) {
|
||||
CWallet::IncrementNoteWitnesses(pindex, pblock, sproutTree, saplingTree);
|
||||
void BuildWitnessCache(const CBlockIndex* pindex, bool witnessOnly) {
|
||||
CWallet::BuildWitnessCache(pindex, witnessOnly);
|
||||
}
|
||||
|
||||
void DecrementNoteWitnesses(const CBlockIndex* pindex) {
|
||||
CWallet::DecrementNoteWitnesses(pindex);
|
||||
}
|
||||
@@ -77,11 +75,6 @@ CWalletTx GetValidReceive(const libzcash::SproutSpendingKey& sk, CAmount value,
|
||||
return GetValidReceive(*params, sk, value, randomInputs, version);
|
||||
}
|
||||
|
||||
libzcash::SproutNote GetNote(const libzcash::SproutSpendingKey& sk,
|
||||
const CTransaction& tx, size_t js, size_t n) {
|
||||
return GetNote(*params, sk, tx, js, n);
|
||||
}
|
||||
|
||||
CWalletTx GetValidSpend(const libzcash::SproutSpendingKey& sk,
|
||||
const libzcash::SproutNote& note, CAmount value) {
|
||||
return GetValidSpend(*params, sk, note, value);
|
||||
@@ -116,7 +109,7 @@ std::pair<JSOutPoint, SaplingOutPoint> CreateValidBlock(TestWallet& wallet,
|
||||
wallet.AddToWallet(wtx, true, NULL);
|
||||
|
||||
block.vtx.push_back(wtx);
|
||||
wallet.IncrementNoteWitnesses(&index, &block, sproutTree, saplingTree);
|
||||
wallet.BuildWitnessCache(&index, false);
|
||||
|
||||
return std::make_pair(jsoutpt, saplingNotes[0]);
|
||||
}
|
||||
@@ -141,341 +134,8 @@ TEST(WalletTests, SetupDatadirLocationRunAsFirstTest) {
|
||||
boost::filesystem::create_directories(pathTemp);
|
||||
mapArgs["-datadir"] = pathTemp.string();
|
||||
}
|
||||
/*
|
||||
|
||||
TEST(WalletTests, SproutNoteDataSerialisation) {
|
||||
auto sk = libzcash::SproutSpendingKey::random();
|
||||
auto wtx = GetValidReceive(sk, 10, true);
|
||||
auto note = GetNote(sk, wtx, 0, 1);
|
||||
auto nullifier = note.nullifier(sk);
|
||||
|
||||
mapSproutNoteData_t noteData;
|
||||
JSOutPoint jsoutpt {wtx.GetHash(), 0, 1};
|
||||
SproutNoteData nd {sk.address(), nullifier};
|
||||
SproutMerkleTree tree;
|
||||
nd.witnesses.push_front(tree.witness());
|
||||
noteData[jsoutpt] = nd;
|
||||
|
||||
CDataStream ss(SER_DISK, CLIENT_VERSION);
|
||||
ss << noteData;
|
||||
|
||||
mapSproutNoteData_t noteData2;
|
||||
ss >> noteData2;
|
||||
|
||||
EXPECT_EQ(noteData, noteData2);
|
||||
EXPECT_EQ(noteData[jsoutpt].witnesses, noteData2[jsoutpt].witnesses);
|
||||
}
|
||||
|
||||
|
||||
TEST(WalletTests, FindUnspentSproutNotes) {
|
||||
SelectParams(CBaseChainParams::TESTNET);
|
||||
CWallet wallet;
|
||||
auto sk = libzcash::SproutSpendingKey::random();
|
||||
wallet.AddSproutSpendingKey(sk);
|
||||
|
||||
auto wtx = GetValidReceive(sk, 10, true);
|
||||
auto note = GetNote(sk, wtx, 0, 1);
|
||||
auto nullifier = note.nullifier(sk);
|
||||
|
||||
mapSproutNoteData_t noteData;
|
||||
JSOutPoint jsoutpt {wtx.GetHash(), 0, 1};
|
||||
SproutNoteData nd {sk.address(), nullifier};
|
||||
noteData[jsoutpt] = nd;
|
||||
|
||||
wtx.SetSproutNoteData(noteData);
|
||||
wallet.AddToWallet(wtx, true, NULL);
|
||||
EXPECT_FALSE(wallet.IsSproutSpent(nullifier));
|
||||
|
||||
// We currently have an unspent and unconfirmed note in the wallet (depth of -1)
|
||||
std::vector<CSproutNotePlaintextEntry> sproutEntries;
|
||||
std::vector<SaplingNoteEntry> saplingEntries;
|
||||
wallet.GetFilteredNotes(sproutEntries, saplingEntries, "", 0);
|
||||
EXPECT_EQ(0, sproutEntries.size());
|
||||
sproutEntries.clear();
|
||||
saplingEntries.clear();
|
||||
wallet.GetFilteredNotes(sproutEntries, saplingEntries, "", -1);
|
||||
EXPECT_EQ(1, sproutEntries.size());
|
||||
sproutEntries.clear();
|
||||
saplingEntries.clear();
|
||||
|
||||
// Fake-mine the transaction
|
||||
EXPECT_EQ(-1, chainActive.Height());
|
||||
CBlock block;
|
||||
block.vtx.push_back(wtx);
|
||||
block.hashMerkleRoot = block.BuildMerkleTree();
|
||||
auto blockHash = block.GetHash();
|
||||
CBlockIndex fakeIndex {block};
|
||||
mapBlockIndex.insert(std::make_pair(blockHash, &fakeIndex));
|
||||
chainActive.SetTip(&fakeIndex);
|
||||
EXPECT_TRUE(chainActive.Contains(&fakeIndex));
|
||||
EXPECT_EQ(0, chainActive.Height());
|
||||
|
||||
wtx.SetMerkleBranch(block);
|
||||
wallet.AddToWallet(wtx, true, NULL);
|
||||
EXPECT_FALSE(wallet.IsSproutSpent(nullifier));
|
||||
|
||||
|
||||
// We now have an unspent and confirmed note in the wallet (depth of 1)
|
||||
wallet.GetFilteredNotes(sproutEntries, saplingEntries, "", 0);
|
||||
EXPECT_EQ(1, sproutEntries.size());
|
||||
sproutEntries.clear();
|
||||
saplingEntries.clear();
|
||||
wallet.GetFilteredNotes(sproutEntries, saplingEntries, "", 1);
|
||||
EXPECT_EQ(1, sproutEntries.size());
|
||||
sproutEntries.clear();
|
||||
saplingEntries.clear();
|
||||
wallet.GetFilteredNotes(sproutEntries, saplingEntries, "", 2);
|
||||
EXPECT_EQ(0, sproutEntries.size());
|
||||
sproutEntries.clear();
|
||||
saplingEntries.clear();
|
||||
|
||||
|
||||
// Let's spend the note.
|
||||
auto wtx2 = GetValidSpend(sk, note, 5);
|
||||
wallet.AddToWallet(wtx2, true, NULL);
|
||||
EXPECT_FALSE(wallet.IsSproutSpent(nullifier));
|
||||
|
||||
// Fake-mine a spend transaction
|
||||
EXPECT_EQ(0, chainActive.Height());
|
||||
CBlock block2;
|
||||
block2.vtx.push_back(wtx2);
|
||||
block2.hashMerkleRoot = block2.BuildMerkleTree();
|
||||
block2.hashPrevBlock = blockHash;
|
||||
auto blockHash2 = block2.GetHash();
|
||||
CBlockIndex fakeIndex2 {block2};
|
||||
mapBlockIndex.insert(std::make_pair(blockHash2, &fakeIndex2));
|
||||
fakeIndex2.SetHeight(1);
|
||||
chainActive.SetTip(&fakeIndex2);
|
||||
EXPECT_TRUE(chainActive.Contains(&fakeIndex2));
|
||||
EXPECT_EQ(1, chainActive.Height());
|
||||
|
||||
wtx2.SetMerkleBranch(block2);
|
||||
wallet.AddToWallet(wtx2, true, NULL);
|
||||
EXPECT_TRUE(wallet.IsSproutSpent(nullifier));
|
||||
|
||||
// The note has been spent. By default, GetFilteredNotes() ignores spent notes.
|
||||
wallet.GetFilteredNotes(sproutEntries, saplingEntries, "", 0);
|
||||
EXPECT_EQ(0, sproutEntries.size());
|
||||
sproutEntries.clear();
|
||||
saplingEntries.clear();
|
||||
// Let's include spent notes to retrieve it.
|
||||
wallet.GetFilteredNotes(sproutEntries, saplingEntries, "", 0, false);
|
||||
EXPECT_EQ(1, sproutEntries.size());
|
||||
sproutEntries.clear();
|
||||
saplingEntries.clear();
|
||||
// The spent note has two confirmations.
|
||||
wallet.GetFilteredNotes(sproutEntries, saplingEntries, "", 2, false);
|
||||
EXPECT_EQ(1, sproutEntries.size());
|
||||
sproutEntries.clear();
|
||||
saplingEntries.clear();
|
||||
// It does not have 3 confirmations.
|
||||
wallet.GetFilteredNotes(sproutEntries, saplingEntries, "", 3, false);
|
||||
EXPECT_EQ(0, sproutEntries.size());
|
||||
sproutEntries.clear();
|
||||
saplingEntries.clear();
|
||||
|
||||
|
||||
// Let's receive a new note
|
||||
CWalletTx wtx3;
|
||||
{
|
||||
auto wtx = GetValidReceive(sk, 20, true);
|
||||
auto note = GetNote(sk, wtx, 0, 1);
|
||||
auto nullifier = note.nullifier(sk);
|
||||
|
||||
mapSproutNoteData_t noteData;
|
||||
JSOutPoint jsoutpt {wtx.GetHash(), 0, 1};
|
||||
SproutNoteData nd {sk.address(), nullifier};
|
||||
noteData[jsoutpt] = nd;
|
||||
|
||||
wtx.SetSproutNoteData(noteData);
|
||||
wallet.AddToWallet(wtx, true, NULL);
|
||||
EXPECT_FALSE(wallet.IsSproutSpent(nullifier));
|
||||
|
||||
wtx3 = wtx;
|
||||
}
|
||||
|
||||
// Fake-mine the new transaction
|
||||
EXPECT_EQ(1, chainActive.Height());
|
||||
CBlock block3;
|
||||
block3.vtx.push_back(wtx3);
|
||||
block3.hashMerkleRoot = block3.BuildMerkleTree();
|
||||
block3.hashPrevBlock = blockHash2;
|
||||
auto blockHash3 = block3.GetHash();
|
||||
CBlockIndex fakeIndex3 {block3};
|
||||
mapBlockIndex.insert(std::make_pair(blockHash3, &fakeIndex3));
|
||||
fakeIndex3.SetHeight(2);
|
||||
chainActive.SetTip(&fakeIndex3);
|
||||
EXPECT_TRUE(chainActive.Contains(&fakeIndex3));
|
||||
EXPECT_EQ(2, chainActive.Height());
|
||||
|
||||
wtx3.SetMerkleBranch(block3);
|
||||
wallet.AddToWallet(wtx3, true, NULL);
|
||||
|
||||
// We now have an unspent note which has one confirmation, in addition to our spent note.
|
||||
wallet.GetFilteredNotes(sproutEntries, saplingEntries, "", 1);
|
||||
EXPECT_EQ(1, sproutEntries.size());
|
||||
sproutEntries.clear();
|
||||
saplingEntries.clear();
|
||||
// Let's return the spent note too.
|
||||
wallet.GetFilteredNotes(sproutEntries, saplingEntries, "", 1, false);
|
||||
EXPECT_EQ(2, sproutEntries.size());
|
||||
sproutEntries.clear();
|
||||
saplingEntries.clear();
|
||||
// Increasing number of confirmations will exclude our new unspent note.
|
||||
wallet.GetFilteredNotes(sproutEntries, saplingEntries, "", 2, false);
|
||||
EXPECT_EQ(1, sproutEntries.size());
|
||||
sproutEntries.clear();
|
||||
saplingEntries.clear();
|
||||
// If we also ignore spent notes at this depth, we won't find any notes.
|
||||
wallet.GetFilteredNotes(sproutEntries, saplingEntries, "", 2, true);
|
||||
EXPECT_EQ(0, sproutEntries.size());
|
||||
sproutEntries.clear();
|
||||
saplingEntries.clear();
|
||||
|
||||
// Tear down
|
||||
chainActive.SetTip(NULL);
|
||||
mapBlockIndex.erase(blockHash);
|
||||
mapBlockIndex.erase(blockHash2);
|
||||
mapBlockIndex.erase(blockHash3);
|
||||
}
|
||||
|
||||
|
||||
TEST(WalletTests, SetSproutNoteAddrsInCWalletTx) {
|
||||
auto sk = libzcash::SproutSpendingKey::random();
|
||||
auto wtx = GetValidReceive(sk, 10, true);
|
||||
auto note = GetNote(sk, wtx, 0, 1);
|
||||
auto nullifier = note.nullifier(sk);
|
||||
EXPECT_EQ(0, wtx.mapSproutNoteData.size());
|
||||
|
||||
mapSproutNoteData_t noteData;
|
||||
JSOutPoint jsoutpt {wtx.GetHash(), 0, 1};
|
||||
SproutNoteData nd {sk.address(), nullifier};
|
||||
noteData[jsoutpt] = nd;
|
||||
|
||||
wtx.SetSproutNoteData(noteData);
|
||||
EXPECT_EQ(noteData, wtx.mapSproutNoteData);
|
||||
}
|
||||
|
||||
TEST(WalletTests, SetSaplingNoteAddrsInCWalletTx) {
|
||||
SelectParams(CBaseChainParams::REGTEST);
|
||||
UpdateNetworkUpgradeParameters(Consensus::UPGRADE_OVERWINTER, Consensus::NetworkUpgrade::ALWAYS_ACTIVE);
|
||||
UpdateNetworkUpgradeParameters(Consensus::UPGRADE_SAPLING, Consensus::NetworkUpgrade::ALWAYS_ACTIVE);
|
||||
auto consensusParams = Params().GetConsensus();
|
||||
|
||||
TestWallet wallet;
|
||||
|
||||
std::vector<unsigned char, secure_allocator<unsigned char>> rawSeed(32);
|
||||
HDSeed seed(rawSeed);
|
||||
auto sk = libzcash::SaplingExtendedSpendingKey::Master(seed);
|
||||
auto expsk = sk.expsk;
|
||||
auto fvk = expsk.full_viewing_key();
|
||||
auto ivk = fvk.in_viewing_key();
|
||||
auto pk = sk.DefaultAddress();
|
||||
|
||||
libzcash::SaplingNote note(pk, 50000);
|
||||
auto cm = note.cm().get();
|
||||
SaplingMerkleTree tree;
|
||||
tree.append(cm);
|
||||
auto anchor = tree.root();
|
||||
auto witness = tree.witness();
|
||||
|
||||
auto nf = note.nullifier(fvk, witness.position());
|
||||
ASSERT_TRUE(nf);
|
||||
uint256 nullifier = nf.get();
|
||||
|
||||
auto builder = TransactionBuilder(consensusParams, 1);
|
||||
ASSERT_TRUE(builder.AddSaplingSpend(expsk, note, anchor, witness));
|
||||
builder.AddSaplingOutput(fvk.ovk, pk, 50000, {});
|
||||
builder.SetFee(0);
|
||||
auto maybe_tx = builder.Build();
|
||||
ASSERT_EQ(static_cast<bool>(maybe_tx), true);
|
||||
auto tx = maybe_tx.get();
|
||||
|
||||
CWalletTx wtx {&wallet, tx};
|
||||
|
||||
EXPECT_EQ(0, wtx.mapSaplingNoteData.size());
|
||||
mapSaplingNoteData_t noteData;
|
||||
|
||||
SaplingOutPoint op {wtx.GetHash(), 0};
|
||||
SaplingNoteData nd;
|
||||
nd.nullifier = nullifier;
|
||||
nd.ivk = ivk;
|
||||
nd.witnesses.push_front(witness);
|
||||
nd.witnessHeight = 123;
|
||||
noteData.insert(std::make_pair(op, nd));
|
||||
|
||||
wtx.SetSaplingNoteData(noteData);
|
||||
EXPECT_EQ(noteData, wtx.mapSaplingNoteData);
|
||||
|
||||
// Test individual fields in case equality operator is defined/changed.
|
||||
EXPECT_EQ(ivk, wtx.mapSaplingNoteData[op].ivk);
|
||||
EXPECT_EQ(nullifier, wtx.mapSaplingNoteData[op].nullifier);
|
||||
EXPECT_EQ(nd.witnessHeight, wtx.mapSaplingNoteData[op].witnessHeight);
|
||||
EXPECT_TRUE(witness == wtx.mapSaplingNoteData[op].witnesses.front());
|
||||
|
||||
// Revert to default
|
||||
UpdateNetworkUpgradeParameters(Consensus::UPGRADE_SAPLING, Consensus::NetworkUpgrade::NO_ACTIVATION_HEIGHT);
|
||||
UpdateNetworkUpgradeParameters(Consensus::UPGRADE_OVERWINTER, Consensus::NetworkUpgrade::NO_ACTIVATION_HEIGHT);
|
||||
}
|
||||
|
||||
TEST(WalletTests, SetSproutInvalidNoteAddrsInCWalletTx) {
|
||||
CWalletTx wtx;
|
||||
EXPECT_EQ(0, wtx.mapSproutNoteData.size());
|
||||
|
||||
mapSproutNoteData_t noteData;
|
||||
auto sk = libzcash::SproutSpendingKey::random();
|
||||
JSOutPoint jsoutpt {wtx.GetHash(), 0, 1};
|
||||
SproutNoteData nd {sk.address(), uint256()};
|
||||
noteData[jsoutpt] = nd;
|
||||
|
||||
EXPECT_THROW(wtx.SetSproutNoteData(noteData), std::logic_error);
|
||||
}
|
||||
|
||||
// The following test is the same as SetInvalidSaplingNoteDataInCWalletTx
|
||||
// TEST(WalletTests, SetSaplingInvalidNoteAddrsInCWalletTx)
|
||||
|
||||
// Cannot add note data for an index which does not exist in tx.vShieldedOutput
|
||||
TEST(WalletTests, SetInvalidSaplingNoteDataInCWalletTx) {
|
||||
CWalletTx wtx;
|
||||
EXPECT_EQ(0, wtx.mapSaplingNoteData.size());
|
||||
|
||||
mapSaplingNoteData_t noteData;
|
||||
SaplingOutPoint op {uint256(), 1};
|
||||
SaplingNoteData nd;
|
||||
noteData.insert(std::make_pair(op, nd));
|
||||
|
||||
EXPECT_THROW(wtx.SetSaplingNoteData(noteData), std::logic_error);
|
||||
}
|
||||
|
||||
TEST(WalletTests, GetSproutNoteNullifier) {
|
||||
CWallet wallet;
|
||||
|
||||
auto sk = libzcash::SproutSpendingKey::random();
|
||||
auto address = sk.address();
|
||||
auto dec = ZCNoteDecryption(sk.receiving_key());
|
||||
|
||||
auto wtx = GetValidReceive(sk, 10, true);
|
||||
auto note = GetNote(sk, wtx, 0, 1);
|
||||
auto nullifier = note.nullifier(sk);
|
||||
|
||||
auto hSig = wtx.vjoinsplit[0].h_sig(
|
||||
*params, wtx.joinSplitPubKey);
|
||||
|
||||
auto ret = wallet.GetSproutNoteNullifier(
|
||||
wtx.vjoinsplit[0],
|
||||
address,
|
||||
dec,
|
||||
hSig, 1);
|
||||
EXPECT_NE(nullifier, ret);
|
||||
|
||||
wallet.AddSproutSpendingKey(sk);
|
||||
|
||||
ret = wallet.GetSproutNoteNullifier(
|
||||
wtx.vjoinsplit[0],
|
||||
address,
|
||||
dec,
|
||||
hSig, 1);
|
||||
EXPECT_EQ(nullifier, ret);
|
||||
}
|
||||
|
||||
TEST(WalletTests, FindMySaplingNotes) {
|
||||
SelectParams(CBaseChainParams::REGTEST);
|
||||
@@ -579,92 +239,6 @@ TEST(WalletTests, FindMySaplingNotesWithIvkOnly) {
|
||||
UpdateNetworkUpgradeParameters(Consensus::UPGRADE_OVERWINTER, Consensus::NetworkUpgrade::NO_ACTIVATION_HEIGHT);
|
||||
}
|
||||
|
||||
TEST(WalletTests, FindMySproutNotes) {
|
||||
CWallet wallet;
|
||||
|
||||
auto sk = libzcash::SproutSpendingKey::random();
|
||||
auto sk2 = libzcash::SproutSpendingKey::random();
|
||||
wallet.AddSproutSpendingKey(sk2);
|
||||
|
||||
auto wtx = GetValidReceive(sk, 10, true);
|
||||
auto note = GetNote(sk, wtx, 0, 1);
|
||||
auto nullifier = note.nullifier(sk);
|
||||
|
||||
auto noteMap = wallet.FindMySproutNotes(wtx);
|
||||
EXPECT_EQ(0, noteMap.size());
|
||||
|
||||
wallet.AddSproutSpendingKey(sk);
|
||||
|
||||
noteMap = wallet.FindMySproutNotes(wtx);
|
||||
EXPECT_EQ(2, noteMap.size());
|
||||
|
||||
JSOutPoint jsoutpt {wtx.GetHash(), 0, 1};
|
||||
SproutNoteData nd {sk.address(), nullifier};
|
||||
EXPECT_EQ(1, noteMap.count(jsoutpt));
|
||||
EXPECT_EQ(nd, noteMap[jsoutpt]);
|
||||
}
|
||||
|
||||
TEST(WalletTests, FindMySproutNotesInEncryptedWallet) {
|
||||
TestWallet wallet;
|
||||
uint256 r {GetRandHash()};
|
||||
CKeyingMaterial vMasterKey (r.begin(), r.end());
|
||||
|
||||
auto sk = libzcash::SproutSpendingKey::random();
|
||||
wallet.AddSproutSpendingKey(sk);
|
||||
|
||||
ASSERT_TRUE(wallet.EncryptKeys(vMasterKey));
|
||||
|
||||
auto wtx = GetValidReceive(sk, 10, true);
|
||||
auto note = GetNote(sk, wtx, 0, 1);
|
||||
auto nullifier = note.nullifier(sk);
|
||||
|
||||
auto noteMap = wallet.FindMySproutNotes(wtx);
|
||||
EXPECT_EQ(2, noteMap.size());
|
||||
|
||||
JSOutPoint jsoutpt {wtx.GetHash(), 0, 1};
|
||||
SproutNoteData nd {sk.address(), nullifier};
|
||||
EXPECT_EQ(1, noteMap.count(jsoutpt));
|
||||
EXPECT_NE(nd, noteMap[jsoutpt]);
|
||||
|
||||
ASSERT_TRUE(wallet.Unlock(vMasterKey));
|
||||
|
||||
noteMap = wallet.FindMySproutNotes(wtx);
|
||||
EXPECT_EQ(2, noteMap.size());
|
||||
EXPECT_EQ(1, noteMap.count(jsoutpt));
|
||||
EXPECT_EQ(nd, noteMap[jsoutpt]);
|
||||
}
|
||||
|
||||
TEST(WalletTests, GetConflictedSproutNotes) {
|
||||
CWallet wallet;
|
||||
|
||||
auto sk = libzcash::SproutSpendingKey::random();
|
||||
wallet.AddSproutSpendingKey(sk);
|
||||
|
||||
auto wtx = GetValidReceive(sk, 10, true);
|
||||
auto note = GetNote(sk, wtx, 0, 1);
|
||||
auto nullifier = note.nullifier(sk);
|
||||
|
||||
auto wtx2 = GetValidSpend(sk, note, 5);
|
||||
auto wtx3 = GetValidSpend(sk, note, 10);
|
||||
auto hash2 = wtx2.GetHash();
|
||||
auto hash3 = wtx3.GetHash();
|
||||
|
||||
// No conflicts for no spends
|
||||
EXPECT_EQ(0, wallet.GetConflicts(hash2).size());
|
||||
wallet.AddToWallet(wtx, true, NULL);
|
||||
EXPECT_EQ(0, wallet.GetConflicts(hash2).size());
|
||||
|
||||
// No conflicts for one spend
|
||||
wallet.AddToWallet(wtx2, true, NULL);
|
||||
EXPECT_EQ(0, wallet.GetConflicts(hash2).size());
|
||||
|
||||
// Conflicts for two spends
|
||||
wallet.AddToWallet(wtx3, true, NULL);
|
||||
auto c3 = wallet.GetConflicts(hash2);
|
||||
EXPECT_EQ(2, c3.size());
|
||||
EXPECT_EQ(std::set<uint256>({hash2, hash3}), c3);
|
||||
}
|
||||
|
||||
// Generate note A and spend to create note B, from which we spend to create two conflicting transactions
|
||||
TEST(WalletTests, GetConflictedSaplingNotes) {
|
||||
SelectParams(CBaseChainParams::REGTEST);
|
||||
@@ -724,8 +298,8 @@ TEST(WalletTests, GetConflictedSaplingNotes) {
|
||||
wallet.AddToWallet(wtx, true, NULL);
|
||||
|
||||
// Simulate receiving new block and ChainTip signal
|
||||
wallet.IncrementNoteWitnesses(&fakeIndex, &block, sproutTree, saplingTree);
|
||||
wallet.UpdateSaplingNullifierNoteMapForBlock(&block);
|
||||
wallet.BuildWitnessCache(&fakeIndex, false);
|
||||
wallet.UpdateNullifierNoteMapForBlock(&block);
|
||||
|
||||
// Retrieve the updated wtx from wallet
|
||||
uint256 hash = wtx.GetHash();
|
||||
@@ -1008,8 +582,8 @@ TEST(WalletTests, NavigateFromSaplingNullifierToNote) {
|
||||
}
|
||||
|
||||
// Simulate receiving new block and ChainTip signal
|
||||
wallet.IncrementNoteWitnesses(&fakeIndex, &block, sproutTree, saplingTree);
|
||||
wallet.UpdateSaplingNullifierNoteMapForBlock(&block);
|
||||
wallet.BuildWitnessCache(&fakeIndex, false);
|
||||
wallet.UpdateNullifierNoteMapForBlock(&block);
|
||||
|
||||
// Retrieve the updated wtx from wallet
|
||||
uint256 hash = wtx.GetHash();
|
||||
@@ -1126,8 +700,8 @@ TEST(WalletTests, SpentSaplingNoteIsFromMe) {
|
||||
// Simulate receiving new block and ChainTip signal.
|
||||
// This triggers calculation of nullifiers for notes belonging to this wallet
|
||||
// in the output descriptions of wtx.
|
||||
wallet.IncrementNoteWitnesses(&fakeIndex, &block, sproutTree, saplingTree);
|
||||
wallet.UpdateSaplingNullifierNoteMapForBlock(&block);
|
||||
wallet.BuildWitnessCache(&fakeIndex, false);
|
||||
wallet.UpdateNullifierNoteMapForBlock(&block);
|
||||
|
||||
// Retrieve the updated wtx from wallet
|
||||
wtx = wallet.mapWallet[wtx.GetHash()];
|
||||
@@ -1263,7 +837,7 @@ TEST(WalletTests, CachedWitnessesEmptyChain) {
|
||||
CBlockIndex index(block);
|
||||
SproutMerkleTree sproutTree;
|
||||
SaplingMerkleTree saplingTree;
|
||||
wallet.IncrementNoteWitnesses(&index, &block, sproutTree, saplingTree);
|
||||
wallet.BuildWitnessCache(&index, false);
|
||||
|
||||
::GetWitnessesAndAnchors(wallet, sproutNotes, saplingNotes, sproutWitnesses, saplingWitnesses);
|
||||
|
||||
@@ -1354,7 +928,7 @@ TEST(WalletTests, CachedWitnessesChainTip) {
|
||||
EXPECT_NE(anchors1.second, anchors3.second);
|
||||
|
||||
// Re-incrementing with the same block should give the same result
|
||||
wallet.IncrementNoteWitnesses(&index2, &block2, sproutTree, saplingTree);
|
||||
wallet.BuildWitnessCache(&index2, false);
|
||||
auto anchors4 = GetWitnessesAndAnchors(wallet, sproutNotes, saplingNotes, sproutWitnesses, saplingWitnesses);
|
||||
EXPECT_NE(anchors4.first, anchors4.second);
|
||||
|
||||
@@ -1364,7 +938,7 @@ TEST(WalletTests, CachedWitnessesChainTip) {
|
||||
EXPECT_EQ(anchors2.second, anchors4.second);
|
||||
|
||||
// Incrementing with the same block again should not change the cache
|
||||
wallet.IncrementNoteWitnesses(&index2, &block2, sproutTree, saplingTree);
|
||||
wallet.BuildWitnessCache(&index2, false);
|
||||
std::vector<boost::optional<SproutWitness>> sproutWitnesses5;
|
||||
std::vector<boost::optional<SaplingWitness>> saplingWitnesses5;
|
||||
|
||||
@@ -1447,7 +1021,7 @@ TEST(WalletTests, CachedWitnessesDecrementFirst) {
|
||||
EXPECT_NE(anchors2.second, anchors4.second);
|
||||
|
||||
// Re-incrementing with the same block should give the same result
|
||||
wallet.IncrementNoteWitnesses(&index2, &block2, sproutTree, saplingTree);
|
||||
wallet.BuildWitnessCache(&index2, false);
|
||||
|
||||
auto anchors5 = GetWitnessesAndAnchors(wallet, sproutNotes, saplingNotes, sproutWitnesses, saplingWitnesses);
|
||||
|
||||
@@ -1504,7 +1078,7 @@ TEST(WalletTests, CachedWitnessesCleanIndex) {
|
||||
for (size_t i = 0; i < numBlocks; i++) {
|
||||
SproutMerkleTree sproutRiPrevTree {sproutRiTree};
|
||||
SaplingMerkleTree saplingRiPrevTree {saplingRiTree};
|
||||
wallet.IncrementNoteWitnesses(&(indices[i]), &(blocks[i]), sproutRiTree, saplingRiTree);
|
||||
wallet.BuildWitnessCache(&indices[i], false);
|
||||
|
||||
auto anchors = GetWitnessesAndAnchors(wallet, sproutNotes, saplingNotes, sproutWitnesses, saplingWitnesses);
|
||||
for (size_t j = 0; j < numBlocks; j++) {
|
||||
@@ -1531,7 +1105,7 @@ TEST(WalletTests, CachedWitnessesCleanIndex) {
|
||||
}
|
||||
|
||||
{
|
||||
wallet.IncrementNoteWitnesses(&(indices[i]), &(blocks[i]), sproutRiPrevTree, saplingRiPrevTree);
|
||||
wallet.BuildWitnessCache(&indices[i], false);
|
||||
auto anchors = GetWitnessesAndAnchors(wallet, sproutNotes, saplingNotes, sproutWitnesses, saplingWitnesses);
|
||||
for (size_t j = 0; j < numBlocks; j++) {
|
||||
EXPECT_TRUE((bool) sproutWitnesses[j]);
|
||||
@@ -1737,88 +1311,6 @@ TEST(WalletTests, SetBestChainIgnoresTxsWithoutShieldedData) {
|
||||
wallet.SetBestChain(walletdb, loc);
|
||||
}
|
||||
|
||||
TEST(WalletTests, UpdateSproutNullifierNoteMap) {
|
||||
TestWallet wallet;
|
||||
uint256 r {GetRandHash()};
|
||||
CKeyingMaterial vMasterKey (r.begin(), r.end());
|
||||
|
||||
auto sk = libzcash::SproutSpendingKey::random();
|
||||
wallet.AddSproutSpendingKey(sk);
|
||||
|
||||
ASSERT_TRUE(wallet.EncryptKeys(vMasterKey));
|
||||
|
||||
auto wtx = GetValidReceive(sk, 10, true);
|
||||
auto note = GetNote(sk, wtx, 0, 1);
|
||||
auto nullifier = note.nullifier(sk);
|
||||
|
||||
// Pretend that we called FindMySproutNotes while the wallet was locked
|
||||
mapSproutNoteData_t noteData;
|
||||
JSOutPoint jsoutpt {wtx.GetHash(), 0, 1};
|
||||
SproutNoteData nd {sk.address()};
|
||||
noteData[jsoutpt] = nd;
|
||||
wtx.SetSproutNoteData(noteData);
|
||||
|
||||
wallet.AddToWallet(wtx, true, NULL);
|
||||
EXPECT_EQ(0, wallet.mapSproutNullifiersToNotes.count(nullifier));
|
||||
|
||||
EXPECT_FALSE(wallet.UpdateNullifierNoteMap());
|
||||
|
||||
ASSERT_TRUE(wallet.Unlock(vMasterKey));
|
||||
|
||||
EXPECT_TRUE(wallet.UpdateNullifierNoteMap());
|
||||
EXPECT_EQ(1, wallet.mapSproutNullifiersToNotes.count(nullifier));
|
||||
EXPECT_EQ(wtx.GetHash(), wallet.mapSproutNullifiersToNotes[nullifier].hash);
|
||||
EXPECT_EQ(0, wallet.mapSproutNullifiersToNotes[nullifier].js);
|
||||
EXPECT_EQ(1, wallet.mapSproutNullifiersToNotes[nullifier].n);
|
||||
}
|
||||
|
||||
TEST(WalletTests, UpdatedSproutNoteData) {
|
||||
TestWallet wallet;
|
||||
|
||||
auto sk = libzcash::SproutSpendingKey::random();
|
||||
wallet.AddSproutSpendingKey(sk);
|
||||
|
||||
auto wtx = GetValidReceive(sk, 10, true);
|
||||
auto note = GetNote(sk, wtx, 0, 0);
|
||||
auto note2 = GetNote(sk, wtx, 0, 1);
|
||||
auto nullifier = note.nullifier(sk);
|
||||
auto nullifier2 = note2.nullifier(sk);
|
||||
auto wtx2 = wtx;
|
||||
|
||||
// First pretend we added the tx to the wallet and
|
||||
// we don't have the key for the second note
|
||||
mapSproutNoteData_t noteData;
|
||||
JSOutPoint jsoutpt {wtx.GetHash(), 0, 0};
|
||||
SproutNoteData nd {sk.address(), nullifier};
|
||||
noteData[jsoutpt] = nd;
|
||||
wtx.SetSproutNoteData(noteData);
|
||||
|
||||
// Pretend we mined the tx by adding a fake witness
|
||||
SproutMerkleTree tree;
|
||||
wtx.mapSproutNoteData[jsoutpt].witnesses.push_front(tree.witness());
|
||||
wtx.mapSproutNoteData[jsoutpt].witnessHeight = 100;
|
||||
|
||||
// Now pretend we added the key for the second note, and
|
||||
// the tx was "added" to the wallet again to update it.
|
||||
// This happens via the 'z_importkey' RPC method.
|
||||
JSOutPoint jsoutpt2 {wtx2.GetHash(), 0, 1};
|
||||
SproutNoteData nd2 {sk.address(), nullifier2};
|
||||
noteData[jsoutpt2] = nd2;
|
||||
wtx2.SetSproutNoteData(noteData);
|
||||
|
||||
// The txs should initially be different
|
||||
EXPECT_NE(wtx.mapSproutNoteData, wtx2.mapSproutNoteData);
|
||||
EXPECT_EQ(1, wtx.mapSproutNoteData[jsoutpt].witnesses.size());
|
||||
EXPECT_EQ(100, wtx.mapSproutNoteData[jsoutpt].witnessHeight);
|
||||
|
||||
// After updating, they should be the same
|
||||
EXPECT_TRUE(wallet.UpdatedNoteData(wtx2, wtx));
|
||||
EXPECT_EQ(wtx.mapSproutNoteData, wtx2.mapSproutNoteData);
|
||||
EXPECT_EQ(1, wtx.mapSproutNoteData[jsoutpt].witnesses.size());
|
||||
EXPECT_EQ(100, wtx.mapSproutNoteData[jsoutpt].witnessHeight);
|
||||
// TODO: The new note should get witnessed (but maybe not here) (#1350)
|
||||
}
|
||||
|
||||
TEST(WalletTests, UpdatedSaplingNoteData) {
|
||||
SelectParams(CBaseChainParams::REGTEST);
|
||||
UpdateNetworkUpgradeParameters(Consensus::UPGRADE_OVERWINTER, Consensus::NetworkUpgrade::ALWAYS_ACTIVE);
|
||||
@@ -1886,8 +1378,8 @@ TEST(WalletTests, UpdatedSaplingNoteData) {
|
||||
wallet.AddToWallet(wtx, true, NULL);
|
||||
|
||||
// Simulate receiving new block and ChainTip signal
|
||||
wallet.IncrementNoteWitnesses(&fakeIndex, &block, sproutTree, saplingTree);
|
||||
wallet.UpdateSaplingNullifierNoteMapForBlock(&block);
|
||||
wallet.BuildWitnessCache(&fakeIndex, false);
|
||||
wallet.UpdateNullifierNoteMapForBlock(&block);
|
||||
|
||||
// Retrieve the updated wtx from wallet
|
||||
uint256 hash = wtx.GetHash();
|
||||
@@ -1915,7 +1407,7 @@ TEST(WalletTests, UpdatedSaplingNoteData) {
|
||||
|
||||
EXPECT_EQ(2, wtx2.mapSaplingNoteData.size());
|
||||
EXPECT_EQ(1, wtx2.mapSaplingNoteData[sop0].witnesses.size()); // wtx2 has fake witness for payment output
|
||||
EXPECT_EQ(0, wtx2.mapSaplingNoteData[sop1].witnesses.size()); // wtx2 never had incrementnotewitness called
|
||||
EXPECT_EQ(0, wtx2.mapSaplingNoteData[sop1].witnesses.size()); // wtx2 never had BuildWitnessCache called
|
||||
|
||||
// After updating, they should be the same
|
||||
EXPECT_TRUE(wallet.UpdatedNoteData(wtx2, wtx));
|
||||
@@ -1943,37 +1435,6 @@ TEST(WalletTests, UpdatedSaplingNoteData) {
|
||||
UpdateNetworkUpgradeParameters(Consensus::UPGRADE_OVERWINTER, Consensus::NetworkUpgrade::NO_ACTIVATION_HEIGHT);
|
||||
}
|
||||
|
||||
TEST(WalletTests, MarkAffectedSproutTransactionsDirty) {
|
||||
TestWallet wallet;
|
||||
|
||||
auto sk = libzcash::SproutSpendingKey::random();
|
||||
wallet.AddSproutSpendingKey(sk);
|
||||
|
||||
auto wtx = GetValidReceive(sk, 10, true);
|
||||
auto hash = wtx.GetHash();
|
||||
auto note = GetNote(sk, wtx, 0, 1);
|
||||
auto nullifier = note.nullifier(sk);
|
||||
auto wtx2 = GetValidSpend(sk, note, 5);
|
||||
|
||||
mapSproutNoteData_t noteData;
|
||||
JSOutPoint jsoutpt {hash, 0, 1};
|
||||
SproutNoteData nd {sk.address(), nullifier};
|
||||
noteData[jsoutpt] = nd;
|
||||
|
||||
wtx.SetSproutNoteData(noteData);
|
||||
wallet.AddToWallet(wtx, true, NULL);
|
||||
wallet.MarkAffectedTransactionsDirty(wtx);
|
||||
|
||||
// After getting a cached value, the first tx should be clean
|
||||
wallet.mapWallet[hash].GetDebit(ISMINE_ALL);
|
||||
EXPECT_TRUE(wallet.mapWallet[hash].fDebitCached);
|
||||
|
||||
// After adding the note spend, the first tx should be dirty
|
||||
wallet.AddToWallet(wtx2, true, NULL);
|
||||
wallet.MarkAffectedTransactionsDirty(wtx2);
|
||||
EXPECT_FALSE(wallet.mapWallet[hash].fDebitCached);
|
||||
}
|
||||
|
||||
TEST(WalletTests, MarkAffectedSaplingTransactionsDirty) {
|
||||
SelectParams(CBaseChainParams::REGTEST);
|
||||
UpdateNetworkUpgradeParameters(Consensus::UPGRADE_OVERWINTER, Consensus::NetworkUpgrade::ALWAYS_ACTIVE);
|
||||
@@ -2001,7 +1462,7 @@ TEST(WalletTests, MarkAffectedSaplingTransactionsDirty) {
|
||||
auto scriptPubKey = GetScriptForDestination(tsk.GetPubKey().GetID());
|
||||
|
||||
// Generate shielding tx from transparent to Sapling
|
||||
// 0.0005 t-ZEC in, 0.0004 z-ZEC out, 0.0001 t-ZEC fee
|
||||
// 0.0005 t-HUSH in, 0.0004 z-HUSH out, 0.0001 t-HUSH fee
|
||||
auto builder = TransactionBuilder(consensusParams, 1, &keystore);
|
||||
builder.AddTransparentInput(COutPoint(), scriptPubKey, 50000);
|
||||
builder.AddSaplingOutput(fvk.ovk, pk, 40000, {});
|
||||
@@ -2040,8 +1501,8 @@ TEST(WalletTests, MarkAffectedSaplingTransactionsDirty) {
|
||||
wallet.AddToWallet(wtx, true, NULL);
|
||||
|
||||
// Simulate receiving new block and ChainTip signal
|
||||
wallet.IncrementNoteWitnesses(&fakeIndex, &block, sproutTree, saplingTree);
|
||||
wallet.UpdateSaplingNullifierNoteMapForBlock(&block);
|
||||
wallet.BuildWitnessCache(&fakeIndex, false);
|
||||
wallet.UpdateNullifierNoteMapForBlock(&block);
|
||||
|
||||
// Retrieve the updated wtx from wallet
|
||||
uint256 hash = wtx.GetHash();
|
||||
@@ -2095,39 +1556,7 @@ TEST(WalletTests, MarkAffectedSaplingTransactionsDirty) {
|
||||
UpdateNetworkUpgradeParameters(Consensus::UPGRADE_SAPLING, Consensus::NetworkUpgrade::NO_ACTIVATION_HEIGHT);
|
||||
UpdateNetworkUpgradeParameters(Consensus::UPGRADE_OVERWINTER, Consensus::NetworkUpgrade::NO_ACTIVATION_HEIGHT);
|
||||
}
|
||||
|
||||
TEST(WalletTests, SproutNoteLocking) {
|
||||
TestWallet wallet;
|
||||
|
||||
auto sk = libzcash::SproutSpendingKey::random();
|
||||
wallet.AddSproutSpendingKey(sk);
|
||||
|
||||
auto wtx = GetValidReceive(sk, 10, true);
|
||||
auto wtx2 = GetValidReceive(sk, 10, true);
|
||||
|
||||
JSOutPoint jsoutpt {wtx.GetHash(), 0, 0};
|
||||
JSOutPoint jsoutpt2 {wtx2.GetHash(),0, 0};
|
||||
|
||||
// Test selective locking
|
||||
wallet.LockNote(jsoutpt);
|
||||
EXPECT_TRUE(wallet.IsLockedNote(jsoutpt));
|
||||
EXPECT_FALSE(wallet.IsLockedNote(jsoutpt2));
|
||||
|
||||
// Test selective unlocking
|
||||
wallet.UnlockNote(jsoutpt);
|
||||
EXPECT_FALSE(wallet.IsLockedNote(jsoutpt));
|
||||
|
||||
// Test multiple locking
|
||||
wallet.LockNote(jsoutpt);
|
||||
wallet.LockNote(jsoutpt2);
|
||||
EXPECT_TRUE(wallet.IsLockedNote(jsoutpt));
|
||||
EXPECT_TRUE(wallet.IsLockedNote(jsoutpt2));
|
||||
|
||||
// Test unlock all
|
||||
wallet.UnlockAllSproutNotes();
|
||||
EXPECT_FALSE(wallet.IsLockedNote(jsoutpt));
|
||||
EXPECT_FALSE(wallet.IsLockedNote(jsoutpt2));
|
||||
}
|
||||
*/
|
||||
|
||||
TEST(WalletTests, SaplingNoteLocking) {
|
||||
TestWallet wallet;
|
||||
|
||||
@@ -106,303 +106,6 @@ TEST(wallet_zkeys_tests, StoreAndLoadSaplingZkeys) {
|
||||
EXPECT_TRUE(wallet.HaveSaplingIncomingViewingKey(dpa2));
|
||||
}
|
||||
|
||||
/**
|
||||
* This test covers methods on CWallet
|
||||
* GenerateNewSproutZKey()
|
||||
* AddSproutZKey()
|
||||
* LoadZKey()
|
||||
* LoadZKeyMetadata()
|
||||
*/
|
||||
TEST(wallet_zkeys_tests, store_and_load_zkeys) {
|
||||
SelectParams(CBaseChainParams::MAIN);
|
||||
|
||||
CWallet wallet;
|
||||
|
||||
// wallet should be empty
|
||||
std::set<libzcash::SproutPaymentAddress> addrs;
|
||||
wallet.GetSproutPaymentAddresses(addrs);
|
||||
ASSERT_EQ(0, addrs.size());
|
||||
|
||||
// wallet should have one key
|
||||
auto addr = wallet.GenerateNewSproutZKey();
|
||||
wallet.GetSproutPaymentAddresses(addrs);
|
||||
ASSERT_EQ(1, addrs.size());
|
||||
|
||||
// verify wallet has spending key for the address
|
||||
ASSERT_TRUE(wallet.HaveSproutSpendingKey(addr));
|
||||
|
||||
// manually add new spending key to wallet
|
||||
auto sk = libzcash::SproutSpendingKey::random();
|
||||
ASSERT_TRUE(wallet.AddSproutZKey(sk));
|
||||
|
||||
// verify wallet did add it
|
||||
addr = sk.address();
|
||||
ASSERT_TRUE(wallet.HaveSproutSpendingKey(addr));
|
||||
|
||||
// verify spending key stored correctly
|
||||
libzcash::SproutSpendingKey keyOut;
|
||||
wallet.GetSproutSpendingKey(addr, keyOut);
|
||||
ASSERT_EQ(sk, keyOut);
|
||||
|
||||
// verify there are two keys
|
||||
wallet.GetSproutPaymentAddresses(addrs);
|
||||
ASSERT_EQ(2, addrs.size());
|
||||
ASSERT_EQ(1, addrs.count(addr));
|
||||
|
||||
// Load a third key into the wallet
|
||||
sk = libzcash::SproutSpendingKey::random();
|
||||
ASSERT_TRUE(wallet.LoadZKey(sk));
|
||||
|
||||
// attach metadata to this third key
|
||||
addr = sk.address();
|
||||
int64_t now = GetTime();
|
||||
CKeyMetadata meta(now);
|
||||
ASSERT_TRUE(wallet.LoadZKeyMetadata(addr, meta));
|
||||
|
||||
// check metadata is the same
|
||||
CKeyMetadata m= wallet.mapSproutZKeyMetadata[addr];
|
||||
ASSERT_EQ(m.nCreateTime, now);
|
||||
}
|
||||
|
||||
/**
|
||||
* This test covers methods on CWallet
|
||||
* AddSproutViewingKey()
|
||||
* RemoveSproutViewingKey()
|
||||
* LoadSproutViewingKey()
|
||||
*/
|
||||
TEST(wallet_zkeys_tests, StoreAndLoadViewingKeys) {
|
||||
SelectParams(CBaseChainParams::MAIN);
|
||||
|
||||
CWallet wallet;
|
||||
|
||||
// wallet should be empty
|
||||
std::set<libzcash::SproutPaymentAddress> addrs;
|
||||
wallet.GetSproutPaymentAddresses(addrs);
|
||||
ASSERT_EQ(0, addrs.size());
|
||||
|
||||
// manually add new viewing key to wallet
|
||||
auto sk = libzcash::SproutSpendingKey::random();
|
||||
auto vk = sk.viewing_key();
|
||||
ASSERT_TRUE(wallet.AddSproutViewingKey(vk));
|
||||
|
||||
// verify wallet did add it
|
||||
auto addr = sk.address();
|
||||
ASSERT_TRUE(wallet.HaveSproutViewingKey(addr));
|
||||
// and that we don't have the corresponding spending key
|
||||
ASSERT_FALSE(wallet.HaveSproutSpendingKey(addr));
|
||||
|
||||
// verify viewing key stored correctly
|
||||
libzcash::SproutViewingKey vkOut;
|
||||
wallet.GetSproutViewingKey(addr, vkOut);
|
||||
ASSERT_EQ(vk, vkOut);
|
||||
|
||||
// Load a second viewing key into the wallet
|
||||
auto sk2 = libzcash::SproutSpendingKey::random();
|
||||
ASSERT_TRUE(wallet.LoadSproutViewingKey(sk2.viewing_key()));
|
||||
|
||||
// verify wallet did add it
|
||||
auto addr2 = sk2.address();
|
||||
ASSERT_TRUE(wallet.HaveSproutViewingKey(addr2));
|
||||
ASSERT_FALSE(wallet.HaveSproutSpendingKey(addr2));
|
||||
|
||||
// Remove the first viewing key
|
||||
ASSERT_TRUE(wallet.RemoveSproutViewingKey(vk));
|
||||
ASSERT_FALSE(wallet.HaveSproutViewingKey(addr));
|
||||
ASSERT_TRUE(wallet.HaveSproutViewingKey(addr2));
|
||||
}
|
||||
|
||||
/**
|
||||
* This test covers methods on CWalletDB
|
||||
* WriteZKey()
|
||||
*/
|
||||
TEST(wallet_zkeys_tests, write_zkey_direct_to_db) {
|
||||
SelectParams(CBaseChainParams::TESTNET);
|
||||
|
||||
// Get temporary and unique path for file.
|
||||
// Note: / operator to append paths
|
||||
boost::filesystem::path pathTemp = boost::filesystem::temp_directory_path() / boost::filesystem::unique_path();
|
||||
boost::filesystem::create_directories(pathTemp);
|
||||
mapArgs["-datadir"] = pathTemp.string();
|
||||
|
||||
bool fFirstRun;
|
||||
CWallet wallet("wallet.dat");
|
||||
ASSERT_EQ(DB_LOAD_OK, wallet.LoadWallet(fFirstRun));
|
||||
|
||||
// No default CPubKey set
|
||||
ASSERT_TRUE(fFirstRun);
|
||||
|
||||
// wallet should be empty
|
||||
std::set<libzcash::SproutPaymentAddress> addrs;
|
||||
wallet.GetSproutPaymentAddresses(addrs);
|
||||
ASSERT_EQ(0, addrs.size());
|
||||
|
||||
// Add random key to the wallet
|
||||
auto paymentAddress = wallet.GenerateNewSproutZKey();
|
||||
|
||||
// wallet should have one key
|
||||
wallet.GetSproutPaymentAddresses(addrs);
|
||||
ASSERT_EQ(1, addrs.size());
|
||||
|
||||
// create random key and add it to database directly, bypassing wallet
|
||||
auto sk = libzcash::SproutSpendingKey::random();
|
||||
auto addr = sk.address();
|
||||
int64_t now = GetTime();
|
||||
CKeyMetadata meta(now);
|
||||
CWalletDB db("wallet.dat");
|
||||
db.WriteZKey(addr, sk, meta);
|
||||
|
||||
// wallet should not be aware of key
|
||||
ASSERT_FALSE(wallet.HaveSproutSpendingKey(addr));
|
||||
|
||||
// wallet sees one key
|
||||
wallet.GetSproutPaymentAddresses(addrs);
|
||||
ASSERT_EQ(1, addrs.size());
|
||||
|
||||
// wallet should have default metadata for addr with null createtime
|
||||
CKeyMetadata m = wallet.mapSproutZKeyMetadata[addr];
|
||||
ASSERT_EQ(m.nCreateTime, 0);
|
||||
ASSERT_NE(m.nCreateTime, now);
|
||||
|
||||
// load the wallet again
|
||||
ASSERT_EQ(DB_LOAD_OK, wallet.LoadWallet(fFirstRun));
|
||||
|
||||
// wallet can now see the spending key
|
||||
ASSERT_TRUE(wallet.HaveSproutSpendingKey(addr));
|
||||
|
||||
// check key is the same
|
||||
libzcash::SproutSpendingKey keyOut;
|
||||
wallet.GetSproutSpendingKey(addr, keyOut);
|
||||
ASSERT_EQ(sk, keyOut);
|
||||
|
||||
// wallet should have two keys
|
||||
wallet.GetSproutPaymentAddresses(addrs);
|
||||
ASSERT_EQ(2, addrs.size());
|
||||
|
||||
// check metadata is now the same
|
||||
m = wallet.mapSproutZKeyMetadata[addr];
|
||||
ASSERT_EQ(m.nCreateTime, now);
|
||||
}
|
||||
|
||||
/**
|
||||
* This test covers methods on CWalletDB
|
||||
* WriteSproutViewingKey()
|
||||
*/
|
||||
TEST(wallet_zkeys_tests, WriteViewingKeyDirectToDB) {
|
||||
SelectParams(CBaseChainParams::TESTNET);
|
||||
|
||||
// Get temporary and unique path for file.
|
||||
// Note: / operator to append paths
|
||||
boost::filesystem::path pathTemp = boost::filesystem::temp_directory_path() / boost::filesystem::unique_path();
|
||||
boost::filesystem::create_directories(pathTemp);
|
||||
mapArgs["-datadir"] = pathTemp.string();
|
||||
|
||||
bool fFirstRun;
|
||||
CWallet wallet("wallet-vkey.dat");
|
||||
ASSERT_EQ(DB_LOAD_OK, wallet.LoadWallet(fFirstRun));
|
||||
|
||||
// No default CPubKey set
|
||||
ASSERT_TRUE(fFirstRun);
|
||||
|
||||
// create random viewing key and add it to database directly, bypassing wallet
|
||||
auto sk = libzcash::SproutSpendingKey::random();
|
||||
auto vk = sk.viewing_key();
|
||||
auto addr = sk.address();
|
||||
int64_t now = GetTime();
|
||||
CKeyMetadata meta(now);
|
||||
CWalletDB db("wallet-vkey.dat");
|
||||
db.WriteSproutViewingKey(vk);
|
||||
|
||||
// wallet should not be aware of viewing key
|
||||
ASSERT_FALSE(wallet.HaveSproutViewingKey(addr));
|
||||
|
||||
// load the wallet again
|
||||
ASSERT_EQ(DB_LOAD_OK, wallet.LoadWallet(fFirstRun));
|
||||
|
||||
// wallet can now see the viewing key
|
||||
ASSERT_TRUE(wallet.HaveSproutViewingKey(addr));
|
||||
|
||||
// check key is the same
|
||||
libzcash::SproutViewingKey vkOut;
|
||||
wallet.GetSproutViewingKey(addr, vkOut);
|
||||
ASSERT_EQ(vk, vkOut);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* This test covers methods on CWalletDB to load/save crypted z keys.
|
||||
*/
|
||||
TEST(wallet_zkeys_tests, write_cryptedzkey_direct_to_db) {
|
||||
SelectParams(CBaseChainParams::TESTNET);
|
||||
|
||||
// Get temporary and unique path for file.
|
||||
// Note: / operator to append paths
|
||||
boost::filesystem::path pathTemp = boost::filesystem::temp_directory_path() / boost::filesystem::unique_path();
|
||||
boost::filesystem::create_directories(pathTemp);
|
||||
mapArgs["-datadir"] = pathTemp.string();
|
||||
|
||||
bool fFirstRun;
|
||||
CWallet wallet("wallet_crypted.dat");
|
||||
ASSERT_EQ(DB_LOAD_OK, wallet.LoadWallet(fFirstRun));
|
||||
|
||||
// No default CPubKey set
|
||||
ASSERT_TRUE(fFirstRun);
|
||||
|
||||
// wallet should be empty
|
||||
std::set<libzcash::SproutPaymentAddress> addrs;
|
||||
wallet.GetSproutPaymentAddresses(addrs);
|
||||
ASSERT_EQ(0, addrs.size());
|
||||
|
||||
// Add random key to the wallet
|
||||
auto paymentAddress = wallet.GenerateNewSproutZKey();
|
||||
|
||||
// wallet should have one key
|
||||
wallet.GetSproutPaymentAddresses(addrs);
|
||||
ASSERT_EQ(1, addrs.size());
|
||||
|
||||
// encrypt wallet
|
||||
SecureString strWalletPass;
|
||||
strWalletPass.reserve(100);
|
||||
strWalletPass = "hello";
|
||||
ASSERT_TRUE(wallet.EncryptWallet(strWalletPass));
|
||||
|
||||
// adding a new key will fail as the wallet is locked
|
||||
EXPECT_ANY_THROW(wallet.GenerateNewSproutZKey());
|
||||
|
||||
// unlock wallet and then add
|
||||
wallet.Unlock(strWalletPass);
|
||||
auto paymentAddress2 = wallet.GenerateNewSproutZKey();
|
||||
|
||||
// Create a new wallet from the existing wallet path
|
||||
CWallet wallet2("wallet_crypted.dat");
|
||||
ASSERT_EQ(DB_LOAD_OK, wallet2.LoadWallet(fFirstRun));
|
||||
|
||||
// Confirm it's not the same as the other wallet
|
||||
ASSERT_TRUE(&wallet != &wallet2);
|
||||
|
||||
// wallet should have two keys
|
||||
wallet2.GetSproutPaymentAddresses(addrs);
|
||||
ASSERT_EQ(2, addrs.size());
|
||||
|
||||
// check we have entries for our payment addresses
|
||||
ASSERT_TRUE(addrs.count(paymentAddress));
|
||||
ASSERT_TRUE(addrs.count(paymentAddress2));
|
||||
|
||||
// spending key is crypted, so we can't extract valid payment address
|
||||
libzcash::SproutSpendingKey keyOut;
|
||||
wallet2.GetSproutSpendingKey(paymentAddress, keyOut);
|
||||
ASSERT_FALSE(paymentAddress == keyOut.address());
|
||||
|
||||
// unlock wallet to get spending keys and verify payment addresses
|
||||
wallet2.Unlock(strWalletPass);
|
||||
|
||||
wallet2.GetSproutSpendingKey(paymentAddress, keyOut);
|
||||
ASSERT_EQ(paymentAddress, keyOut.address());
|
||||
|
||||
wallet2.GetSproutSpendingKey(paymentAddress2, keyOut);
|
||||
ASSERT_EQ(paymentAddress2, keyOut.address());
|
||||
}
|
||||
|
||||
/**
|
||||
* This test covers methods on CWalletDB to load/save crypted sapling z keys.
|
||||
*/
|
||||
|
||||
@@ -1,319 +0,0 @@
|
||||
// Copyright (c) 2017 The Zcash developers
|
||||
// Distributed under the MIT software license, see the accompanying
|
||||
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
|
||||
|
||||
/******************************************************************************
|
||||
* Copyright © 2014-2019 The SuperNET Developers. *
|
||||
* *
|
||||
* See the AUTHORS, DEVELOPER-AGREEMENT and LICENSE files at *
|
||||
* the top-level directory of this distribution for the individual copyright *
|
||||
* holder information and the developer policies on copyright and licensing. *
|
||||
* *
|
||||
* Unless otherwise agreed in a custom licensing agreement, no part of the *
|
||||
* SuperNET software, including this file may be copied, modified, propagated *
|
||||
* or distributed except according to the terms contained in the LICENSE file *
|
||||
* *
|
||||
* Removal or modification of this copyright notice is prohibited. *
|
||||
* *
|
||||
******************************************************************************/
|
||||
|
||||
#include "rpc/server.h"
|
||||
#include "init.h"
|
||||
#include "key_io.h"
|
||||
#include "main.h"
|
||||
#include "script/script.h"
|
||||
#include "script/standard.h"
|
||||
#include "sync.h"
|
||||
#include "util.h"
|
||||
#include "utiltime.h"
|
||||
#include "wallet.h"
|
||||
|
||||
#include <fstream>
|
||||
#include <stdint.h>
|
||||
|
||||
#include <boost/algorithm/string.hpp>
|
||||
#include <boost/date_time/posix_time/posix_time.hpp>
|
||||
|
||||
#include <univalue.h>
|
||||
|
||||
#include "paymentdisclosure.h"
|
||||
#include "paymentdisclosuredb.h"
|
||||
|
||||
#include "zcash/Note.hpp"
|
||||
#include "zcash/NoteEncryption.hpp"
|
||||
|
||||
using namespace std;
|
||||
using namespace libzcash;
|
||||
|
||||
// Function declaration for function implemented in wallet/rpcwallet.cpp
|
||||
bool EnsureWalletIsAvailable(bool avoidException);
|
||||
|
||||
/**
|
||||
* RPC call to generate a payment disclosure
|
||||
*/
|
||||
UniValue z_getpaymentdisclosure(const UniValue& params, bool fHelp, const CPubKey& mypk)
|
||||
{
|
||||
if (!EnsureWalletIsAvailable(fHelp))
|
||||
return NullUniValue;
|
||||
|
||||
string enableArg = "paymentdisclosure";
|
||||
auto fEnablePaymentDisclosure = fExperimentalMode && GetBoolArg("-" + enableArg, true);
|
||||
string strPaymentDisclosureDisabledMsg = "";
|
||||
if (!fEnablePaymentDisclosure) {
|
||||
strPaymentDisclosureDisabledMsg = experimentalDisabledHelpMsg("z_getpaymentdisclosure", enableArg);
|
||||
}
|
||||
|
||||
if (fHelp || params.size() < 3 || params.size() > 4 )
|
||||
throw runtime_error(
|
||||
"z_getpaymentdisclosure \"txid\" \"js_index\" \"output_index\" (\"message\") \n"
|
||||
"\nGenerate a payment disclosure for a given joinsplit output.\n"
|
||||
"\nEXPERIMENTAL FEATURE\n"
|
||||
+ strPaymentDisclosureDisabledMsg +
|
||||
"\nArguments:\n"
|
||||
"1. \"txid\" (string, required) \n"
|
||||
"2. \"js_index\" (string, required) \n"
|
||||
"3. \"output_index\" (string, required) \n"
|
||||
"4. \"message\" (string, optional) \n"
|
||||
"\nResult:\n"
|
||||
"\"paymentdisclosure\" (string) Hex data string, with \"zpd:\" prefix.\n"
|
||||
"\nExamples:\n"
|
||||
+ HelpExampleCli("z_getpaymentdisclosure", "96f12882450429324d5f3b48630e3168220e49ab7b0f066e5c2935a6b88bb0f2 0 0 \"refund\"")
|
||||
+ HelpExampleRpc("z_getpaymentdisclosure", "\"96f12882450429324d5f3b48630e3168220e49ab7b0f066e5c2935a6b88bb0f2\", 0, 0, \"refund\"")
|
||||
);
|
||||
|
||||
if (!fEnablePaymentDisclosure) {
|
||||
throw JSONRPCError(RPC_WALLET_ERROR, "Error: payment disclosure is disabled.");
|
||||
}
|
||||
|
||||
LOCK2(cs_main, pwalletMain->cs_wallet);
|
||||
|
||||
EnsureWalletIsUnlocked();
|
||||
|
||||
// Check wallet knows about txid
|
||||
string txid = params[0].get_str();
|
||||
uint256 hash;
|
||||
hash.SetHex(txid);
|
||||
|
||||
CTransaction tx;
|
||||
uint256 hashBlock;
|
||||
|
||||
// Check txid has been seen
|
||||
if (!GetTransaction(hash, tx, hashBlock, true)) {
|
||||
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "No information available about transaction");
|
||||
}
|
||||
|
||||
// Check tx has been confirmed
|
||||
if (hashBlock.IsNull()) {
|
||||
throw JSONRPCError(RPC_MISC_ERROR, "Transaction has not been confirmed yet");
|
||||
}
|
||||
|
||||
// Check is mine
|
||||
if (!pwalletMain->mapWallet.count(hash)) {
|
||||
throw JSONRPCError(RPC_MISC_ERROR, "Transaction does not belong to the wallet");
|
||||
}
|
||||
const CWalletTx& wtx = pwalletMain->mapWallet[hash];
|
||||
|
||||
// Check if shielded tx
|
||||
if (wtx.vjoinsplit.empty()) {
|
||||
throw JSONRPCError(RPC_MISC_ERROR, "Transaction is not a shielded transaction");
|
||||
}
|
||||
|
||||
// Check js_index
|
||||
int js_index = params[1].get_int();
|
||||
if (js_index < 0 || js_index >= wtx.vjoinsplit.size()) {
|
||||
throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid js_index");
|
||||
}
|
||||
|
||||
// Check output_index
|
||||
int output_index = params[2].get_int();
|
||||
if (output_index < 0 || output_index >= ZC_NUM_JS_OUTPUTS) {
|
||||
throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid output_index");
|
||||
}
|
||||
|
||||
// Get message if it exists
|
||||
string msg;
|
||||
if (params.size() == 4) {
|
||||
msg = params[3].get_str();
|
||||
}
|
||||
|
||||
// Create PaymentDisclosureKey
|
||||
PaymentDisclosureKey key = {hash, (size_t)js_index, (uint8_t)output_index };
|
||||
|
||||
// TODO: In future, perhaps init the DB in init.cpp
|
||||
shared_ptr<PaymentDisclosureDB> db = PaymentDisclosureDB::sharedInstance();
|
||||
PaymentDisclosureInfo info;
|
||||
if (!db->Get(key, info)) {
|
||||
throw JSONRPCError(RPC_DATABASE_ERROR, "Could not find payment disclosure info for the given joinsplit output");
|
||||
}
|
||||
|
||||
PaymentDisclosure pd( wtx.joinSplitPubKey, key, info, msg );
|
||||
CDataStream ss(SER_NETWORK, PROTOCOL_VERSION);
|
||||
ss << pd;
|
||||
string strHex = HexStr(ss.begin(), ss.end());
|
||||
return PAYMENT_DISCLOSURE_BLOB_STRING_PREFIX + strHex;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* RPC call to validate a payment disclosure data blob.
|
||||
*/
|
||||
UniValue z_validatepaymentdisclosure(const UniValue& params, bool fHelp, const CPubKey& mypk)
|
||||
{
|
||||
if (!EnsureWalletIsAvailable(fHelp))
|
||||
return NullUniValue;
|
||||
|
||||
string enableArg = "paymentdisclosure";
|
||||
auto fEnablePaymentDisclosure = fExperimentalMode && GetBoolArg("-" + enableArg, true);
|
||||
string strPaymentDisclosureDisabledMsg = "";
|
||||
if (!fEnablePaymentDisclosure) {
|
||||
strPaymentDisclosureDisabledMsg = experimentalDisabledHelpMsg("z_validatepaymentdisclosure", enableArg);
|
||||
}
|
||||
|
||||
if (fHelp || params.size() != 1)
|
||||
throw runtime_error(
|
||||
"z_validatepaymentdisclosure \"paymentdisclosure\"\n"
|
||||
"\nValidates a payment disclosure.\n"
|
||||
"\nEXPERIMENTAL FEATURE\n"
|
||||
+ strPaymentDisclosureDisabledMsg +
|
||||
"\nArguments:\n"
|
||||
"1. \"paymentdisclosure\" (string, required) Hex data string, with \"zpd:\" prefix.\n"
|
||||
"\nExamples:\n"
|
||||
+ HelpExampleCli("z_validatepaymentdisclosure", "\"zpd:706462ff004c561a0447ba2ec51184e6c204...\"")
|
||||
+ HelpExampleRpc("z_validatepaymentdisclosure", "\"zpd:706462ff004c561a0447ba2ec51184e6c204...\"")
|
||||
);
|
||||
|
||||
if (!fEnablePaymentDisclosure) {
|
||||
throw JSONRPCError(RPC_WALLET_ERROR, "Error: payment disclosure is disabled.");
|
||||
}
|
||||
|
||||
LOCK2(cs_main, pwalletMain->cs_wallet);
|
||||
|
||||
EnsureWalletIsUnlocked();
|
||||
|
||||
// Verify the payment disclosure input begins with "zpd:" prefix.
|
||||
string strInput = params[0].get_str();
|
||||
size_t pos = strInput.find(PAYMENT_DISCLOSURE_BLOB_STRING_PREFIX);
|
||||
if (pos != 0) {
|
||||
throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid parameter, payment disclosure prefix not found.");
|
||||
}
|
||||
string hexInput = strInput.substr(strlen(PAYMENT_DISCLOSURE_BLOB_STRING_PREFIX));
|
||||
if (!IsHex(hexInput))
|
||||
{
|
||||
throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid parameter, expected payment disclosure data in hexadecimal format.");
|
||||
}
|
||||
|
||||
// Unserialize the payment disclosure data into an object
|
||||
PaymentDisclosure pd;
|
||||
CDataStream ss(ParseHex(hexInput), SER_NETWORK, PROTOCOL_VERSION);
|
||||
try {
|
||||
ss >> pd;
|
||||
// too much data is ignored, but if not enough data, exception of type ios_base::failure is thrown,
|
||||
// CBaseDataStream::read(): end of data: iostream error
|
||||
} catch (const std::exception &e) {
|
||||
throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid parameter, payment disclosure data is malformed.");
|
||||
}
|
||||
|
||||
if (pd.payload.marker != PAYMENT_DISCLOSURE_PAYLOAD_MAGIC_BYTES) {
|
||||
throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid parameter, payment disclosure marker not found.");
|
||||
}
|
||||
|
||||
if (pd.payload.version != PAYMENT_DISCLOSURE_VERSION_EXPERIMENTAL) {
|
||||
throw JSONRPCError(RPC_INVALID_PARAMETER, "Payment disclosure version is unsupported.");
|
||||
}
|
||||
|
||||
uint256 hash = pd.payload.txid;
|
||||
CTransaction tx;
|
||||
uint256 hashBlock;
|
||||
// Check if we have seen the transaction
|
||||
if (!GetTransaction(hash, tx, hashBlock, true)) {
|
||||
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "No information available about transaction");
|
||||
}
|
||||
|
||||
// Check if the transaction has been confirmed
|
||||
if (hashBlock.IsNull()) {
|
||||
throw JSONRPCError(RPC_MISC_ERROR, "Transaction has not been confirmed yet");
|
||||
}
|
||||
|
||||
// Check if shielded tx
|
||||
if (tx.vjoinsplit.empty()) {
|
||||
throw JSONRPCError(RPC_MISC_ERROR, "Transaction is not a shielded transaction");
|
||||
}
|
||||
|
||||
UniValue errs(UniValue::VARR);
|
||||
UniValue o(UniValue::VOBJ);
|
||||
o.push_back(Pair("txid", pd.payload.txid.ToString()));
|
||||
|
||||
// Check js_index
|
||||
if (pd.payload.js >= tx.vjoinsplit.size()) {
|
||||
errs.push_back("Payment disclosure refers to an invalid joinsplit index");
|
||||
}
|
||||
o.push_back(Pair("jsIndex", pd.payload.js));
|
||||
|
||||
if (pd.payload.n < 0 || pd.payload.n >= ZC_NUM_JS_OUTPUTS) {
|
||||
errs.push_back("Payment disclosure refers to an invalid output index");
|
||||
}
|
||||
o.push_back(Pair("outputIndex", pd.payload.n));
|
||||
o.push_back(Pair("version", pd.payload.version));
|
||||
o.push_back(Pair("onetimePrivKey", pd.payload.esk.ToString()));
|
||||
o.push_back(Pair("message", pd.payload.message));
|
||||
o.push_back(Pair("joinSplitPubKey", tx.joinSplitPubKey.ToString()));
|
||||
|
||||
// Verify the payment disclosure was signed using the same key as the transaction i.e. the joinSplitPrivKey.
|
||||
uint256 dataToBeSigned = SerializeHash(pd.payload, SER_GETHASH, 0);
|
||||
bool sigVerified = (crypto_sign_verify_detached(pd.payloadSig.data(),
|
||||
dataToBeSigned.begin(), 32,
|
||||
tx.joinSplitPubKey.begin()) == 0);
|
||||
o.push_back(Pair("signatureVerified", sigVerified));
|
||||
if (!sigVerified) {
|
||||
errs.push_back("Payment disclosure signature does not match transaction signature");
|
||||
}
|
||||
|
||||
// Check the payment address is valid
|
||||
SproutPaymentAddress zaddr = pd.payload.zaddr;
|
||||
{
|
||||
o.push_back(Pair("paymentAddress", EncodePaymentAddress(zaddr)));
|
||||
|
||||
try {
|
||||
// Decrypt the note to get value and memo field
|
||||
JSDescription jsdesc = tx.vjoinsplit[pd.payload.js];
|
||||
uint256 h_sig = jsdesc.h_sig(*pzcashParams, tx.joinSplitPubKey);
|
||||
|
||||
ZCPaymentDisclosureNoteDecryption decrypter;
|
||||
|
||||
ZCNoteEncryption::Ciphertext ciphertext = jsdesc.ciphertexts[pd.payload.n];
|
||||
|
||||
uint256 pk_enc = zaddr.pk_enc;
|
||||
auto plaintext = decrypter.decryptWithEsk(ciphertext, pk_enc, pd.payload.esk, h_sig, pd.payload.n);
|
||||
|
||||
CDataStream ssPlain(SER_NETWORK, PROTOCOL_VERSION);
|
||||
ssPlain << plaintext;
|
||||
SproutNotePlaintext npt;
|
||||
ssPlain >> npt;
|
||||
|
||||
string memoHexString = HexStr(npt.memo().data(), npt.memo().data() + npt.memo().size());
|
||||
o.push_back(Pair("memo", memoHexString));
|
||||
o.push_back(Pair("value", ValueFromAmount(npt.value())));
|
||||
|
||||
// Check the blockchain commitment matches decrypted note commitment
|
||||
uint256 cm_blockchain = jsdesc.commitments[pd.payload.n];
|
||||
SproutNote note = npt.note(zaddr);
|
||||
uint256 cm_decrypted = note.cm();
|
||||
bool cm_match = (cm_decrypted == cm_blockchain);
|
||||
o.push_back(Pair("commitmentMatch", cm_match));
|
||||
if (!cm_match) {
|
||||
errs.push_back("Commitment derived from payment disclosure does not match blockchain commitment");
|
||||
}
|
||||
} catch (const std::exception &e) {
|
||||
errs.push_back(string("Error while decrypting payment disclosure note: ") + string(e.what()) );
|
||||
}
|
||||
}
|
||||
|
||||
bool isValid = errs.empty();
|
||||
o.push_back(Pair("valid", isValid));
|
||||
if (!isValid) {
|
||||
o.push_back(Pair("errors", errs));
|
||||
}
|
||||
|
||||
return o;
|
||||
}
|
||||
@@ -149,6 +149,43 @@ UniValue convertpassphrase(const UniValue& params, bool fHelp, const CPubKey& my
|
||||
return ret;
|
||||
}
|
||||
|
||||
UniValue rescan(const UniValue& params, bool fHelp, const CPubKey& mypk)
|
||||
{
|
||||
//LOCK2(cs_main, pwalletMain->cs_wallet);
|
||||
|
||||
if (!EnsureWalletIsAvailable(fHelp))
|
||||
return NullUniValue;
|
||||
|
||||
if (fHelp || params.size() > 1)
|
||||
throw runtime_error(
|
||||
"rescan \"height\"\n"
|
||||
"\nRescan all transactions from genesis or given block height.\n"
|
||||
"\nArguments:\n"
|
||||
"1. \"height\" (integer, optional) The block height to rescan from\n"
|
||||
"\nExamples:\n"
|
||||
"\nRescan from block height 555\n"
|
||||
+ HelpExampleCli("rescan", "\"555\"") +
|
||||
"\nRescan from genesis block\n"
|
||||
+ HelpExampleCli("rescan","")
|
||||
);
|
||||
|
||||
// Height to rescan from
|
||||
int nRescanHeight = 0;
|
||||
if (params.size() > 0)
|
||||
nRescanHeight = params[0].get_int();
|
||||
if (nRescanHeight < 0 || nRescanHeight > chainActive.Height()) {
|
||||
throw JSONRPCError(RPC_INVALID_PARAMETER, "Block height out of range");
|
||||
}
|
||||
|
||||
LogPrintf("Rescanning from height=%d\n", nRescanHeight);
|
||||
//pwalletMain->ScanForWalletTransactions(chainActive[nRescanHeight],true);
|
||||
bool update = false;
|
||||
pwalletMain->ScanForWalletTransactions(chainActive.Genesis(),update);
|
||||
//TODO: can we return something more useful?
|
||||
return NullUniValue;
|
||||
}
|
||||
|
||||
|
||||
UniValue importprivkey(const UniValue& params, bool fHelp, const CPubKey& mypk)
|
||||
{
|
||||
if (!EnsureWalletIsAvailable(fHelp))
|
||||
@@ -666,18 +703,10 @@ UniValue dumpwallet_impl(const UniValue& params, bool fHelp, bool fDumpZKeys)
|
||||
file << "\n";
|
||||
|
||||
if (fDumpZKeys) {
|
||||
std::set<libzcash::SproutPaymentAddress> sproutAddresses;
|
||||
pwalletMain->GetSproutPaymentAddresses(sproutAddresses);
|
||||
file << "\n";
|
||||
file << "# Zkeys\n";
|
||||
file << "\n";
|
||||
for (auto addr : sproutAddresses) {
|
||||
libzcash::SproutSpendingKey key;
|
||||
if (pwalletMain->GetSproutSpendingKey(addr, key)) {
|
||||
std::string strTime = EncodeDumpTime(pwalletMain->mapSproutZKeyMetadata[addr].nCreateTime);
|
||||
file << strprintf("%s %s # zaddr=%s\n", EncodeSpendingKey(key), strTime, EncodePaymentAddress(addr));
|
||||
}
|
||||
}
|
||||
|
||||
std::set<libzcash::SaplingPaymentAddress> saplingAddresses;
|
||||
pwalletMain->GetSaplingPaymentAddresses(saplingAddresses);
|
||||
file << "\n";
|
||||
@@ -864,48 +893,27 @@ UniValue z_importviewingkey(const UniValue& params, bool fHelp, const CPubKey& m
|
||||
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid viewing key");
|
||||
}
|
||||
|
||||
if (boost::get<libzcash::SproutViewingKey>(&viewingkey) == nullptr) {
|
||||
if (params.size() < 4) {
|
||||
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Missing zaddr for Sapling viewing key.");
|
||||
}
|
||||
string strAddress = params[3].get_str();
|
||||
auto address = DecodePaymentAddress(strAddress);
|
||||
if (!IsValidPaymentAddress(address)) {
|
||||
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid zaddr");
|
||||
}
|
||||
if (params.size() < 4) {
|
||||
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Missing zaddr for Sapling viewing key.");
|
||||
}
|
||||
string strAddress = params[3].get_str();
|
||||
auto address = DecodePaymentAddress(strAddress);
|
||||
if (!IsValidPaymentAddress(address)) {
|
||||
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid zaddr");
|
||||
}
|
||||
|
||||
auto addr = boost::get<libzcash::SaplingPaymentAddress>(address);
|
||||
auto ivk = boost::get<libzcash::SaplingIncomingViewingKey>(viewingkey);
|
||||
auto addr = boost::get<libzcash::SaplingPaymentAddress>(address);
|
||||
auto ivk = boost::get<libzcash::SaplingIncomingViewingKey>(viewingkey);
|
||||
|
||||
if (pwalletMain->HaveSaplingIncomingViewingKey(addr)) {
|
||||
if (fIgnoreExistingKey) {
|
||||
return NullUniValue;
|
||||
}
|
||||
} else {
|
||||
pwalletMain->MarkDirty();
|
||||
|
||||
if (!pwalletMain->AddSaplingIncomingViewingKey(ivk, addr)) {
|
||||
throw JSONRPCError(RPC_WALLET_ERROR, "Error adding viewing key to wallet");
|
||||
}
|
||||
if (pwalletMain->HaveSaplingIncomingViewingKey(addr)) {
|
||||
if (fIgnoreExistingKey) {
|
||||
return NullUniValue;
|
||||
}
|
||||
} else {
|
||||
auto vkey = boost::get<libzcash::SproutViewingKey>(viewingkey);
|
||||
auto addr = vkey.address();
|
||||
if (pwalletMain->HaveSproutSpendingKey(addr)) {
|
||||
throw JSONRPCError(RPC_WALLET_ERROR, "The wallet already contains the private key for this viewing key");
|
||||
}
|
||||
pwalletMain->MarkDirty();
|
||||
|
||||
// Don't throw error in case a viewing key is already there
|
||||
if (pwalletMain->HaveSproutViewingKey(addr)) {
|
||||
if (fIgnoreExistingKey) {
|
||||
return NullUniValue;
|
||||
}
|
||||
} else {
|
||||
pwalletMain->MarkDirty();
|
||||
|
||||
if (!pwalletMain->AddSproutViewingKey(vkey)) {
|
||||
throw JSONRPCError(RPC_WALLET_ERROR, "Error adding viewing key to wallet");
|
||||
}
|
||||
if (!pwalletMain->AddSaplingIncomingViewingKey(ivk, addr)) {
|
||||
throw JSONRPCError(RPC_WALLET_ERROR, "Error adding viewing key to wallet");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -985,26 +993,12 @@ UniValue z_exportviewingkey(const UniValue& params, bool fHelp, const CPubKey& m
|
||||
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid zaddr");
|
||||
}
|
||||
|
||||
if (boost::get<libzcash::SproutPaymentAddress>(&address) == nullptr) {
|
||||
auto addr = boost::get<libzcash::SaplingPaymentAddress>(address);
|
||||
libzcash::SaplingIncomingViewingKey ivk;
|
||||
if(!pwalletMain->GetSaplingIncomingViewingKey(addr, ivk)) {
|
||||
throw JSONRPCError(RPC_WALLET_ERROR, "Wallet does not hold viewing key for this zaddr");
|
||||
}
|
||||
return EncodeViewingKey(ivk);
|
||||
auto addr = boost::get<libzcash::SaplingPaymentAddress>(address);
|
||||
libzcash::SaplingIncomingViewingKey ivk;
|
||||
if(!pwalletMain->GetSaplingIncomingViewingKey(addr, ivk)) {
|
||||
throw JSONRPCError(RPC_WALLET_ERROR, "Wallet does not hold viewing key for this zaddr");
|
||||
}
|
||||
|
||||
auto addr = boost::get<libzcash::SproutPaymentAddress>(address);
|
||||
libzcash::SproutViewingKey vk;
|
||||
if (!pwalletMain->GetSproutViewingKey(addr, vk)) {
|
||||
libzcash::SproutSpendingKey k;
|
||||
if (!pwalletMain->GetSproutSpendingKey(addr, k)) {
|
||||
throw JSONRPCError(RPC_WALLET_ERROR, "Wallet does not hold private key or viewing key for this zaddr");
|
||||
}
|
||||
vk = k.viewing_key();
|
||||
}
|
||||
|
||||
return EncodeViewingKey(vk);
|
||||
return EncodeViewingKey(ivk);
|
||||
}
|
||||
|
||||
extern int32_t KOMODO_NSPV;
|
||||
|
||||
478
src/wallet/rpchushwallet.cpp
Normal file
478
src/wallet/rpchushwallet.cpp
Normal file
@@ -0,0 +1,478 @@
|
||||
// Copyright (c) 2019-2020 The Hush developers
|
||||
// Copyright (c) 2019 Cryptoforge
|
||||
// Distributed under the MIT software license, see the accompanying
|
||||
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
|
||||
|
||||
#include "init.h"
|
||||
#include "key_io.h"
|
||||
#include "rpc/server.h"
|
||||
#include "wallet.h"
|
||||
#include "rpchushwallet.h"
|
||||
#include "utilmoneystr.h"
|
||||
#include <utf8.h>
|
||||
|
||||
using namespace std;
|
||||
using namespace libzcash;
|
||||
|
||||
bool EnsureWalletIsAvailable(bool avoidException);
|
||||
|
||||
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) {
|
||||
|
||||
LOCK2(cs_main, pwalletMain->cs_wallet);
|
||||
|
||||
//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
|
||||
std::set<libzcash::SaplingPaymentAddress> addresses;
|
||||
pwalletMain->GetSaplingPaymentAddresses(addresses);
|
||||
for (auto addr : addresses) {
|
||||
libzcash::SaplingExtendedSpendingKey extsk;
|
||||
if (pwalletMain->GetSaplingExtendedSpendingKey(addr, extsk)) {
|
||||
auto pt = libzcash::SaplingNotePlaintext::decrypt(
|
||||
outputDesc.encCiphertext, extsk.expsk.full_viewing_key().in_viewing_key(), outputDesc.ephemeralKey, outputDesc.cm);
|
||||
|
||||
if (pt) {
|
||||
auto note = pt.get();
|
||||
obj.push_back(Pair("address",EncodePaymentAddress(addr)));
|
||||
obj.push_back(Pair("amount", ValueFromAmount(CAmount(note.value()))));
|
||||
obj.push_back(Pair("shieldedOutputIndex",i));
|
||||
|
||||
//Check Change Status
|
||||
if (wtx.vShieldedSpend.size()!=0) {
|
||||
std::set<std::pair<PaymentAddress, uint256>> nullifierSet;
|
||||
nullifierSet = pwalletMain->GetNullifiersForAddresses({addr});
|
||||
BOOST_FOREACH(const SpendDescription& spendDesc, wtx.vShieldedSpend) {
|
||||
if (nullifierSet.count(std::make_pair(addr, spendDesc.nullifier))) {
|
||||
changeTx = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
obj.push_back(Pair("change",changeTx));
|
||||
if (addr == zsAddress || !filterByAddress) {
|
||||
totalReceived += CAmount(note.value());
|
||||
zsReceived.push_back(obj);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
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) {
|
||||
|
||||
LOCK2(cs_main, pwalletMain->cs_wallet);
|
||||
|
||||
//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) {
|
||||
HDSeed seed;
|
||||
if (pwalletMain->GetHDSeed(seed)) {
|
||||
auto opt = libzcash::SaplingOutgoingPlaintext::decrypt(
|
||||
outputDesc.outCiphertext,ovkForShieldingFromTaddr(seed),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 {
|
||||
std::set<libzcash::SaplingPaymentAddress> addresses;
|
||||
pwalletMain->GetSaplingPaymentAddresses(addresses);
|
||||
for (auto addr : addresses) {
|
||||
libzcash::SaplingExtendedSpendingKey extsk;
|
||||
if (pwalletMain->GetSaplingExtendedSpendingKey(addr, extsk)) {
|
||||
auto opt = libzcash::SaplingOutgoingPlaintext::decrypt(
|
||||
outputDesc.outCiphertext,extsk.expsk.full_viewing_key().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) {
|
||||
std::set<std::pair<PaymentAddress, uint256>> nullifierSet;
|
||||
nullifierSet = pwalletMain->GetNullifiersForAddresses({sentAddr});
|
||||
BOOST_FOREACH(const SpendDescription& spendDesc, wtx.vShieldedSpend) {
|
||||
if (nullifierSet.count(std::make_pair(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) {
|
||||
|
||||
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 (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<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);
|
||||
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);
|
||||
}
|
||||
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);
|
||||
}
|
||||
|
||||
}
|
||||
22
src/wallet/rpchushwallet.h
Normal file
22
src/wallet/rpchushwallet.h
Normal file
@@ -0,0 +1,22 @@
|
||||
// Copyright (c) 2020 The Hush developers
|
||||
// Copyright (c) 2016 The Bitcoin Core developers
|
||||
// Distributed under the MIT software license, see the accompanying
|
||||
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
|
||||
|
||||
#ifndef BITCOIN_WALLET_RPCHUSHWALLET_H
|
||||
#define BITCOIN_WALLET_RPCHUSHWALLET_H
|
||||
|
||||
struct balancestruct {
|
||||
CAmount confirmed;
|
||||
CAmount unconfirmed;
|
||||
CAmount locked;
|
||||
CAmount immature;
|
||||
};
|
||||
|
||||
void zsTxSpendsToJSON(const CWalletTx& wtx, UniValue& spends, CAmount& totalSpends, CAmount& filteredSpends, const std::string& strAddress, bool filterByAddress);
|
||||
void zsTxReceivedToJSON(const CWalletTx& wtx, UniValue& received, CAmount& totalReceived, const std::string& strAddress, bool filterByAddress);
|
||||
void zsTxSendsToJSON(const CWalletTx& wtx, UniValue& sends, CAmount& totalSends, const std::string& strAddress, bool filterByAddress);
|
||||
void zsWalletTxJSON(const CWalletTx& wtx, UniValue& ret, const std::string strAddress, bool fBool, const int returnType);
|
||||
|
||||
|
||||
#endif //BITCOIN_WALLET_RPCWALLET_H
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -23,6 +23,7 @@
|
||||
#define BITCOIN_WALLET_WALLET_H
|
||||
|
||||
#include "amount.h"
|
||||
#include "asyncrpcoperation.h"
|
||||
#include "coins.h"
|
||||
#include "key.h"
|
||||
#include "keystore.h"
|
||||
@@ -60,6 +61,11 @@ extern unsigned int nTxConfirmTarget;
|
||||
extern bool bSpendZeroConfChange;
|
||||
extern bool fSendFreeTransactions;
|
||||
extern bool fPayAtLeastCustomFee;
|
||||
extern bool fTxDeleteEnabled;
|
||||
extern bool fTxConflictDeleteEnabled;
|
||||
extern int fDeleteInterval;
|
||||
extern unsigned int fDeleteTransactionsAfterNBlocks;
|
||||
extern unsigned int fKeepLastNTransactions;
|
||||
|
||||
|
||||
//! -paytxfee default
|
||||
@@ -82,6 +88,18 @@ extern unsigned int WITNESS_CACHE_SIZE;
|
||||
//! Size of HD seed in bytes
|
||||
static const size_t HD_WALLET_SEED_LENGTH = 32;
|
||||
|
||||
//Default Transaction Rentention N-BLOCKS
|
||||
static const int DEFAULT_TX_DELETE_INTERVAL = 1000;
|
||||
|
||||
//Default Transaction Rentention N-BLOCKS
|
||||
static const unsigned int DEFAULT_TX_RETENTION_BLOCKS = 10000;
|
||||
|
||||
//Default Retention Last N-Transactions
|
||||
static const unsigned int DEFAULT_TX_RETENTION_LASTTX = 200;
|
||||
|
||||
//Amount of transactions to delete per run while syncing
|
||||
static const int MAX_DELETE_TX_SIZE = 50000;
|
||||
|
||||
class CBlockIndex;
|
||||
class CCoinControl;
|
||||
class COutput;
|
||||
@@ -218,9 +236,10 @@ public:
|
||||
std::string ToString() const;
|
||||
};
|
||||
|
||||
// NOTE: wallet.dat format depends on this data structure :(
|
||||
class SproutNoteData
|
||||
{
|
||||
public:
|
||||
{
|
||||
public:
|
||||
libzcash::SproutPaymentAddress address;
|
||||
|
||||
/**
|
||||
@@ -246,20 +265,14 @@ public:
|
||||
/**
|
||||
* Block height corresponding to the most current witness.
|
||||
*
|
||||
* When we first create a SproutNoteData in CWallet::FindMySproutNotes, this is set to
|
||||
* When we first create a SaplingNoteData in CWallet::FindMySaplingNotes, this is set to
|
||||
* -1 as a placeholder. The next time CWallet::ChainTip is called, we can
|
||||
* determine what height the witness cache for this note is valid for (even
|
||||
* if no witnesses were cached), and so can set the correct value in
|
||||
* CWallet::IncrementNoteWitnesses and CWallet::DecrementNoteWitnesses.
|
||||
* CWallet::BuildWitnessCache and CWallet::DecrementNoteWitnesses.
|
||||
*/
|
||||
int witnessHeight;
|
||||
|
||||
SproutNoteData() : address(), nullifier(), witnessHeight {-1} { }
|
||||
SproutNoteData(libzcash::SproutPaymentAddress a) :
|
||||
address {a}, nullifier(), witnessHeight {-1} { }
|
||||
SproutNoteData(libzcash::SproutPaymentAddress a, uint256 n) :
|
||||
address {a}, nullifier {n}, witnessHeight {-1} { }
|
||||
|
||||
ADD_SERIALIZE_METHODS;
|
||||
|
||||
template <typename Stream, typename Operation>
|
||||
@@ -270,27 +283,22 @@ public:
|
||||
READWRITE(witnessHeight);
|
||||
}
|
||||
|
||||
friend bool operator<(const SproutNoteData& a, const SproutNoteData& b) {
|
||||
return (a.address < b.address ||
|
||||
(a.address == b.address && a.nullifier < b.nullifier));
|
||||
}
|
||||
|
||||
friend bool operator==(const SproutNoteData& a, const SproutNoteData& b) {
|
||||
return (a.address == b.address && a.nullifier == b.nullifier);
|
||||
}
|
||||
|
||||
friend bool operator!=(const SproutNoteData& a, const SproutNoteData& b) {
|
||||
return !(a == b);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
class SaplingNoteData
|
||||
{
|
||||
public:
|
||||
/**
|
||||
* We initialize the height to -1 for the same reason as we do in SproutNoteData.
|
||||
* See the comment in that class for a full description.
|
||||
* Block height corresponding to the most current witness.
|
||||
*
|
||||
* When we first create a SaplingNoteData in CWallet::FindMySaplingNotes, this is set to
|
||||
* -1 as a placeholder. The next time CWallet::ChainTip is called, we can
|
||||
* determine what height the witness cache for this note is valid for (even
|
||||
* if no witnesses were cached), and so can set the correct value in
|
||||
* CWallet::BuildWitnessCache and CWallet::DecrementNoteWitnesses.
|
||||
*/
|
||||
|
||||
SaplingNoteData() : witnessHeight {-1}, nullifier() { }
|
||||
SaplingNoteData(libzcash::SaplingIncomingViewingKey ivk) : ivk {ivk}, witnessHeight {-1}, nullifier() { }
|
||||
SaplingNoteData(libzcash::SaplingIncomingViewingKey ivk, uint256 n) : ivk {ivk}, witnessHeight {-1}, nullifier(n) { }
|
||||
@@ -300,6 +308,9 @@ public:
|
||||
libzcash::SaplingIncomingViewingKey ivk;
|
||||
boost::optional<uint256> nullifier;
|
||||
|
||||
//In Memory Only
|
||||
bool witnessRootValidated;
|
||||
|
||||
ADD_SERIALIZE_METHODS;
|
||||
|
||||
template <typename Stream, typename Operation>
|
||||
@@ -323,18 +334,11 @@ public:
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
// NOTE: this sprout structure is serialized into wallet.dat, removing it would change wallet.dat format on disk :(
|
||||
typedef std::map<JSOutPoint, SproutNoteData> mapSproutNoteData_t;
|
||||
typedef std::map<SaplingOutPoint, SaplingNoteData> mapSaplingNoteData_t;
|
||||
|
||||
/** Decrypted note, its location in a transaction, and number of confirmations. */
|
||||
struct CSproutNotePlaintextEntry
|
||||
{
|
||||
JSOutPoint jsop;
|
||||
libzcash::SproutPaymentAddress address;
|
||||
libzcash::SproutNotePlaintext plaintext;
|
||||
int confirmations;
|
||||
};
|
||||
|
||||
/** Sapling note, its location in a transaction, and number of confirmations. */
|
||||
struct SaplingNoteEntry
|
||||
{
|
||||
@@ -565,11 +569,8 @@ public:
|
||||
MarkDirty();
|
||||
}
|
||||
|
||||
void SetSproutNoteData(mapSproutNoteData_t ¬eData);
|
||||
void SetSaplingNoteData(mapSaplingNoteData_t ¬eData);
|
||||
|
||||
std::pair<libzcash::SproutNotePlaintext, libzcash::SproutPaymentAddress> DecryptSproutNote(
|
||||
JSOutPoint jsop) const;
|
||||
boost::optional<std::pair<
|
||||
libzcash::SaplingNotePlaintext,
|
||||
libzcash::SaplingPaymentAddress>> DecryptSaplingNote(SaplingOutPoint op) const;
|
||||
@@ -777,11 +778,12 @@ private:
|
||||
* detect and report conflicts (double-spends).
|
||||
*/
|
||||
typedef TxSpendMap<uint256> TxNullifiers;
|
||||
TxNullifiers mapTxSproutNullifiers;
|
||||
TxNullifiers mapTxSaplingNullifiers;
|
||||
|
||||
std::vector<CTransaction> pendingSaplingConsolidationTxs;
|
||||
AsyncRPCOperationId saplingConsolidationOperationId;
|
||||
|
||||
void AddToTransparentSpends(const COutPoint& outpoint, const uint256& wtxid);
|
||||
void AddToSproutSpends(const uint256& nullifier, const uint256& wtxid);
|
||||
void AddToSaplingSpends(const uint256& nullifier, const uint256& wtxid);
|
||||
void AddToSpends(const uint256& wtxid);
|
||||
|
||||
@@ -793,6 +795,7 @@ public:
|
||||
*/
|
||||
int64_t nWitnessCacheSize;
|
||||
bool needsRescan = false;
|
||||
bool fSaplingConsolidationEnabled = false;
|
||||
|
||||
void ClearNoteWitnessCache();
|
||||
|
||||
@@ -800,13 +803,15 @@ public:
|
||||
std::set<uint256> GetNullifiers();
|
||||
|
||||
protected:
|
||||
|
||||
int SaplingWitnessMinimumHeight(const uint256& nullifier, int nWitnessHeight, int nMinimumHeight);
|
||||
|
||||
/**
|
||||
* pindex is the new tip being connected.
|
||||
*/
|
||||
void IncrementNoteWitnesses(const CBlockIndex* pindex,
|
||||
const CBlock* pblock,
|
||||
SproutMerkleTree& sproutTree,
|
||||
SaplingMerkleTree& saplingTree);
|
||||
int VerifyAndSetInitialWitness(const CBlockIndex* pindex, bool witnessOnly);
|
||||
void BuildWitnessCache(const CBlockIndex* pindex, bool witnessOnly);
|
||||
|
||||
/**
|
||||
* pindex is the old tip being disconnected.
|
||||
*/
|
||||
@@ -822,11 +827,11 @@ protected:
|
||||
try {
|
||||
for (std::pair<const uint256, CWalletTx>& wtxItem : mapWallet) {
|
||||
auto wtx = wtxItem.second;
|
||||
// We skip transactions for which mapSproutNoteData and mapSaplingNoteData
|
||||
// are empty. This covers transactions that have no Sprout or Sapling data
|
||||
// We skip transactions for which mapSaplingNoteData
|
||||
// is empty. This covers transactions that have no Sapling data
|
||||
// (i.e. are purely transparent), as well as shielding and unshielding
|
||||
// transactions in which we only have transparent addresses involved.
|
||||
if (!(wtx.mapSproutNoteData.empty() && wtx.mapSaplingNoteData.empty())) {
|
||||
if (!(wtx.mapSaplingNoteData.empty())) {
|
||||
if (!walletdb.WriteTx(wtxItem.first, wtx)) {
|
||||
LogPrintf("SetBestChain(): Failed to write CWalletTx, aborting atomic write\n");
|
||||
walletdb.TxnAbort();
|
||||
@@ -884,7 +889,6 @@ public:
|
||||
|
||||
std::set<int64_t> setKeyPool;
|
||||
std::map<CKeyID, CKeyMetadata> mapKeyMetadata;
|
||||
std::map<libzcash::SproutPaymentAddress, CKeyMetadata> mapSproutZKeyMetadata;
|
||||
std::map<libzcash::SaplingIncomingViewingKey, CKeyMetadata> mapSaplingZKeyMetadata;
|
||||
|
||||
typedef std::map<unsigned int, CMasterKey> MasterKeyMap;
|
||||
@@ -1001,8 +1005,9 @@ public:
|
||||
bool SelectCoinsMinConf(const CAmount& nTargetValue, int nConfMine, int nConfTheirs, std::vector<COutput> vCoins, std::set<std::pair<const CWalletTx*,unsigned int> >& setCoinsRet, CAmount& nValueRet) const;
|
||||
|
||||
bool IsSpent(const uint256& hash, unsigned int n) const;
|
||||
bool IsSproutSpent(const uint256& nullifier) const;
|
||||
unsigned int GetSpendDepth(const uint256& hash, unsigned int n) const;
|
||||
bool IsSaplingSpent(const uint256& nullifier) const;
|
||||
unsigned int GetSaplingSpendDepth(const uint256& nullifier) const;
|
||||
|
||||
bool IsLockedCoin(uint256 hash, unsigned int n) const;
|
||||
void LockCoin(COutPoint& output);
|
||||
@@ -1013,8 +1018,6 @@ public:
|
||||
bool IsLockedNote(const JSOutPoint& outpt) const;
|
||||
void LockNote(const JSOutPoint& output);
|
||||
void UnlockNote(const JSOutPoint& output);
|
||||
void UnlockAllSproutNotes();
|
||||
std::vector<JSOutPoint> ListLockedSproutNotes();
|
||||
|
||||
bool IsLockedNote(const SaplingOutPoint& output) const;
|
||||
void LockNote(const SaplingOutPoint& output);
|
||||
@@ -1064,31 +1067,6 @@ public:
|
||||
|
||||
void GetKeyBirthTimes(std::map<CKeyID, int64_t> &mapKeyBirth) const;
|
||||
|
||||
/**
|
||||
* Sprout ZKeys
|
||||
*/
|
||||
//! Generates a new Sprout zaddr
|
||||
libzcash::SproutPaymentAddress GenerateNewSproutZKey();
|
||||
//! Adds spending key to the store, and saves it to disk
|
||||
bool AddSproutZKey(const libzcash::SproutSpendingKey &key);
|
||||
//! Adds spending key to the store, without saving it to disk (used by LoadWallet)
|
||||
bool LoadZKey(const libzcash::SproutSpendingKey &key);
|
||||
//! Load spending key metadata (used by LoadWallet)
|
||||
bool LoadZKeyMetadata(const libzcash::SproutPaymentAddress &addr, const CKeyMetadata &meta);
|
||||
//! Adds an encrypted spending key to the store, without saving it to disk (used by LoadWallet)
|
||||
bool LoadCryptedZKey(const libzcash::SproutPaymentAddress &addr, const libzcash::ReceivingKey &rk, const std::vector<unsigned char> &vchCryptedSecret);
|
||||
//! Adds an encrypted spending key to the store, and saves it to disk (virtual method, declared in crypter.h)
|
||||
bool AddCryptedSproutSpendingKey(
|
||||
const libzcash::SproutPaymentAddress &address,
|
||||
const libzcash::ReceivingKey &rk,
|
||||
const std::vector<unsigned char> &vchCryptedSecret);
|
||||
|
||||
//! Adds a Sprout viewing key to the store, and saves it to disk.
|
||||
bool AddSproutViewingKey(const libzcash::SproutViewingKey &vk);
|
||||
bool RemoveSproutViewingKey(const libzcash::SproutViewingKey &vk);
|
||||
//! Adds a Sprout viewing key to the store, without saving it to disk (used by LoadWallet)
|
||||
bool LoadSproutViewingKey(const libzcash::SproutViewingKey &dest);
|
||||
|
||||
/**
|
||||
* Sapling ZKeys
|
||||
*/
|
||||
@@ -1139,7 +1117,7 @@ public:
|
||||
bool UpdateNullifierNoteMap();
|
||||
void UpdateNullifierNoteMapWithTx(const CWalletTx& wtx);
|
||||
void UpdateSaplingNullifierNoteMapWithTx(CWalletTx& wtx);
|
||||
void UpdateSaplingNullifierNoteMapForBlock(const CBlock* pblock);
|
||||
void UpdateNullifierNoteMapForBlock(const CBlock* pblock);
|
||||
bool AddToWallet(const CWalletTx& wtxIn, bool fFromLoadWallet, CWalletDB* pwalletdb);
|
||||
void EraseFromWallet(const uint256 &hash);
|
||||
void SyncTransaction(const CTransaction& tx, const CBlock* pblock);
|
||||
@@ -1149,6 +1127,10 @@ public:
|
||||
std::vector<uint256> commitments,
|
||||
std::vector<boost::optional<SproutWitness>>& witnesses,
|
||||
uint256 &final_anchor);
|
||||
void ReorderWalletTransactions(std::map<std::pair<int,int>, CWalletTx*> &mapSorted, int64_t &maxOrderPos);
|
||||
void UpdateWalletTransactionOrder(std::map<std::pair<int,int>, CWalletTx*> &mapSorted, bool resetOrder);
|
||||
void DeleteTransactions(std::vector<uint256> &removeTxs);
|
||||
void DeleteWalletTransactions(const CBlockIndex* pindex);
|
||||
int ScanForWalletTransactions(CBlockIndex* pindexStart, bool fUpdate = false);
|
||||
void ReacceptWalletTransactions();
|
||||
void ResendWalletTransactions(int64_t nBestBlockTime);
|
||||
@@ -1181,21 +1163,9 @@ public:
|
||||
|
||||
std::set<CTxDestination> GetAccountAddresses(const std::string& strAccount) const;
|
||||
|
||||
boost::optional<uint256> GetSproutNoteNullifier(
|
||||
const JSDescription& jsdesc,
|
||||
const libzcash::SproutPaymentAddress& address,
|
||||
const ZCNoteDecryption& dec,
|
||||
const uint256& hSig,
|
||||
uint8_t n) const;
|
||||
mapSproutNoteData_t FindMySproutNotes(const CTransaction& tx) const;
|
||||
std::pair<mapSaplingNoteData_t, SaplingIncomingViewingKeyMap> FindMySaplingNotes(const CTransaction& tx) const;
|
||||
bool IsSproutNullifierFromMe(const uint256& nullifier) const;
|
||||
bool IsSaplingNullifierFromMe(const uint256& nullifier) const;
|
||||
|
||||
void GetSproutNoteWitnesses(
|
||||
std::vector<JSOutPoint> notes,
|
||||
std::vector<boost::optional<SproutWitness>>& witnesses,
|
||||
uint256 &final_anchor);
|
||||
void GetSaplingNoteWitnesses(
|
||||
std::vector<SaplingOutPoint> notes,
|
||||
std::vector<boost::optional<SaplingWitness>>& witnesses,
|
||||
@@ -1215,7 +1185,12 @@ public:
|
||||
CAmount GetCredit(const CTransaction& tx, int32_t voutNum, const isminefilter& filter) const;
|
||||
CAmount GetCredit(const CTransaction& tx, const isminefilter& filter) const;
|
||||
CAmount GetChange(const CTransaction& tx) const;
|
||||
void ChainTip(const CBlockIndex *pindex, const CBlock *pblock, SproutMerkleTree sproutTree, SaplingMerkleTree saplingTree, bool added);
|
||||
void ChainTip(
|
||||
const CBlockIndex *pindex,
|
||||
const CBlock *pblock,
|
||||
boost::optional<std::pair<SproutMerkleTree, SaplingMerkleTree>> added);
|
||||
void RunSaplingConsolidation(int blockHeight);
|
||||
bool CommitConsolidationTx(const CTransaction& tx);
|
||||
/** Saves witness caches and best block locator to disk. */
|
||||
void SetBestChain(const CBlockLocator& loc);
|
||||
std::set<std::pair<libzcash::PaymentAddress, uint256>> GetNullifiersForAddresses(const std::set<libzcash::PaymentAddress> & addresses);
|
||||
@@ -1241,6 +1216,13 @@ public:
|
||||
}
|
||||
}
|
||||
|
||||
//void GetScriptForMining(boost::shared_ptr<CReserveScript> &script);
|
||||
void ResetRequestCount(const uint256 &hash)
|
||||
{
|
||||
LOCK(cs_wallet);
|
||||
mapRequestCount[hash] = 0;
|
||||
};
|
||||
|
||||
unsigned int GetKeyPoolSize()
|
||||
{
|
||||
AssertLockHeld(cs_wallet); // setKeyPool
|
||||
@@ -1317,8 +1299,7 @@ public:
|
||||
bool LoadCryptedHDSeed(const uint256& seedFp, const std::vector<unsigned char>& seed);
|
||||
|
||||
/* Find notes filtered by payment address, min depth, ability to spend */
|
||||
void GetFilteredNotes(std::vector<CSproutNotePlaintextEntry>& sproutEntries,
|
||||
std::vector<SaplingNoteEntry>& saplingEntries,
|
||||
void GetFilteredNotes(std::vector<SaplingNoteEntry>& saplingEntries,
|
||||
std::string address,
|
||||
int minDepth=1,
|
||||
bool ignoreSpent=true,
|
||||
@@ -1326,8 +1307,7 @@ public:
|
||||
|
||||
/* Find notes filtered by payment addresses, min depth, max depth, if they are spent,
|
||||
if a spending key is required, and if they are locked */
|
||||
void GetFilteredNotes(std::vector<CSproutNotePlaintextEntry>& sproutEntries,
|
||||
std::vector<SaplingNoteEntry>& saplingEntries,
|
||||
void GetFilteredNotes(std::vector<SaplingNoteEntry>& saplingEntries,
|
||||
std::set<libzcash::PaymentAddress>& filterAddresses,
|
||||
int minDepth=1,
|
||||
int maxDepth=INT_MAX,
|
||||
@@ -1406,7 +1386,6 @@ private:
|
||||
public:
|
||||
PaymentAddressBelongsToWallet(CWallet *wallet) : m_wallet(wallet) {}
|
||||
|
||||
bool operator()(const libzcash::SproutPaymentAddress &zaddr) const;
|
||||
bool operator()(const libzcash::SaplingPaymentAddress &zaddr) const;
|
||||
bool operator()(const libzcash::InvalidEncoding& no) const;
|
||||
};
|
||||
@@ -1419,7 +1398,6 @@ private:
|
||||
public:
|
||||
IncomingViewingKeyBelongsToWallet(CWallet *wallet) : m_wallet(wallet) {}
|
||||
|
||||
bool operator()(const libzcash::SproutPaymentAddress &zaddr) const;
|
||||
bool operator()(const libzcash::SaplingPaymentAddress &zaddr) const;
|
||||
bool operator()(const libzcash::InvalidEncoding& no) const;
|
||||
};
|
||||
@@ -1431,7 +1409,6 @@ private:
|
||||
public:
|
||||
HaveSpendingKeyForPaymentAddress(CWallet *wallet) : m_wallet(wallet) {}
|
||||
|
||||
bool operator()(const libzcash::SproutPaymentAddress &zaddr) const;
|
||||
bool operator()(const libzcash::SaplingPaymentAddress &zaddr) const;
|
||||
bool operator()(const libzcash::InvalidEncoding& no) const;
|
||||
};
|
||||
@@ -1443,7 +1420,6 @@ private:
|
||||
public:
|
||||
GetSpendingKeyForPaymentAddress(CWallet *wallet) : m_wallet(wallet) {}
|
||||
|
||||
boost::optional<libzcash::SpendingKey> operator()(const libzcash::SproutPaymentAddress &zaddr) const;
|
||||
boost::optional<libzcash::SpendingKey> operator()(const libzcash::SaplingPaymentAddress &zaddr) const;
|
||||
boost::optional<libzcash::SpendingKey> operator()(const libzcash::InvalidEncoding& no) const;
|
||||
};
|
||||
@@ -1515,7 +1491,6 @@ public:
|
||||
) : m_wallet(wallet), params(params), nTime(_nTime), hdKeypath(_hdKeypath), seedFpStr(_seedFp), log(_log) {}
|
||||
|
||||
|
||||
SpendingKeyAddResult operator()(const libzcash::SproutSpendingKey &sk) const;
|
||||
SpendingKeyAddResult operator()(const libzcash::SaplingExtendedSpendingKey &sk) const;
|
||||
SpendingKeyAddResult operator()(const libzcash::InvalidEncoding& no) const;
|
||||
};
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
// Copyright (c) 2009-2010 Satoshi Nakamoto
|
||||
// Copyright (c) 2009-2014 The Bitcoin Core developers
|
||||
// Copyright (c) 2019 The Hush developers
|
||||
// Copyright (c) 2019-2020 The Hush developers
|
||||
// Distributed under the MIT software license, see the accompanying
|
||||
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
|
||||
|
||||
@@ -124,26 +124,6 @@ bool CWalletDB::WriteCryptedKey(const CPubKey& vchPubKey,
|
||||
return true;
|
||||
}
|
||||
|
||||
bool CWalletDB::WriteCryptedZKey(const libzcash::SproutPaymentAddress & addr,
|
||||
const libzcash::ReceivingKey &rk,
|
||||
const std::vector<unsigned char>& vchCryptedSecret,
|
||||
const CKeyMetadata &keyMeta)
|
||||
{
|
||||
const bool fEraseUnencryptedKey = true;
|
||||
nWalletDBUpdated++;
|
||||
|
||||
if (!Write(std::make_pair(std::string("zkeymeta"), addr), keyMeta))
|
||||
return false;
|
||||
|
||||
if (!Write(std::make_pair(std::string("czkey"), addr), std::make_pair(rk, vchCryptedSecret), false))
|
||||
return false;
|
||||
if (fEraseUnencryptedKey)
|
||||
{
|
||||
Erase(std::make_pair(std::string("zkey"), addr));
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool CWalletDB::WriteCryptedSaplingZKey(
|
||||
const libzcash::SaplingExtendedFullViewingKey &extfvk,
|
||||
const std::vector<unsigned char>& vchCryptedSecret,
|
||||
@@ -172,16 +152,6 @@ bool CWalletDB::WriteMasterKey(unsigned int nID, const CMasterKey& kMasterKey)
|
||||
return Write(std::make_pair(std::string("mkey"), nID), kMasterKey, true);
|
||||
}
|
||||
|
||||
bool CWalletDB::WriteZKey(const libzcash::SproutPaymentAddress& addr, const libzcash::SproutSpendingKey& key, const CKeyMetadata &keyMeta)
|
||||
{
|
||||
nWalletDBUpdated++;
|
||||
|
||||
if (!Write(std::make_pair(std::string("zkeymeta"), addr), keyMeta))
|
||||
return false;
|
||||
|
||||
// pair is: tuple_key("zkey", paymentaddress) --> secretkey
|
||||
return Write(std::make_pair(std::string("zkey"), addr), key, false);
|
||||
}
|
||||
bool CWalletDB::WriteSaplingZKey(const libzcash::SaplingIncomingViewingKey &ivk,
|
||||
const libzcash::SaplingExtendedSpendingKey &key,
|
||||
const CKeyMetadata &keyMeta)
|
||||
@@ -203,18 +173,6 @@ bool CWalletDB::WriteSaplingPaymentAddress(
|
||||
return Write(std::make_pair(std::string("sapzaddr"), addr), ivk, false);
|
||||
}
|
||||
|
||||
bool CWalletDB::WriteSproutViewingKey(const libzcash::SproutViewingKey &vk)
|
||||
{
|
||||
nWalletDBUpdated++;
|
||||
return Write(std::make_pair(std::string("vkey"), vk), '1');
|
||||
}
|
||||
|
||||
bool CWalletDB::EraseSproutViewingKey(const libzcash::SproutViewingKey &vk)
|
||||
{
|
||||
nWalletDBUpdated++;
|
||||
return Erase(std::make_pair(std::string("vkey"), vk));
|
||||
}
|
||||
|
||||
bool CWalletDB::WriteCScript(const uint160& hash, const CScript& redeemScript)
|
||||
{
|
||||
nWalletDBUpdated++;
|
||||
@@ -552,6 +510,7 @@ ReadKeyValue(CWallet* pwallet, CDataStream& ssKey, CDataStream& ssValue,
|
||||
}
|
||||
else if (strType == "vkey")
|
||||
{
|
||||
/*
|
||||
libzcash::SproutViewingKey vk;
|
||||
ssKey >> vk;
|
||||
char fYes;
|
||||
@@ -559,24 +518,27 @@ ReadKeyValue(CWallet* pwallet, CDataStream& ssKey, CDataStream& ssValue,
|
||||
if (fYes == '1')
|
||||
pwallet->LoadSproutViewingKey(vk);
|
||||
|
||||
// Viewing keys have no birthday information for now,
|
||||
// so set the wallet birthday to the beginning of time.
|
||||
Viewing keys have no birthday information for now,
|
||||
so set the wallet birthday to the beginning of time.
|
||||
pwallet->nTimeFirstKey = 1;
|
||||
*/
|
||||
}
|
||||
else if (strType == "zkey")
|
||||
{
|
||||
/*
|
||||
libzcash::SproutPaymentAddress addr;
|
||||
ssKey >> addr;
|
||||
libzcash::SproutSpendingKey key;
|
||||
ssValue >> key;
|
||||
|
||||
if (!pwallet->LoadZKey(key))
|
||||
{
|
||||
strErr = "Error reading wallet database: LoadZKey failed";
|
||||
return false;
|
||||
}
|
||||
//if (!pwallet->LoadZKey(key))
|
||||
//{
|
||||
// strErr = "Error reading wallet database: LoadZKey failed";
|
||||
// return false;
|
||||
//}
|
||||
|
||||
wss.nZKeys++;
|
||||
*/
|
||||
}
|
||||
else if (strType == "sapzkey")
|
||||
{
|
||||
@@ -691,6 +653,7 @@ ReadKeyValue(CWallet* pwallet, CDataStream& ssKey, CDataStream& ssValue,
|
||||
}
|
||||
else if (strType == "czkey")
|
||||
{
|
||||
/*
|
||||
libzcash::SproutPaymentAddress addr;
|
||||
ssKey >> addr;
|
||||
// Deserialization of a pair is just one item after another
|
||||
@@ -707,6 +670,7 @@ ReadKeyValue(CWallet* pwallet, CDataStream& ssKey, CDataStream& ssValue,
|
||||
return false;
|
||||
}
|
||||
wss.fIsEncrypted = true;
|
||||
*/
|
||||
}
|
||||
else if (strType == "csapzkey")
|
||||
{
|
||||
@@ -742,13 +706,15 @@ ReadKeyValue(CWallet* pwallet, CDataStream& ssKey, CDataStream& ssValue,
|
||||
}
|
||||
else if (strType == "zkeymeta")
|
||||
{
|
||||
/*
|
||||
libzcash::SproutPaymentAddress addr;
|
||||
ssKey >> addr;
|
||||
CKeyMetadata keyMeta;
|
||||
ssValue >> keyMeta;
|
||||
wss.nZKeyMeta++;
|
||||
*/
|
||||
|
||||
pwallet->LoadZKeyMetadata(addr, keyMeta);
|
||||
// pwallet->LoadZKeyMetadata(addr, keyMeta);
|
||||
|
||||
// ignore earliest key creation time as taddr will exist before any zaddr
|
||||
}
|
||||
@@ -1208,6 +1174,12 @@ bool BackupWallet(const CWallet& wallet, const string& strDest)
|
||||
return false;
|
||||
}
|
||||
|
||||
bool CWalletDB::Compact(CDBEnv& dbenv, const std::string& strFile)
|
||||
{
|
||||
bool fSuccess = dbenv.Compact(strFile);
|
||||
return fSuccess;
|
||||
}
|
||||
|
||||
//
|
||||
// Try to (very carefully!) recover wallet.dat if there is a problem.
|
||||
//
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
// Copyright (c) 2009-2010 Satoshi Nakamoto
|
||||
// Copyright (c) 2009-2013 The Bitcoin Core developers
|
||||
// Copyright (c) 2009-2013 The Hush developers
|
||||
// Distributed under the MIT software license, see the accompanying
|
||||
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
|
||||
// file COPYING or https://www.opensource.org/licenses/mit-license.php
|
||||
|
||||
/******************************************************************************
|
||||
* Copyright © 2014-2019 The SuperNET Developers. *
|
||||
@@ -189,6 +190,7 @@ public:
|
||||
DBErrors LoadWallet(CWallet* pwallet);
|
||||
DBErrors FindWalletTx(CWallet* pwallet, std::vector<uint256>& vTxHash, std::vector<CWalletTx>& vWtx);
|
||||
DBErrors ZapWalletTx(CWallet* pwallet, std::vector<CWalletTx>& vWtx);
|
||||
static bool Compact(CDBEnv& dbenv, const std::string& strFile);
|
||||
static bool Recover(CDBEnv& dbenv, const std::string& filename, bool fOnlyKeys);
|
||||
static bool Recover(CDBEnv& dbenv, const std::string& filename);
|
||||
|
||||
@@ -198,23 +200,15 @@ public:
|
||||
bool WriteHDChain(const CHDChain& chain);
|
||||
|
||||
/// Write spending key to wallet database, where key is payment address and value is spending key.
|
||||
bool WriteZKey(const libzcash::SproutPaymentAddress& addr, const libzcash::SproutSpendingKey& key, const CKeyMetadata &keyMeta);
|
||||
bool WriteSaplingZKey(const libzcash::SaplingIncomingViewingKey &ivk,
|
||||
const libzcash::SaplingExtendedSpendingKey &key,
|
||||
const CKeyMetadata &keyMeta);
|
||||
bool WriteSaplingPaymentAddress(const libzcash::SaplingPaymentAddress &addr,
|
||||
const libzcash::SaplingIncomingViewingKey &ivk);
|
||||
bool WriteCryptedZKey(const libzcash::SproutPaymentAddress & addr,
|
||||
const libzcash::ReceivingKey & rk,
|
||||
const std::vector<unsigned char>& vchCryptedSecret,
|
||||
const CKeyMetadata &keyMeta);
|
||||
bool WriteCryptedSaplingZKey(const libzcash::SaplingExtendedFullViewingKey &extfvk,
|
||||
const std::vector<unsigned char>& vchCryptedSecret,
|
||||
const CKeyMetadata &keyMeta);
|
||||
|
||||
bool WriteSproutViewingKey(const libzcash::SproutViewingKey &vk);
|
||||
bool EraseSproutViewingKey(const libzcash::SproutViewingKey &vk);
|
||||
|
||||
private:
|
||||
CWalletDB(const CWalletDB&);
|
||||
void operator=(const CWalletDB&);
|
||||
|
||||
Reference in New Issue
Block a user