// Copyright (c) 2016 The Zcash developers
// Distributed under the MIT software license, see the accompanying
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
# include "asyncrpcoperation_sendmany.h"
# include "asyncrpcqueue.h"
# include "amount.h"
# include "core_io.h"
# include "init.h"
# include "main.h"
# include "net.h"
# include "netbase.h"
# include "rpcserver.h"
# include "timedata.h"
# include "util.h"
# include "utilmoneystr.h"
# include "wallet.h"
# include "walletdb.h"
# include "script/interpreter.h"
# include "utiltime.h"
# include "rpcprotocol.h"
# include "zcash/IncrementalMerkleTree.hpp"
# include "sodium.h"
# include <iostream>
# include <chrono>
# include <thread>
# include <string>
using namespace libzcash ;
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 )
{
if ( minDepth < 0 ) {
throw JSONRPCError ( RPC_INVALID_PARAMETER , " Minconf cannot be negative " ) ;
}
if ( fromAddress . size ( ) = = 0 ) {
throw JSONRPCError ( RPC_INVALID_PARAMETER , " From address parameter missing " ) ;
}
if ( tOutputs . size ( ) = = 0 & & zOutputs . size ( ) = = 0 ) {
throw JSONRPCError ( RPC_INVALID_PARAMETER , " No recipients " ) ;
}
fromtaddr_ = CBitcoinAddress ( fromAddress ) ;
isfromtaddr_ = fromtaddr_ . IsValid ( ) ;
isfromzaddr_ = false ;
libzcash : : PaymentAddress addr ;
if ( ! isfromtaddr_ ) {
CZCPaymentAddress address ( fromAddress ) ;
try {
PaymentAddress addr = address . Get ( ) ;
// We don't need to lock on the wallet as spending key related methods are thread-safe
SpendingKey key ;
if ( ! pwalletMain - > GetSpendingKey ( addr , key ) ) {
throw JSONRPCError ( RPC_INVALID_ADDRESS_OR_KEY , " Invalid from address, no spending key found for zaddr " ) ;
}
isfromzaddr_ = true ;
frompaymentaddress_ = addr ;
spendingkey_ = key ;
} catch ( std : : runtime_error e ) {
throw JSONRPCError ( RPC_INVALID_ADDRESS_OR_KEY , string ( " runtime error: " ) + e . what ( ) ) ;
}
}
}
AsyncRPCOperation_sendmany : : ~ AsyncRPCOperation_sendmany ( ) {
}
void AsyncRPCOperation_sendmany : : main ( ) {
if ( isCancelled ( ) )
return ;
set_state ( OperationStatus : : EXECUTING ) ;
start_execution_clock ( ) ;
bool success = false ;
try {
success = main_impl ( ) ;
} catch ( Object objError ) {
int code = find_value ( objError , " code " ) . get_int ( ) ;
std : : string message = find_value ( objError , " message " ) . get_str ( ) ;
set_error_code ( code ) ;
set_error_message ( message ) ;
} catch ( runtime_error e ) {
set_error_code ( - 1 ) ;
set_error_message ( " runtime error: " + string ( e . what ( ) ) ) ;
} catch ( logic_error e ) {
set_error_code ( - 1 ) ;
set_error_message ( " logic error: " + string ( e . what ( ) ) ) ;
} catch ( . . . ) {
set_error_code ( - 2 ) ;
set_error_message ( " unknown error " ) ;
}
stop_execution_clock ( ) ;
if ( success ) {
set_state ( OperationStatus : : SUCCESS ) ;
} else {
set_state ( OperationStatus : : FAILED ) ;
}
std : : string s = strprintf ( " async rpc %s finished (status=%s " , getId ( ) , getStateAsString ( ) ) ;
if ( success ) {
s + = strprintf ( " , tx=%s) \n " , tx_ . ToString ( ) ) ;
} else {
s + = strprintf ( " , error=%s) \n " , getErrorMessage ( ) ) ;
}
LogPrintf ( " %s " , s ) ;
}
// 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 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 ) ;
bool isPureTaddrOnlyTx = ( isfromtaddr_ & & z_outputs_ . size ( ) = = 0 ) ;
CAmount minersFee = ASYNC_RPC_OPERATION_DEFAULT_MINERS_FEE ;
// Regardless of the from address, add all taddr outputs to the raw transaction.
if ( isfromtaddr_ & & ! find_utxos ( isSingleZaddrOutput ) ) {
throw JSONRPCError ( RPC_WALLET_INSUFFICIENT_FUNDS , " Insufficient funds, no UTXOs found for taddr from address. " ) ;
}
if ( isfromzaddr_ & & ! find_unspent_notes ( ) ) {
throw JSONRPCError ( RPC_WALLET_INSUFFICIENT_FUNDS , " Insufficient funds, no unspent notes found for zaddr from address. " ) ;
}
CAmount t_inputs_total = 0 ;
for ( SendManyInputUTXO & t : t_inputs_ ) {
t_inputs_total + = std : : get < 2 > ( t ) ;
}
CAmount z_inputs_total = 0 ;
for ( SendManyInputJSOP & t : z_inputs_ ) {
z_inputs_total + = std : : get < 2 > ( t ) ;
}
CAmount t_outputs_total = 0 ;
for ( SendManyRecipient & t : t_outputs_ ) {
t_outputs_total + = std : : get < 1 > ( t ) ;
}
CAmount z_outputs_total = 0 ;
for ( SendManyRecipient & t : z_outputs_ ) {
z_outputs_total + = std : : get < 1 > ( t ) ;
}
CAmount sendAmount = z_outputs_total + t_outputs_total ;
CAmount targetAmount = sendAmount + minersFee ;
if ( isfromtaddr_ & & ( t_inputs_total < targetAmount ) ) {
throw JSONRPCError ( RPC_WALLET_INSUFFICIENT_FUNDS , strprintf ( " Insufficient transparent funds, have %ld, need %ld plus fee %ld " , t_inputs_total , t_outputs_total , minersFee ) ) ;
}
if ( isfromzaddr_ & & ( z_inputs_total < targetAmount ) ) {
throw JSONRPCError ( RPC_WALLET_INSUFFICIENT_FUNDS , strprintf ( " Insufficient protected funds, have %ld, need %ld plus fee %ld " , z_inputs_total , z_outputs_total , minersFee ) ) ;
}
// If from address is a taddr, select UTXOs to spend
CAmount selectedUTXOAmount = 0 ;
bool selectedUTXOCoinbase = false ;
if ( isfromtaddr_ ) {
std : : vector < SendManyInputUTXO > selectedTInputs ;
for ( SendManyInputUTXO & t : t_inputs_ ) {
bool b = std : : get < 3 > ( t ) ;
if ( b ) {
selectedUTXOCoinbase = true ;
}
selectedUTXOAmount + = std : : get < 2 > ( t ) ;
selectedTInputs . push_back ( t ) ;
if ( selectedUTXOAmount > = targetAmount ) {
break ;
}
}
t_inputs_ = selectedTInputs ;
t_inputs_total = selectedUTXOAmount ;
// update the transaction with these inputs
CMutableTransaction rawTx ( tx_ ) ;
for ( SendManyInputUTXO & t : t_inputs_ ) {
uint256 txid = std : : get < 0 > ( t ) ;
int vout = std : : get < 1 > ( t ) ;
CAmount amount = std : : get < 2 > ( t ) ;
CTxIn in ( COutPoint ( txid , vout ) ) ;
rawTx . vin . push_back ( in ) ;
}
tx_ = CTransaction ( rawTx ) ;
}
LogPrint ( " asyncrpc " , " %s: spending %s to send %s with fee %s \n " ,
getId ( ) . substr ( 0 , 10 ) , FormatMoney ( targetAmount , false ) , FormatMoney ( sendAmount , false ) , FormatMoney ( minersFee , false ) ) ;
LogPrint ( " asyncrpc " , " - transparent input: %s \n " , FormatMoney ( t_inputs_total , false ) ) ;
LogPrint ( " asyncrpc " , " - private input: %s \n " , FormatMoney ( z_inputs_total , false ) ) ;
LogPrint ( " asyncrpc " , " - transparent output: %s \n " , FormatMoney ( t_outputs_total , false ) ) ;
LogPrint ( " asyncrpc " , " - private output: %s \n " , FormatMoney ( z_outputs_total , false ) ) ;
/**
* SCENARIO # 1
*
* taddr - > taddrs
*
* There are no zaddrs or joinsplits involved .
*/
if ( isPureTaddrOnlyTx ) {
add_taddr_outputs_to_tx ( ) ;
CAmount funds = selectedUTXOAmount ;
CAmount fundsSpent = t_outputs_total + minersFee ;
CAmount change = funds - fundsSpent ;
if ( change > 0 ) {
add_taddr_change_output_to_tx ( change ) ;
}
Object obj ;
obj . push_back ( Pair ( " rawtxn " , EncodeHexTx ( tx_ ) ) ) ;
sign_send_raw_transaction ( obj ) ;
return true ;
}
/**
* END SCENARIO # 1
*/
// Prepare raw transaction to handle JoinSplits
CMutableTransaction mtx ( tx_ ) ;
mtx . nVersion = 2 ;
crypto_sign_keypair ( joinSplitPubKey_ . begin ( ) , joinSplitPrivKey_ ) ;
mtx . joinSplitPubKey = joinSplitPubKey_ ;
tx_ = CTransaction ( mtx ) ;
// Copy zinputs and zoutputs to more flexible containers
std : : deque < SendManyInputJSOP > zInputsDeque ;
for ( auto o : z_inputs_ ) {
zInputsDeque . push_back ( o ) ;
}
std : : deque < SendManyRecipient > zOutputsDeque ;
for ( auto o : z_outputs_ ) {
zOutputsDeque . push_back ( o ) ;
}
/**
* SCENARIO # 2
*
* taddr - > taddrs
* - > zaddrs
*
* Note : Consensus rule states that coinbase utxos can only be sent to a zaddr .
* Any change over and above the amount specified by the user will be sent
* to the same zaddr the user is sending funds to .
*/
if ( isfromtaddr_ ) {
add_taddr_outputs_to_tx ( ) ;
CAmount funds = selectedUTXOAmount ;
CAmount fundsSpent = t_outputs_total + minersFee + z_outputs_total ;
CAmount change = funds - fundsSpent ;
// If there is a single zaddr and there are coinbase utxos, change goes to the zaddr.
if ( change > 0 ) {
if ( isSingleZaddrOutput & & selectedUTXOCoinbase ) {
std : : string address = std : : get < 0 > ( zOutputsDeque . front ( ) ) ;
SendManyRecipient smr ( address , change , std : : string ( ) ) ;
zOutputsDeque . push_back ( smr ) ;
} else if ( ! isSingleZaddrOutput & & selectedUTXOCoinbase ) {
// This should not happen and is not allowed
throw JSONRPCError ( RPC_WALLET_ERROR , " Wallet selected Coinbase UTXOs as valid inputs when it should not have done " ) ;
} else {
// If there is a single zaddr and no coinbase utxos, just use a regular output for change.
add_taddr_change_output_to_tx ( change ) ;
}
}
// Create joinsplits, where each output represents a zaddr recipient.
Object obj ;
while ( zOutputsDeque . size ( ) > 0 ) {
AsyncJoinSplitInfo info ;
info . vpub_old = 0 ;
info . vpub_new = 0 ;
int n = 0 ;
while ( n + + < 2 & & zOutputsDeque . size ( ) > 0 ) {
SendManyRecipient smr = zOutputsDeque . front ( ) ;
std : : string address = std : : get < 0 > ( smr ) ;
CAmount value = std : : get < 1 > ( smr ) ;
std : : string hexMemo = std : : get < 2 > ( smr ) ;
zOutputsDeque . pop_front ( ) ;
PaymentAddress pa = CZCPaymentAddress ( address ) . Get ( ) ;
JSOutput jso = JSOutput ( pa , value ) ;
if ( hexMemo . size ( ) > 0 ) {
jso . memo = get_memo_from_hex_string ( hexMemo ) ;
}
info . vjsout . push_back ( jso ) ;
// Funds are removed from the value pool and enter the private pool
info . vpub_old + = value ;
}
obj = perform_joinsplit ( info ) ;
}
sign_send_raw_transaction ( obj ) ;
return true ;
}
/**
* END SCENARIO # 2
*/
/**
* SCENARIO # 3
*
* zaddr - > taddrs
* - > zaddrs
*
* Processing order :
* Part 1 : taddrs and miners fee
* Part 2 : zaddrs
*/
/**
* SCENARIO # 3
* Part 1 : Add to the transparent value pool .
*/
Object obj ;
CAmount jsChange = 0 ; // this is updated after each joinsplit
bool minersFeeProcessed = false ;
if ( t_outputs_total > 0 ) {
add_taddr_outputs_to_tx ( ) ;
CAmount taddrTargetAmount = t_outputs_total + minersFee ;
minersFeeProcessed = true ;
while ( zInputsDeque . size ( ) > 0 & & taddrTargetAmount > 0 ) {
AsyncJoinSplitInfo info ;
info . vpub_old = 0 ;
info . vpub_new = 0 ;
std : : vector < JSOutPoint > outPoints ;
int n = 0 ;
while ( n + + < 2 & & taddrTargetAmount > 0 ) {
SendManyInputJSOP o = zInputsDeque . front ( ) ;
JSOutPoint outPoint = std : : get < 0 > ( o ) ;
Note note = std : : get < 1 > ( o ) ;
CAmount noteFunds = std : : get < 2 > ( o ) ;
zInputsDeque . pop_front ( ) ;
info . notes . push_back ( note ) ;
outPoints . push_back ( outPoint ) ;
// Put value back into the value pool
if ( noteFunds > = taddrTargetAmount ) {
jsChange = noteFunds - taddrTargetAmount ;
info . vpub_new + = taddrTargetAmount ;
} else {
info . vpub_new + = noteFunds ;
}
taddrTargetAmount - = noteFunds ;
if ( taddrTargetAmount < = 0 ) {
break ;
}
}
if ( jsChange > 0 ) {
info . vjsout . push_back ( JSOutput ( ) ) ;
info . vjsout . push_back ( JSOutput ( frompaymentaddress_ , jsChange ) ) ;
}
obj = perform_joinsplit ( info , outPoints ) ;
}
}
/**
* SCENARIO # 3
* Part 2 : Send to zaddrs by chaining JoinSplits together and immediately consuming any change
*/
if ( z_outputs_total > 0 ) {
// Keep track of treestate within this transaction
boost : : unordered_map < uint256 , ZCIncrementalMerkleTree , CCoinsKeyHasher > intermediates ;
std : : vector < uint256 > previousCommitments ;
// NOTE: Randomization of input and output order could break this in future
const int changeOutputIndex = 1 ;
while ( zOutputsDeque . size ( ) > 0 ) {
AsyncJoinSplitInfo info ;
info . vpub_old = 0 ;
info . vpub_new = 0 ;
CAmount jsInputValue = 0 ;
uint256 jsAnchor ;
std : : vector < boost : : optional < ZCIncrementalWitness > > witnesses ;
JSDescription prevJoinSplit ;
// Keep track of previous JoinSplit and its commitments
if ( tx_ . vjoinsplit . size ( ) > 0 ) {
prevJoinSplit = tx_ . vjoinsplit . back ( ) ;
}
// If there is no change, the chain has terminated so we can reset the tracked treestate.
if ( jsChange = = 0 & & tx_ . vjoinsplit . size ( ) > 0 ) {
intermediates . clear ( ) ;
previousCommitments . clear ( ) ;
}
//
// Consume change as the first input of the JoinSplit.
//
if ( jsChange > 0 ) {
LOCK2 ( cs_main , pwalletMain - > cs_wallet ) ;
// Update tree state with previous joinsplit
ZCIncrementalMerkleTree tree ;
auto it = intermediates . find ( prevJoinSplit . anchor ) ;
if ( it ! = intermediates . end ( ) ) {
tree = it - > second ;
} else if ( ! pcoinsTip - > GetAnchorAt ( prevJoinSplit . anchor , tree ) ) {
throw JSONRPCError ( RPC_WALLET_ERROR , " Could not find previous JoinSplit anchor " ) ;
}
// NOTE: We assume the last commitment, output 1, is the change we want
for ( const uint256 & commitment : prevJoinSplit . commitments ) {
tree . append ( commitment ) ;
previousCommitments . push_back ( 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 ) ;
// Decrypt the change note's ciphertext to retrieve some data we need
ZCNoteDecryption decryptor ( spendingkey_ . viewing_key ( ) ) ;
auto hSig = prevJoinSplit . h_sig ( * pzcashParams , tx_ . joinSplitPubKey ) ;
try {
NotePlaintext plaintext = NotePlaintext : : decrypt (
decryptor ,
prevJoinSplit . ciphertexts [ changeOutputIndex ] ,
prevJoinSplit . ephemeralKey ,
hSig ,
( unsigned char ) changeOutputIndex ) ;
Note note = plaintext . note ( frompaymentaddress_ ) ;
info . notes . push_back ( note ) ;
jsInputValue + = plaintext . value ;
} catch ( const std : : exception e ) {
throw JSONRPCError ( RPC_WALLET_ERROR , strprintf ( " Error decrypting output note of previous JoinSplit: %s " , e . what ( ) ) ) ;
}
}
//
// Consume spendable non-change notes
//
std : : vector < Note > vInputNotes ;
std : : vector < JSOutPoint > vOutPoints ;
uint256 inputAnchor ;
int numInputsNeeded = ( jsChange > 0 ) ? 1 : 0 ;
while ( numInputsNeeded + + < 2 & & zInputsDeque . size ( ) > 0 ) {
SendManyInputJSOP t = zInputsDeque . front ( ) ;
JSOutPoint jso = std : : get < 0 > ( t ) ;
Note note = std : : get < 1 > ( t ) ;
CAmount noteFunds = std : : get < 2 > ( t ) ;
zInputsDeque . pop_front ( ) ;
vOutPoints . push_back ( jso ) ;
vInputNotes . push_back ( note ) ;
jsInputValue + = noteFunds ;
}
// 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 " ) ;
}
for ( auto & optionalWitness : vInputWitnesses ) {
if ( ! optionalWitness ) {
throw JSONRPCError ( RPC_WALLET_ERROR , " Witness for note commitment is null " ) ;
}
ZCIncrementalWitness w = * optionalWitness ; // could use .get();
if ( jsChange > 0 ) {
for ( const uint256 & commitment : previousCommitments ) {
w . append ( commitment ) ;
}
if ( jsAnchor ! = w . root ( ) ) {
throw JSONRPCError ( RPC_WALLET_ERROR , " Witness for spendable note does not have same anchor as change input " ) ;
}
}
witnesses . push_back ( w ) ;
}
// The jsAnchor is null if this JoinSplit is at the start of a new chain
if ( jsAnchor . IsNull ( ) ) {
jsAnchor = inputAnchor ;
}
// Add spendable notes as inputs
std : : copy ( vInputNotes . begin ( ) , vInputNotes . end ( ) , std : : back_inserter ( info . notes ) ) ;
}
//
// Find recipient to transfer funds to
//
SendManyRecipient smr = zOutputsDeque . front ( ) ;
std : : string address = std : : get < 0 > ( smr ) ;
CAmount value = std : : get < 1 > ( smr ) ;
std : : string hexMemo = std : : get < 2 > ( smr ) ;
zOutputsDeque . pop_front ( ) ;
// Will we have any change? Has the miners fee been processed yet?
jsChange = 0 ;
CAmount outAmount = value ;
if ( ! minersFeeProcessed ) {
if ( jsInputValue < minersFee ) {
throw JSONRPCError ( RPC_WALLET_ERROR , " Not enough funds to pay miners fee " ) ;
}
outAmount + = minersFee ;
}
if ( jsInputValue > outAmount ) {
jsChange = jsInputValue - outAmount ;
} else if ( outAmount > jsInputValue ) {
// Any amount due is owed to the recipient. Let the miners fee get paid first.
CAmount due = outAmount - jsInputValue ;
SendManyRecipient r = SendManyRecipient ( address , due , hexMemo ) ;
zOutputsDeque . push_front ( r ) ;
// reduce the amount being sent right now to the value of all inputs
value = jsInputValue ;
if ( ! minersFeeProcessed ) {
value - = minersFee ;
}
}
if ( ! minersFeeProcessed ) {
minersFeeProcessed = true ;
info . vpub_new + = minersFee ; // funds flowing back to public pool
}
// create output for recipient
PaymentAddress pa = CZCPaymentAddress ( address ) . Get ( ) ;
JSOutput jso = JSOutput ( pa , value ) ;
if ( hexMemo . size ( ) > 0 ) {
jso . memo = get_memo_from_hex_string ( hexMemo ) ;
}
info . vjsout . push_back ( jso ) ;
// create output for any change
if ( jsChange > 0 ) {
info . vjsout . push_back ( JSOutput ( frompaymentaddress_ , jsChange ) ) ;
}
obj = perform_joinsplit ( info , witnesses , jsAnchor ) ;
}
}
sign_send_raw_transaction ( obj ) ;
return true ;
}
/**
* Sign and send a raw transaction .
* Raw transaction as hex string should be in object field " rawtxn "
*/
void AsyncRPCOperation_sendmany : : sign_send_raw_transaction ( Object obj )
{
// Sign the raw transaction
Value rawtxnValue = find_value ( obj , " rawtxn " ) ;
if ( rawtxnValue . is_null ( ) ) {
throw JSONRPCError ( RPC_WALLET_ERROR , " Missing hex data for raw transaction " ) ;
}
std : : string rawtxn = rawtxnValue . get_str ( ) ;
Value signResultValue = signrawtransaction ( { Value ( rawtxn ) } , false ) ;
Object signResultObject = signResultValue . get_obj ( ) ;
Value completeValue = find_value ( signResultObject , " complete " ) ;
bool complete = completeValue . get_bool ( ) ;
if ( ! complete ) {
// TODO: Maybe get "errors" and print array vErrors into a string
throw JSONRPCError ( RPC_WALLET_ENCRYPTION_FAILED , " Failed to sign transaction " ) ;
}
Value hexValue = find_value ( signResultObject , " hex " ) ;
if ( hexValue . is_null ( ) ) {
throw JSONRPCError ( RPC_WALLET_ERROR , " Missing hex data for signed transaction " ) ;
}
std : : string signedtxn = hexValue . get_str ( ) ;
// Send the signed transaction
if ( ! testmode ) {
Value sendResultValue = sendrawtransaction ( { Value ( signedtxn ) } , false ) ;
if ( sendResultValue . is_null ( ) ) {
throw JSONRPCError ( RPC_WALLET_ERROR , " Send raw transaction did not return an error or a txid. " ) ;
}
std : : string txid = sendResultValue . get_str ( ) ;
Object o ;
o . push_back ( Pair ( " txid " , txid ) ) ;
//o.push_back(Pair("hex", signedtxn));
set_result ( Value ( o ) ) ;
} else {
// Test mode does not send the transaction to the network.
CDataStream stream ( ParseHex ( signedtxn ) , SER_NETWORK , PROTOCOL_VERSION ) ;
CTransaction tx ;
stream > > tx ;
Object o ;
o . push_back ( Pair ( " test " , 1 ) ) ;
o . push_back ( Pair ( " txid " , tx . GetTxid ( ) . ToString ( ) ) ) ;
o . push_back ( Pair ( " hex " , signedtxn ) ) ;
set_result ( Value ( o ) ) ;
}
}
bool AsyncRPCOperation_sendmany : : find_utxos ( bool fAcceptCoinbase = false ) {
set < CBitcoinAddress > setAddress = { fromtaddr_ } ;
vector < COutput > vecOutputs ;
LOCK2 ( cs_main , pwalletMain - > cs_wallet ) ;
pwalletMain - > AvailableCoins ( vecOutputs , false , NULL , true ) ;
BOOST_FOREACH ( const COutput & out , vecOutputs ) {
if ( out . nDepth < mindepth_ ) {
continue ;
}
if ( setAddress . size ( ) ) {
CTxDestination address ;
if ( ! ExtractDestination ( out . tx - > vout [ out . i ] . scriptPubKey , address ) ) {
continue ;
}
if ( ! setAddress . count ( address ) ) {
continue ;
}
}
// By default we ignore coinbase outputs
bool isCoinbase = out . tx - > IsCoinBase ( ) ;
if ( isCoinbase & & fAcceptCoinbase = = false ) {
continue ;
}
CAmount nValue = out . tx - > vout [ out . i ] . nValue ;
SendManyInputUTXO utxo ( out . tx - > GetTxid ( ) , out . i , nValue , isCoinbase ) ;
t_inputs_ . push_back ( utxo ) ;
}
if ( fAcceptCoinbase = = false & & t_inputs_ . size ( ) = = 0 ) {
throw JSONRPCError ( RPC_WALLET_INSUFFICIENT_FUNDS , " Could not find any non-coinbase UTXOs to spend. " ) ;
}
return t_inputs_ . size ( ) > 0 ;
}
bool AsyncRPCOperation_sendmany : : find_unspent_notes ( ) {
LOCK2 ( cs_main , pwalletMain - > cs_wallet ) ;
for ( auto & p : pwalletMain - > mapWallet ) {
CWalletTx wtx = p . second ;
// Filter the transactions before checking for notes
if ( ! CheckFinalTx ( wtx ) | | wtx . GetBlocksToMaturity ( ) > 0 | | wtx . GetDepthInMainChain ( ) < mindepth_ ) {
continue ;
}
mapNoteData_t mapNoteData = pwalletMain - > FindMyNotes ( wtx ) ;
if ( mapNoteData . size ( ) = = 0 ) {
continue ;
}
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
if ( ! ( pa = = frompaymentaddress_ ) ) {
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
// 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 (
decryptor ,
wtx . vjoinsplit [ i ] . ciphertexts [ j ] ,
wtx . vjoinsplit [ i ] . ephemeralKey ,
hSig ,
( unsigned char ) j ) ;
z_inputs_ . push_back ( SendManyInputJSOP ( jsop , plaintext . note ( pa ) , CAmount ( plaintext . value ) ) ) ;
std : : string data ( plaintext . memo . begin ( ) , plaintext . memo . end ( ) ) ;
LogPrint ( " asyncrpc " , " %s: found unspent note (txid=%s, vjoinsplit=%d, ciphertext=%d, amount=%s, memo=%s) \n " ,
getId ( ) . substr ( 0 , 10 ) ,
wtx . GetTxid ( ) . ToString ( ) . substr ( 0 , 10 ) ,
i , j ,
FormatMoney ( plaintext . value , false ) ,
HexStr ( data ) . substr ( 0 , 10 )
) ;
} catch ( const std : : exception & ) {
// Couldn't decrypt with this spending key
}
}
}
if ( z_inputs_ . size ( ) = = 0 ) {
return false ;
}
// sort in descending order, so big notes appear first
std : : sort ( z_inputs_ . begin ( ) , z_inputs_ . end ( ) , [ ] ( SendManyInputJSOP i , SendManyInputJSOP j ) - > bool {
return ( std : : get < 2 > ( i ) > std : : get < 2 > ( j ) ) ;
} ) ;
return true ;
}
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 ) ;
}
Object AsyncRPCOperation_sendmany : : perform_joinsplit ( AsyncJoinSplitInfo & info , std : : vector < JSOutPoint > & outPoints ) {
std : : vector < boost : : optional < ZCIncrementalWitness > > witnesses ;
uint256 anchor ;
{
LOCK ( cs_main ) ;
pwalletMain - > GetNoteWitnesses ( outPoints , witnesses , anchor ) ;
}
return perform_joinsplit ( info , witnesses , anchor ) ;
}
Object AsyncRPCOperation_sendmany : : perform_joinsplit (
AsyncJoinSplitInfo & info ,
std : : vector < boost : : optional < ZCIncrementalWitness > > witnesses ,
uint256 anchor )
{
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 ] , spendingkey_ ) ) ;
}
// Make sure there are two inputs and two outputs
while ( info . vjsin . size ( ) < ZC_NUM_JS_INPUTS ) {
info . vjsin . push_back ( JSInput ( ) ) ;
}
while ( info . vjsout . size ( ) < ZC_NUM_JS_OUTPUTS ) {
info . vjsout . push_back ( JSOutput ( ) ) ;
}
if ( info . vjsout . size ( ) ! = ZC_NUM_JS_INPUTS | | info . vjsin . size ( ) ! = ZC_NUM_JS_OUTPUTS ) {
throw runtime_error ( " unsupported joinsplit input/output counts " ) ;
}
CMutableTransaction mtx ( tx_ ) ;
LogPrint ( " asyncrpc " , " %s: creating joinsplit at index %d (vpub_old=%s, vpub_new=%s, in[0]=%s, in[1]=%s, out[0]=%s, out[1]=%s) \n " ,
getId ( ) . substr ( 0 , 10 ) ,
tx_ . vjoinsplit . size ( ) ,
FormatMoney ( info . vpub_old , false ) , FormatMoney ( info . vpub_new , false ) ,
FormatMoney ( info . vjsin [ 0 ] . note . value , false ) , FormatMoney ( info . vjsin [ 1 ] . note . value , false ) ,
FormatMoney ( info . vjsout [ 0 ] . value , false ) , FormatMoney ( info . vjsout [ 1 ] . value , false )
) ;
// Generate the proof, this can take over a minute.
JSDescription jsdesc ( * pzcashParams ,
joinSplitPubKey_ ,
anchor ,
{ info . vjsin [ 0 ] , info . vjsin [ 1 ] } ,
{ info . vjsout [ 0 ] , info . vjsout [ 1 ] } ,
info . vpub_old ,
info . vpub_new ,
! this - > testmode ) ;
if ( ! ( jsdesc . Verify ( * zcashParams_ , joinSplitPubKey_ ) ) ) {
throw std : : runtime_error ( " error verifying joinsplit " ) ;
}
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
if ( ! ( crypto_sign_detached ( & mtx . joinSplitSig [ 0 ] , NULL ,
dataToBeSigned . begin ( ) , 32 ,
joinSplitPrivKey_
) = = 0 ) )
{
throw std : : runtime_error ( " crypto_sign_detached failed " ) ;
}
// Sanity check
if ( ! ( crypto_sign_verify_detached ( & mtx . joinSplitSig [ 0 ] ,
dataToBeSigned . begin ( ) , 32 ,
mtx . joinSplitPubKey . begin ( )
) = = 0 ) )
{
throw std : : runtime_error ( " crypto_sign_verify_detached failed " ) ;
}
CTransaction rawTx ( mtx ) ;
tx_ = rawTx ;
CDataStream ss ( SER_NETWORK , PROTOCOL_VERSION ) ;
ss < < rawTx ;
std : : string encryptedNote1 ;
std : : string encryptedNote2 ;
{
CDataStream ss2 ( SER_NETWORK , PROTOCOL_VERSION ) ;
ss2 < < ( ( unsigned char ) 0x00 ) ;
ss2 < < jsdesc . ephemeralKey ;
ss2 < < jsdesc . ciphertexts [ 0 ] ;
ss2 < < jsdesc . h_sig ( * pzcashParams , joinSplitPubKey_ ) ;
encryptedNote1 = HexStr ( ss2 . begin ( ) , ss2 . end ( ) ) ;
}
{
CDataStream ss2 ( SER_NETWORK , PROTOCOL_VERSION ) ;
ss2 < < ( ( unsigned char ) 0x01 ) ;
ss2 < < jsdesc . ephemeralKey ;
ss2 < < jsdesc . ciphertexts [ 1 ] ;
ss2 < < jsdesc . h_sig ( * pzcashParams , joinSplitPubKey_ ) ;
encryptedNote2 = HexStr ( ss2 . begin ( ) , ss2 . end ( ) ) ;
}
Object obj ;
obj . push_back ( Pair ( " encryptednote1 " , encryptedNote1 ) ) ;
obj . push_back ( Pair ( " encryptednote2 " , encryptedNote2 ) ) ;
obj . push_back ( Pair ( " rawtxn " , HexStr ( ss . begin ( ) , ss . end ( ) ) ) ) ;
return obj ;
}
void AsyncRPCOperation_sendmany : : add_taddr_outputs_to_tx ( ) {
CMutableTransaction rawTx ( tx_ ) ;
for ( SendManyRecipient & r : t_outputs_ ) {
std : : string outputAddress = std : : get < 0 > ( r ) ;
CAmount nAmount = std : : get < 1 > ( r ) ;
CBitcoinAddress address ( outputAddress ) ;
if ( ! address . IsValid ( ) ) {
throw JSONRPCError ( RPC_INVALID_ADDRESS_OR_KEY , " Invalid output address, not a valid taddr. " ) ;
}
CScript scriptPubKey = GetScriptForDestination ( address . Get ( ) ) ;
CTxOut out ( nAmount , scriptPubKey ) ;
rawTx . vout . push_back ( out ) ;
}
tx_ = CTransaction ( rawTx ) ;
}
void AsyncRPCOperation_sendmany : : add_taddr_change_output_to_tx ( CAmount amount ) {
LOCK2 ( cs_main , pwalletMain - > cs_wallet ) ;
EnsureWalletIsUnlocked ( ) ;
CReserveKey keyChange ( pwalletMain ) ;
CPubKey vchPubKey ;
bool ret = keyChange . GetReservedKey ( vchPubKey ) ;
if ( ! ret ) {
throw JSONRPCError ( RPC_WALLET_KEYPOOL_RAN_OUT , " Could not generate a taddr to use as a change address " ) ; // should never fail, as we just unlocked
}
CScript scriptPubKey = GetScriptForDestination ( vchPubKey . GetID ( ) ) ;
CTxOut out ( amount , scriptPubKey ) ;
CMutableTransaction rawTx ( tx_ ) ;
rawTx . vout . push_back ( out ) ;
tx_ = CTransaction ( rawTx ) ;
}
boost : : array < unsigned char , ZC_MEMO_SIZE > AsyncRPCOperation_sendmany : : get_memo_from_hex_string ( std : : string s ) {
boost : : array < unsigned char , ZC_MEMO_SIZE > memo = { { 0x00 } } ;
std : : vector < unsigned char > rawMemo = ParseHex ( s . c_str ( ) ) ;
// If ParseHex comes across a non-hex char, it will stop but still return results so far.
size_t slen = s . length ( ) ;
if ( slen % 2 ! = 0 | | ( slen > 0 & & rawMemo . size ( ) ! = slen / 2 ) ) {
throw JSONRPCError ( RPC_INVALID_PARAMETER , " Memo must be in hexadecimal format " ) ;
}
if ( rawMemo . size ( ) > ZC_MEMO_SIZE ) {
throw JSONRPCError ( RPC_INVALID_PARAMETER , strprintf ( " Memo size of %d is too big, maximum allowed is %d " , rawMemo . size ( ) , ZC_MEMO_SIZE ) ) ;
}
// copy vector into boost array
int lenMemo = rawMemo . size ( ) ;
for ( int i = 0 ; i < ZC_MEMO_SIZE & & i < lenMemo ; i + + ) {
memo [ i ] = rawMemo [ i ] ;
}
return memo ;
}