Merge remote-tracking branch 'zcash/master' into rebase2
# Conflicts: # .travis.yml # Makefile.am # README.md # configure.ac # depends/Makefile # depends/builders/darwin.mk # depends/funcs.mk # depends/hosts/darwin.mk # depends/packages/googlemock.mk # depends/packages/googletest.mk # depends/packages/libsnark.mk # depends/packages/libsodium.mk # depends/packages/packages.mk # depends/packages/rust.mk # src/Makefile.am # src/Makefile.gtest.include # src/chainparams.cpp # src/chainparams.h # src/checkpoints.h # src/clientversion.h # src/coins.cpp # src/consensus/consensus.h # src/gtest/test_mempool.cpp # src/httprpc.cpp # src/init.cpp # src/komodo-tx.cpp # src/main.cpp # src/miner.cpp # src/policy/fees.cpp # src/policy/fees.h # src/rpcmining.cpp # src/rpcrawtransaction.cpp # src/rpcserver.cpp # src/test/policyestimator_tests.cpp # src/test/rpc_wallet_tests.cpp # src/test/transaction_tests.cpp # src/txdb.cpp # src/txmempool.cpp # src/wallet/asyncrpcoperation_sendmany.cpp # src/wallet/rpcwallet.cpp # src/wallet/wallet.cpp # src/wallet/wallet.h # src/zcash/CreateJoinSplit.cpp # zcutil/build.sh
This commit is contained in:
@@ -5,6 +5,7 @@
|
||||
#include "asyncrpcoperation_sendmany.h"
|
||||
#include "asyncrpcqueue.h"
|
||||
#include "amount.h"
|
||||
#include "consensus/upgrades.h"
|
||||
#include "core_io.h"
|
||||
#include "init.h"
|
||||
#include "main.h"
|
||||
@@ -30,6 +31,8 @@
|
||||
#include <thread>
|
||||
#include <string>
|
||||
|
||||
#include "paymentdisclosuredb.h"
|
||||
|
||||
using namespace libzcash;
|
||||
|
||||
int find_output(UniValue obj, int n) {
|
||||
@@ -50,13 +53,14 @@ int find_output(UniValue obj, int n) {
|
||||
}
|
||||
|
||||
AsyncRPCOperation_sendmany::AsyncRPCOperation_sendmany(
|
||||
CMutableTransaction contextualTx,
|
||||
std::string fromAddress,
|
||||
std::vector<SendManyRecipient> tOutputs,
|
||||
std::vector<SendManyRecipient> zOutputs,
|
||||
int minDepth,
|
||||
CAmount fee,
|
||||
UniValue contextInfo) :
|
||||
fromaddress_(fromAddress), t_outputs_(tOutputs), z_outputs_(zOutputs), mindepth_(minDepth), fee_(fee), contextinfo_(contextInfo)
|
||||
tx_(contextualTx), fromaddress_(fromAddress), t_outputs_(tOutputs), z_outputs_(zOutputs), mindepth_(minDepth), fee_(fee), contextinfo_(contextInfo)
|
||||
{
|
||||
assert(fee_ >= 0);
|
||||
|
||||
@@ -95,12 +99,20 @@ AsyncRPCOperation_sendmany::AsyncRPCOperation_sendmany(
|
||||
}
|
||||
}
|
||||
|
||||
if (isfromzaddr_ && minDepth==0) {
|
||||
throw JSONRPCError(RPC_INVALID_PARAMETER, "Minconf cannot be zero when sending from zaddr");
|
||||
}
|
||||
|
||||
// Log the context info i.e. the call parameters to z_sendmany
|
||||
if (LogAcceptCategory("zrpcunsafe")) {
|
||||
LogPrint("zrpcunsafe", "%s: z_sendmany initialized (params=%s)\n", getId(), contextInfo.write());
|
||||
} else {
|
||||
LogPrint("zrpc", "%s: z_sendmany initialized\n", getId());
|
||||
}
|
||||
|
||||
|
||||
// Enable payment disclosure if requested
|
||||
paymentDisclosureMode = fExperimentalMode && GetBoolArg("-paymentdisclosure", false);
|
||||
}
|
||||
|
||||
AsyncRPCOperation_sendmany::~AsyncRPCOperation_sendmany() {
|
||||
@@ -167,6 +179,21 @@ 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:
|
||||
@@ -283,6 +310,15 @@ bool AsyncRPCOperation_sendmany::main_impl() {
|
||||
t_inputs_ = selectedTInputs;
|
||||
t_inputs_total = selectedUTXOAmount;
|
||||
|
||||
// Check mempooltxinputlimit to avoid creating a transaction which the local mempool rejects
|
||||
size_t limit = (size_t)GetArg("-mempooltxinputlimit", 0);
|
||||
if (limit > 0) {
|
||||
size_t n = t_inputs_.size();
|
||||
if (n > limit) {
|
||||
throw JSONRPCError(RPC_WALLET_ERROR, strprintf("Too many transparent inputs %zu > limit %zu", n, limit));
|
||||
}
|
||||
}
|
||||
|
||||
// update the transaction with these inputs
|
||||
CMutableTransaction rawTx(tx_);
|
||||
for (SendManyInputUTXO & t : t_inputs_) {
|
||||
@@ -304,6 +340,12 @@ bool AsyncRPCOperation_sendmany::main_impl() {
|
||||
LogPrint("zrpcunsafe", "%s: private output: %s\n", getId(), FormatMoney(z_outputs_total));
|
||||
LogPrint("zrpc", "%s: fee: %s\n", getId(), FormatMoney(minersFee));
|
||||
|
||||
// Grab the current consensus branch ID
|
||||
{
|
||||
LOCK(cs_main);
|
||||
consensusBranchId_ = CurrentEpochBranchId(chainActive.Height() + 1, Params().GetConsensus());
|
||||
}
|
||||
|
||||
/**
|
||||
* SCENARIO #1
|
||||
*
|
||||
@@ -339,16 +381,20 @@ bool AsyncRPCOperation_sendmany::main_impl() {
|
||||
|
||||
// Prepare raw transaction to handle JoinSplits
|
||||
CMutableTransaction mtx(tx_);
|
||||
mtx.nVersion = 2;
|
||||
crypto_sign_keypair(joinSplitPubKey_.begin(), joinSplitPrivKey_);
|
||||
mtx.joinSplitPubKey = joinSplitPubKey_;
|
||||
mtx.nLockTime = (uint32_t)time(NULL) - 60; // jl777
|
||||
tx_ = CTransaction(mtx);
|
||||
|
||||
// Copy zinputs and zoutputs to more flexible containers
|
||||
std::deque<SendManyInputJSOP> zInputsDeque;
|
||||
std::deque<SendManyInputJSOP> zInputsDeque; // zInputsDeque stores minimum numbers of notes for target amount
|
||||
CAmount tmp = 0;
|
||||
for (auto o : z_inputs_) {
|
||||
zInputsDeque.push_back(o);
|
||||
tmp += std::get<2>(o);
|
||||
if (tmp >= targetAmount) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
std::deque<SendManyRecipient> zOutputsDeque;
|
||||
for (auto o : z_outputs_) {
|
||||
@@ -446,283 +492,220 @@ bool AsyncRPCOperation_sendmany::main_impl() {
|
||||
* zaddr -> taddrs
|
||||
* -> zaddrs
|
||||
*
|
||||
* Processing order:
|
||||
* Part 1: taddrs and miners fee
|
||||
* Part 2: zaddrs
|
||||
*/
|
||||
|
||||
/**
|
||||
* SCENARIO #3
|
||||
* Part 1: Add to the transparent value pool.
|
||||
* 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 minersFeeProcessed = false;
|
||||
|
||||
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();
|
||||
CAmount taddrTargetAmount = t_outputs_total + minersFee;
|
||||
minersFeeProcessed = true;
|
||||
while (zInputsDeque.size() > 0 && taddrTargetAmount > 0) {
|
||||
AsyncJoinSplitInfo info;
|
||||
info.vpub_old = 0;
|
||||
info.vpub_new = 0;
|
||||
std::vector<JSOutPoint> outPoints;
|
||||
int n = 0;
|
||||
while (n++ < ZC_NUM_JS_INPUTS && taddrTargetAmount > 0) {
|
||||
SendManyInputJSOP o = zInputsDeque.front();
|
||||
JSOutPoint outPoint = std::get<0>(o);
|
||||
Note note = std::get<1>(o);
|
||||
CAmount noteFunds = std::get<2>(o);
|
||||
zInputsDeque.pop_front();
|
||||
|
||||
info.notes.push_back(note);
|
||||
outPoints.push_back(outPoint);
|
||||
|
||||
int wtxHeight = -1;
|
||||
int wtxDepth = -1;
|
||||
{
|
||||
LOCK2(cs_main, pwalletMain->cs_wallet);
|
||||
const CWalletTx& wtx = pwalletMain->mapWallet[outPoint.hash];
|
||||
wtxHeight = mapBlockIndex[wtx.hashBlock]->nHeight;
|
||||
wtxDepth = wtx.GetDepthInMainChain();
|
||||
}
|
||||
LogPrint("zrpcunsafe", "%s: spending note (txid=%s, vjoinsplit=%d, ciphertext=%d, amount=%s, height=%d, confirmations=%d)\n",
|
||||
getId(),
|
||||
outPoint.hash.ToString().substr(0, 10),
|
||||
outPoint.js,
|
||||
int(outPoint.n), // uint8_t
|
||||
FormatMoney(noteFunds),
|
||||
wtxHeight,
|
||||
wtxDepth
|
||||
);
|
||||
|
||||
|
||||
// Put value back into the value pool
|
||||
if (noteFunds >= taddrTargetAmount) {
|
||||
jsChange = noteFunds - taddrTargetAmount;
|
||||
info.vpub_new += taddrTargetAmount;
|
||||
} else {
|
||||
info.vpub_new += noteFunds;
|
||||
}
|
||||
|
||||
taddrTargetAmount -= noteFunds;
|
||||
if (taddrTargetAmount <= 0) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (jsChange > 0) {
|
||||
info.vjsout.push_back(JSOutput());
|
||||
info.vjsout.push_back(JSOutput(frompaymentaddress_, jsChange));
|
||||
|
||||
LogPrint("zrpcunsafe", "%s: generating note for change (amount=%s)\n",
|
||||
getId(),
|
||||
FormatMoney(jsChange)
|
||||
);
|
||||
}
|
||||
|
||||
obj = perform_joinsplit(info, outPoints);
|
||||
|
||||
if (jsChange > 0) {
|
||||
changeOutputIndex = find_output(obj, 1);
|
||||
}
|
||||
}
|
||||
vpubNewTarget += t_outputs_total;
|
||||
}
|
||||
|
||||
// Keep track of treestate within this transaction
|
||||
boost::unordered_map<uint256, ZCIncrementalMerkleTree, CCoinsKeyHasher> intermediates;
|
||||
std::vector<uint256> previousCommitments;
|
||||
|
||||
/**
|
||||
* SCENARIO #3
|
||||
* Part 2: Send to zaddrs by chaining JoinSplits together and immediately consuming any change
|
||||
*/
|
||||
if (z_outputs_total>0) {
|
||||
while (!vpubNewProcessed) {
|
||||
AsyncJoinSplitInfo info;
|
||||
info.vpub_old = 0;
|
||||
info.vpub_new = 0;
|
||||
|
||||
// Keep track of treestate within this transaction
|
||||
boost::unordered_map<uint256, ZCIncrementalMerkleTree, CCoinsKeyHasher> intermediates;
|
||||
std::vector<uint256> previousCommitments;
|
||||
CAmount jsInputValue = 0;
|
||||
uint256 jsAnchor;
|
||||
std::vector<boost::optional<ZCIncrementalWitness>> witnesses;
|
||||
|
||||
while (zOutputsDeque.size() > 0) {
|
||||
AsyncJoinSplitInfo info;
|
||||
info.vpub_old = 0;
|
||||
info.vpub_new = 0;
|
||||
JSDescription prevJoinSplit;
|
||||
|
||||
CAmount jsInputValue = 0;
|
||||
uint256 jsAnchor;
|
||||
std::vector<boost::optional<ZCIncrementalWitness>> witnesses;
|
||||
// Keep track of previous JoinSplit and its commitments
|
||||
if (tx_.vjoinsplit.size() > 0) {
|
||||
prevJoinSplit = tx_.vjoinsplit.back();
|
||||
}
|
||||
|
||||
JSDescription prevJoinSplit;
|
||||
// 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();
|
||||
}
|
||||
|
||||
// Keep track of previous JoinSplit and its commitments
|
||||
if (tx_.vjoinsplit.size() > 0) {
|
||||
prevJoinSplit = tx_.vjoinsplit.back();
|
||||
//
|
||||
// Consume change as the first input of the JoinSplit.
|
||||
//
|
||||
if (jsChange > 0) {
|
||||
LOCK2(cs_main, pwalletMain->cs_wallet);
|
||||
|
||||
// Update tree state with previous joinsplit
|
||||
ZCIncrementalMerkleTree tree;
|
||||
auto it = intermediates.find(prevJoinSplit.anchor);
|
||||
if (it != intermediates.end()) {
|
||||
tree = it->second;
|
||||
} else if (!pcoinsTip->GetAnchorAt(prevJoinSplit.anchor, tree)) {
|
||||
throw JSONRPCError(RPC_WALLET_ERROR, "Could not find previous JoinSplit anchor");
|
||||
}
|
||||
|
||||
// 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();
|
||||
assert(changeOutputIndex != -1);
|
||||
boost::optional<ZCIncrementalWitness> 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(spendingkey_.receiving_key());
|
||||
auto hSig = prevJoinSplit.h_sig(*pzcashParams, tx_.joinSplitPubKey);
|
||||
try {
|
||||
NotePlaintext plaintext = NotePlaintext::decrypt(
|
||||
decryptor,
|
||||
prevJoinSplit.ciphertexts[changeOutputIndex],
|
||||
prevJoinSplit.ephemeralKey,
|
||||
hSig,
|
||||
(unsigned char) changeOutputIndex);
|
||||
|
||||
Note note = plaintext.note(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<Note> vInputNotes;
|
||||
std::vector<JSOutPoint> vOutPoints;
|
||||
std::vector<boost::optional<ZCIncrementalWitness>> 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);
|
||||
Note 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");
|
||||
}
|
||||
|
||||
//
|
||||
// Consume change as the first input of the JoinSplit.
|
||||
//
|
||||
if (jsChange > 0) {
|
||||
vOutPoints.push_back(jso);
|
||||
vInputNotes.push_back(note);
|
||||
|
||||
jsInputValue += noteFunds;
|
||||
|
||||
int wtxHeight = -1;
|
||||
int wtxDepth = -1;
|
||||
{
|
||||
LOCK2(cs_main, pwalletMain->cs_wallet);
|
||||
|
||||
// Update tree state with previous joinsplit
|
||||
ZCIncrementalMerkleTree tree;
|
||||
auto it = intermediates.find(prevJoinSplit.anchor);
|
||||
if (it != intermediates.end()) {
|
||||
tree = it->second;
|
||||
} else if (!pcoinsTip->GetAnchorAt(prevJoinSplit.anchor, tree)) {
|
||||
throw JSONRPCError(RPC_WALLET_ERROR, "Could not find previous JoinSplit anchor");
|
||||
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 = mapBlockIndex[wtx.hashBlock]->nHeight;
|
||||
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) {
|
||||
|
||||
assert(changeOutputIndex != -1);
|
||||
boost::optional<ZCIncrementalWitness> 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 (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");
|
||||
}
|
||||
ZCIncrementalWitness 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");
|
||||
}
|
||||
}
|
||||
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(spendingkey_.viewing_key());
|
||||
auto hSig = prevJoinSplit.h_sig(*pzcashParams, tx_.joinSplitPubKey);
|
||||
try {
|
||||
NotePlaintext plaintext = NotePlaintext::decrypt(
|
||||
decryptor,
|
||||
prevJoinSplit.ciphertexts[changeOutputIndex],
|
||||
prevJoinSplit.ephemeralKey,
|
||||
hSig,
|
||||
(unsigned char) changeOutputIndex);
|
||||
|
||||
Note note = plaintext.note(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()));
|
||||
}
|
||||
witnesses.push_back(w);
|
||||
}
|
||||
|
||||
|
||||
//
|
||||
// Consume spendable non-change notes
|
||||
//
|
||||
std::vector<Note> vInputNotes;
|
||||
std::vector<JSOutPoint> vOutPoints;
|
||||
std::vector<boost::optional<ZCIncrementalWitness>> 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);
|
||||
Note 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];
|
||||
wtxHeight = mapBlockIndex[wtx.hashBlock]->nHeight;
|
||||
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
|
||||
);
|
||||
// The jsAnchor is null if this JoinSplit is at the start of a new chain
|
||||
if (jsAnchor.IsNull()) {
|
||||
jsAnchor = inputAnchor;
|
||||
}
|
||||
|
||||
// Add history of previous commitments to witness
|
||||
if (vInputNotes.size() > 0) {
|
||||
// Add spendable notes as inputs
|
||||
std::copy(vInputNotes.begin(), vInputNotes.end(), std::back_inserter(info.notes));
|
||||
}
|
||||
|
||||
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");
|
||||
}
|
||||
ZCIncrementalWitness 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
|
||||
//
|
||||
// Find recipient to transfer funds to
|
||||
std::string address, hexMemo;
|
||||
CAmount value = 0;
|
||||
if (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);
|
||||
address = std::get<0>(smr);
|
||||
value = std::get<1>(smr);
|
||||
hexMemo = std::get<2>(smr);
|
||||
zOutputsDeque.pop_front();
|
||||
}
|
||||
|
||||
// Will we have any change? Has the miners fee been processed yet?
|
||||
jsChange = 0;
|
||||
CAmount outAmount = value;
|
||||
if (!minersFeeProcessed) {
|
||||
if (jsInputValue < minersFee) {
|
||||
throw JSONRPCError(RPC_WALLET_ERROR, "Not enough funds to pay miners fee");
|
||||
}
|
||||
outAmount += minersFee;
|
||||
// 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) {
|
||||
@@ -733,42 +716,44 @@ bool AsyncRPCOperation_sendmany::main_impl() {
|
||||
|
||||
// reduce the amount being sent right now to the value of all inputs
|
||||
value = jsInputValue;
|
||||
if (!minersFeeProcessed) {
|
||||
value -= minersFee;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!minersFeeProcessed) {
|
||||
minersFeeProcessed = true;
|
||||
info.vpub_new += minersFee; // funds flowing back to public pool
|
||||
}
|
||||
|
||||
// create output for recipient
|
||||
// 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 = CZCPaymentAddress(address).Get();
|
||||
JSOutput jso = JSOutput(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(frompaymentaddress_, jsChange));
|
||||
// create output for any change
|
||||
if (jsChange>0) {
|
||||
info.vjsout.push_back(JSOutput(frompaymentaddress_, jsChange));
|
||||
|
||||
LogPrint("zrpcunsafe", "%s: generating note for change (amount=%s)\n",
|
||||
getId(),
|
||||
FormatMoney(jsChange)
|
||||
);
|
||||
}
|
||||
LogPrint("zrpcunsafe", "%s: generating note for change (amount=%s)\n",
|
||||
getId(),
|
||||
FormatMoney(jsChange)
|
||||
);
|
||||
}
|
||||
|
||||
obj = perform_joinsplit(info, witnesses, jsAnchor);
|
||||
obj = perform_joinsplit(info, witnesses, jsAnchor);
|
||||
|
||||
if (jsChange > 0) {
|
||||
changeOutputIndex = find_output(obj, 1);
|
||||
}
|
||||
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;
|
||||
}
|
||||
@@ -850,6 +835,10 @@ bool AsyncRPCOperation_sendmany::find_utxos(bool fAcceptCoinbase=false) {
|
||||
pwalletMain->AvailableCoins(vecOutputs, false, NULL, true, fAcceptCoinbase);
|
||||
|
||||
BOOST_FOREACH(const COutput& out, vecOutputs) {
|
||||
if (!out.fSpendable) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (out.nDepth < mindepth_) {
|
||||
continue;
|
||||
}
|
||||
@@ -921,7 +910,7 @@ UniValue AsyncRPCOperation_sendmany::perform_joinsplit(AsyncJoinSplitInfo & info
|
||||
std::vector<boost::optional < ZCIncrementalWitness>> witnesses;
|
||||
uint256 anchor;
|
||||
{
|
||||
LOCK2(cs_main, pwalletMain->cs_wallet);
|
||||
LOCK(cs_main);
|
||||
anchor = pcoinsTip->GetBestAnchor(); // As there are no inputs, ask the wallet for the best anchor
|
||||
}
|
||||
return perform_joinsplit(info, witnesses, anchor);
|
||||
@@ -992,7 +981,9 @@ UniValue AsyncRPCOperation_sendmany::perform_joinsplit(
|
||||
#else
|
||||
boost::array<size_t, ZC_NUM_JS_INPUTS> inputMap;
|
||||
boost::array<size_t, ZC_NUM_JS_OUTPUTS> outputMap;
|
||||
#endif
|
||||
|
||||
uint256 esk; // payment disclosure - secret
|
||||
|
||||
JSDescription jsdesc = JSDescription::Randomized(
|
||||
*pzcashParams,
|
||||
joinSplitPubKey_,
|
||||
@@ -1003,8 +994,8 @@ UniValue AsyncRPCOperation_sendmany::perform_joinsplit(
|
||||
outputMap,
|
||||
info.vpub_old,
|
||||
info.vpub_new,
|
||||
!this->testmode);
|
||||
|
||||
!this->testmode,
|
||||
&esk); // parameter expects pointer to esk, so pass in address
|
||||
{
|
||||
auto verifier = libzcash::ProofVerifier::Strict();
|
||||
if (!(jsdesc.Verify(*pzcashParams, verifier, joinSplitPubKey_))) {
|
||||
@@ -1017,7 +1008,7 @@ UniValue AsyncRPCOperation_sendmany::perform_joinsplit(
|
||||
// Empty output script.
|
||||
CScript scriptCode;
|
||||
CTransaction signTx(mtx);
|
||||
uint256 dataToBeSigned = SignatureHash(scriptCode, signTx, NOT_AN_INPUT, SIGHASH_ALL);
|
||||
uint256 dataToBeSigned = SignatureHash(scriptCode, signTx, NOT_AN_INPUT, SIGHASH_ALL, 0, consensusBranchId_);
|
||||
|
||||
// Add the signature
|
||||
if (!(crypto_sign_detached(&mtx.joinSplitSig[0], NULL,
|
||||
@@ -1073,6 +1064,28 @@ UniValue AsyncRPCOperation_sendmany::perform_joinsplit(
|
||||
arrOutputMap.push_back(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::PaymentAddress zaddr = output.addr; // randomized output
|
||||
PaymentDisclosureInfo pdInfo = {PAYMENT_DISCLOSURE_VERSION_EXPERIMENTAL, esk, joinSplitPrivKey, zaddr};
|
||||
paymentDisclosureData_.push_back(PaymentDisclosureKeyInfo(pdKey, pdInfo));
|
||||
|
||||
CZCPaymentAddress address(zaddr);
|
||||
LogPrint("paymentdisclosure", "%s: Payment Disclosure: js=%d, n=%d, zaddr=%s\n", getId(), js_index, int(mapped_index), address.ToString());
|
||||
}
|
||||
// !!! Payment disclosure END
|
||||
|
||||
UniValue obj(UniValue::VOBJ);
|
||||
obj.push_back(Pair("encryptednote1", encryptedNote1));
|
||||
obj.push_back(Pair("encryptednote2", encryptedNote2));
|
||||
|
||||
Reference in New Issue
Block a user