Refactor to use wallet note tracking from commit a72379
This commit is contained in:
parent
97b6f365a1
commit
87f7c98795
|
@ -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) {
|
||||||
|
@ -528,10 +520,8 @@ bool AsyncRPCOperation_sendmany::main_impl() {
|
||||||
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;
|
||||||
|
|
|
@ -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
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
Loading…
Reference in New Issue