Refactor to use wallet note tracking from commit a72379

This commit is contained in:
Simon 2016-09-03 14:13:27 -07:00
parent 97b6f365a1
commit 87f7c98795
2 changed files with 75 additions and 69 deletions

View File

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

View File

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