@ -1,5 +1,6 @@
// Copyright (c) 2010 Satoshi Nakamoto
// Copyright (c) 2009-2014 The Bitcoin Core developers
// Copyright (c) 2019 The Hush developers
// Distributed under the MIT software license, see the accompanying
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
@ -4528,6 +4529,248 @@ UniValue z_sendmany(const UniValue& params, bool fHelp)
return operationId ;
}
UniValue z_createrawtransaction ( const UniValue & params , bool fHelp )
{
if ( ! EnsureWalletIsAvailable ( fHelp ) )
return NullUniValue ;
if ( fHelp | | params . size ( ) < 2 | | params . size ( ) > 4 )
throw runtime_error (
" z_createrawtransaction [{ \" txid \" : \" id \" , \" outindex \" :n, \" address \" : \" address \" },...] { \" address \" : \" address \" , \" amount \" :5.555, \" memo \" : \" ... \" ,...} ( locktime ) ( expiryheight ) \n "
" \n Create a raw shielded transaction, involving at least one shielded input or output. Amounts are decimal numbers with at most 8 digits of precision. \n "
" Returns hex-encoded raw transaction. \n "
" Note that the transaction's inputs are not signed, and it is not stored in the wallet or transmitted to the network. \n "
" \n Arguments: \n "
" 1. \" inputs \" (string, required) A json array of json objects, UTXOs or Sapling Notes \n "
" [ \n "
" { \n "
" \" txid \" : \" id \" , (string, required) The transaction id \n "
" \" vout \" :n (numeric, required) The transparent output number \n "
" \" sequence \" :n (numeric, optional) The sequence number \n "
" }, \n "
" { \n "
" \" txid \" : \" id \" , (string, required) The transaction id \n "
" \" outindex \" :n (numeric, required) The JoinSplit output number (reported by z_listunspent) \n "
" \" address \" :n (string, required) The address which owns the note \n "
" } \n "
" ,... \n "
" ] \n "
" 2. \" outputs \" (string, required) A json array of json objects \n "
" [ \n "
" { \n "
" \" address \" :n (string, required) The address receiving funds \n "
" \" amount \" :n (numeric, required) The amount to send \n "
" \" memo \" :n (string, optional) Hex-encoded memo, shielded outputs only \n "
" } \n "
" ,... \n "
" ] \n "
" 3. locktime (numeric, optional, default=0) Raw locktime. Non-0 value also locktime-activates inputs \n "
" 4. expiryheight (numeric, optional, default= " + strprintf ( " %d " , DEFAULT_TX_EXPIRY_DELTA ) + " ) Expiry height of transaction (if Overwinter is active) \n "
" \n Result: \n "
" \" transaction \" (string) hex string of the transaction \n "
" \n Examples \n "
+ HelpExampleCli ( " z_createrawtransaction " , " \" [{ \\ \" txid \\ \" : \\ \" myid \\ \" , \\ \" outindex \\ \" :0}] \" \" { \\ \" address \\ \" : \\ \" zaddr \\ \" , \\ \" amount \\ \" :5} \" " )
) ;
LOCK2 ( cs_main , pwalletMain - > cs_wallet ) ;
UniValue inputs = params [ 0 ] . get_array ( ) ;
UniValue outputs = params [ 1 ] . get_array ( ) ;
bool fromTaddr = false ;
bool fromSapling = false ;
uint32_t branchId = CurrentEpochBranchId ( chainActive . Height ( ) , Params ( ) . GetConsensus ( ) ) ;
// Keep track of addresses to spot duplicates
set < std : : string > setAddress ;
// Recipients
std : : vector < SendManyRecipient > taddrRecipients ;
std : : vector < SendManyRecipient > zaddrRecipients ;
CAmount nTotalOut = 0 ;
if ( inputs . size ( ) = = 0 )
throw JSONRPCError ( RPC_INVALID_PARAMETER , " Invalid parameter, inputs array is empty. " ) ;
if ( outputs . size ( ) = = 0 )
throw JSONRPCError ( RPC_INVALID_PARAMETER , " Invalid parameter, outputs array is empty. " ) ;
int nextBlockHeight = chainActive . Height ( ) + 1 ;
TransactionBuilder builder = TransactionBuilder ( Params ( ) . GetConsensus ( ) , nextBlockHeight , pwalletMain ) ;
//builder.SetFee(minersFee);
// process inputs
for ( const UniValue & input : inputs . getValues ( ) ) {
const UniValue & o = input . get_obj ( ) ;
uint256 txid = ParseHashO ( o , " txid " ) ;
const UniValue & vout_v = find_value ( o , " vout " ) ;
const UniValue & outindex = find_value ( o , " outindex " ) ;
const UniValue & amount = find_value ( o , " amount " ) ;
const UniValue & addr = find_value ( o , " address " ) ;
int nOutindex = outindex . get_int ( ) ;
int nAmount = amount . get_int ( ) ;
int nOutput = vout_v . get_int ( ) ;
std : : string fromAddress = addr . get_str ( ) ;
if ( ! vout_v . isNum ( ) & & ! outindex . isNum ( ) )
throw JSONRPCError ( RPC_INVALID_PARAMETER , " Invalid input, must provide either vout or outindex " ) ;
if ( vout_v . isNum ( ) & & outindex . isNum ( ) )
throw JSONRPCError ( RPC_INVALID_PARAMETER , " Invalid input, cannot provide both vout and outindex " ) ;
// add input to transaction builder
if ( nOutput ) {
// transparent input
if ( nOutput < 0 )
throw JSONRPCError ( RPC_INVALID_PARAMETER , " Invalid parameter, vout must be positive " ) ;
builder . AddTransparentInput ( COutPoint ( txid , nOutput ) , CScript ( ) , nAmount ) ;
} else if ( nOutindex ) {
// shielded input
if ( nOutindex < 0 )
throw JSONRPCError ( RPC_INVALID_PARAMETER , " Invalid parameter, outindex must be positive " ) ;
SaplingExpandedSpendingKey expsk ;
uint256 ovk ;
auto address = DecodePaymentAddress ( fromAddress ) ;
if ( ! boost : : apply_visitor ( HaveSpendingKeyForPaymentAddress ( pwalletMain ) , address ) ) {
throw JSONRPCError ( RPC_INVALID_ADDRESS_OR_KEY , " Invalid from address, no spending key found for zaddr " ) ;
}
SpendingKey spendingkey = boost : : apply_visitor ( GetSpendingKeyForPaymentAddress ( pwalletMain ) , address ) . get ( ) ;
auto sk = boost : : get < libzcash : : SaplingExtendedSpendingKey > ( spendingkey ) ;
expsk = sk . expsk ;
ovk = expsk . full_viewing_key ( ) . ovk ;
// Fetch Sapling anchor and witnesses
std : : vector < SaplingOutPoint > ops ;
std : : vector < SaplingNote > notes ;
uint256 anchor ;
std : : vector < SaplingNoteEntry > z_sapling_inputs ;
for ( auto t : z_sapling_inputs ) {
ops . push_back ( t . op ) ;
notes . push_back ( t . note ) ;
}
std : : vector < boost : : optional < SaplingWitness > > witnesses ;
{
LOCK2 ( cs_main , pwalletMain - > cs_wallet ) ;
pwalletMain - > GetSaplingNoteWitnesses ( ops , witnesses , anchor ) ;
}
assert ( builder . AddSaplingSpend ( expsk , notes [ 0 ] , anchor , witnesses [ 0 ] . get ( ) ) ) ;
}
}
// Build the transaction
auto maybe_tx = builder . Build ( ) ;
if ( ! maybe_tx ) {
throw JSONRPCError ( RPC_WALLET_ERROR , " Failed to build transaction. " ) ;
}
//auto tx = maybe_tx.get();
// process outputs
for ( const UniValue & o : outputs . getValues ( ) ) {
if ( ! o . isObject ( ) )
throw JSONRPCError ( RPC_INVALID_PARAMETER , " Invalid parameter, expected object " ) ;
// sanity check, report error if unknown key-value pairs
for ( const string & name_ : o . getKeys ( ) ) {
std : : string s = name_ ;
if ( s ! = " address " & & s ! = " amount " & & s ! = " memo " )
throw JSONRPCError ( RPC_INVALID_PARAMETER , string ( " Invalid parameter, unknown key: " ) + s ) ;
}
string address = find_value ( o , " address " ) . get_str ( ) ;
bool isZaddr = false ;
CTxDestination taddr = DecodeDestination ( address ) ;
if ( ! IsValidDestination ( taddr ) ) {
auto res = DecodePaymentAddress ( address ) ;
if ( IsValidPaymentAddress ( res , branchId ) ) {
isZaddr = true ;
bool toSapling = boost : : get < libzcash : : SaplingPaymentAddress > ( & res ) ! = nullptr ;
bool toSprout = ! toSapling ;
if ( GetTime ( ) > KOMODO_SAPLING_DEADLINE )
{
if ( toSprout )
throw JSONRPCError ( RPC_INVALID_PARAMETER , " Sprout usage has expired " ) ;
}
} else {
throw JSONRPCError ( RPC_INVALID_PARAMETER , string ( " Invalid parameter, unknown address format: " ) + address ) ;
}
}
setAddress . insert ( address ) ;
UniValue memoValue = find_value ( o , " memo " ) ;
string memo ;
if ( ! memoValue . isNull ( ) ) {
memo = memoValue . get_str ( ) ;
if ( ! isZaddr ) {
throw JSONRPCError ( RPC_INVALID_PARAMETER , " Memo cannot be used with a taddr. It can only be used with a zaddr. " ) ;
} else if ( ! IsHex ( memo ) ) {
throw JSONRPCError ( RPC_INVALID_PARAMETER , " Invalid parameter, expected memo data in hexadecimal format. " ) ;
}
if ( memo . length ( ) > ZC_MEMO_SIZE * 2 ) {
throw JSONRPCError ( RPC_INVALID_PARAMETER , strprintf ( " Invalid parameter, size of memo is larger than maximum allowed %d " , ZC_MEMO_SIZE ) ) ;
}
}
UniValue av = find_value ( o , " amount " ) ;
CAmount nAmount = AmountFromValue ( av ) ;
if ( nAmount < 0 )
throw JSONRPCError ( RPC_INVALID_PARAMETER , " Invalid parameter, amount must be positive " ) ;
if ( isZaddr ) {
zaddrRecipients . push_back ( SendManyRecipient ( address , nAmount , memo ) ) ;
} else {
taddrRecipients . push_back ( SendManyRecipient ( address , nAmount , memo ) ) ;
}
nTotalOut + = nAmount ;
}
CMutableTransaction mtx ;
mtx . fOverwintered = true ;
mtx . nVersionGroupId = SAPLING_VERSION_GROUP_ID ;
mtx . nVersion = SAPLING_TX_VERSION ;
unsigned int max_tx_size = MAX_TX_SIZE_AFTER_SAPLING ;
size_t txsize = 0 ;
for ( int i = 0 ; i < zaddrRecipients . size ( ) ; i + + ) {
auto address = std : : get < 0 > ( zaddrRecipients [ i ] ) ;
auto res = DecodePaymentAddress ( address ) ;
bool toSapling = boost : : get < libzcash : : SaplingPaymentAddress > ( & res ) ! = nullptr ;
if ( toSapling ) {
mtx . vShieldedOutput . push_back ( OutputDescription ( ) ) ;
}
}
CTransaction tx ( mtx ) ;
txsize + = GetSerializeSize ( tx , 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
int nMinDepth = 1 ;
if ( params . size ( ) > 2 ) {
nMinDepth = params [ 2 ] . get_int ( ) ;
}
if ( nMinDepth < 0 ) {
throw JSONRPCError ( RPC_INVALID_PARAMETER , " Minimum number of confirmations cannot be less than 0 " ) ;
}
return EncodeHexTx ( builder . Build ( ) . get ( ) ) ;
}
/**
When estimating the number of coinbase utxos we can shield in a single transaction :