2019-04-02 16:51:32 -04:00
// Copyright (c) 2010 Satoshi Nakamoto
2020-04-16 13:14:08 -04:00
// Copyright (c) 2009-2020 The Bitcoin Core developers
2019-04-02 16:51:32 -04:00
// Distributed under the MIT software license, see the accompanying
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
# include <rpc/rawtransaction_util.h>
2020-11-09 21:20:40 +00:00
# include <block_proof.h>
2019-04-02 16:51:32 -04:00
# include <coins.h>
# include <core_io.h>
# include <key_io.h>
2020-10-28 03:15:23 +00:00
# include <pegins.h>
2019-04-02 16:51:32 -04:00
# include <policy/policy.h>
# include <primitives/transaction.h>
2020-11-09 21:20:40 +00:00
# include <primitives/bitcoin/merkleblock.h>
# include <primitives/bitcoin/transaction.h>
2019-06-20 02:39:38 +09:00
# include <rpc/request.h>
2019-04-02 16:51:32 -04:00
# include <rpc/util.h>
2019-06-06 22:52:24 +02:00
# include <script/sign.h>
# include <script/signingprovider.h>
2019-04-02 16:51:32 -04:00
# include <tinyformat.h>
# include <univalue.h>
2019-04-02 17:03:37 -04:00
# include <util/rbf.h>
2019-04-02 16:51:32 -04:00
# include <util/strencodings.h>
2020-11-09 21:20:21 +00:00
# include <validation.h>
2019-04-02 16:51:32 -04:00
2020-11-09 21:20:40 +00:00
template < typename T_tx >
unsigned int GetPeginTxnOutputIndex ( const T_tx & txn , const CScript & witnessProgram , const std : : vector < std : : pair < CScript , CScript > > & fedpegscripts )
{
for ( const auto & scripts : fedpegscripts ) {
2020-11-27 21:29:55 +00:00
CScript mainchain_script = GetScriptForDestination ( WitnessV0ScriptHash ( calculate_contract ( scripts . second , witnessProgram ) ) ) ;
2020-11-09 21:20:40 +00:00
if ( scripts . first . IsPayToScriptHash ( ) ) {
mainchain_script = GetScriptForDestination ( ScriptHash ( mainchain_script ) ) ;
}
for ( unsigned int nOut = 0 ; nOut < txn . vout . size ( ) ; nOut + + ) {
if ( txn . vout [ nOut ] . scriptPubKey = = mainchain_script ) {
return nOut ;
}
}
}
return txn . vout . size ( ) ;
}
// Modifies an existing transaction input in-place to be a valid peg-in input, and inserts the witness if deemed valid.
template < typename T_tx_ref , typename T_merkle_block >
static void CreatePegInInputInner ( CMutableTransaction & mtx , uint32_t input_idx , T_tx_ref & txBTCRef , T_merkle_block & merkleBlock , const std : : set < CScript > & claim_scripts , const std : : vector < unsigned char > & txData , const std : : vector < unsigned char > & txOutProofData )
{
if ( ( mtx . vin . size ( ) > input_idx & & ! mtx . vin [ input_idx ] . scriptSig . empty ( ) ) | | ( mtx . witness . vtxinwit . size ( ) > input_idx & & ! mtx . witness . vtxinwit [ input_idx ] . IsNull ( ) ) ) {
throw JSONRPCError ( RPC_INVALID_PARAMETER , " Attempting to add a peg-in to an input that already has a scriptSig or witness " ) ;
}
CDataStream ssTx ( txData , SER_NETWORK , PROTOCOL_VERSION ) ;
try {
ssTx > > txBTCRef ;
}
catch ( . . . ) {
throw JSONRPCError ( RPC_TYPE_ERROR , " The included bitcoinTx is malformed. Are you sure that is the whole string? " ) ;
}
CDataStream ssTxOutProof ( txOutProofData , SER_NETWORK , PROTOCOL_VERSION | SERIALIZE_TRANSACTION_NO_WITNESS ) ;
try {
ssTxOutProof > > merkleBlock ;
}
catch ( . . . ) {
throw JSONRPCError ( RPC_TYPE_ERROR , " The included txoutproof is malformed. Are you sure that is the whole string? " ) ;
}
if ( ! ssTxOutProof . empty ( ) ) {
throw JSONRPCError ( RPC_INVALID_PARAMETER , " Invalid tx out proof " ) ;
}
std : : vector < uint256 > txHashes ;
std : : vector < unsigned int > txIndices ;
if ( merkleBlock . txn . ExtractMatches ( txHashes , txIndices ) ! = merkleBlock . header . hashMerkleRoot )
throw JSONRPCError ( RPC_INVALID_PARAMETER , " Invalid tx out proof " ) ;
if ( txHashes . size ( ) ! = 1 | | txHashes [ 0 ] ! = txBTCRef - > GetHash ( ) )
throw JSONRPCError ( RPC_INVALID_PARAMETER , " The txoutproof must contain bitcoinTx and only bitcoinTx " ) ;
CScript witness_script ;
unsigned int nOut = txBTCRef - > vout . size ( ) ;
const auto fedpegscripts = GetValidFedpegScripts ( : : ChainActive ( ) . Tip ( ) , Params ( ) . GetConsensus ( ) , true /* nextblock_validation */ ) ;
for ( const CScript & script : claim_scripts ) {
nOut = GetPeginTxnOutputIndex ( * txBTCRef , script , fedpegscripts ) ;
if ( nOut ! = txBTCRef - > vout . size ( ) ) {
witness_script = script ;
break ;
}
}
if ( nOut = = txBTCRef - > vout . size ( ) ) {
if ( claim_scripts . size ( ) = = 1 ) {
throw JSONRPCError ( RPC_INVALID_PARAMETER , " Given claim_script does not match the given Bitcoin transaction. " ) ;
} else {
throw JSONRPCError ( RPC_INVALID_PARAMETER , " Failed to find output in bitcoinTx to the mainchain_address from getpeginaddress " ) ;
}
}
2020-12-04 15:17:46 +00:00
CHECK_NONFATAL ( witness_script ! = CScript ( ) ) ;
2020-11-09 21:20:40 +00:00
int version = - 1 ;
std : : vector < unsigned char > witness_program ;
if ( ! witness_script . IsWitnessProgram ( version , witness_program ) | | version ! = 0 ) {
throw JSONRPCError ( RPC_INVALID_PARAMETER , " Given or recovered script is not a v0 witness program. " ) ;
}
CAmount value = 0 ;
if ( ! GetAmountFromParentChainPegin ( value , * txBTCRef , nOut ) ) {
throw JSONRPCError ( RPC_INVALID_PARAMETER , strprintf ( " Amounts to pegin must be explicit and asset must be %s " , Params ( ) . GetConsensus ( ) . parent_pegged_asset . GetHex ( ) ) ) ;
}
// Add/replace input in mtx
if ( mtx . vin . size ( ) < = input_idx ) {
mtx . vin . resize ( input_idx + 1 ) ;
}
mtx . vin [ input_idx ] = CTxIn ( COutPoint ( txHashes [ 0 ] , nOut ) , CScript ( ) , ~ ( uint32_t ) 0 ) ;
// Construct pegin proof
CScriptWitness pegin_witness = CreatePeginWitness ( value , Params ( ) . GetConsensus ( ) . pegged_asset , Params ( ) . ParentGenesisBlockHash ( ) , witness_script , txBTCRef , merkleBlock ) ;
// Peg-in witness isn't valid, even though the block header is(without depth check)
// We re-check depth before returning with more descriptive result
std : : string err ;
if ( ! IsValidPeginWitness ( pegin_witness , fedpegscripts , mtx . vin [ input_idx ] . prevout , err , false ) ) {
throw JSONRPCError ( RPC_INVALID_PARAMETER , strprintf ( " Constructed peg-in witness is invalid: %s " , err ) ) ;
}
// Put input witness in transaction
mtx . vin [ input_idx ] . m_is_pegin = true ;
CTxInWitness txinwit ;
txinwit . m_pegin_witness = pegin_witness ;
if ( mtx . witness . vtxinwit . size ( ) < = input_idx ) {
mtx . witness . vtxinwit . resize ( input_idx + 1 ) ;
}
mtx . witness . vtxinwit [ input_idx ] = txinwit ;
}
void CreatePegInInput ( CMutableTransaction & mtx , uint32_t input_idx , CTransactionRef & tx_btc , CMerkleBlock & merkle_block , const std : : set < CScript > & claim_scripts , const std : : vector < unsigned char > & txData , const std : : vector < unsigned char > & txOutProofData )
{
CreatePegInInputInner ( mtx , input_idx , tx_btc , merkle_block , claim_scripts , txData , txOutProofData ) ;
}
void CreatePegInInput ( CMutableTransaction & mtx , uint32_t input_idx , Sidechain : : Bitcoin : : CTransactionRef & tx_btc , Sidechain : : Bitcoin : : CMerkleBlock & merkle_block , const std : : set < CScript > & claim_scripts , const std : : vector < unsigned char > & txData , const std : : vector < unsigned char > & txOutProofData )
{
CreatePegInInputInner ( mtx , input_idx , tx_btc , merkle_block , claim_scripts , txData , txOutProofData ) ;
}
CMutableTransaction ConstructTransaction ( const UniValue & inputs_in , const UniValue & outputs_in , const UniValue & locktime , bool rbf , const UniValue & assets_in , std : : vector < CPubKey > * output_pubkeys_out , bool allow_peg_in )
2019-04-02 16:51:32 -04:00
{
2020-09-17 15:29:32 +02:00
if ( outputs_in . isNull ( ) ) {
2019-07-11 18:50:45 +01:00
throw JSONRPCError ( RPC_INVALID_PARAMETER , " Invalid parameter, output argument must be non-null " ) ;
2020-09-17 15:29:32 +02:00
}
2019-07-11 18:50:45 +01:00
UniValue inputs ;
2020-09-17 15:29:32 +02:00
if ( inputs_in . isNull ( ) ) {
2019-07-11 18:50:45 +01:00
inputs = UniValue : : VARR ;
2020-09-17 15:29:32 +02:00
} else {
2019-07-11 18:50:45 +01:00
inputs = inputs_in . get_array ( ) ;
2020-09-17 15:29:32 +02:00
}
2019-04-02 16:51:32 -04:00
const bool outputs_is_obj = outputs_in . isObject ( ) ;
UniValue outputs = outputs_is_obj ? outputs_in . get_obj ( ) : outputs_in . get_array ( ) ;
CMutableTransaction rawTx ;
if ( ! locktime . isNull ( ) ) {
int64_t nLockTime = locktime . get_int64 ( ) ;
if ( nLockTime < 0 | | nLockTime > LOCKTIME_MAX )
throw JSONRPCError ( RPC_INVALID_PARAMETER , " Invalid parameter, locktime out of range " ) ;
rawTx . nLockTime = nLockTime ;
}
2020-10-28 03:15:23 +00:00
UniValue assets ;
if ( ! assets_in . isNull ( ) ) {
assets = assets_in . get_obj ( ) ;
}
2019-04-02 16:51:32 -04:00
for ( unsigned int idx = 0 ; idx < inputs . size ( ) ; idx + + ) {
const UniValue & input = inputs [ idx ] ;
const UniValue & o = input . get_obj ( ) ;
uint256 txid = ParseHashO ( o , " txid " ) ;
const UniValue & vout_v = find_value ( o , " vout " ) ;
if ( ! vout_v . isNum ( ) )
throw JSONRPCError ( RPC_INVALID_PARAMETER , " Invalid parameter, missing vout key " ) ;
int nOutput = vout_v . get_int ( ) ;
if ( nOutput < 0 )
2020-09-30 20:43:05 +03:30
throw JSONRPCError ( RPC_INVALID_PARAMETER , " Invalid parameter, vout cannot be negative " ) ;
2019-04-02 16:51:32 -04:00
uint32_t nSequence ;
2019-04-27 19:44:38 +02:00
if ( rbf ) {
2019-04-02 16:51:32 -04:00
nSequence = MAX_BIP125_RBF_SEQUENCE ; /* CTxIn::SEQUENCE_FINAL - 2 */
} else if ( rawTx . nLockTime ) {
nSequence = CTxIn : : SEQUENCE_FINAL - 1 ;
} else {
nSequence = CTxIn : : SEQUENCE_FINAL ;
}
// set the sequence number if passed in the parameters object
const UniValue & sequenceObj = find_value ( o , " sequence " ) ;
if ( sequenceObj . isNum ( ) ) {
int64_t seqNr64 = sequenceObj . get_int64 ( ) ;
if ( seqNr64 < 0 | | seqNr64 > CTxIn : : SEQUENCE_FINAL ) {
throw JSONRPCError ( RPC_INVALID_PARAMETER , " Invalid parameter, sequence number is out of range " ) ;
} else {
nSequence = ( uint32_t ) seqNr64 ;
}
}
CTxIn in ( COutPoint ( txid , nOutput ) , CScript ( ) , nSequence ) ;
rawTx . vin . push_back ( in ) ;
2020-11-09 21:20:40 +00:00
// Get the pegin stuff if it's there
const UniValue & pegin_tx = find_value ( o , " pegin_bitcoin_tx " ) ;
const UniValue & pegin_tx_proof = find_value ( o , " pegin_txout_proof " ) ;
const UniValue & pegin_script = find_value ( o , " pegin_claim_script " ) ;
if ( ! pegin_tx . isNull ( ) & & ! pegin_tx_proof . isNull ( ) & & ! pegin_script . isNull ( ) & & allow_peg_in ) {
if ( ! IsHex ( pegin_script . get_str ( ) ) ) {
throw JSONRPCError ( RPC_INVALID_PARAMETER , " Given claim_script is not hex. " ) ;
}
// If given manually, no need for it to be a witness script
std : : vector < unsigned char > claim_script_bytes ( ParseHex ( pegin_script . get_str ( ) ) ) ;
CScript claim_script ( claim_script_bytes . begin ( ) , claim_script_bytes . end ( ) ) ;
std : : set < CScript > claim_scripts ;
claim_scripts . insert ( std : : move ( claim_script ) ) ;
if ( Params ( ) . GetConsensus ( ) . ParentChainHasPow ( ) ) {
Sidechain : : Bitcoin : : CTransactionRef tx_btc ;
Sidechain : : Bitcoin : : CMerkleBlock merkle_block ;
CreatePegInInput ( rawTx , idx , tx_btc , merkle_block , claim_scripts , ParseHex ( pegin_tx . get_str ( ) ) , ParseHex ( pegin_tx_proof . get_str ( ) ) ) ;
if ( ! CheckParentProofOfWork ( merkle_block . header . GetHash ( ) , merkle_block . header . nBits , Params ( ) . GetConsensus ( ) ) ) {
throw JSONRPCError ( RPC_INVALID_PARAMETER , " Invalid tx out proof " ) ;
}
} else {
CTransactionRef tx_btc ;
CMerkleBlock merkle_block ;
CreatePegInInput ( rawTx , idx , tx_btc , merkle_block , claim_scripts , ParseHex ( pegin_tx . get_str ( ) ) , ParseHex ( pegin_tx_proof . get_str ( ) ) ) ;
if ( ! CheckProofSignedParent ( merkle_block . header , Params ( ) . GetConsensus ( ) ) ) {
throw JSONRPCError ( RPC_INVALID_PARAMETER , " Invalid tx out proof " ) ;
}
}
} else if ( ! pegin_tx . isNull ( ) | | ! pegin_tx_proof . isNull ( ) | | ! pegin_script . isNull ( ) ) {
if ( allow_peg_in ) {
throw JSONRPCError ( RPC_INVALID_PARAMETER , " Some but not all pegin_ arguments provided " ) ;
} else {
throw JSONRPCError ( RPC_INVALID_PARAMETER , " pegin_ arguments provided but this command does not support peg-ins " ) ;
}
}
2019-04-02 16:51:32 -04:00
}
if ( ! outputs_is_obj ) {
// Translate array of key-value pairs into dict
UniValue outputs_dict = UniValue ( UniValue : : VOBJ ) ;
for ( size_t i = 0 ; i < outputs . size ( ) ; + + i ) {
const UniValue & output = outputs [ i ] ;
if ( ! output . isObject ( ) ) {
throw JSONRPCError ( RPC_INVALID_PARAMETER , " Invalid parameter, key-value pair not an object as expected " ) ;
}
if ( output . size ( ) ! = 1 ) {
throw JSONRPCError ( RPC_INVALID_PARAMETER , " Invalid parameter, key-value pair must contain exactly one key " ) ;
}
outputs_dict . pushKVs ( output ) ;
}
outputs = std : : move ( outputs_dict ) ;
}
2020-10-28 03:15:23 +00:00
// Keep track of the fee output so we can add it in the very end of the transaction.
CTxOut fee_out ;
2019-04-02 16:51:32 -04:00
// Duplicate checking
std : : set < CTxDestination > destinations ;
bool has_data { false } ;
for ( const std : : string & name_ : outputs . getKeys ( ) ) {
2020-10-28 03:15:23 +00:00
// ELEMENTS:
// Asset defaults to policyAsset
CAsset asset ( : : policyAsset ) ;
if ( ! assets . isNull ( ) ) {
if ( ! find_value ( assets , name_ ) . isNull ( ) ) {
asset = CAsset ( ParseHashO ( assets , name_ ) ) ;
}
}
2019-04-02 16:51:32 -04:00
if ( name_ = = " data " ) {
if ( has_data ) {
throw JSONRPCError ( RPC_INVALID_PARAMETER , " Invalid parameter, duplicate key: data " ) ;
}
has_data = true ;
std : : vector < unsigned char > data = ParseHexV ( outputs [ name_ ] . getValStr ( ) , " Data " ) ;
2020-10-28 03:15:23 +00:00
CTxOut out ( asset , 0 , CScript ( ) < < OP_RETURN < < data ) ;
rawTx . vout . push_back ( out ) ;
2020-11-09 21:20:10 +00:00
if ( output_pubkeys_out ) {
output_pubkeys_out - > push_back ( CPubKey ( ) ) ;
}
2020-10-28 03:15:23 +00:00
} else if ( name_ = = " vdata " ) {
// ELEMENTS: support multi-push OP_RETURN
UniValue vdata = outputs [ name_ ] . get_array ( ) ;
CScript datascript = CScript ( ) < < OP_RETURN ;
for ( size_t i = 0 ; i < vdata . size ( ) ; i + + ) {
std : : vector < unsigned char > data = ParseHexV ( vdata [ i ] . get_str ( ) , " Data " ) ;
datascript < < data ;
}
CTxOut out ( asset , 0 , datascript ) ;
rawTx . vout . push_back ( out ) ;
2020-11-09 21:20:10 +00:00
if ( output_pubkeys_out ) {
output_pubkeys_out - > push_back ( CPubKey ( ) ) ;
}
2020-10-28 03:15:23 +00:00
} else if ( name_ = = " fee " ) {
// ELEMENTS: explicit fee outputs
CAmount nAmount = AmountFromValue ( outputs [ name_ ] ) ;
fee_out = CTxOut ( asset , nAmount , CScript ( ) ) ;
} else if ( name_ = = " burn " ) {
CScript datascript = CScript ( ) < < OP_RETURN ;
CAmount nAmount = AmountFromValue ( outputs [ name_ ] ) ;
CTxOut out ( asset , nAmount , datascript ) ;
2019-04-02 16:51:32 -04:00
rawTx . vout . push_back ( out ) ;
2020-11-09 21:20:10 +00:00
if ( output_pubkeys_out ) {
output_pubkeys_out - > push_back ( CPubKey ( ) ) ;
}
2019-04-02 16:51:32 -04:00
} else {
CTxDestination destination = DecodeDestination ( name_ ) ;
if ( ! IsValidDestination ( destination ) ) {
throw JSONRPCError ( RPC_INVALID_ADDRESS_OR_KEY , std : : string ( " Invalid Bitcoin address: " ) + name_ ) ;
}
if ( ! destinations . insert ( destination ) . second ) {
throw JSONRPCError ( RPC_INVALID_PARAMETER , std : : string ( " Invalid parameter, duplicated address: " ) + name_ ) ;
}
CScript scriptPubKey = GetScriptForDestination ( destination ) ;
CAmount nAmount = AmountFromValue ( outputs [ name_ ] ) ;
2020-10-28 03:15:23 +00:00
CTxOut out ( asset , nAmount , scriptPubKey ) ;
2020-11-09 21:20:10 +00:00
CPubKey blind_pub ;
2020-10-28 03:15:23 +00:00
if ( IsBlindDestination ( destination ) ) {
2020-11-09 21:20:10 +00:00
blind_pub = GetDestinationBlindingKey ( destination ) ;
if ( ! output_pubkeys_out ) {
// Only use the pubkey-in-nonce hack if the caller is not getting the pubkeys the nice way.
out . nNonce . vchCommitment = std : : vector < unsigned char > ( blind_pub . begin ( ) , blind_pub . end ( ) ) ;
}
2020-10-28 03:15:23 +00:00
}
2019-04-02 16:51:32 -04:00
rawTx . vout . push_back ( out ) ;
2020-11-09 21:20:10 +00:00
if ( output_pubkeys_out ) {
output_pubkeys_out - > push_back ( blind_pub ) ;
}
2019-04-02 16:51:32 -04:00
}
}
2020-10-28 03:15:23 +00:00
// Add fee output in the end.
if ( ! fee_out . nValue . IsNull ( ) & & fee_out . nValue . GetAmount ( ) > 0 ) {
rawTx . vout . push_back ( fee_out ) ;
2020-11-09 21:20:10 +00:00
if ( output_pubkeys_out ) {
output_pubkeys_out - > push_back ( CPubKey ( ) ) ;
}
2020-10-28 03:15:23 +00:00
}
2019-04-27 19:44:38 +02:00
if ( rbf & & rawTx . vin . size ( ) > 0 & & ! SignalsOptInRBF ( CTransaction ( rawTx ) ) ) {
2019-04-02 16:51:32 -04:00
throw JSONRPCError ( RPC_INVALID_PARAMETER , " Invalid parameter combination: Sequence number(s) contradict replaceable option " ) ;
}
return rawTx ;
}
2020-11-09 21:20:10 +00:00
2019-04-02 16:51:32 -04:00
/** Pushes a JSON object for script verification or signing errors to vErrorsRet. */
2020-10-28 03:15:23 +00:00
static void TxInErrorToJSON ( const CTxIn & txin , const CTxInWitness & txinwit , UniValue & vErrorsRet , const std : : string & strMessage )
2019-04-02 16:51:32 -04:00
{
UniValue entry ( UniValue : : VOBJ ) ;
entry . pushKV ( " txid " , txin . prevout . hash . ToString ( ) ) ;
entry . pushKV ( " vout " , ( uint64_t ) txin . prevout . n ) ;
UniValue witness ( UniValue : : VARR ) ;
2020-10-28 03:15:23 +00:00
for ( unsigned int i = 0 ; i < txinwit . scriptWitness . stack . size ( ) ; i + + ) {
2020-11-26 01:09:12 +00:00
witness . push_back ( HexStr ( txinwit . scriptWitness . stack [ i ] ) ) ;
2019-04-02 16:51:32 -04:00
}
entry . pushKV ( " witness " , witness ) ;
2020-06-24 15:48:26 +02:00
entry . pushKV ( " scriptSig " , HexStr ( txin . scriptSig ) ) ;
2019-04-02 16:51:32 -04:00
entry . pushKV ( " sequence " , ( uint64_t ) txin . nSequence ) ;
entry . pushKV ( " error " , strMessage ) ;
vErrorsRet . push_back ( entry ) ;
}
2019-07-05 17:39:31 -04:00
void ParsePrevouts ( const UniValue & prevTxsUnival , FillableSigningProvider * keystore , std : : map < COutPoint , Coin > & coins )
2019-04-02 16:51:32 -04:00
{
if ( ! prevTxsUnival . isNull ( ) ) {
UniValue prevTxs = prevTxsUnival . get_array ( ) ;
for ( unsigned int idx = 0 ; idx < prevTxs . size ( ) ; + + idx ) {
const UniValue & p = prevTxs [ idx ] ;
if ( ! p . isObject ( ) ) {
throw JSONRPCError ( RPC_DESERIALIZATION_ERROR , " expected object with { \" txid' \" , \" vout \" , \" scriptPubKey \" } " ) ;
}
UniValue prevOut = p . get_obj ( ) ;
RPCTypeCheckObj ( prevOut ,
{
{ " txid " , UniValueType ( UniValue : : VSTR ) } ,
{ " vout " , UniValueType ( UniValue : : VNUM ) } ,
{ " scriptPubKey " , UniValueType ( UniValue : : VSTR ) } ,
} ) ;
uint256 txid = ParseHashO ( prevOut , " txid " ) ;
int nOut = find_value ( prevOut , " vout " ) . get_int ( ) ;
if ( nOut < 0 ) {
2020-09-30 20:43:05 +03:30
throw JSONRPCError ( RPC_DESERIALIZATION_ERROR , " vout cannot be negative " ) ;
2019-04-02 16:51:32 -04:00
}
COutPoint out ( txid , nOut ) ;
std : : vector < unsigned char > pkData ( ParseHexO ( prevOut , " scriptPubKey " ) ) ;
CScript scriptPubKey ( pkData . begin ( ) , pkData . end ( ) ) ;
{
auto coin = coins . find ( out ) ;
if ( coin ! = coins . end ( ) & & ! coin - > second . IsSpent ( ) & & coin - > second . out . scriptPubKey ! = scriptPubKey ) {
std : : string err ( " Previous output scriptPubKey mismatch: \n " ) ;
err = err + ScriptToAsmStr ( coin - > second . out . scriptPubKey ) + " \n vs: \n " +
ScriptToAsmStr ( scriptPubKey ) ;
throw JSONRPCError ( RPC_DESERIALIZATION_ERROR , err ) ;
}
Coin newcoin ;
newcoin . out . scriptPubKey = scriptPubKey ;
2020-10-28 03:15:23 +00:00
newcoin . out . nValue = CConfidentialValue ( MAX_MONEY ) ;
2019-04-02 16:51:32 -04:00
if ( prevOut . exists ( " amount " ) ) {
2020-10-28 03:15:23 +00:00
newcoin . out . nValue = CConfidentialValue ( AmountFromValue ( find_value ( prevOut , " amount " ) ) ) ;
} else if ( prevOut . exists ( " amountcommitment " ) ) {
// Segwit sigs require the amount commitment to be sighashed
2020-11-28 23:04:05 +00:00
newcoin . out . nValue . vchCommitment = ParseHexO ( prevOut , " amountcommitment " ) ;
2019-04-02 16:51:32 -04:00
}
newcoin . nHeight = 1 ;
coins [ out ] = std : : move ( newcoin ) ;
}
// if redeemScript and private keys were given, add redeemScript to the keystore so it can be signed
2019-06-20 18:01:23 +10:00
const bool is_p2sh = scriptPubKey . IsPayToScriptHash ( ) ;
const bool is_p2wsh = scriptPubKey . IsPayToWitnessScriptHash ( ) ;
if ( keystore & & ( is_p2sh | | is_p2wsh ) ) {
2019-04-02 16:51:32 -04:00
RPCTypeCheckObj ( prevOut ,
{
{ " redeemScript " , UniValueType ( UniValue : : VSTR ) } ,
{ " witnessScript " , UniValueType ( UniValue : : VSTR ) } ,
} , true ) ;
UniValue rs = find_value ( prevOut , " redeemScript " ) ;
UniValue ws = find_value ( prevOut , " witnessScript " ) ;
2019-06-20 14:02:06 +10:00
if ( rs . isNull ( ) & & ws . isNull ( ) ) {
throw JSONRPCError ( RPC_INVALID_PARAMETER , " Missing redeemScript/witnessScript " ) ;
}
2019-06-20 18:01:23 +10:00
// work from witnessScript when possible
std : : vector < unsigned char > scriptData ( ! ws . isNull ( ) ? ParseHexV ( ws , " witnessScript " ) : ParseHexV ( rs , " redeemScript " ) ) ;
CScript script ( scriptData . begin ( ) , scriptData . end ( ) ) ;
keystore - > AddCScript ( script ) ;
// Automatically also add the P2WSH wrapped version of the script (to deal with P2SH-P2WSH).
// This is done for redeemScript only for compatibility, it is encouraged to use the explicit witnessScript field instead.
2020-03-31 13:36:34 -04:00
CScript witness_output_script { GetScriptForDestination ( WitnessV0ScriptHash ( script ) ) } ;
2019-06-20 18:01:23 +10:00
keystore - > AddCScript ( witness_output_script ) ;
if ( ! ws . isNull ( ) & & ! rs . isNull ( ) ) {
// if both witnessScript and redeemScript are provided,
// they should either be the same (for backwards compat),
// or the redeemScript should be the encoded form of
// the witnessScript (ie, for p2sh-p2wsh)
if ( ws . get_str ( ) ! = rs . get_str ( ) ) {
std : : vector < unsigned char > redeemScriptData ( ParseHexV ( rs , " redeemScript " ) ) ;
CScript redeemScript ( redeemScriptData . begin ( ) , redeemScriptData . end ( ) ) ;
if ( redeemScript ! = witness_output_script ) {
throw JSONRPCError ( RPC_INVALID_PARAMETER , " redeemScript does not correspond to witnessScript " ) ;
}
}
}
if ( is_p2sh ) {
const CTxDestination p2sh { ScriptHash ( script ) } ;
const CTxDestination p2sh_p2wsh { ScriptHash ( witness_output_script ) } ;
if ( scriptPubKey = = GetScriptForDestination ( p2sh ) ) {
// traditional p2sh; arguably an error if
// we got here with rs.IsNull(), because
// that means the p2sh script was specified
// via witnessScript param, but for now
// we'll just quietly accept it
} else if ( scriptPubKey = = GetScriptForDestination ( p2sh_p2wsh ) ) {
// p2wsh encoded as p2sh; ideally the witness
// script was specified in the witnessScript
// param, but also support specifying it via
// redeemScript param for backwards compat
// (in which case ws.IsNull() == true)
} else {
// otherwise, can't generate scriptPubKey from
// either script, so we got unusable parameters
throw JSONRPCError ( RPC_INVALID_PARAMETER , " redeemScript/witnessScript does not match scriptPubKey " ) ;
}
} else if ( is_p2wsh ) {
// plain p2wsh; could throw an error if script
// was specified by redeemScript rather than
// witnessScript (ie, ws.IsNull() == true), but
// accept it for backwards compat
const CTxDestination p2wsh { WitnessV0ScriptHash ( script ) } ;
if ( scriptPubKey ! = GetScriptForDestination ( p2wsh ) ) {
throw JSONRPCError ( RPC_INVALID_PARAMETER , " redeemScript/witnessScript does not match scriptPubKey " ) ;
}
}
2019-04-02 16:51:32 -04:00
}
}
}
2019-07-05 17:39:31 -04:00
}
2019-04-02 16:51:32 -04:00
2020-11-26 01:07:51 +00:00
// ELEMENTS: check whether pegin inputs make sense against the current fedpegscript
bool ValidateTransactionPeginInputs ( const CMutableTransaction & mtx , std : : map < int , std : : string > & input_errors )
2019-07-05 17:39:31 -04:00
{
2020-11-09 21:20:21 +00:00
const auto & fedpegscripts = GetValidFedpegScripts ( : : ChainActive ( ) . Tip ( ) , Params ( ) . GetConsensus ( ) , true /* nextblock_validation */ ) ;
2020-10-28 03:15:23 +00:00
// Track an immature peg-in that's otherwise valid, give warning
bool immature_pegin = false ;
2019-04-02 16:51:32 -04:00
for ( unsigned int i = 0 ; i < mtx . vin . size ( ) ; i + + ) {
2020-11-26 01:07:51 +00:00
const CTxIn & txin = mtx . vin [ i ] ;
2020-10-28 03:15:23 +00:00
std : : string err ;
2020-11-26 01:07:51 +00:00
if ( txin . m_is_pegin & & ( mtx . witness . vtxinwit . size ( ) < = i | | ! IsValidPeginWitness ( mtx . witness . vtxinwit [ i ] . m_pegin_witness , fedpegscripts , txin . prevout , err , false ) ) ) {
input_errors [ i ] = " Peg-in input has invalid proof. " ;
2020-10-28 03:15:23 +00:00
continue ;
}
// Report warning about immature peg-in though
2020-11-26 01:07:51 +00:00
if ( txin . m_is_pegin & & ! IsValidPeginWitness ( mtx . witness . vtxinwit [ i ] . m_pegin_witness , fedpegscripts , txin . prevout , err , true ) ) {
2020-12-04 15:17:46 +00:00
CHECK_NONFATAL ( err = = " Needs more confirmations. " ) ;
2020-10-28 03:15:23 +00:00
immature_pegin = true ;
2019-04-02 16:51:32 -04:00
}
2020-11-26 01:07:51 +00:00
}
return immature_pegin ;
}
2020-10-28 03:15:23 +00:00
2019-11-18 14:56:52 -05:00
void SignTransaction ( CMutableTransaction & mtx , const SigningProvider * keystore , const std : : map < COutPoint , Coin > & coins , const UniValue & hashType , UniValue & result )
2019-07-05 17:39:31 -04:00
{
2019-04-02 16:51:32 -04:00
int nHashType = ParseSighashString ( hashType ) ;
// Script verification errors
2020-02-10 19:19:59 -05:00
std : : map < int , std : : string > input_errors ;
2019-04-02 16:51:32 -04:00
2020-11-26 01:07:51 +00:00
bool immature_pegin = ValidateTransactionPeginInputs ( mtx , input_errors ) ;
2020-02-10 19:19:59 -05:00
bool complete = SignTransaction ( mtx , keystore , coins , nHashType , input_errors ) ;
2020-11-26 01:07:51 +00:00
SignTransactionResultToJSON ( mtx , complete , coins , input_errors , immature_pegin , result ) ;
2020-02-10 19:19:59 -05:00
}
2019-04-02 16:51:32 -04:00
2020-11-26 01:07:51 +00:00
void SignTransactionResultToJSON ( CMutableTransaction & mtx , bool complete , const std : : map < COutPoint , Coin > & coins , std : : map < int , std : : string > & input_errors , bool immature_pegin , UniValue & result )
2020-02-10 19:19:59 -05:00
{
// Make errors UniValue
UniValue vErrors ( UniValue : : VARR ) ;
for ( const auto & err_pair : input_errors ) {
if ( err_pair . second = = " Missing amount " ) {
// This particular error needs to be an exception for some reason
throw JSONRPCError ( RPC_TYPE_ERROR , strprintf ( " Missing amount for %s " , coins . at ( mtx . vin . at ( err_pair . first ) . prevout ) . out . ToString ( ) ) ) ;
2019-04-02 16:51:32 -04:00
}
2020-11-26 01:07:51 +00:00
TxInErrorToJSON ( mtx . vin . at ( err_pair . first ) , mtx . witness . vtxinwit . at ( err_pair . first ) , vErrors , err_pair . second ) ;
2019-04-02 16:51:32 -04:00
}
result . pushKV ( " hex " , EncodeHexTx ( CTransaction ( mtx ) ) ) ;
2020-02-10 19:19:59 -05:00
result . pushKV ( " complete " , complete ) ;
2019-04-02 16:51:32 -04:00
if ( ! vErrors . empty ( ) ) {
2019-11-18 14:56:52 -05:00
if ( result . exists ( " errors " ) ) {
vErrors . push_backV ( result [ " errors " ] . getValues ( ) ) ;
}
2019-04-02 16:51:32 -04:00
result . pushKV ( " errors " , vErrors ) ;
}
2020-10-28 03:15:23 +00:00
if ( immature_pegin ) {
result . pushKV ( " warning " , " Possibly immature peg-in input(s) detected, signed anyways. " ) ;
}
2019-04-02 16:51:32 -04:00
}
2020-10-28 03:15:23 +00:00