Merge #13557: BIP 174 PSBT Serializations and RPCs
020628e3a4Tests for PSBT (Andrew Chow)a4b06fb42eCreate wallet RPCs for PSBT (Andrew Chow)c27fe419efCreate utility RPCs for PSBT (Andrew Chow)8b5ef27937SignPSBTInput wrapper function (Andrew Chow)58a8e28918Refactor transaction creation and transaction funding logic (Andrew Chow)e9d86a43adMethods for interacting with PSBT structs (Andrew Chow)12bcc64f27Add pubkeys and whether input was witness to SignatureData (Andrew Chow)41c607f09bImplement PSBT Structures and un/serialization methods per BIP 174 (Andrew Chow) Pull request description: This Pull Request fully implements the [updated](https://github.com/bitcoin/bips/pull/694) BIP 174 specification. It is based upon #13425 which implements the majority of the signing logic. BIP 174 specifies a binary transaction format which contains the information necessary for a signer to produce signatures for the transaction and holds the signatures for an input while the input does not have a complete set of signatures. This PR contains structs for PSBT, serialization, and deserialzation code. Some changes to `SignatureData` have been made to support detection of UTXO type and storing public keys. *** Many RPCs have been added to handle PSBTs. `walletprocesspsbt` takes a PSBT format transaction, updates the PSBT with any inputs related to this wallet, signs, and finalizes the transaction. There is also an option to not sign and just update. `walletcreatefundedpsbt` creates a PSBT from user provided data in the same form as createrawtransaction. It also funds the transaction and takes an options argument in the same form as `fundrawtransaction`. The resulting PSBT is blank with no input or output data filled in. It is analogous to a combination of `createrawtransaction` and `fundrawtransaction` `decodepsbt` takes a PSBT and decodes it to JSON. It is analogous to `decoderawtransaction` `combinepsbt` takes multiple PSBTs for the same tx and combines them. It is analogous to `combinerawtransaction` `finalizepsbt` takes a PSBT and finalizes the inputs. If all inputs are final, it extracts the network serialized transaction and returns that instead of a PSBT unless instructed otherwise. `createpsbt` is like `createrawtransaction` but for PSBTs instead of raw transactions. `convertpsbt` takes a network serialized transaction and converts it into a psbt. The resulting psbt will lose all signature data and an explicit flag must be set to allow transactions with signature data to be converted. *** This supersedes #12136 Tree-SHA512: 1ac7a79e5bc669933f0a6fcc93ded55263fdde9e8c144a30266b13ef9f62aacf43edd4cbca1ffbe003090b067e9643c9298c79be69d7c1b10231b32acafb6338
This commit is contained in:
+450
-119
@@ -3432,6 +3432,122 @@ static UniValue listunspent(const JSONRPCRequest& request)
|
||||
return results;
|
||||
}
|
||||
|
||||
void FundTransaction(CWallet* const pwallet, CMutableTransaction& tx, CAmount& fee_out, int& change_position, UniValue options)
|
||||
{
|
||||
// Make sure the results are valid at least up to the most recent block
|
||||
// the user could have gotten from another RPC command prior to now
|
||||
pwallet->BlockUntilSyncedToCurrentChain();
|
||||
|
||||
CCoinControl coinControl;
|
||||
change_position = -1;
|
||||
bool lockUnspents = false;
|
||||
UniValue subtractFeeFromOutputs;
|
||||
std::set<int> setSubtractFeeFromOutputs;
|
||||
|
||||
if (!options.isNull()) {
|
||||
if (options.type() == UniValue::VBOOL) {
|
||||
// backward compatibility bool only fallback
|
||||
coinControl.fAllowWatchOnly = options.get_bool();
|
||||
}
|
||||
else {
|
||||
RPCTypeCheckArgument(options, UniValue::VOBJ);
|
||||
RPCTypeCheckObj(options,
|
||||
{
|
||||
{"changeAddress", UniValueType(UniValue::VSTR)},
|
||||
{"changePosition", UniValueType(UniValue::VNUM)},
|
||||
{"change_type", UniValueType(UniValue::VSTR)},
|
||||
{"includeWatching", UniValueType(UniValue::VBOOL)},
|
||||
{"lockUnspents", UniValueType(UniValue::VBOOL)},
|
||||
{"feeRate", UniValueType()}, // will be checked below
|
||||
{"subtractFeeFromOutputs", UniValueType(UniValue::VARR)},
|
||||
{"replaceable", UniValueType(UniValue::VBOOL)},
|
||||
{"conf_target", UniValueType(UniValue::VNUM)},
|
||||
{"estimate_mode", UniValueType(UniValue::VSTR)},
|
||||
},
|
||||
true, true);
|
||||
|
||||
if (options.exists("changeAddress")) {
|
||||
CTxDestination dest = DecodeDestination(options["changeAddress"].get_str());
|
||||
|
||||
if (!IsValidDestination(dest)) {
|
||||
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "changeAddress must be a valid bitcoin address");
|
||||
}
|
||||
|
||||
coinControl.destChange = dest;
|
||||
}
|
||||
|
||||
if (options.exists("changePosition"))
|
||||
change_position = options["changePosition"].get_int();
|
||||
|
||||
if (options.exists("change_type")) {
|
||||
if (options.exists("changeAddress")) {
|
||||
throw JSONRPCError(RPC_INVALID_PARAMETER, "Cannot specify both changeAddress and address_type options");
|
||||
}
|
||||
coinControl.m_change_type = pwallet->m_default_change_type;
|
||||
if (!ParseOutputType(options["change_type"].get_str(), *coinControl.m_change_type)) {
|
||||
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, strprintf("Unknown change type '%s'", options["change_type"].get_str()));
|
||||
}
|
||||
}
|
||||
|
||||
if (options.exists("includeWatching"))
|
||||
coinControl.fAllowWatchOnly = options["includeWatching"].get_bool();
|
||||
|
||||
if (options.exists("lockUnspents"))
|
||||
lockUnspents = options["lockUnspents"].get_bool();
|
||||
|
||||
if (options.exists("feeRate"))
|
||||
{
|
||||
coinControl.m_feerate = CFeeRate(AmountFromValue(options["feeRate"]));
|
||||
coinControl.fOverrideFeeRate = true;
|
||||
}
|
||||
|
||||
if (options.exists("subtractFeeFromOutputs"))
|
||||
subtractFeeFromOutputs = options["subtractFeeFromOutputs"].get_array();
|
||||
|
||||
if (options.exists("replaceable")) {
|
||||
coinControl.m_signal_bip125_rbf = options["replaceable"].get_bool();
|
||||
}
|
||||
if (options.exists("conf_target")) {
|
||||
if (options.exists("feeRate")) {
|
||||
throw JSONRPCError(RPC_INVALID_PARAMETER, "Cannot specify both conf_target and feeRate");
|
||||
}
|
||||
coinControl.m_confirm_target = ParseConfirmTarget(options["conf_target"]);
|
||||
}
|
||||
if (options.exists("estimate_mode")) {
|
||||
if (options.exists("feeRate")) {
|
||||
throw JSONRPCError(RPC_INVALID_PARAMETER, "Cannot specify both estimate_mode and feeRate");
|
||||
}
|
||||
if (!FeeModeFromString(options["estimate_mode"].get_str(), coinControl.m_fee_mode)) {
|
||||
throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid estimate_mode parameter");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (tx.vout.size() == 0)
|
||||
throw JSONRPCError(RPC_INVALID_PARAMETER, "TX must have at least one output");
|
||||
|
||||
if (change_position != -1 && (change_position < 0 || (unsigned int)change_position > tx.vout.size()))
|
||||
throw JSONRPCError(RPC_INVALID_PARAMETER, "changePosition out of bounds");
|
||||
|
||||
for (unsigned int idx = 0; idx < subtractFeeFromOutputs.size(); idx++) {
|
||||
int pos = subtractFeeFromOutputs[idx].get_int();
|
||||
if (setSubtractFeeFromOutputs.count(pos))
|
||||
throw JSONRPCError(RPC_INVALID_PARAMETER, strprintf("Invalid parameter, duplicated position: %d", pos));
|
||||
if (pos < 0)
|
||||
throw JSONRPCError(RPC_INVALID_PARAMETER, strprintf("Invalid parameter, negative position: %d", pos));
|
||||
if (pos >= int(tx.vout.size()))
|
||||
throw JSONRPCError(RPC_INVALID_PARAMETER, strprintf("Invalid parameter, position too large: %d", pos));
|
||||
setSubtractFeeFromOutputs.insert(pos);
|
||||
}
|
||||
|
||||
std::string strFailReason;
|
||||
|
||||
if (!pwallet->FundTransaction(tx, fee_out, change_position, strFailReason, lockUnspents, setSubtractFeeFromOutputs, coinControl)) {
|
||||
throw JSONRPCError(RPC_WALLET_ERROR, strFailReason);
|
||||
}
|
||||
}
|
||||
|
||||
static UniValue fundrawtransaction(const JSONRPCRequest& request)
|
||||
{
|
||||
std::shared_ptr<CWallet> const wallet = GetWalletForJSONRPCRequest(request);
|
||||
@@ -3499,100 +3615,7 @@ static UniValue fundrawtransaction(const JSONRPCRequest& request)
|
||||
+ HelpExampleCli("sendrawtransaction", "\"signedtransactionhex\"")
|
||||
);
|
||||
|
||||
RPCTypeCheck(request.params, {UniValue::VSTR});
|
||||
|
||||
// Make sure the results are valid at least up to the most recent block
|
||||
// the user could have gotten from another RPC command prior to now
|
||||
pwallet->BlockUntilSyncedToCurrentChain();
|
||||
|
||||
CCoinControl coinControl;
|
||||
int changePosition = -1;
|
||||
bool lockUnspents = false;
|
||||
UniValue subtractFeeFromOutputs;
|
||||
std::set<int> setSubtractFeeFromOutputs;
|
||||
|
||||
if (!request.params[1].isNull()) {
|
||||
if (request.params[1].type() == UniValue::VBOOL) {
|
||||
// backward compatibility bool only fallback
|
||||
coinControl.fAllowWatchOnly = request.params[1].get_bool();
|
||||
}
|
||||
else {
|
||||
RPCTypeCheck(request.params, {UniValue::VSTR, UniValue::VOBJ, UniValue::VBOOL});
|
||||
|
||||
UniValue options = request.params[1];
|
||||
|
||||
RPCTypeCheckObj(options,
|
||||
{
|
||||
{"changeAddress", UniValueType(UniValue::VSTR)},
|
||||
{"changePosition", UniValueType(UniValue::VNUM)},
|
||||
{"change_type", UniValueType(UniValue::VSTR)},
|
||||
{"includeWatching", UniValueType(UniValue::VBOOL)},
|
||||
{"lockUnspents", UniValueType(UniValue::VBOOL)},
|
||||
{"feeRate", UniValueType()}, // will be checked below
|
||||
{"subtractFeeFromOutputs", UniValueType(UniValue::VARR)},
|
||||
{"replaceable", UniValueType(UniValue::VBOOL)},
|
||||
{"conf_target", UniValueType(UniValue::VNUM)},
|
||||
{"estimate_mode", UniValueType(UniValue::VSTR)},
|
||||
},
|
||||
true, true);
|
||||
|
||||
if (options.exists("changeAddress")) {
|
||||
CTxDestination dest = DecodeDestination(options["changeAddress"].get_str());
|
||||
|
||||
if (!IsValidDestination(dest)) {
|
||||
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "changeAddress must be a valid bitcoin address");
|
||||
}
|
||||
|
||||
coinControl.destChange = dest;
|
||||
}
|
||||
|
||||
if (options.exists("changePosition"))
|
||||
changePosition = options["changePosition"].get_int();
|
||||
|
||||
if (options.exists("change_type")) {
|
||||
if (options.exists("changeAddress")) {
|
||||
throw JSONRPCError(RPC_INVALID_PARAMETER, "Cannot specify both changeAddress and address_type options");
|
||||
}
|
||||
coinControl.m_change_type = pwallet->m_default_change_type;
|
||||
if (!ParseOutputType(options["change_type"].get_str(), *coinControl.m_change_type)) {
|
||||
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, strprintf("Unknown change type '%s'", options["change_type"].get_str()));
|
||||
}
|
||||
}
|
||||
|
||||
if (options.exists("includeWatching"))
|
||||
coinControl.fAllowWatchOnly = options["includeWatching"].get_bool();
|
||||
|
||||
if (options.exists("lockUnspents"))
|
||||
lockUnspents = options["lockUnspents"].get_bool();
|
||||
|
||||
if (options.exists("feeRate"))
|
||||
{
|
||||
coinControl.m_feerate = CFeeRate(AmountFromValue(options["feeRate"]));
|
||||
coinControl.fOverrideFeeRate = true;
|
||||
}
|
||||
|
||||
if (options.exists("subtractFeeFromOutputs"))
|
||||
subtractFeeFromOutputs = options["subtractFeeFromOutputs"].get_array();
|
||||
|
||||
if (options.exists("replaceable")) {
|
||||
coinControl.m_signal_bip125_rbf = options["replaceable"].get_bool();
|
||||
}
|
||||
if (options.exists("conf_target")) {
|
||||
if (options.exists("feeRate")) {
|
||||
throw JSONRPCError(RPC_INVALID_PARAMETER, "Cannot specify both conf_target and feeRate");
|
||||
}
|
||||
coinControl.m_confirm_target = ParseConfirmTarget(options["conf_target"]);
|
||||
}
|
||||
if (options.exists("estimate_mode")) {
|
||||
if (options.exists("feeRate")) {
|
||||
throw JSONRPCError(RPC_INVALID_PARAMETER, "Cannot specify both estimate_mode and feeRate");
|
||||
}
|
||||
if (!FeeModeFromString(options["estimate_mode"].get_str(), coinControl.m_fee_mode)) {
|
||||
throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid estimate_mode parameter");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
RPCTypeCheck(request.params, {UniValue::VSTR, UniValueType(), UniValue::VBOOL});
|
||||
|
||||
// parse hex string from parameter
|
||||
CMutableTransaction tx;
|
||||
@@ -3602,34 +3625,14 @@ static UniValue fundrawtransaction(const JSONRPCRequest& request)
|
||||
throw JSONRPCError(RPC_DESERIALIZATION_ERROR, "TX decode failed");
|
||||
}
|
||||
|
||||
if (tx.vout.size() == 0)
|
||||
throw JSONRPCError(RPC_INVALID_PARAMETER, "TX must have at least one output");
|
||||
|
||||
if (changePosition != -1 && (changePosition < 0 || (unsigned int)changePosition > tx.vout.size()))
|
||||
throw JSONRPCError(RPC_INVALID_PARAMETER, "changePosition out of bounds");
|
||||
|
||||
for (unsigned int idx = 0; idx < subtractFeeFromOutputs.size(); idx++) {
|
||||
int pos = subtractFeeFromOutputs[idx].get_int();
|
||||
if (setSubtractFeeFromOutputs.count(pos))
|
||||
throw JSONRPCError(RPC_INVALID_PARAMETER, strprintf("Invalid parameter, duplicated position: %d", pos));
|
||||
if (pos < 0)
|
||||
throw JSONRPCError(RPC_INVALID_PARAMETER, strprintf("Invalid parameter, negative position: %d", pos));
|
||||
if (pos >= int(tx.vout.size()))
|
||||
throw JSONRPCError(RPC_INVALID_PARAMETER, strprintf("Invalid parameter, position too large: %d", pos));
|
||||
setSubtractFeeFromOutputs.insert(pos);
|
||||
}
|
||||
|
||||
CAmount nFeeOut;
|
||||
std::string strFailReason;
|
||||
|
||||
if (!pwallet->FundTransaction(tx, nFeeOut, changePosition, strFailReason, lockUnspents, setSubtractFeeFromOutputs, coinControl)) {
|
||||
throw JSONRPCError(RPC_WALLET_ERROR, strFailReason);
|
||||
}
|
||||
CAmount fee;
|
||||
int change_position;
|
||||
FundTransaction(pwallet, tx, fee, change_position, request.params[1]);
|
||||
|
||||
UniValue result(UniValue::VOBJ);
|
||||
result.pushKV("hex", EncodeHexTx(tx));
|
||||
result.pushKV("changepos", changePosition);
|
||||
result.pushKV("fee", ValueFromAmount(nFeeOut));
|
||||
result.pushKV("fee", ValueFromAmount(fee));
|
||||
result.pushKV("changepos", change_position);
|
||||
|
||||
return result;
|
||||
}
|
||||
@@ -4400,6 +4403,332 @@ UniValue sethdseed(const JSONRPCRequest& request)
|
||||
return NullUniValue;
|
||||
}
|
||||
|
||||
bool ParseHDKeypath(std::string keypath_str, std::vector<uint32_t>& keypath)
|
||||
{
|
||||
std::stringstream ss(keypath_str);
|
||||
std::string item;
|
||||
bool first = true;
|
||||
while (std::getline(ss, item, '/')) {
|
||||
if (item.compare("m") == 0) {
|
||||
if (first) {
|
||||
first = false;
|
||||
continue;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
// Finds whether it is hardened
|
||||
uint32_t path = 0;
|
||||
size_t pos = item.find("'");
|
||||
if (pos != std::string::npos) {
|
||||
// The hardened tick can only be in the last index of the string
|
||||
if (pos != item.size() - 1) {
|
||||
return false;
|
||||
}
|
||||
path |= 0x80000000;
|
||||
item = item.substr(0, item.size() - 1); // Drop the last character which is the hardened tick
|
||||
}
|
||||
|
||||
// Ensure this is only numbers
|
||||
if (item.find_first_not_of( "0123456789" ) != std::string::npos) {
|
||||
return false;
|
||||
}
|
||||
uint32_t number;
|
||||
ParseUInt32(item, &number);
|
||||
path |= number;
|
||||
|
||||
keypath.push_back(path);
|
||||
first = false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void AddKeypathToMap(const CWallet* pwallet, const CKeyID& keyID, std::map<CPubKey, std::vector<uint32_t>>& hd_keypaths)
|
||||
{
|
||||
CPubKey vchPubKey;
|
||||
if (!pwallet->GetPubKey(keyID, vchPubKey)) {
|
||||
return;
|
||||
}
|
||||
CKeyMetadata meta;
|
||||
auto it = pwallet->mapKeyMetadata.find(keyID);
|
||||
if (it != pwallet->mapKeyMetadata.end()) {
|
||||
meta = it->second;
|
||||
}
|
||||
std::vector<uint32_t> keypath;
|
||||
if (!meta.hdKeypath.empty()) {
|
||||
if (!ParseHDKeypath(meta.hdKeypath, keypath)) {
|
||||
throw JSONRPCError(RPC_INTERNAL_ERROR, "Internal keypath is broken");
|
||||
}
|
||||
// Get the proper master key id
|
||||
CKey key;
|
||||
pwallet->GetKey(meta.hd_seed_id, key);
|
||||
CExtKey masterKey;
|
||||
masterKey.SetSeed(key.begin(), key.size());
|
||||
// Add to map
|
||||
keypath.insert(keypath.begin(), ReadLE32(masterKey.key.GetPubKey().GetID().begin()));
|
||||
} else { // Single pubkeys get the master fingerprint of themselves
|
||||
keypath.insert(keypath.begin(), ReadLE32(vchPubKey.GetID().begin()));
|
||||
}
|
||||
hd_keypaths.emplace(vchPubKey, keypath);
|
||||
}
|
||||
|
||||
bool FillPSBT(const CWallet* pwallet, PartiallySignedTransaction& psbtx, const CTransaction* txConst, int sighash_type, bool sign, bool bip32derivs)
|
||||
{
|
||||
LOCK(pwallet->cs_wallet);
|
||||
// Get all of the previous transactions
|
||||
bool complete = true;
|
||||
for (unsigned int i = 0; i < txConst->vin.size(); ++i) {
|
||||
const CTxIn& txin = txConst->vin[i];
|
||||
PSBTInput& input = psbtx.inputs.at(i);
|
||||
|
||||
// If we don't know about this input, skip it and let someone else deal with it
|
||||
const uint256& txhash = txin.prevout.hash;
|
||||
const auto& it = pwallet->mapWallet.find(txhash);
|
||||
if (it != pwallet->mapWallet.end()) {
|
||||
const CWalletTx& wtx = it->second;
|
||||
CTxOut utxo = wtx.tx->vout[txin.prevout.n];
|
||||
input.non_witness_utxo = wtx.tx;
|
||||
input.witness_utxo = utxo;
|
||||
}
|
||||
|
||||
// Get the Sighash type
|
||||
if (sign && input.sighash_type > 0 && input.sighash_type != sighash_type) {
|
||||
throw JSONRPCError(RPC_DESERIALIZATION_ERROR, "Specified Sighash and sighash in PSBT do not match.");
|
||||
}
|
||||
|
||||
SignatureData sigdata;
|
||||
if (sign) {
|
||||
complete &= SignPSBTInput(*pwallet, *psbtx.tx, input, sigdata, i, sighash_type);
|
||||
} else {
|
||||
complete &= SignPSBTInput(PublicOnlySigningProvider(pwallet), *psbtx.tx, input, sigdata, i, sighash_type);
|
||||
}
|
||||
|
||||
// Drop the unnecessary UTXO
|
||||
if (sigdata.witness) {
|
||||
input.non_witness_utxo = nullptr;
|
||||
} else {
|
||||
input.witness_utxo.SetNull();
|
||||
}
|
||||
|
||||
// Get public key paths
|
||||
if (bip32derivs) {
|
||||
for (const auto& pubkey_it : sigdata.misc_pubkeys) {
|
||||
AddKeypathToMap(pwallet, pubkey_it.first, input.hd_keypaths);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Fill in the bip32 keypaths and redeemscripts for the outputs so that hardware wallets can identify change
|
||||
for (unsigned int i = 0; i < txConst->vout.size(); ++i) {
|
||||
const CTxOut& out = txConst->vout.at(i);
|
||||
PSBTOutput& psbt_out = psbtx.outputs.at(i);
|
||||
|
||||
// Dummy tx so we can use ProduceSignature to get stuff out
|
||||
CMutableTransaction dummy_tx;
|
||||
dummy_tx.vin.push_back(CTxIn());
|
||||
dummy_tx.vout.push_back(CTxOut());
|
||||
|
||||
// Fill a SignatureData with output info
|
||||
SignatureData sigdata;
|
||||
psbt_out.FillSignatureData(sigdata);
|
||||
|
||||
MutableTransactionSignatureCreator creator(psbtx.tx.get_ptr(), 0, out.nValue, 1);
|
||||
ProduceSignature(*pwallet, creator, out.scriptPubKey, sigdata);
|
||||
psbt_out.FromSignatureData(sigdata);
|
||||
|
||||
// Get public key paths
|
||||
if (bip32derivs) {
|
||||
for (const auto& pubkey_it : sigdata.misc_pubkeys) {
|
||||
AddKeypathToMap(pwallet, pubkey_it.first, psbt_out.hd_keypaths);
|
||||
}
|
||||
}
|
||||
}
|
||||
return complete;
|
||||
}
|
||||
|
||||
UniValue walletprocesspsbt(const JSONRPCRequest& request)
|
||||
{
|
||||
std::shared_ptr<CWallet> const wallet = GetWalletForJSONRPCRequest(request);
|
||||
CWallet* const pwallet = wallet.get();
|
||||
|
||||
if (!EnsureWalletIsAvailable(pwallet, request.fHelp)) {
|
||||
return NullUniValue;
|
||||
}
|
||||
|
||||
if (request.fHelp || request.params.size() < 1 || request.params.size() > 4)
|
||||
throw std::runtime_error(
|
||||
"walletprocesspsbt \"psbt\" ( sign \"sighashtype\" bip32derivs )\n"
|
||||
"\nUpdate a PSBT with input information from our wallet and then sign inputs\n"
|
||||
"that we can sign for.\n"
|
||||
+ HelpRequiringPassphrase(pwallet) + "\n"
|
||||
|
||||
"\nArguments:\n"
|
||||
"1. \"psbt\" (string, required) The transaction base64 string\n"
|
||||
"2. sign (boolean, optional, default=true) Also sign the transaction when updating\n"
|
||||
"3. \"sighashtype\" (string, optional, default=ALL) The signature hash type to sign with if not specified by the PSBT. Must be one of\n"
|
||||
" \"ALL\"\n"
|
||||
" \"NONE\"\n"
|
||||
" \"SINGLE\"\n"
|
||||
" \"ALL|ANYONECANPAY\"\n"
|
||||
" \"NONE|ANYONECANPAY\"\n"
|
||||
" \"SINGLE|ANYONECANPAY\"\n"
|
||||
"4. bip32derivs (boolean, optiona, default=false) If true, includes the BIP 32 derivation paths for public keys if we know them\n"
|
||||
|
||||
"\nResult:\n"
|
||||
"{\n"
|
||||
" \"psbt\" : \"value\", (string) The base64-encoded partially signed transaction\n"
|
||||
" \"complete\" : true|false, (boolean) If the transaction has a complete set of signatures\n"
|
||||
" ]\n"
|
||||
"}\n"
|
||||
|
||||
"\nExamples:\n"
|
||||
+ HelpExampleCli("walletprocesspsbt", "\"psbt\"")
|
||||
);
|
||||
|
||||
RPCTypeCheck(request.params, {UniValue::VSTR, UniValue::VBOOL, UniValue::VSTR});
|
||||
|
||||
// Unserialize the transaction
|
||||
PartiallySignedTransaction psbtx;
|
||||
std::string error;
|
||||
if (!DecodePSBT(psbtx, request.params[0].get_str(), error)) {
|
||||
throw JSONRPCError(RPC_DESERIALIZATION_ERROR, strprintf("TX decode failed %s", error));
|
||||
}
|
||||
|
||||
// Get the sighash type
|
||||
int nHashType = ParseSighashString(request.params[2]);
|
||||
|
||||
// Use CTransaction for the constant parts of the
|
||||
// transaction to avoid rehashing.
|
||||
const CTransaction txConst(*psbtx.tx);
|
||||
|
||||
// Fill transaction with our data and also sign
|
||||
bool sign = request.params[1].isNull() ? true : request.params[1].get_bool();
|
||||
bool bip32derivs = request.params[3].isNull() ? false : request.params[3].get_bool();
|
||||
bool complete = FillPSBT(pwallet, psbtx, &txConst, nHashType, sign, bip32derivs);
|
||||
|
||||
UniValue result(UniValue::VOBJ);
|
||||
CDataStream ssTx(SER_NETWORK, PROTOCOL_VERSION);
|
||||
ssTx << psbtx;
|
||||
result.push_back(Pair("psbt", EncodeBase64((unsigned char*)ssTx.data(), ssTx.size())));
|
||||
result.push_back(Pair("complete", complete));
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
UniValue walletcreatefundedpsbt(const JSONRPCRequest& request)
|
||||
{
|
||||
std::shared_ptr<CWallet> const wallet = GetWalletForJSONRPCRequest(request);
|
||||
CWallet* const pwallet = wallet.get();
|
||||
|
||||
if (!EnsureWalletIsAvailable(pwallet, request.fHelp)) {
|
||||
return NullUniValue;
|
||||
}
|
||||
|
||||
if (request.fHelp || request.params.size() < 2 || request.params.size() > 6)
|
||||
throw std::runtime_error(
|
||||
"walletcreatefundedpsbt [{\"txid\":\"id\",\"vout\":n},...] [{\"address\":amount},{\"data\":\"hex\"},...] ( locktime ) ( replaceable ) ( options bip32derivs )\n"
|
||||
"\nCreates and funds a transaction in the Partially Signed Transaction format. Inputs will be added if supplied inputs are not enough\n"
|
||||
"Implements the Creator and Updater roles.\n"
|
||||
"\nArguments:\n"
|
||||
"1. \"inputs\" (array, required) A json array of json objects\n"
|
||||
" [\n"
|
||||
" {\n"
|
||||
" \"txid\":\"id\", (string, required) The transaction id\n"
|
||||
" \"vout\":n, (numeric, required) The output number\n"
|
||||
" \"sequence\":n (numeric, optional) The sequence number\n"
|
||||
" } \n"
|
||||
" ,...\n"
|
||||
" ]\n"
|
||||
"2. \"outputs\" (array, required) a json array with outputs (key-value pairs)\n"
|
||||
" [\n"
|
||||
" {\n"
|
||||
" \"address\": x.xxx, (obj, optional) A key-value pair. The key (string) is the bitcoin address, the value (float or string) is the amount in " + CURRENCY_UNIT + "\n"
|
||||
" },\n"
|
||||
" {\n"
|
||||
" \"data\": \"hex\" (obj, optional) A key-value pair. The key must be \"data\", the value is hex encoded data\n"
|
||||
" }\n"
|
||||
" ,... More key-value pairs of the above form. For compatibility reasons, a dictionary, which holds the key-value pairs directly, is also\n"
|
||||
" accepted as second parameter.\n"
|
||||
" ]\n"
|
||||
"3. locktime (numeric, optional, default=0) Raw locktime. Non-0 value also locktime-activates inputs\n"
|
||||
"4. replaceable (boolean, optional, default=false) Marks this transaction as BIP125 replaceable.\n"
|
||||
" Allows this transaction to be replaced by a transaction with higher fees. If provided, it is an error if explicit sequence numbers are incompatible.\n"
|
||||
"5. options (object, optional)\n"
|
||||
" {\n"
|
||||
" \"changeAddress\" (string, optional, default pool address) The bitcoin address to receive the change\n"
|
||||
" \"changePosition\" (numeric, optional, default random) The index of the change output\n"
|
||||
" \"change_type\" (string, optional) The output type to use. Only valid if changeAddress is not specified. Options are \"legacy\", \"p2sh-segwit\", and \"bech32\". Default is set by -changetype.\n"
|
||||
" \"includeWatching\" (boolean, optional, default false) Also select inputs which are watch only\n"
|
||||
" \"lockUnspents\" (boolean, optional, default false) Lock selected unspent outputs\n"
|
||||
" \"feeRate\" (numeric, optional, default not set: makes wallet determine the fee) Set a specific fee rate in " + CURRENCY_UNIT + "/kB\n"
|
||||
" \"subtractFeeFromOutputs\" (array, optional) A json array of integers.\n"
|
||||
" The fee will be equally deducted from the amount of each specified output.\n"
|
||||
" The outputs are specified by their zero-based index, before any change output is added.\n"
|
||||
" Those recipients will receive less bitcoins than you enter in their corresponding amount field.\n"
|
||||
" If no outputs are specified here, the sender pays the fee.\n"
|
||||
" [vout_index,...]\n"
|
||||
" \"replaceable\" (boolean, optional) Marks this transaction as BIP125 replaceable.\n"
|
||||
" Allows this transaction to be replaced by a transaction with higher fees\n"
|
||||
" \"conf_target\" (numeric, optional) Confirmation target (in blocks)\n"
|
||||
" \"estimate_mode\" (string, optional, default=UNSET) The fee estimate mode, must be one of:\n"
|
||||
" \"UNSET\"\n"
|
||||
" \"ECONOMICAL\"\n"
|
||||
" \"CONSERVATIVE\"\n"
|
||||
" }\n"
|
||||
"6. bip32derivs (boolean, optiona, default=false) If true, includes the BIP 32 derivation paths for public keys if we know them\n"
|
||||
"\nResult:\n"
|
||||
"{\n"
|
||||
" \"psbt\": \"value\", (string) The resulting raw transaction (base64-encoded string)\n"
|
||||
" \"fee\": n, (numeric) Fee in " + CURRENCY_UNIT + " the resulting transaction pays\n"
|
||||
" \"changepos\": n (numeric) The position of the added change output, or -1\n"
|
||||
"}\n"
|
||||
"\nExamples:\n"
|
||||
"\nCreate a transaction with no inputs\n"
|
||||
+ HelpExampleCli("walletcreatefundedpsbt", "\"[{\\\"txid\\\":\\\"myid\\\",\\\"vout\\\":0}]\" \"[{\\\"data\\\":\\\"00010203\\\"}]\"")
|
||||
);
|
||||
|
||||
RPCTypeCheck(request.params, {
|
||||
UniValue::VARR,
|
||||
UniValueType(), // ARR or OBJ, checked later
|
||||
UniValue::VNUM,
|
||||
UniValue::VBOOL,
|
||||
UniValue::VOBJ
|
||||
}, true
|
||||
);
|
||||
|
||||
CAmount fee;
|
||||
int change_position;
|
||||
CMutableTransaction rawTx = ConstructTransaction(request.params[0], request.params[1], request.params[2], request.params[3]);
|
||||
FundTransaction(pwallet, rawTx, fee, change_position, request.params[4]);
|
||||
|
||||
// Make a blank psbt
|
||||
PartiallySignedTransaction psbtx;
|
||||
psbtx.tx = rawTx;
|
||||
for (unsigned int i = 0; i < rawTx.vin.size(); ++i) {
|
||||
psbtx.inputs.push_back(PSBTInput());
|
||||
}
|
||||
for (unsigned int i = 0; i < rawTx.vout.size(); ++i) {
|
||||
psbtx.outputs.push_back(PSBTOutput());
|
||||
}
|
||||
|
||||
// Use CTransaction for the constant parts of the
|
||||
// transaction to avoid rehashing.
|
||||
const CTransaction txConst(*psbtx.tx);
|
||||
|
||||
// Fill transaction with out data but don't sign
|
||||
bool bip32derivs = request.params[5].isNull() ? false : request.params[5].get_bool();
|
||||
FillPSBT(pwallet, psbtx, &txConst, 1, false, bip32derivs);
|
||||
|
||||
// Serialize the PSBT
|
||||
CDataStream ssTx(SER_NETWORK, PROTOCOL_VERSION);
|
||||
ssTx << psbtx;
|
||||
|
||||
UniValue result(UniValue::VOBJ);
|
||||
result.pushKV("psbt", EncodeBase64((unsigned char*)ssTx.data(), ssTx.size()));
|
||||
result.pushKV("fee", ValueFromAmount(fee));
|
||||
result.pushKV("changepos", change_position);
|
||||
return result;
|
||||
}
|
||||
|
||||
extern UniValue abortrescan(const JSONRPCRequest& request); // in rpcdump.cpp
|
||||
extern UniValue dumpprivkey(const JSONRPCRequest& request); // in rpcdump.cpp
|
||||
extern UniValue importprivkey(const JSONRPCRequest& request);
|
||||
@@ -4416,6 +4745,8 @@ static const CRPCCommand commands[] =
|
||||
{ // category name actor (function) argNames
|
||||
// --------------------- ------------------------ ----------------------- ----------
|
||||
{ "rawtransactions", "fundrawtransaction", &fundrawtransaction, {"hexstring","options","iswitness"} },
|
||||
{ "wallet", "walletprocesspsbt", &walletprocesspsbt, {"psbt","sign","sighashtype","bip32derivs"} },
|
||||
{ "wallet", "walletcreatefundedpsbt", &walletcreatefundedpsbt, {"inputs","outputs","locktime","replaceable","options","bip32derivs"} },
|
||||
{ "hidden", "resendwallettransactions", &resendwallettransactions, {} },
|
||||
{ "wallet", "abandontransaction", &abandontransaction, {"txid"} },
|
||||
{ "wallet", "abortrescan", &abortrescan, {} },
|
||||
|
||||
Reference in New Issue
Block a user