Browse Source

Refactor to use wallet note tracking from commit a72379

pull/145/head
Simon 8 years ago
parent
commit
87f7c98795
  1. 123
      src/wallet/asyncrpcoperation_sendmany.cpp
  2. 21
      src/wallet/asyncrpcoperation_sendmany.h

123
src/wallet/asyncrpcoperation_sendmany.cpp

@ -120,7 +120,7 @@ void AsyncRPCOperation_sendmany::main() {
// Notes:
// 1. Currently there is no limit set on the number of joinsplits, so size of tx could be invalid.
// 2. Note selection is not optimal
// 3. Spendable notes are not locked, so another operation could also try to use them
// 3. Spendable notes are not locked, so an operation running in parallel could also try to use them
bool AsyncRPCOperation_sendmany::main_impl() {
bool isSingleZaddrOutput = (t_outputs_.size()==0 && z_outputs_.size()==1);
@ -142,8 +142,8 @@ bool AsyncRPCOperation_sendmany::main_impl() {
}
CAmount z_inputs_total = 0;
for (SendManyInputNPT & p : z_inputs_) {
z_inputs_total += p.second;
for (SendManyInputJSOP & t : z_inputs_) {
z_inputs_total += std::get<2>(t);
}
CAmount t_outputs_total = 0;
@ -198,6 +198,7 @@ bool AsyncRPCOperation_sendmany::main_impl() {
tx_ = CTransaction(rawTx);
}
// TODO: Replace with logging to debug.log
#if 1
std::cout << "t_inputs_total: " << t_inputs_total << std::endl;
std::cout << "z_inputs_total: " << z_inputs_total << std::endl;
@ -244,7 +245,7 @@ bool AsyncRPCOperation_sendmany::main_impl() {
tx_ = CTransaction(mtx);
// Copy zinputs and zoutputs to more flexible containers
std::deque<SendManyInputNPT> zInputsDeque;
std::deque<SendManyInputJSOP> zInputsDeque;
for (auto o : z_inputs_) {
zInputsDeque.push_back(o);
}
@ -348,18 +349,17 @@ bool AsyncRPCOperation_sendmany::main_impl() {
AsyncJoinSplitInfo info;
info.vpub_old = 0;
info.vpub_new = 0;
std::vector<JSOutPoint> outPoints;
int n = 0;
while (n++ < 2 && taddrTargetAmount > 0) {
SendManyInputNPT o = zInputsDeque.front();
NotePlaintext npt = o.first;
CAmount noteFunds = o.second;
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();
libzcash::Note inputNote = npt.note(frompaymentaddress_);
uint256 inputCommitment = inputNote.cm();
info.notes.push_back(inputNote);
info.commitments.push_back(inputCommitment);
info.keys.push_back(spendingkey_);
info.notes.push_back(note);
outPoints.push_back(outPoint);
// Put value back into the value pool
if (noteFunds >= taddrTargetAmount) {
@ -380,7 +380,7 @@ bool AsyncRPCOperation_sendmany::main_impl() {
info.vjsout.push_back(JSOutput(frompaymentaddress_, jsChange));
}
obj = perform_joinsplit(info);
obj = perform_joinsplit(info, outPoints);
}
}
@ -444,10 +444,6 @@ bool AsyncRPCOperation_sendmany::main_impl() {
jsAnchor = changeWitness.root();
uint256 changeCommitment = prevJoinSplit.commitments[changeOutputIndex];
intermediates.insert(std::make_pair(tree.root(), tree));
// Update info with this change
info.commitments.push_back(changeCommitment);
info.keys.push_back(spendingkey_);
witnesses.push_back(changeWitness);
// Decrypt the change note's ciphertext to retrieve some data we need
@ -466,8 +462,7 @@ bool AsyncRPCOperation_sendmany::main_impl() {
jsInputValue += plaintext.value;
} catch (const std::exception e) {
std::cout << "exception: " << e.what() << std::endl;
throw JSONRPCError(RPC_WALLET_ERROR, "Could not decrypt output note of previous join split in chain");
throw JSONRPCError(RPC_WALLET_ERROR, strprintf("Error decrypting output note of previous JoinSplit: %s", e.what()));
}
}
@ -476,21 +471,18 @@ bool AsyncRPCOperation_sendmany::main_impl() {
// Consume spendable non-change notes
//
std::vector<Note> vInputNotes;
std::vector<uint256> vInputCommitments;
std::vector<SpendingKey> vInputSpendingKey;
std::vector<JSOutPoint> vOutPoints;
uint256 inputAnchor;
int numInputsNeeded = (jsChange>0) ? 1 : 0;
while (numInputsNeeded++ < 2 && zInputsDeque.size() > 0) {
SendManyInputNPT o = zInputsDeque.front();
NotePlaintext npt = o.first;
CAmount noteFunds = o.second;
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();
libzcash::Note inputNote = npt.note(frompaymentaddress_);
uint256 inputCommitment = inputNote.cm();
vInputNotes.push_back(inputNote);
vInputCommitments.push_back(inputCommitment);
vInputSpendingKey.push_back(spendingkey_);
vOutPoints.push_back(jso);
vInputNotes.push_back(note);
jsInputValue += noteFunds;
}
@ -500,7 +492,7 @@ bool AsyncRPCOperation_sendmany::main_impl() {
std::vector<boost::optional<ZCIncrementalWitness>> vInputWitnesses;
{
LOCK(cs_main);
pwalletMain->WitnessNoteCommitment(vInputCommitments, vInputWitnesses, inputAnchor);
pwalletMain->GetNoteWitnesses(vOutPoints, vInputWitnesses, inputAnchor);
}
if (vInputWitnesses.size()==0) {
@ -527,11 +519,9 @@ bool AsyncRPCOperation_sendmany::main_impl() {
if (jsAnchor.IsNull()) {
jsAnchor = inputAnchor;
}
// Populate info struct with note inputs
std::copy(vInputCommitments.begin(), vInputCommitments.end(), std::back_inserter(info.commitments));
// Add spendable notes as inputs
std::copy(vInputNotes.begin(), vInputNotes.end(), std::back_inserter(info.notes));
std::copy(vInputSpendingKey.begin(), vInputSpendingKey.end(), std::back_inserter(info.keys));
}
@ -717,7 +707,6 @@ bool AsyncRPCOperation_sendmany::find_unspent_notes() {
for (auto & pair : mapNoteData) {
JSOutPoint jsop = pair.first;
CNoteData nd = pair.second;
PaymentAddress pa = nd.address;
// skip notes which belong to a different payment address in the wallet
@ -725,11 +714,22 @@ bool AsyncRPCOperation_sendmany::find_unspent_notes() {
continue;
}
// skip note which has been spent
if (pwalletMain->IsSpent(nd.nullifier)) {
continue;
}
int i = jsop.js; // Index into CTransaction.vjoinsplit
int j = jsop.n; // Index into JSDescription.ciphertexts
// determine amount of funds in the note and if it has been spent
ZCNoteDecryption decryptor(spendingkey_.viewing_key());
// Get cached decryptor
ZCNoteDecryption decryptor;
if (!pwalletMain->GetNoteDecryptor(pa, decryptor)) {
// Note decryptors are created when the wallet is loaded, so it should always exist
throw JSONRPCError(RPC_WALLET_ERROR, "Could not find note decryptor");
}
// determine amount of funds in the note
auto hSig = wtx.vjoinsplit[i].h_sig(*pzcashParams, wtx.joinSplitPubKey);
try {
NotePlaintext plaintext = NotePlaintext::decrypt(
@ -739,28 +739,15 @@ bool AsyncRPCOperation_sendmany::find_unspent_notes() {
hSig,
(unsigned char) j);
uint256 nullifier = plaintext.note(frompaymentaddress_).nullifier(spendingkey_);
bool isSpent = pwalletMain->IsSpent(nullifier);
if (isSpent) {
std::cout << "Found SPENT note at txid : " << wtx.GetTxid().ToString() << std::endl;
std::cout << "... vjoinsplit index: " << i << std::endl;
std::cout << "... jsdescription index: " << j << std::endl;
std::cout << "... amount: " << FormatMoney(plaintext.value, false) << std::endl;
continue;
}
z_inputs_.push_back(SendManyInputNPT(plaintext, CAmount(plaintext.value)));
z_inputs_.push_back(SendManyInputJSOP(jsop, plaintext.note(pa), CAmount(plaintext.value)));
// TODO: Replace with logging to debug.log
#if 1
std::cout << "Found note at txid : " << wtx.GetTxid().ToString() << std::endl;
std::cout << "... vjoinsplit index: " << i << std::endl;
std::cout << "... jsdescription index: " << j << std::endl;
std::cout << "... payment address: " << CZCPaymentAddress(pa).ToString() << std::endl;
std::cout << "... spent: " << isSpent << std::endl;
std::cout << "... spent: " << pwalletMain->IsSpent(nd.nullifier) << std::endl;
std::string data(plaintext.memo.begin(), plaintext.memo.end());
std::cout << "... memo: " << HexStr(data) << std::endl;
std::cout << "... amount: " << FormatMoney(plaintext.value, false) << std::endl;
@ -777,8 +764,8 @@ bool AsyncRPCOperation_sendmany::find_unspent_notes() {
}
// sort in descending order, so big notes appear first
std::sort(z_inputs_.begin(), z_inputs_.end(), [](SendManyInputNPT i, SendManyInputNPT j) -> bool {
return (i.second > j.second);
std::sort(z_inputs_.begin(), z_inputs_.end(), [](SendManyInputJSOP i, SendManyInputJSOP j) -> bool {
return ( std::get<2>(i) > std::get<2>(j));
});
return true;
@ -787,14 +774,21 @@ bool AsyncRPCOperation_sendmany::find_unspent_notes() {
Object AsyncRPCOperation_sendmany::perform_joinsplit(AsyncJoinSplitInfo & info) {
std::vector<boost::optional < ZCIncrementalWitness>> witnesses;
uint256 anchor;
{
LOCK2(cs_main, pwalletMain->cs_wallet);
anchor = pcoinsTip->GetBestAnchor(); // As there are no inputs, ask the wallet for the best anchor
}
return perform_joinsplit(info, witnesses, anchor);
}
// Lock critical section (accesses blockchain)
Object AsyncRPCOperation_sendmany::perform_joinsplit(AsyncJoinSplitInfo & info, std::vector<JSOutPoint> & outPoints) {
std::vector<boost::optional < ZCIncrementalWitness>> witnesses;
uint256 anchor;
{
LOCK(cs_main);
pwalletMain->WitnessNoteCommitment(info.commitments, witnesses, anchor);
pwalletMain->GetNoteWitnesses(outPoints, witnesses, anchor);
}
// Unlock critical section
return perform_joinsplit(info, witnesses, anchor);
}
@ -803,15 +797,19 @@ Object AsyncRPCOperation_sendmany::perform_joinsplit(
std::vector<boost::optional < ZCIncrementalWitness>> witnesses,
uint256 anchor)
{
if (!(witnesses.size() == info.notes.size()) || !(info.notes.size() == info.keys.size())) {
throw runtime_error("number of notes and witnesses and keys do not match");
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], info.keys[i]));
info.vjsin.push_back(JSInput(*witnesses[i], info.notes[i], spendingkey_));
}
// Make sure there are two inputs and two outputs
@ -829,6 +827,7 @@ Object AsyncRPCOperation_sendmany::perform_joinsplit(
CMutableTransaction mtx(tx_);
// TODO: Replace with logging to debug.log
#if 1
std::cout << "joinsplit chain length = " << tx_.vjoinsplit.size() << std::endl;
std::cout << "vpub_old: " << info.vpub_old << std::endl;

21
src/wallet/asyncrpcoperation_sendmany.h

@ -12,6 +12,7 @@
#include "zcash/JoinSplit.hpp"
#include "zcash/Address.hpp"
#include "json/json_spirit_value.h"
#include "wallet.h"
#include <tuple>
@ -27,17 +28,15 @@ 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> SendManyInputUTXO;
// Input NPT is a pair of the plaintext note and amount
typedef std::pair<NotePlaintext, CAmount> SendManyInputNPT;
// Input JSOP is a tuple of JSOutpoint, note and amount
typedef std::tuple<JSOutPoint, Note, CAmount> SendManyInputJSOP;
// Package of info needed to perform a joinsplit
// Package of info which is passed to perform_joinsplit methods.
struct AsyncJoinSplitInfo
{
std::vector<JSInput> vjsin;
std::vector<JSOutput> vjsout;
std::vector<Note> notes;
std::vector<SpendingKey> keys;
std::vector<uint256> commitments;
CAmount vpub_old = 0;
CAmount vpub_new = 0;
};
@ -73,7 +72,7 @@ private:
std::vector<SendManyRecipient> t_outputs_;
std::vector<SendManyRecipient> z_outputs_;
std::vector<SendManyInputUTXO> t_inputs_;
std::vector<SendManyInputNPT> z_inputs_;
std::vector<SendManyInputJSOP> z_inputs_;
CTransaction tx_;
@ -83,11 +82,19 @@ private:
bool find_utxos(bool fAcceptCoinbase);
boost::array<unsigned char, ZC_MEMO_SIZE> get_memo_from_hex_string(std::string s);
bool main_impl();
Object perform_joinsplit( AsyncJoinSplitInfo &);
// JoinSplit without any input notes to spend
Object perform_joinsplit(AsyncJoinSplitInfo &);
// JoinSplit with input notes to spend (JSOutPoints))
Object perform_joinsplit(AsyncJoinSplitInfo &, std::vector<JSOutPoint> & );
// JoinSplit where you have the witnesses and anchor
Object perform_joinsplit(
AsyncJoinSplitInfo & info,
std::vector<boost::optional < ZCIncrementalWitness>> witnesses,
uint256 anchor);
void sign_send_raw_transaction(Object obj); // throws exception if there was an error
};

Loading…
Cancel
Save