Merge remote-tracking branch 'zcash/master' into rebase
# Conflicts: # README.md # src/Makefile.gtest.include # src/chainparams.cpp # src/init.cpp # src/miner.cpp # src/wallet/wallet.cpp
This commit is contained in:
@@ -52,9 +52,12 @@ AsyncRPCOperation_sendmany::AsyncRPCOperation_sendmany(
|
||||
std::string fromAddress,
|
||||
std::vector<SendManyRecipient> tOutputs,
|
||||
std::vector<SendManyRecipient> zOutputs,
|
||||
int minDepth) :
|
||||
fromaddress_(fromAddress), t_outputs_(tOutputs), z_outputs_(zOutputs), mindepth_(minDepth)
|
||||
int minDepth,
|
||||
CAmount fee) :
|
||||
fromaddress_(fromAddress), t_outputs_(tOutputs), z_outputs_(zOutputs), mindepth_(minDepth), fee_(fee)
|
||||
{
|
||||
assert(fee_ > 0);
|
||||
|
||||
if (minDepth < 0) {
|
||||
throw JSONRPCError(RPC_INVALID_PARAMETER, "Minconf cannot be negative");
|
||||
}
|
||||
@@ -149,8 +152,8 @@ bool AsyncRPCOperation_sendmany::main_impl() {
|
||||
bool isSingleZaddrOutput = (t_outputs_.size()==0 && z_outputs_.size()==1);
|
||||
bool isMultipleZaddrOutput = (t_outputs_.size()==0 && z_outputs_.size()>=1);
|
||||
bool isPureTaddrOnlyTx = (isfromtaddr_ && z_outputs_.size() == 0);
|
||||
CAmount minersFee = ASYNC_RPC_OPERATION_DEFAULT_MINERS_FEE;
|
||||
|
||||
CAmount minersFee = fee_;
|
||||
|
||||
// When spending coinbase utxos, you can only specify a single zaddr as the change must go somewhere
|
||||
// and if there are multiple zaddrs, we don't know where to send it.
|
||||
if (isfromtaddr_) {
|
||||
@@ -202,17 +205,29 @@ bool AsyncRPCOperation_sendmany::main_impl() {
|
||||
assert(!isfromzaddr_ || t_inputs_total == 0);
|
||||
|
||||
if (isfromtaddr_ && (t_inputs_total < targetAmount)) {
|
||||
throw JSONRPCError(RPC_WALLET_INSUFFICIENT_FUNDS, strprintf("Insufficient transparent funds, have %ld, need %ld", t_inputs_total, targetAmount));
|
||||
throw JSONRPCError(RPC_WALLET_INSUFFICIENT_FUNDS,
|
||||
strprintf("Insufficient transparent funds, have %s, need %s",
|
||||
FormatMoney(t_inputs_total), FormatMoney(targetAmount)));
|
||||
}
|
||||
|
||||
if (isfromzaddr_ && (z_inputs_total < targetAmount)) {
|
||||
throw JSONRPCError(RPC_WALLET_INSUFFICIENT_FUNDS, strprintf("Insufficient protected funds, have %ld, need %ld", z_inputs_total, targetAmount));
|
||||
throw JSONRPCError(RPC_WALLET_INSUFFICIENT_FUNDS,
|
||||
strprintf("Insufficient protected funds, have %s, need %s",
|
||||
FormatMoney(z_inputs_total), FormatMoney(targetAmount)));
|
||||
}
|
||||
|
||||
// If from address is a taddr, select UTXOs to spend
|
||||
CAmount selectedUTXOAmount = 0;
|
||||
bool selectedUTXOCoinbase = false;
|
||||
if (isfromtaddr_) {
|
||||
// Get dust threshold
|
||||
CKey secret;
|
||||
secret.MakeNewKey(true);
|
||||
CScript scriptPubKey = GetScriptForDestination(secret.GetPubKey().GetID());
|
||||
CTxOut out(CAmount(1), scriptPubKey);
|
||||
CAmount dustThreshold = out.GetDustThreshold(minRelayTxFee);
|
||||
CAmount dustChange = -1;
|
||||
|
||||
std::vector<SendManyInputUTXO> selectedTInputs;
|
||||
for (SendManyInputUTXO & t : t_inputs_) {
|
||||
bool b = std::get<3>(t);
|
||||
@@ -222,9 +237,21 @@ bool AsyncRPCOperation_sendmany::main_impl() {
|
||||
selectedUTXOAmount += std::get<2>(t);
|
||||
selectedTInputs.push_back(t);
|
||||
if (selectedUTXOAmount >= targetAmount) {
|
||||
break;
|
||||
// Select another utxo if there is change less than the dust threshold.
|
||||
dustChange = selectedUTXOAmount - targetAmount;
|
||||
if (dustChange == 0 || dustChange >= dustThreshold) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// If there is transparent change, is it valid or is it dust?
|
||||
if (dustChange < dustThreshold && dustChange != 0) {
|
||||
throw JSONRPCError(RPC_WALLET_INSUFFICIENT_FUNDS,
|
||||
strprintf("Insufficient transparent funds, have %s, need %s more to avoid creating invalid change output %s (dust threshold is %s)",
|
||||
FormatMoney(t_inputs_total), FormatMoney(dustThreshold - dustChange), FormatMoney(dustChange), FormatMoney(dustThreshold)));
|
||||
}
|
||||
|
||||
t_inputs_ = selectedTInputs;
|
||||
t_inputs_total = selectedUTXOAmount;
|
||||
|
||||
@@ -298,7 +325,22 @@ bool AsyncRPCOperation_sendmany::main_impl() {
|
||||
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_inputs_.size() > 0) {
|
||||
LOCK2(cs_main, pwalletMain->cs_wallet);
|
||||
for (auto t : z_inputs_) {
|
||||
JSOutPoint jso = std::get<0>(t);
|
||||
std::vector<JSOutPoint> vOutPoints = { jso };
|
||||
uint256 inputAnchor;
|
||||
std::vector<boost::optional<ZCIncrementalWitness>> vInputWitnesses;
|
||||
pwalletMain->GetNoteWitnesses(vOutPoints, vInputWitnesses, inputAnchor);
|
||||
jsopWitnessAnchorMap[ jso.ToString() ] = WitnessAnchorData{ vInputWitnesses[0], inputAnchor };
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* SCENARIO #2
|
||||
*
|
||||
@@ -321,7 +363,9 @@ bool AsyncRPCOperation_sendmany::main_impl() {
|
||||
if (selectedUTXOCoinbase) {
|
||||
assert(isSingleZaddrOutput);
|
||||
throw JSONRPCError(RPC_WALLET_ERROR, strprintf(
|
||||
"Change %ld not allowed. When protecting coinbase funds, the wallet does not allow any change as there is currently no way to specify a change address in z_sendmany.", change));
|
||||
"Change %s not allowed. When protecting 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 {
|
||||
add_taddr_change_output_to_tx(change);
|
||||
LogPrint("zrpc", "%s: transparent change in transaction output (amount=%s)\n",
|
||||
@@ -496,15 +540,23 @@ bool AsyncRPCOperation_sendmany::main_impl() {
|
||||
throw JSONRPCError(RPC_WALLET_ERROR, "Could not find previous JoinSplit anchor");
|
||||
}
|
||||
|
||||
assert(changeOutputIndex != -1);
|
||||
boost::optional<ZCIncrementalWitness> changeWitness;
|
||||
int n = 0;
|
||||
for (const uint256& commitment : prevJoinSplit.commitments) {
|
||||
tree.append(commitment);
|
||||
previousCommitments.push_back(commitment);
|
||||
previousCommitments.push_back(commitment);
|
||||
if (!changeWitness && changeOutputIndex == n++) {
|
||||
changeWitness = tree.witness();
|
||||
} else if (changeWitness) {
|
||||
changeWitness.get().append(commitment);
|
||||
}
|
||||
}
|
||||
ZCIncrementalWitness changeWitness = tree.witness();
|
||||
jsAnchor = changeWitness.root();
|
||||
uint256 changeCommitment = prevJoinSplit.commitments[changeOutputIndex];
|
||||
intermediates.insert(std::make_pair(tree.root(), tree));
|
||||
witnesses.push_back(changeWitness);
|
||||
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());
|
||||
@@ -538,6 +590,7 @@ bool AsyncRPCOperation_sendmany::main_impl() {
|
||||
//
|
||||
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) {
|
||||
@@ -547,6 +600,14 @@ bool AsyncRPCOperation_sendmany::main_impl() {
|
||||
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);
|
||||
|
||||
@@ -563,12 +624,7 @@ bool AsyncRPCOperation_sendmany::main_impl() {
|
||||
|
||||
// Add history of previous commitments to witness
|
||||
if (vInputNotes.size() > 0) {
|
||||
std::vector<boost::optional<ZCIncrementalWitness>> vInputWitnesses;
|
||||
{
|
||||
LOCK(cs_main);
|
||||
pwalletMain->GetNoteWitnesses(vOutPoints, vInputWitnesses, inputAnchor);
|
||||
}
|
||||
|
||||
|
||||
if (vInputWitnesses.size()==0) {
|
||||
throw JSONRPCError(RPC_WALLET_ERROR, "Could not find witness for note commitment");
|
||||
}
|
||||
@@ -760,6 +816,11 @@ bool AsyncRPCOperation_sendmany::find_utxos(bool fAcceptCoinbase=false) {
|
||||
t_inputs_.push_back(utxo);
|
||||
}
|
||||
|
||||
// sort in ascending order, so smaller utxos appear first
|
||||
std::sort(t_inputs_.begin(), t_inputs_.end(), [](SendManyInputUTXO i, SendManyInputUTXO j) -> bool {
|
||||
return ( std::get<2>(i) < std::get<2>(j));
|
||||
});
|
||||
|
||||
return t_inputs_.size() > 0;
|
||||
}
|
||||
|
||||
@@ -894,8 +955,11 @@ Object AsyncRPCOperation_sendmany::perform_joinsplit(
|
||||
info.vpub_new,
|
||||
!this->testmode);
|
||||
|
||||
if (!(jsdesc.Verify(*pzcashParams, joinSplitPubKey_))) {
|
||||
throw std::runtime_error("error verifying joinsplit");
|
||||
{
|
||||
auto verifier = libzcash::ProofVerifier::Strict();
|
||||
if (!(jsdesc.Verify(*pzcashParams, verifier, joinSplitPubKey_))) {
|
||||
throw std::runtime_error("error verifying joinsplit");
|
||||
}
|
||||
}
|
||||
|
||||
mtx.vjoinsplit.push_back(jsdesc);
|
||||
|
||||
@@ -14,9 +14,10 @@
|
||||
#include "json/json_spirit_value.h"
|
||||
#include "wallet.h"
|
||||
|
||||
#include <unordered_map>
|
||||
#include <tuple>
|
||||
|
||||
// TODO: Compute fee based on a heuristic, e.g. (num tx output * dust threshold) + joinsplit bytes * ?
|
||||
// Default transaction fee if caller does not specify one.
|
||||
#define ASYNC_RPC_OPERATION_DEFAULT_MINERS_FEE 10000
|
||||
|
||||
using namespace libzcash;
|
||||
@@ -41,9 +42,15 @@ struct AsyncJoinSplitInfo
|
||||
CAmount vpub_new = 0;
|
||||
};
|
||||
|
||||
// A struct to help us track the witness and anchor for a given JSOutPoint
|
||||
struct WitnessAnchorData {
|
||||
boost::optional<ZCIncrementalWitness> witness;
|
||||
uint256 anchor;
|
||||
};
|
||||
|
||||
class AsyncRPCOperation_sendmany : public AsyncRPCOperation {
|
||||
public:
|
||||
AsyncRPCOperation_sendmany(std::string fromAddress, std::vector<SendManyRecipient> tOutputs, std::vector<SendManyRecipient> zOutputs, int minDepth);
|
||||
AsyncRPCOperation_sendmany(std::string fromAddress, std::vector<SendManyRecipient> tOutputs, std::vector<SendManyRecipient> zOutputs, int minDepth, CAmount fee = ASYNC_RPC_OPERATION_DEFAULT_MINERS_FEE);
|
||||
virtual ~AsyncRPCOperation_sendmany();
|
||||
|
||||
// We don't want to be copied or moved around
|
||||
@@ -59,6 +66,7 @@ public:
|
||||
private:
|
||||
friend class TEST_FRIEND_AsyncRPCOperation_sendmany; // class for unit testing
|
||||
|
||||
CAmount fee_;
|
||||
int mindepth_;
|
||||
std::string fromaddress_;
|
||||
bool isfromtaddr_;
|
||||
@@ -70,7 +78,9 @@ private:
|
||||
uint256 joinSplitPubKey_;
|
||||
unsigned char joinSplitPrivKey_[crypto_sign_SECRETKEYBYTES];
|
||||
|
||||
|
||||
// The key is the result string from calling JSOutPoint::ToString()
|
||||
std::unordered_map<std::string, WitnessAnchorData> jsopWitnessAnchorMap;
|
||||
|
||||
std::vector<SendManyRecipient> t_outputs_;
|
||||
std::vector<SendManyRecipient> z_outputs_;
|
||||
std::vector<SendManyInputUTXO> t_inputs_;
|
||||
|
||||
@@ -5,7 +5,9 @@
|
||||
#include "base58.h"
|
||||
#include "chainparams.h"
|
||||
#include "main.h"
|
||||
#include "primitives/block.h"
|
||||
#include "random.h"
|
||||
#include "utiltest.h"
|
||||
#include "wallet/wallet.h"
|
||||
#include "zcash/JoinSplit.hpp"
|
||||
#include "zcash/Note.hpp"
|
||||
@@ -29,9 +31,11 @@ public:
|
||||
|
||||
MOCK_METHOD2(WriteTx, bool(uint256 hash, const CWalletTx& wtx));
|
||||
MOCK_METHOD1(WriteWitnessCacheSize, bool(int64_t nWitnessCacheSize));
|
||||
MOCK_METHOD1(WriteBestBlock, bool(const CBlockLocator& loc));
|
||||
};
|
||||
|
||||
template void CWallet::WriteWitnessCache<MockWalletDB>(MockWalletDB& walletdb);
|
||||
template void CWallet::SetBestChainINTERNAL<MockWalletDB>(
|
||||
MockWalletDB& walletdb, const CBlockLocator& loc);
|
||||
|
||||
class TestWallet : public CWallet {
|
||||
public:
|
||||
@@ -50,11 +54,11 @@ public:
|
||||
ZCIncrementalMerkleTree tree) {
|
||||
CWallet::IncrementNoteWitnesses(pindex, pblock, tree);
|
||||
}
|
||||
void DecrementNoteWitnesses() {
|
||||
CWallet::DecrementNoteWitnesses();
|
||||
void DecrementNoteWitnesses(const CBlockIndex* pindex) {
|
||||
CWallet::DecrementNoteWitnesses(pindex);
|
||||
}
|
||||
void WriteWitnessCache(MockWalletDB& walletdb) {
|
||||
CWallet::WriteWitnessCache(walletdb);
|
||||
void SetBestChain(MockWalletDB& walletdb, const CBlockLocator& loc) {
|
||||
CWallet::SetBestChainINTERNAL(walletdb, loc);
|
||||
}
|
||||
bool UpdatedNoteData(const CWalletTx& wtxIn, CWalletTx& wtx) {
|
||||
return CWallet::UpdatedNoteData(wtxIn, wtx);
|
||||
@@ -65,119 +69,17 @@ public:
|
||||
};
|
||||
|
||||
CWalletTx GetValidReceive(const libzcash::SpendingKey& sk, CAmount value, bool randomInputs) {
|
||||
CMutableTransaction mtx;
|
||||
mtx.nVersion = 2; // Enable JoinSplits
|
||||
mtx.vin.resize(2);
|
||||
if (randomInputs) {
|
||||
mtx.vin[0].prevout.hash = GetRandHash();
|
||||
mtx.vin[1].prevout.hash = GetRandHash();
|
||||
} else {
|
||||
mtx.vin[0].prevout.hash = uint256S("0000000000000000000000000000000000000000000000000000000000000001");
|
||||
mtx.vin[1].prevout.hash = uint256S("0000000000000000000000000000000000000000000000000000000000000002");
|
||||
}
|
||||
mtx.vin[0].prevout.n = 0;
|
||||
mtx.vin[1].prevout.n = 0;
|
||||
|
||||
// Generate an ephemeral keypair.
|
||||
uint256 joinSplitPubKey;
|
||||
unsigned char joinSplitPrivKey[crypto_sign_SECRETKEYBYTES];
|
||||
crypto_sign_keypair(joinSplitPubKey.begin(), joinSplitPrivKey);
|
||||
mtx.joinSplitPubKey = joinSplitPubKey;
|
||||
|
||||
boost::array<libzcash::JSInput, 2> inputs = {
|
||||
libzcash::JSInput(), // dummy input
|
||||
libzcash::JSInput() // dummy input
|
||||
};
|
||||
|
||||
boost::array<libzcash::JSOutput, 2> outputs = {
|
||||
libzcash::JSOutput(sk.address(), value),
|
||||
libzcash::JSOutput(sk.address(), value)
|
||||
};
|
||||
|
||||
boost::array<libzcash::Note, 2> output_notes;
|
||||
|
||||
// Prepare JoinSplits
|
||||
uint256 rt;
|
||||
JSDescription jsdesc {*params, mtx.joinSplitPubKey, rt,
|
||||
inputs, outputs, value, 0, false};
|
||||
mtx.vjoinsplit.push_back(jsdesc);
|
||||
|
||||
// Empty output script.
|
||||
CScript scriptCode;
|
||||
CTransaction signTx(mtx);
|
||||
uint256 dataToBeSigned = SignatureHash(scriptCode, signTx, NOT_AN_INPUT, SIGHASH_ALL);
|
||||
|
||||
// Add the signature
|
||||
assert(crypto_sign_detached(&mtx.joinSplitSig[0], NULL,
|
||||
dataToBeSigned.begin(), 32,
|
||||
joinSplitPrivKey
|
||||
) == 0);
|
||||
|
||||
CTransaction tx {mtx};
|
||||
CWalletTx wtx {NULL, tx};
|
||||
return wtx;
|
||||
return GetValidReceive(*params, sk, value, randomInputs);
|
||||
}
|
||||
|
||||
libzcash::Note GetNote(const libzcash::SpendingKey& sk,
|
||||
const CTransaction& tx, size_t js, size_t n) {
|
||||
ZCNoteDecryption decryptor {sk.viewing_key()};
|
||||
auto hSig = tx.vjoinsplit[js].h_sig(*params, tx.joinSplitPubKey);
|
||||
auto note_pt = libzcash::NotePlaintext::decrypt(
|
||||
decryptor,
|
||||
tx.vjoinsplit[js].ciphertexts[n],
|
||||
tx.vjoinsplit[js].ephemeralKey,
|
||||
hSig,
|
||||
(unsigned char) n);
|
||||
return note_pt.note(sk.address());
|
||||
return GetNote(*params, sk, tx, js, n);
|
||||
}
|
||||
|
||||
CWalletTx GetValidSpend(const libzcash::SpendingKey& sk,
|
||||
const libzcash::Note& note, CAmount value) {
|
||||
CMutableTransaction mtx;
|
||||
mtx.vout.resize(2);
|
||||
mtx.vout[0].nValue = value;
|
||||
mtx.vout[1].nValue = 0;
|
||||
|
||||
// Generate an ephemeral keypair.
|
||||
uint256 joinSplitPubKey;
|
||||
unsigned char joinSplitPrivKey[crypto_sign_SECRETKEYBYTES];
|
||||
crypto_sign_keypair(joinSplitPubKey.begin(), joinSplitPrivKey);
|
||||
mtx.joinSplitPubKey = joinSplitPubKey;
|
||||
|
||||
// Fake tree for the unused witness
|
||||
ZCIncrementalMerkleTree tree;
|
||||
|
||||
boost::array<libzcash::JSInput, 2> inputs = {
|
||||
libzcash::JSInput(tree.witness(), note, sk),
|
||||
libzcash::JSInput() // dummy input
|
||||
};
|
||||
|
||||
boost::array<libzcash::JSOutput, 2> outputs = {
|
||||
libzcash::JSOutput(), // dummy output
|
||||
libzcash::JSOutput() // dummy output
|
||||
};
|
||||
|
||||
boost::array<libzcash::Note, 2> output_notes;
|
||||
|
||||
// Prepare JoinSplits
|
||||
uint256 rt;
|
||||
JSDescription jsdesc {*params, mtx.joinSplitPubKey, rt,
|
||||
inputs, outputs, 0, value, false};
|
||||
mtx.vjoinsplit.push_back(jsdesc);
|
||||
|
||||
// Empty output script.
|
||||
CScript scriptCode;
|
||||
CTransaction signTx(mtx);
|
||||
uint256 dataToBeSigned = SignatureHash(scriptCode, signTx, NOT_AN_INPUT, SIGHASH_ALL);
|
||||
|
||||
// Add the signature
|
||||
assert(crypto_sign_detached(&mtx.joinSplitSig[0], NULL,
|
||||
dataToBeSigned.begin(), 32,
|
||||
joinSplitPrivKey
|
||||
) == 0);
|
||||
CTransaction tx {mtx};
|
||||
CWalletTx wtx {NULL, tx};
|
||||
return wtx;
|
||||
return GetValidSpend(*params, sk, note, value);
|
||||
}
|
||||
|
||||
TEST(wallet_tests, setup_datadir_location_run_as_first_test) {
|
||||
@@ -656,7 +558,7 @@ TEST(wallet_tests, cached_witnesses_empty_chain) {
|
||||
EXPECT_TRUE((bool) witnesses[1]);
|
||||
|
||||
// Until #1302 is implemented, this should triggger an assertion
|
||||
EXPECT_DEATH(wallet.DecrementNoteWitnesses(),
|
||||
EXPECT_DEATH(wallet.DecrementNoteWitnesses(&index),
|
||||
"Assertion `nWitnessCacheSize > 0' failed.");
|
||||
}
|
||||
|
||||
@@ -729,7 +631,7 @@ TEST(wallet_tests, cached_witnesses_chain_tip) {
|
||||
|
||||
// Decrementing should give us the previous anchor
|
||||
uint256 anchor3;
|
||||
wallet.DecrementNoteWitnesses();
|
||||
wallet.DecrementNoteWitnesses(&index2);
|
||||
witnesses.clear();
|
||||
wallet.GetNoteWitnesses(notes, witnesses, anchor3);
|
||||
EXPECT_FALSE((bool) witnesses[0]);
|
||||
@@ -754,6 +656,216 @@ TEST(wallet_tests, cached_witnesses_chain_tip) {
|
||||
}
|
||||
}
|
||||
|
||||
TEST(wallet_tests, CachedWitnessesDecrementFirst) {
|
||||
TestWallet wallet;
|
||||
uint256 anchor2;
|
||||
CBlock block2;
|
||||
CBlockIndex index2(block2);
|
||||
ZCIncrementalMerkleTree tree;
|
||||
|
||||
auto sk = libzcash::SpendingKey::random();
|
||||
wallet.AddSpendingKey(sk);
|
||||
|
||||
{
|
||||
// First transaction (case tested in _empty_chain)
|
||||
auto wtx = GetValidReceive(sk, 10, true);
|
||||
auto note = GetNote(sk, wtx, 0, 1);
|
||||
auto nullifier = note.nullifier(sk);
|
||||
|
||||
mapNoteData_t noteData;
|
||||
JSOutPoint jsoutpt {wtx.GetHash(), 0, 1};
|
||||
CNoteData nd {sk.address(), nullifier};
|
||||
noteData[jsoutpt] = nd;
|
||||
wtx.SetNoteData(noteData);
|
||||
wallet.AddToWallet(wtx, true, NULL);
|
||||
|
||||
// First block (case tested in _empty_chain)
|
||||
CBlock block1;
|
||||
block1.vtx.push_back(wtx);
|
||||
CBlockIndex index1(block1);
|
||||
index1.nHeight = 1;
|
||||
wallet.IncrementNoteWitnesses(&index1, &block1, tree);
|
||||
}
|
||||
|
||||
{
|
||||
// Second transaction (case tested in _chain_tip)
|
||||
auto wtx = GetValidReceive(sk, 50, true);
|
||||
auto note = GetNote(sk, wtx, 0, 1);
|
||||
auto nullifier = note.nullifier(sk);
|
||||
|
||||
mapNoteData_t noteData;
|
||||
JSOutPoint jsoutpt {wtx.GetHash(), 0, 1};
|
||||
CNoteData nd {sk.address(), nullifier};
|
||||
noteData[jsoutpt] = nd;
|
||||
wtx.SetNoteData(noteData);
|
||||
wallet.AddToWallet(wtx, true, NULL);
|
||||
|
||||
std::vector<JSOutPoint> notes {jsoutpt};
|
||||
std::vector<boost::optional<ZCIncrementalWitness>> witnesses;
|
||||
|
||||
// Second block (case tested in _chain_tip)
|
||||
block2.vtx.push_back(wtx);
|
||||
index2.nHeight = 2;
|
||||
wallet.IncrementNoteWitnesses(&index2, &block2, tree);
|
||||
// Called to fetch anchor
|
||||
wallet.GetNoteWitnesses(notes, witnesses, anchor2);
|
||||
}
|
||||
|
||||
{
|
||||
// Third transaction - never mined
|
||||
auto wtx = GetValidReceive(sk, 20, true);
|
||||
auto note = GetNote(sk, wtx, 0, 1);
|
||||
auto nullifier = note.nullifier(sk);
|
||||
|
||||
mapNoteData_t noteData;
|
||||
JSOutPoint jsoutpt {wtx.GetHash(), 0, 1};
|
||||
CNoteData nd {sk.address(), nullifier};
|
||||
noteData[jsoutpt] = nd;
|
||||
wtx.SetNoteData(noteData);
|
||||
wallet.AddToWallet(wtx, true, NULL);
|
||||
|
||||
std::vector<JSOutPoint> notes {jsoutpt};
|
||||
std::vector<boost::optional<ZCIncrementalWitness>> witnesses;
|
||||
uint256 anchor3;
|
||||
|
||||
wallet.GetNoteWitnesses(notes, witnesses, anchor3);
|
||||
EXPECT_FALSE((bool) witnesses[0]);
|
||||
|
||||
// Decrementing (before the transaction has ever seen an increment)
|
||||
// should give us the previous anchor
|
||||
uint256 anchor4;
|
||||
wallet.DecrementNoteWitnesses(&index2);
|
||||
witnesses.clear();
|
||||
wallet.GetNoteWitnesses(notes, witnesses, anchor4);
|
||||
EXPECT_FALSE((bool) witnesses[0]);
|
||||
// Should not equal second anchor because none of these notes had witnesses
|
||||
EXPECT_NE(anchor2, anchor4);
|
||||
|
||||
// Re-incrementing with the same block should give the same result
|
||||
uint256 anchor5;
|
||||
wallet.IncrementNoteWitnesses(&index2, &block2, tree);
|
||||
witnesses.clear();
|
||||
wallet.GetNoteWitnesses(notes, witnesses, anchor5);
|
||||
EXPECT_FALSE((bool) witnesses[0]);
|
||||
EXPECT_EQ(anchor3, anchor5);
|
||||
}
|
||||
}
|
||||
|
||||
TEST(wallet_tests, CachedWitnessesCleanIndex) {
|
||||
TestWallet wallet;
|
||||
CBlock block1;
|
||||
CBlock block2;
|
||||
CBlock block3;
|
||||
CBlockIndex index1(block1);
|
||||
CBlockIndex index2(block2);
|
||||
CBlockIndex index3(block3);
|
||||
ZCIncrementalMerkleTree tree;
|
||||
|
||||
auto sk = libzcash::SpendingKey::random();
|
||||
wallet.AddSpendingKey(sk);
|
||||
|
||||
{
|
||||
// First transaction (case tested in _empty_chain)
|
||||
auto wtx = GetValidReceive(sk, 10, true);
|
||||
auto note = GetNote(sk, wtx, 0, 1);
|
||||
auto nullifier = note.nullifier(sk);
|
||||
|
||||
mapNoteData_t noteData;
|
||||
JSOutPoint jsoutpt {wtx.GetHash(), 0, 1};
|
||||
CNoteData nd {sk.address(), nullifier};
|
||||
noteData[jsoutpt] = nd;
|
||||
wtx.SetNoteData(noteData);
|
||||
wallet.AddToWallet(wtx, true, NULL);
|
||||
|
||||
// First block (case tested in _empty_chain)
|
||||
block1.vtx.push_back(wtx);
|
||||
index1.nHeight = 1;
|
||||
wallet.IncrementNoteWitnesses(&index1, &block1, tree);
|
||||
}
|
||||
|
||||
{
|
||||
// Second transaction (case tested in _chain_tip)
|
||||
auto wtx = GetValidReceive(sk, 50, true);
|
||||
auto note = GetNote(sk, wtx, 0, 1);
|
||||
auto nullifier = note.nullifier(sk);
|
||||
|
||||
mapNoteData_t noteData;
|
||||
JSOutPoint jsoutpt {wtx.GetHash(), 0, 1};
|
||||
CNoteData nd {sk.address(), nullifier};
|
||||
noteData[jsoutpt] = nd;
|
||||
wtx.SetNoteData(noteData);
|
||||
wallet.AddToWallet(wtx, true, NULL);
|
||||
|
||||
// Second block (case tested in _chain_tip)
|
||||
block2.vtx.push_back(wtx);
|
||||
index2.nHeight = 2;
|
||||
wallet.IncrementNoteWitnesses(&index2, &block2, tree);
|
||||
}
|
||||
|
||||
{
|
||||
// Third transaction
|
||||
auto wtx = GetValidReceive(sk, 20, true);
|
||||
auto note = GetNote(sk, wtx, 0, 1);
|
||||
auto nullifier = note.nullifier(sk);
|
||||
|
||||
mapNoteData_t noteData;
|
||||
JSOutPoint jsoutpt {wtx.GetHash(), 0, 1};
|
||||
CNoteData nd {sk.address(), nullifier};
|
||||
noteData[jsoutpt] = nd;
|
||||
wtx.SetNoteData(noteData);
|
||||
wallet.AddToWallet(wtx, true, NULL);
|
||||
|
||||
std::vector<JSOutPoint> notes {jsoutpt};
|
||||
std::vector<boost::optional<ZCIncrementalWitness>> witnesses;
|
||||
uint256 anchor3;
|
||||
|
||||
// Third block
|
||||
block3.vtx.push_back(wtx);
|
||||
index3.nHeight = 3;
|
||||
wallet.IncrementNoteWitnesses(&index3, &block3, tree);
|
||||
wallet.GetNoteWitnesses(notes, witnesses, anchor3);
|
||||
|
||||
// Now pretend we are reindexing: the chain is cleared, and each block is
|
||||
// used to increment witnesses again.
|
||||
wallet.IncrementNoteWitnesses(&index1, &block1, tree);
|
||||
uint256 anchor3a;
|
||||
witnesses.clear();
|
||||
wallet.GetNoteWitnesses(notes, witnesses, anchor3a);
|
||||
EXPECT_TRUE((bool) witnesses[0]);
|
||||
// Should equal third anchor because witness cache unaffected
|
||||
EXPECT_EQ(anchor3, anchor3a);
|
||||
|
||||
wallet.IncrementNoteWitnesses(&index2, &block2, tree);
|
||||
uint256 anchor3b;
|
||||
witnesses.clear();
|
||||
wallet.GetNoteWitnesses(notes, witnesses, anchor3b);
|
||||
EXPECT_TRUE((bool) witnesses[0]);
|
||||
EXPECT_EQ(anchor3, anchor3b);
|
||||
|
||||
// Pretend a reorg happened that was recorded in the block files
|
||||
wallet.DecrementNoteWitnesses(&index2);
|
||||
uint256 anchor3c;
|
||||
witnesses.clear();
|
||||
wallet.GetNoteWitnesses(notes, witnesses, anchor3c);
|
||||
EXPECT_TRUE((bool) witnesses[0]);
|
||||
EXPECT_EQ(anchor3, anchor3c);
|
||||
|
||||
wallet.IncrementNoteWitnesses(&index2, &block2, tree);
|
||||
uint256 anchor3d;
|
||||
witnesses.clear();
|
||||
wallet.GetNoteWitnesses(notes, witnesses, anchor3d);
|
||||
EXPECT_TRUE((bool) witnesses[0]);
|
||||
EXPECT_EQ(anchor3, anchor3d);
|
||||
|
||||
wallet.IncrementNoteWitnesses(&index3, &block3, tree);
|
||||
uint256 anchor3e;
|
||||
witnesses.clear();
|
||||
wallet.GetNoteWitnesses(notes, witnesses, anchor3e);
|
||||
EXPECT_TRUE((bool) witnesses[0]);
|
||||
EXPECT_EQ(anchor3, anchor3e);
|
||||
}
|
||||
}
|
||||
|
||||
TEST(wallet_tests, ClearNoteWitnessCache) {
|
||||
TestWallet wallet;
|
||||
|
||||
@@ -761,6 +873,7 @@ TEST(wallet_tests, ClearNoteWitnessCache) {
|
||||
wallet.AddSpendingKey(sk);
|
||||
|
||||
auto wtx = GetValidReceive(sk, 10, true);
|
||||
auto hash = wtx.GetHash();
|
||||
auto note = GetNote(sk, wtx, 0, 0);
|
||||
auto nullifier = note.nullifier(sk);
|
||||
|
||||
@@ -774,6 +887,8 @@ TEST(wallet_tests, ClearNoteWitnessCache) {
|
||||
// Pretend we mined the tx by adding a fake witness
|
||||
ZCIncrementalMerkleTree tree;
|
||||
wtx.mapNoteData[jsoutpt].witnesses.push_front(tree.witness());
|
||||
wtx.mapNoteData[jsoutpt].witnessHeight = 1;
|
||||
wallet.nWitnessCacheSize = 1;
|
||||
|
||||
wallet.AddToWallet(wtx, true, NULL);
|
||||
|
||||
@@ -785,6 +900,8 @@ TEST(wallet_tests, ClearNoteWitnessCache) {
|
||||
wallet.GetNoteWitnesses(notes, witnesses, anchor2);
|
||||
EXPECT_TRUE((bool) witnesses[0]);
|
||||
EXPECT_FALSE((bool) witnesses[1]);
|
||||
EXPECT_EQ(1, wallet.mapWallet[hash].mapNoteData[jsoutpt].witnessHeight);
|
||||
EXPECT_EQ(1, wallet.nWitnessCacheSize);
|
||||
|
||||
// After clearing, we should not have a witness for either note
|
||||
wallet.ClearNoteWitnessCache();
|
||||
@@ -792,11 +909,14 @@ TEST(wallet_tests, ClearNoteWitnessCache) {
|
||||
wallet.GetNoteWitnesses(notes, witnesses, anchor2);
|
||||
EXPECT_FALSE((bool) witnesses[0]);
|
||||
EXPECT_FALSE((bool) witnesses[1]);
|
||||
EXPECT_EQ(-1, wallet.mapWallet[hash].mapNoteData[jsoutpt].witnessHeight);
|
||||
EXPECT_EQ(0, wallet.nWitnessCacheSize);
|
||||
}
|
||||
|
||||
TEST(wallet_tests, WriteWitnessCache) {
|
||||
TestWallet wallet;
|
||||
MockWalletDB walletdb;
|
||||
CBlockLocator loc;
|
||||
|
||||
auto sk = libzcash::SpendingKey::random();
|
||||
wallet.AddSpendingKey(sk);
|
||||
@@ -807,7 +927,7 @@ TEST(wallet_tests, WriteWitnessCache) {
|
||||
// TxnBegin fails
|
||||
EXPECT_CALL(walletdb, TxnBegin())
|
||||
.WillOnce(Return(false));
|
||||
wallet.WriteWitnessCache(walletdb);
|
||||
wallet.SetBestChain(walletdb, loc);
|
||||
EXPECT_CALL(walletdb, TxnBegin())
|
||||
.WillRepeatedly(Return(true));
|
||||
|
||||
@@ -816,14 +936,14 @@ TEST(wallet_tests, WriteWitnessCache) {
|
||||
.WillOnce(Return(false));
|
||||
EXPECT_CALL(walletdb, TxnAbort())
|
||||
.Times(1);
|
||||
wallet.WriteWitnessCache(walletdb);
|
||||
wallet.SetBestChain(walletdb, loc);
|
||||
|
||||
// WriteTx throws
|
||||
EXPECT_CALL(walletdb, WriteTx(wtx.GetHash(), wtx))
|
||||
.WillOnce(ThrowLogicError());
|
||||
EXPECT_CALL(walletdb, TxnAbort())
|
||||
.Times(1);
|
||||
wallet.WriteWitnessCache(walletdb);
|
||||
wallet.SetBestChain(walletdb, loc);
|
||||
EXPECT_CALL(walletdb, WriteTx(wtx.GetHash(), wtx))
|
||||
.WillRepeatedly(Return(true));
|
||||
|
||||
@@ -832,26 +952,42 @@ TEST(wallet_tests, WriteWitnessCache) {
|
||||
.WillOnce(Return(false));
|
||||
EXPECT_CALL(walletdb, TxnAbort())
|
||||
.Times(1);
|
||||
wallet.WriteWitnessCache(walletdb);
|
||||
wallet.SetBestChain(walletdb, loc);
|
||||
|
||||
// WriteWitnessCacheSize throws
|
||||
EXPECT_CALL(walletdb, WriteWitnessCacheSize(0))
|
||||
.WillOnce(ThrowLogicError());
|
||||
EXPECT_CALL(walletdb, TxnAbort())
|
||||
.Times(1);
|
||||
wallet.WriteWitnessCache(walletdb);
|
||||
wallet.SetBestChain(walletdb, loc);
|
||||
EXPECT_CALL(walletdb, WriteWitnessCacheSize(0))
|
||||
.WillRepeatedly(Return(true));
|
||||
|
||||
// WriteBestBlock fails
|
||||
EXPECT_CALL(walletdb, WriteBestBlock(loc))
|
||||
.WillOnce(Return(false));
|
||||
EXPECT_CALL(walletdb, TxnAbort())
|
||||
.Times(1);
|
||||
wallet.SetBestChain(walletdb, loc);
|
||||
|
||||
// WriteBestBlock throws
|
||||
EXPECT_CALL(walletdb, WriteBestBlock(loc))
|
||||
.WillOnce(ThrowLogicError());
|
||||
EXPECT_CALL(walletdb, TxnAbort())
|
||||
.Times(1);
|
||||
wallet.SetBestChain(walletdb, loc);
|
||||
EXPECT_CALL(walletdb, WriteBestBlock(loc))
|
||||
.WillRepeatedly(Return(true));
|
||||
|
||||
// TxCommit fails
|
||||
EXPECT_CALL(walletdb, TxnCommit())
|
||||
.WillOnce(Return(false));
|
||||
wallet.WriteWitnessCache(walletdb);
|
||||
wallet.SetBestChain(walletdb, loc);
|
||||
EXPECT_CALL(walletdb, TxnCommit())
|
||||
.WillRepeatedly(Return(true));
|
||||
|
||||
// Everything succeeds
|
||||
wallet.WriteWitnessCache(walletdb);
|
||||
wallet.SetBestChain(walletdb, loc);
|
||||
}
|
||||
|
||||
TEST(wallet_tests, UpdateNullifierNoteMap) {
|
||||
@@ -913,6 +1049,7 @@ TEST(wallet_tests, UpdatedNoteData) {
|
||||
// Pretend we mined the tx by adding a fake witness
|
||||
ZCIncrementalMerkleTree tree;
|
||||
wtx.mapNoteData[jsoutpt].witnesses.push_front(tree.witness());
|
||||
wtx.mapNoteData[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.
|
||||
@@ -925,11 +1062,13 @@ TEST(wallet_tests, UpdatedNoteData) {
|
||||
// The txs should initially be different
|
||||
EXPECT_NE(wtx.mapNoteData, wtx2.mapNoteData);
|
||||
EXPECT_EQ(1, wtx.mapNoteData[jsoutpt].witnesses.size());
|
||||
EXPECT_EQ(100, wtx.mapNoteData[jsoutpt].witnessHeight);
|
||||
|
||||
// After updating, they should be the same
|
||||
EXPECT_TRUE(wallet.UpdatedNoteData(wtx2, wtx));
|
||||
EXPECT_EQ(wtx.mapNoteData, wtx2.mapNoteData);
|
||||
EXPECT_EQ(1, wtx.mapNoteData[jsoutpt].witnesses.size());
|
||||
EXPECT_EQ(100, wtx.mapNoteData[jsoutpt].witnessHeight);
|
||||
// TODO: The new note should get witnessed (but maybe not here) (#1350)
|
||||
}
|
||||
|
||||
|
||||
@@ -2622,6 +2622,12 @@ Value zc_benchmark(const json_spirit::Array& params, bool fHelp)
|
||||
sample_times.push_back(benchmark_verify_equihash());
|
||||
} else if (benchmarktype == "validatelargetx") {
|
||||
sample_times.push_back(benchmark_large_tx());
|
||||
} else if (benchmarktype == "trydecryptnotes") {
|
||||
int nAddrs = params[2].get_int();
|
||||
sample_times.push_back(benchmark_try_decrypt_notes(nAddrs));
|
||||
} else if (benchmarktype == "incnotewitnesses") {
|
||||
int nTxs = params[2].get_int();
|
||||
sample_times.push_back(benchmark_increment_note_witnesses(nTxs));
|
||||
} else {
|
||||
throw JSONRPCError(RPC_TYPE_ERROR, "Invalid benchmarktype");
|
||||
}
|
||||
@@ -2849,7 +2855,10 @@ Value zc_raw_joinsplit(const json_spirit::Array& params, bool fHelp)
|
||||
vpub_old,
|
||||
vpub_new);
|
||||
|
||||
assert(jsdesc.Verify(*pzcashParams, joinSplitPubKey));
|
||||
{
|
||||
auto verifier = libzcash::ProofVerifier::Strict();
|
||||
assert(jsdesc.Verify(*pzcashParams, verifier, joinSplitPubKey));
|
||||
}
|
||||
|
||||
mtx.vjoinsplit.push_back(jsdesc);
|
||||
|
||||
@@ -3310,17 +3319,29 @@ Value z_getoperationstatus_IMPL(const Array& params, bool fRemoveFinishedOperati
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
||||
// Here we define the maximum number of zaddr outputs that can be included in a transaction.
|
||||
// If input notes are small, we might actually require more than one joinsplit per zaddr output.
|
||||
// For now though, we assume we use one joinsplit per zaddr output (and the second output note is change).
|
||||
// We reduce the result by 1 to ensure there is room for non-joinsplit CTransaction data.
|
||||
#define Z_SENDMANY_MAX_ZADDR_OUTPUTS ((MAX_TX_SIZE / JSDescription().GetSerializeSize(SER_NETWORK, PROTOCOL_VERSION)) - 1)
|
||||
|
||||
// transaction.h comment: spending taddr output requires CTxIn >= 148 bytes and typical taddr txout is 34 bytes
|
||||
#define CTXIN_SPEND_DUST_SIZE 148
|
||||
#define CTXOUT_REGULAR_SIZE 34
|
||||
|
||||
Value z_sendmany(const Array& params, bool fHelp)
|
||||
{
|
||||
if (!EnsureWalletIsAvailable(fHelp))
|
||||
return Value::null;
|
||||
|
||||
if (fHelp || params.size() < 2 || params.size() > 3)
|
||||
if (fHelp || params.size() < 2 || params.size() > 4)
|
||||
throw runtime_error(
|
||||
"z_sendmany \"fromaddress\" [{\"address\":... ,\"amount\":...},...] ( minconf )\n"
|
||||
"z_sendmany \"fromaddress\" [{\"address\":... ,\"amount\":...},...] ( minconf ) ( fee )\n"
|
||||
"\nSend multiple times. Amounts are double-precision floating point numbers."
|
||||
"\nChange from a taddr flows to a new taddr address, while change from zaddr returns to itself."
|
||||
"\nWhen sending coinbase UTXOs to a zaddr, change is not alllowed. The entire value of the UTXO(s) must be consumed."
|
||||
"\nWhen sending coinbase UTXOs to a zaddr, change is not allowed. The entire value of the UTXO(s) must be consumed."
|
||||
+ strprintf("\nCurrently, the maximum number of zaddr outputs is %d due to transaction size limits.\n", Z_SENDMANY_MAX_ZADDR_OUTPUTS)
|
||||
+ HelpRequiringPassphrase() + "\n"
|
||||
"\nArguments:\n"
|
||||
"1. \"fromaddress\" (string, required) The taddr or zaddr to send the funds from.\n"
|
||||
@@ -3331,6 +3352,8 @@ Value z_sendmany(const Array& params, bool fHelp)
|
||||
" \"memo\":memo (string, optional) If the address is a zaddr, raw data represented in hexadecimal string format\n"
|
||||
" }, ... ]\n"
|
||||
"3. minconf (numeric, optional, default=1) Only use funds confirmed at least this many times.\n"
|
||||
"4. fee (numeric, optional, default="
|
||||
+ strprintf("%s", FormatMoney(ASYNC_RPC_OPERATION_DEFAULT_MINERS_FEE)) + ") The fee amount to attach to this transaction.\n"
|
||||
"\nResult:\n"
|
||||
"\"operationid\" (string) An operationid to pass to z_getoperationstatus to get the result of the operation.\n"
|
||||
);
|
||||
@@ -3371,6 +3394,7 @@ Value z_sendmany(const Array& params, bool fHelp)
|
||||
// Recipients
|
||||
std::vector<SendManyRecipient> taddrRecipients;
|
||||
std::vector<SendManyRecipient> zaddrRecipients;
|
||||
CAmount nTotalOut = 0;
|
||||
|
||||
BOOST_FOREACH(Value& output, outputs)
|
||||
{
|
||||
@@ -3426,6 +3450,32 @@ Value z_sendmany(const Array& params, bool fHelp)
|
||||
} else {
|
||||
taddrRecipients.push_back( SendManyRecipient(address, nAmount, memo) );
|
||||
}
|
||||
|
||||
nTotalOut += nAmount;
|
||||
}
|
||||
|
||||
// Check the number of zaddr outputs does not exceed the limit.
|
||||
if (zaddrRecipients.size() > Z_SENDMANY_MAX_ZADDR_OUTPUTS) {
|
||||
throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid parameter, too many zaddr outputs");
|
||||
}
|
||||
|
||||
// As a sanity check, estimate and verify that the size of the transaction will be valid.
|
||||
// Depending on the input notes, the actual tx size may turn out to be larger and perhaps invalid.
|
||||
size_t txsize = 0;
|
||||
CMutableTransaction mtx;
|
||||
mtx.nVersion = 2;
|
||||
for (int i = 0; i < zaddrRecipients.size(); i++) {
|
||||
mtx.vjoinsplit.push_back(JSDescription());
|
||||
}
|
||||
CTransaction tx(mtx);
|
||||
txsize += tx.GetSerializeSize(SER_NETWORK, tx.nVersion);
|
||||
if (fromTaddr) {
|
||||
txsize += CTXIN_SPEND_DUST_SIZE;
|
||||
txsize += CTXOUT_REGULAR_SIZE; // There will probably be taddr change
|
||||
}
|
||||
txsize += CTXOUT_REGULAR_SIZE * taddrRecipients.size();
|
||||
if (txsize > MAX_TX_SIZE) {
|
||||
throw JSONRPCError(RPC_INVALID_PARAMETER, strprintf("Too many outputs, size of raw transaction would be larger than limit of %d bytes", MAX_TX_SIZE ));
|
||||
}
|
||||
|
||||
// Minimum confirmations
|
||||
@@ -3437,9 +3487,19 @@ Value z_sendmany(const Array& params, bool fHelp)
|
||||
throw JSONRPCError(RPC_INVALID_PARAMETER, "Minimum number of confirmations cannot be less than 0");
|
||||
}
|
||||
|
||||
// Fee in Zatoshis, not currency format)
|
||||
CAmount nFee = ASYNC_RPC_OPERATION_DEFAULT_MINERS_FEE;
|
||||
if (params.size() > 3) {
|
||||
nFee = AmountFromValue( params[3] );
|
||||
// Check that the user specified fee is sane.
|
||||
if (nFee > nTotalOut) {
|
||||
throw JSONRPCError(RPC_INVALID_PARAMETER, strprintf("Fee %s is greater than the sum of outputs %s", FormatMoney(nFee), FormatMoney(nTotalOut)));
|
||||
}
|
||||
}
|
||||
|
||||
// Create operation and add to global queue
|
||||
std::shared_ptr<AsyncRPCQueue> q = getAsyncRPCQueue();
|
||||
std::shared_ptr<AsyncRPCOperation> operation( new AsyncRPCOperation_sendmany(fromaddress, taddrRecipients, zaddrRecipients, nMinDepth) );
|
||||
std::shared_ptr<AsyncRPCOperation> operation( new AsyncRPCOperation_sendmany(fromaddress, taddrRecipients, zaddrRecipients, nMinDepth, nFee) );
|
||||
q->addOperation(operation);
|
||||
AsyncRPCOperationId operationId = operation->getId();
|
||||
return operationId;
|
||||
|
||||
@@ -373,14 +373,14 @@ void CWallet::ChainTip(const CBlockIndex *pindex, const CBlock *pblock,
|
||||
if (added) {
|
||||
IncrementNoteWitnesses(pindex, pblock, tree);
|
||||
} else {
|
||||
DecrementNoteWitnesses();
|
||||
DecrementNoteWitnesses(pindex);
|
||||
}
|
||||
}
|
||||
|
||||
void CWallet::SetBestChain(const CBlockLocator& loc)
|
||||
{
|
||||
CWalletDB walletdb(strWalletFile);
|
||||
walletdb.WriteBestBlock(loc);
|
||||
SetBestChainINTERNAL(walletdb, loc);
|
||||
}
|
||||
|
||||
bool CWallet::SetMinVersion(enum WalletFeature nVersion, CWalletDB* pwalletdbIn, bool fExplicit)
|
||||
@@ -630,8 +630,10 @@ void CWallet::ClearNoteWitnessCache()
|
||||
for (std::pair<const uint256, CWalletTx>& wtxItem : mapWallet) {
|
||||
for (mapNoteData_t::value_type& item : wtxItem.second.mapNoteData) {
|
||||
item.second.witnesses.clear();
|
||||
item.second.witnessHeight = -1;
|
||||
}
|
||||
}
|
||||
nWitnessCacheSize = 0;
|
||||
}
|
||||
|
||||
void CWallet::IncrementNoteWitnesses(const CBlockIndex* pindex,
|
||||
@@ -648,7 +650,7 @@ void CWallet::IncrementNoteWitnesses(const CBlockIndex* pindex,
|
||||
// Only increment witnesses that are behind the current height
|
||||
if (nd->witnessHeight < pindex->nHeight) {
|
||||
// Witnesses being incremented should always be either -1
|
||||
// (never incremented) or one below pindex
|
||||
// (never incremented or decremented) or one below pindex
|
||||
assert((nd->witnessHeight == -1) ||
|
||||
(nd->witnessHeight == pindex->nHeight - 1));
|
||||
// Copy the witness for the previous block if we have one
|
||||
@@ -739,14 +741,13 @@ void CWallet::IncrementNoteWitnesses(const CBlockIndex* pindex,
|
||||
}
|
||||
}
|
||||
|
||||
if (fFileBacked) {
|
||||
CWalletDB walletdb(strWalletFile);
|
||||
WriteWitnessCache(walletdb);
|
||||
}
|
||||
// For performance reasons, we write out the witness cache in
|
||||
// CWallet::SetBestChain() (which also ensures that overall consistency
|
||||
// of the wallet.dat is maintained).
|
||||
}
|
||||
}
|
||||
|
||||
void CWallet::DecrementNoteWitnesses()
|
||||
void CWallet::DecrementNoteWitnesses(const CBlockIndex* pindex)
|
||||
{
|
||||
extern int32_t KOMODO_REWIND;
|
||||
{
|
||||
@@ -756,10 +757,19 @@ void CWallet::DecrementNoteWitnesses()
|
||||
CNoteData* nd = &(item.second);
|
||||
// Check the validity of the cache
|
||||
assert(nWitnessCacheSize >= nd->witnesses.size());
|
||||
if (nd->witnesses.size() > 0) {
|
||||
nd->witnesses.pop_front();
|
||||
// Only increment witnesses that are not above the current height
|
||||
if (nd->witnessHeight <= pindex->nHeight) {
|
||||
// Witnesses being decremented should always be either -1
|
||||
// (never incremented or decremented) or equal to pindex
|
||||
assert((nd->witnessHeight == -1) ||
|
||||
(nd->witnessHeight == pindex->nHeight));
|
||||
if (nd->witnesses.size() > 0) {
|
||||
nd->witnesses.pop_front();
|
||||
}
|
||||
// pindex is the block being removed, so the new witness cache
|
||||
// height is one below it.
|
||||
nd->witnessHeight = pindex->nHeight - 1;
|
||||
}
|
||||
nd->witnessHeight -= 1;
|
||||
}
|
||||
}
|
||||
nWitnessCacheSize -= 1;
|
||||
@@ -770,7 +780,6 @@ void CWallet::DecrementNoteWitnesses()
|
||||
assert(nWitnessCacheSize >= nd->witnesses.size());
|
||||
}
|
||||
}
|
||||
// TODO: If nWitnessCache is zero, we need to regenerate the caches (#1302)
|
||||
if ( nWitnessCacheSize <= 0 )
|
||||
{
|
||||
extern char ASSETCHAINS_SYMBOL[16];
|
||||
@@ -778,10 +787,13 @@ void CWallet::DecrementNoteWitnesses()
|
||||
}
|
||||
if ( KOMODO_REWIND == 0 )
|
||||
assert(nWitnessCacheSize > 0);
|
||||
if (fFileBacked) {
|
||||
CWalletDB walletdb(strWalletFile);
|
||||
WriteWitnessCache(walletdb);
|
||||
}
|
||||
//if (fFileBacked) {
|
||||
// CWalletDB walletdb(strWalletFile);
|
||||
// WriteWitnessCache(walletdb);
|
||||
//}
|
||||
// For performance reasons, we write out the witness cache in
|
||||
// CWallet::SetBestChain() (which also ensures that overall consistency
|
||||
// of the wallet.dat is maintained).
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1109,6 +1121,7 @@ bool CWallet::UpdatedNoteData(const CWalletTx& wtxIn, CWalletTx& wtx)
|
||||
tmp.at(nd.first).witnesses.assign(
|
||||
nd.second.witnesses.cbegin(), nd.second.witnesses.cend());
|
||||
}
|
||||
tmp.at(nd.first).witnessHeight = nd.second.witnessHeight;
|
||||
}
|
||||
// Now copy over the updated note data
|
||||
wtx.mapNoteData = tmp;
|
||||
|
||||
@@ -225,7 +225,15 @@ public:
|
||||
*/
|
||||
std::list<ZCIncrementalWitness> witnesses;
|
||||
|
||||
/** Block height corresponding to the most current witness. */
|
||||
/**
|
||||
* Block height corresponding to the most current witness.
|
||||
*
|
||||
* When we first create a CNoteData in CWallet::FindMyNotes, 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.
|
||||
*/
|
||||
int witnessHeight;
|
||||
|
||||
CNoteData() : address(), nullifier(), witnessHeight {-1} { }
|
||||
@@ -620,41 +628,52 @@ public:
|
||||
void ClearNoteWitnessCache();
|
||||
|
||||
protected:
|
||||
/**
|
||||
* pindex is the new tip being connected.
|
||||
*/
|
||||
void IncrementNoteWitnesses(const CBlockIndex* pindex,
|
||||
const CBlock* pblock,
|
||||
ZCIncrementalMerkleTree& tree);
|
||||
void DecrementNoteWitnesses();
|
||||
/**
|
||||
* pindex is the old tip being disconnected.
|
||||
*/
|
||||
void DecrementNoteWitnesses(const CBlockIndex* pindex);
|
||||
|
||||
template <typename WalletDB>
|
||||
void WriteWitnessCache(WalletDB& walletdb) {
|
||||
void SetBestChainINTERNAL(WalletDB& walletdb, const CBlockLocator& loc) {
|
||||
if (!walletdb.TxnBegin()) {
|
||||
// This needs to be done atomically, so don't do it at all
|
||||
LogPrintf("WriteWitnessCache(): Couldn't start atomic write\n");
|
||||
LogPrintf("SetBestChain(): Couldn't start atomic write\n");
|
||||
return;
|
||||
}
|
||||
try {
|
||||
for (std::pair<const uint256, CWalletTx>& wtxItem : mapWallet) {
|
||||
if (!walletdb.WriteTx(wtxItem.first, wtxItem.second)) {
|
||||
LogPrintf("WriteWitnessCache(): Failed to write CWalletTx, aborting atomic write\n");
|
||||
LogPrintf("SetBestChain(): Failed to write CWalletTx, aborting atomic write\n");
|
||||
walletdb.TxnAbort();
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (!walletdb.WriteWitnessCacheSize(nWitnessCacheSize)) {
|
||||
LogPrintf("WriteWitnessCache(): Failed to write nWitnessCacheSize, aborting atomic write\n");
|
||||
LogPrintf("SetBestChain(): Failed to write nWitnessCacheSize, aborting atomic write\n");
|
||||
walletdb.TxnAbort();
|
||||
return;
|
||||
}
|
||||
if (!walletdb.WriteBestBlock(loc)) {
|
||||
LogPrintf("SetBestChain(): Failed to write best block, aborting atomic write\n");
|
||||
walletdb.TxnAbort();
|
||||
return;
|
||||
}
|
||||
} catch (const std::exception &exc) {
|
||||
// Unexpected failure
|
||||
LogPrintf("WriteWitnessCache(): Unexpected error during atomic write:\n");
|
||||
LogPrintf("SetBestChain(): Unexpected error during atomic write:\n");
|
||||
LogPrintf("%s\n", exc.what());
|
||||
walletdb.TxnAbort();
|
||||
return;
|
||||
}
|
||||
if (!walletdb.TxnCommit()) {
|
||||
// Couldn't commit all to db, but in-memory state is fine
|
||||
LogPrintf("WriteWitnessCache(): Couldn't commit atomic write\n");
|
||||
LogPrintf("SetBestChain(): Couldn't commit atomic write\n");
|
||||
return;
|
||||
}
|
||||
}
|
||||
@@ -944,6 +963,7 @@ public:
|
||||
CAmount GetCredit(const CTransaction& tx, const isminefilter& filter) const;
|
||||
CAmount GetChange(const CTransaction& tx) const;
|
||||
void ChainTip(const CBlockIndex *pindex, const CBlock *pblock, ZCIncrementalMerkleTree tree, bool added);
|
||||
/** Saves witness caches and best block locator to disk. */
|
||||
void SetBestChain(const CBlockLocator& loc);
|
||||
|
||||
DBErrors LoadWallet(bool& fFirstRunRet);
|
||||
|
||||
Reference in New Issue
Block a user