Merge #751: Peg-in fields for PSBT

7cb5f6bb0 Sign PSBTs with peg-in data (Andrew Chow)
73711eb0b Merge psbt peg-in data (Andrew Chow)
4ad849fed Finalize psbt peg-ins (Andrew Chow)
b7e369505 Test createpsbt and decodepsbt with peg-ins (Andrew Chow)
8ece8e0a0 Show peg-in info in decodepsbt (Andrew Chow)
646e76f7d Be able to create peg-in PSBTs in createpegin (Andrew Chow)
b5590b20c Add peg-in PSBT fields (Andrew Chow)

Pull request description:

  Adds peg-in fields for PSBT. `createpsbt` uses the new peg-in raw tx api to allow specifying the peg-in data and then adds it to the psbt. `decodepsbt`, `finalizepsbt` and `walletsignpsbt` are both updated to handle peg-in stuff too. Also tests.

  `walletcreatefundedpsbt` still does not handle peg-in data. It will require handling external inputs which the wallet does not do. I think it would be best to first implement external inputs in `FundTransaction` upstream before allowing `walletcreatefundedpsbt` to accept peg-ins.

Tree-SHA512: 838c2cf0de78b6ad948aaec2932326e2e39b6645c98193c90eedaee7e5cd8ebe3e8ea00e3ba4a67d5f862948195f37aded0e6a08a7c3c20def325d4cabb78ee3
This commit is contained in:
Gregory Sanders 2019-12-03 11:42:52 -05:00
commit 2949ea924f
No known key found for this signature in database
GPG key ID: F3F68E2D86A48FDB
6 changed files with 275 additions and 5 deletions

View file

@ -2,6 +2,7 @@
// Distributed under the MIT software license, see the accompanying
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
#include <pegins.h>
#include <psbt.h>
#include <util/strencodings.h>
#include <confidential_validation.h>
@ -158,6 +159,11 @@ void PSBTInput::Merge(const PSBTInput& input)
if (value_blinding_factor.IsNull() && !input.value_blinding_factor.IsNull()) value_blinding_factor = input.value_blinding_factor;
if (asset.IsNull() && !input.asset.IsNull()) asset = input.asset;
if (asset_blinding_factor.IsNull() && !input.asset_blinding_factor.IsNull()) asset_blinding_factor = input.asset_blinding_factor;
if (peg_in_tx.which() == 0 && peg_in_tx.which() > 0) peg_in_tx = input.peg_in_tx;
if (txout_proof.which() == 0 && peg_in_tx.which() > 0) txout_proof = input.txout_proof;
if (claim_script.empty() && !input.claim_script.empty()) claim_script = input.claim_script;
if (genesis_hash.IsNull() && !input.genesis_hash.IsNull()) genesis_hash = input.genesis_hash;
}
bool PSBTInput::IsSane() const
@ -321,6 +327,22 @@ bool FinalizeAndExtractPSBT(PartiallySignedTransaction& psbtx, CMutableTransacti
for (unsigned int i = 0; i < result.vin.size(); ++i) {
result.vin[i].scriptSig = psbtx.inputs[i].final_script_sig;
result.witness.vtxinwit[i].scriptWitness = psbtx.inputs[i].final_script_witness;
PSBTInput& input = psbtx.inputs[i];
if (input.value && input.peg_in_tx.which() != 0 && input.txout_proof.which() != 0 && !input.claim_script.empty() && !input.genesis_hash.IsNull()) {
CScriptWitness pegin_witness;
if (Params().GetConsensus().ParentChainHasPow()) {
const Sidechain::Bitcoin::CTransactionRef& btc_peg_in_tx = boost::get<Sidechain::Bitcoin::CTransactionRef>(input.peg_in_tx);
const Sidechain::Bitcoin::CMerkleBlock& btc_txout_proof = boost::get<Sidechain::Bitcoin::CMerkleBlock>(input.txout_proof);
pegin_witness = CreatePeginWitness(*input.value, input.asset, input.genesis_hash, input.claim_script, btc_peg_in_tx, btc_txout_proof);
} else {
const CTransactionRef& elem_peg_in_tx = boost::get<CTransactionRef>(input.peg_in_tx);
const CMerkleBlock& elem_txout_proof = boost::get<CMerkleBlock>(input.txout_proof);
pegin_witness = CreatePeginWitness(*input.value, input.asset, input.genesis_hash, input.claim_script, elem_peg_in_tx, elem_txout_proof);
}
result.vin[i].m_is_pegin = true;
result.witness.vtxinwit[i].m_pegin_witness = pegin_witness;
}
}
result.witness.vtxoutwit.resize(result.vout.size());

View file

@ -6,11 +6,17 @@
#define BITCOIN_PSBT_H
#include <attributes.h>
#include <chainparams.h>
#include <node/transaction.h>
#include <primitives/transaction.h>
#include <primitives/bitcoin/transaction.h>
#include <primitives/bitcoin/merkleblock.h>
#include <merkleblock.h>
#include <pubkey.h>
#include <script/sign.h>
#include <boost/variant.hpp>
// Magic bytes
static constexpr uint8_t PSBT_MAGIC_BYTES[5] = {'p', 's', 'b', 't', 0xff};
static constexpr uint8_t PSBT_ELEMENTS_MAGIC_BYTES[5] = {'p', 's', 'e', 't', 0xff};
@ -35,6 +41,11 @@ static constexpr uint8_t PSBT_IN_VALUE = 0x00;
static constexpr uint8_t PSBT_IN_VALUE_BLINDER = 0x01;
static constexpr uint8_t PSBT_IN_ASSET = 0x02;
static constexpr uint8_t PSBT_IN_ASSET_BLINDER = 0x03;
// Peg-in stuff
static constexpr uint8_t PSBT_IN_PEG_IN_TX = 0x04;
static constexpr uint8_t PSBT_IN_TXOUT_PROOF = 0x05;
static constexpr uint8_t PSBT_IN_GENESIS_HASH = 0x06;
static constexpr uint8_t PSBT_IN_CLAIM_SCRIPT = 0x07;
// Output types
static constexpr uint8_t PSBT_OUT_REDEEMSCRIPT = 0x00;
@ -77,6 +88,11 @@ struct PSBTInput
CAsset asset;
uint256 asset_blinding_factor;
boost::variant<boost::blank, CTransactionRef, Sidechain::Bitcoin::CTransactionRef> peg_in_tx;
boost::variant<boost::blank, CMerkleBlock, Sidechain::Bitcoin::CMerkleBlock> txout_proof;
CScript claim_script;
uint256 genesis_hash;
bool IsNull() const;
void FillSignatureData(SignatureData& sigdata) const;
void FromSignatureData(const SignatureData& sigdata);
@ -158,6 +174,49 @@ struct PSBTInput
SerializeToVector(s, final_script_witness.stack);
}
// Write peg-in data
if (Params().GetConsensus().ParentChainHasPow()) {
if (peg_in_tx.which() > 0) {
const Sidechain::Bitcoin::CTransactionRef& btc_peg_in_tx = boost::get<Sidechain::Bitcoin::CTransactionRef>(peg_in_tx);
if (btc_peg_in_tx) {
SerializeToVector(s, PSBT_IN_PROPRIETARY, PSBT_ELEMENTS_ID, PSBT_IN_PEG_IN_TX);
OverrideStream<Stream> os(&s, s.GetType(), s.GetVersion() | SERIALIZE_TRANSACTION_NO_WITNESS);
SerializeToVector(os, btc_peg_in_tx);
}
}
if (txout_proof.which() > 0) {
const Sidechain::Bitcoin::CMerkleBlock& btc_txout_proof = boost::get<Sidechain::Bitcoin::CMerkleBlock>(txout_proof);
if (!btc_txout_proof.header.IsNull()) {
SerializeToVector(s, PSBT_IN_PROPRIETARY, PSBT_ELEMENTS_ID, PSBT_IN_TXOUT_PROOF);
SerializeToVector(s, btc_txout_proof);
}
}
} else {
if (peg_in_tx.which() > 0) {
const CTransactionRef& elem_peg_in_tx = boost::get<CTransactionRef>(peg_in_tx);
if (elem_peg_in_tx) {
SerializeToVector(s, PSBT_IN_PROPRIETARY, PSBT_ELEMENTS_ID, PSBT_IN_PEG_IN_TX);
OverrideStream<Stream> os(&s, s.GetType(), s.GetVersion() | SERIALIZE_TRANSACTION_NO_WITNESS);
SerializeToVector(os, elem_peg_in_tx);
}
}
if (txout_proof.which() > 0) {
const CMerkleBlock& elem_txout_proof = boost::get<CMerkleBlock>(txout_proof);
if (!elem_txout_proof.header.IsNull()) {
SerializeToVector(s, PSBT_IN_PROPRIETARY, PSBT_ELEMENTS_ID, PSBT_IN_TXOUT_PROOF);
SerializeToVector(s, elem_txout_proof);
}
}
}
if (!claim_script.empty()) {
SerializeToVector(s, PSBT_IN_PROPRIETARY, PSBT_ELEMENTS_ID, PSBT_IN_CLAIM_SCRIPT);
s << claim_script;
}
if (!genesis_hash.IsNull()) {
SerializeToVector(s, PSBT_IN_PROPRIETARY, PSBT_ELEMENTS_ID, PSBT_IN_GENESIS_HASH);
SerializeToVector(s, genesis_hash);
}
// Write unknown things
for (auto& entry : unknown) {
s << entry.first;
@ -342,6 +401,64 @@ struct PSBTInput
UnserializeFromVector(s, asset_blinding_factor);
break;
}
case PSBT_IN_PEG_IN_TX:
{
if (peg_in_tx.which() != 0) {
throw std::ios_base::failure("Duplicate Key, peg-in tx already provided");
} else if (subkey_len != 1) {
throw std::ios_base::failure("Peg-in tx key is more than one byte type");
}
if (Params().GetConsensus().ParentChainHasPow()) {
Sidechain::Bitcoin::CTransactionRef tx_btc;
OverrideStream<Stream> os(&s, s.GetType(), s.GetVersion());
UnserializeFromVector(os, tx_btc);
peg_in_tx = tx_btc;
} else {
CTransactionRef tx_btc;
OverrideStream<Stream> os(&s, s.GetType(), s.GetVersion());
UnserializeFromVector(os, tx_btc);
peg_in_tx = tx_btc;
}
break;
}
case PSBT_IN_TXOUT_PROOF:
{
if (txout_proof.which() != 0) {
throw std::ios_base::failure("Duplicate Key, peg-in txout proof already provided");
} else if (subkey_len != 1) {
throw std::ios_base::failure("Peg-in txout proof key is more than one byte type");
}
if (Params().GetConsensus().ParentChainHasPow()) {
Sidechain::Bitcoin::CMerkleBlock tx_proof;
UnserializeFromVector(s, tx_proof);
txout_proof = tx_proof;
} else {
CMerkleBlock tx_proof;
UnserializeFromVector(s, tx_proof);
txout_proof = tx_proof;
}
break;
}
case PSBT_IN_CLAIM_SCRIPT:
{
if (!claim_script.empty()) {
throw std::ios_base::failure("Duplicate Key, peg-in claim script already provided");
} else if (subkey_len != 1) {
throw std::ios_base::failure("Peg-in claim script key is more than one byte type");
}
s >> claim_script;
break;
}
case PSBT_IN_GENESIS_HASH:
{
if (!genesis_hash.IsNull()) {
throw std::ios_base::failure("Duplicate Key, peg-in genesis hash already provided");
} else if (subkey_len != 1) {
throw std::ios_base::failure("Peg-in genesis hash is more than one byte type");
}
UnserializeFromVector(s, genesis_hash);
break;
}
}
break;
}

View file

@ -1660,6 +1660,10 @@ UniValue decodepsbt(const JSONRPCRequest& request)
" \"value_blinding_factor\": \"hex\" , (string) The value blinding factor from the output being spent\n"
" \"asset\": \"hex\" , (string) The (unblinded) asset id of the input\n"
" \"asset_blinding_factor\": \"hex\" , (string) The asset blinding factor from the output being spent\n"
" \"pegin_bitcoin_tx\": \"hex\", (string) The tx providing the peg-in in the format of the getrawtransaction RPC.\n"
" \"pegin_claim_script\": \"hex\", (string) The claim script for the peg-in input\n"
" \"pegin_txout_proof\": \"hex\", (string) The tx providing the peg-in input\n"
" \"pegin_genesis_hash\": \"hex\", (string) The hash of the genesis block for this peg-in\n"
" \"unknown\" : { (json object) The unknown input fields\n"
" \"key\" : \"value\" (key-value pair) An unknown key-value pair\n"
" ...\n"
@ -1836,6 +1840,49 @@ UniValue decodepsbt(const JSONRPCRequest& request)
in.pushKV("asset_blinding_factor", input.asset_blinding_factor.GetHex());
}
// Peg-in stuff
if (Params().GetConsensus().ParentChainHasPow()) {
if (input.peg_in_tx.which() > 0) {
const Sidechain::Bitcoin::CTransactionRef& btc_peg_in_tx = boost::get<Sidechain::Bitcoin::CTransactionRef>(input.peg_in_tx);
if (btc_peg_in_tx) {
CDataStream ss_tx(SER_NETWORK, PROTOCOL_VERSION | SERIALIZE_TRANSACTION_NO_WITNESS);
ss_tx << btc_peg_in_tx;
in.pushKV("pegin_bitcoin_tx", HexStr(ss_tx.begin(), ss_tx.end()));
}
}
if (input.txout_proof.which() > 0) {
const Sidechain::Bitcoin::CMerkleBlock& btc_txout_proof = boost::get<Sidechain::Bitcoin::CMerkleBlock>(input.txout_proof);
if (!btc_txout_proof.header.IsNull()) {
CDataStream ss_mb(SER_NETWORK, PROTOCOL_VERSION | SERIALIZE_TRANSACTION_NO_WITNESS);
ss_mb << btc_txout_proof;
in.pushKV("pegin_txout_proof", HexStr(ss_mb.begin(), ss_mb.end()));
}
}
} else {
if (input.peg_in_tx.which() > 0) {
const CTransactionRef& elem_peg_in_tx = boost::get<CTransactionRef>(input.peg_in_tx);
if (elem_peg_in_tx) {
CDataStream ss_tx(SER_NETWORK, PROTOCOL_VERSION | SERIALIZE_TRANSACTION_NO_WITNESS);
ss_tx << elem_peg_in_tx;
in.pushKV("pegin_bitcoin_tx", HexStr(ss_tx.begin(), ss_tx.end()));
}
}
if (input.txout_proof.which() > 0) {
const CMerkleBlock& elem_txout_proof = boost::get<CMerkleBlock>(input.txout_proof);
if (!elem_txout_proof.header.IsNull()) {
CDataStream ss_mb(SER_NETWORK, PROTOCOL_VERSION | SERIALIZE_TRANSACTION_NO_WITNESS);
ss_mb << elem_txout_proof;
in.pushKV("pegin_txout_proof", HexStr(ss_mb.begin(), ss_mb.end()));
}
}
}
if (!input.claim_script.empty()) {
in.pushKV("pegin_claim_script", HexStr(input.claim_script.begin(), input.claim_script.end()));
}
if (!input.genesis_hash.IsNull()) {
in.pushKV("pegin_genesis_hash", input.genesis_hash.GetHex());
}
// Unknown data
if (input.unknown.size() > 0) {
UniValue unknowns(UniValue::VOBJ);
@ -2062,6 +2109,9 @@ UniValue createpsbt(const JSONRPCRequest& request)
{"txid", RPCArg::Type::STR_HEX, RPCArg::Optional::NO, "The transaction id"},
{"vout", RPCArg::Type::NUM, RPCArg::Optional::NO, "The output number"},
{"sequence", RPCArg::Type::NUM, /* default */ "depends on the value of the 'replaceable' and 'locktime' arguments", "The sequence number"},
{"pegin_bitcoin_tx", RPCArg::Type::STR_HEX, RPCArg::Optional::NO, "The raw bitcoin transaction (in hex) depositing bitcoin to the mainchain_address generated by getpeginaddress"},
{"pegin_txout_proof", RPCArg::Type::STR_HEX, RPCArg::Optional::NO, "A rawtxoutproof (in hex) generated by the mainchain daemon's `gettxoutproof` containing a proof of only bitcoin_tx"},
{"pegin_claim_script", RPCArg::Type::STR_HEX, RPCArg::Optional::NO, "The witness program generated by getpeginaddress."},
},
},
},
@ -2111,7 +2161,7 @@ UniValue createpsbt(const JSONRPCRequest& request)
);
std::vector<CPubKey> output_pubkeys;
CMutableTransaction rawTx = ConstructTransaction(request.params[0], request.params[1], request.params[2], request.params[3], request.params[4], &output_pubkeys, false /* allow_peg_in */);
CMutableTransaction rawTx = ConstructTransaction(request.params[0], request.params[1], request.params[2], request.params[3], request.params[4], &output_pubkeys);
// Make a blank psbt
PartiallySignedTransaction psbtx(rawTx);
@ -2119,6 +2169,42 @@ UniValue createpsbt(const JSONRPCRequest& request)
psbtx.outputs[i].blinding_pubkey = output_pubkeys[i];
}
// Add peg-in stuff if it's there
for (unsigned int i = 0; i < rawTx.vin.size(); ++i) {
if (psbtx.tx->vin[i].m_is_pegin) {
CScriptWitness& pegin_witness = psbtx.tx->witness.vtxinwit[i].m_pegin_witness;
CAmount val;
VectorReader vr_val(SER_NETWORK, PROTOCOL_VERSION, pegin_witness.stack[0], 0);
vr_val >> val;
psbtx.inputs[i].value = val;
VectorReader vr_asset(SER_NETWORK, PROTOCOL_VERSION, pegin_witness.stack[1], 0);
vr_asset >> psbtx.inputs[i].asset;
VectorReader vr_genesis(SER_NETWORK, PROTOCOL_VERSION, pegin_witness.stack[2], 0);
vr_genesis >> psbtx.inputs[i].genesis_hash;
psbtx.inputs[i].claim_script.assign(pegin_witness.stack[3].begin(), pegin_witness.stack[3].end());
VectorReader vr_tx(SER_NETWORK, PROTOCOL_VERSION, pegin_witness.stack[4], 0);
VectorReader vr_proof(SER_NETWORK, PROTOCOL_VERSION, pegin_witness.stack[5], 0);
if (Params().GetConsensus().ParentChainHasPow()) {
Sidechain::Bitcoin::CTransactionRef tx_btc;
vr_tx >> tx_btc;
psbtx.inputs[i].peg_in_tx = tx_btc;
Sidechain::Bitcoin::CMerkleBlock tx_proof;
vr_proof >> tx_proof;
psbtx.inputs[i].txout_proof = tx_proof;
} else {
CTransactionRef tx_btc;
vr_tx >> tx_btc;
psbtx.inputs[i].peg_in_tx = tx_btc;
CMerkleBlock tx_proof;
vr_proof >> tx_proof;
psbtx.inputs[i].txout_proof = tx_proof;
}
pegin_witness.SetNull();
psbtx.tx->vin[i].m_is_pegin = false;
}
}
return EncodePSBT(psbtx);
}

View file

@ -3,6 +3,7 @@
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
#include <confidential_validation.h>
#include <pegins.h>
#include <wallet/psbtwallet.h>
TransactionError FillPSBTInputsData(const CWallet* pwallet, PartiallySignedTransaction& psbtx, bool bip32derivs)
@ -95,6 +96,27 @@ TransactionError SignPSBT(const CWallet* pwallet, PartiallySignedTransaction& ps
}
}
// Stuff in the peg-in data
for (unsigned int i = 0; i < tx.vin.size(); ++i) {
PSBTInput& input = psbtx.inputs[i];
if (input.value && input.peg_in_tx.which() != 0 && input.txout_proof.which() != 0 && !input.claim_script.empty() && !input.genesis_hash.IsNull()) {
CScriptWitness pegin_witness;
if (Params().GetConsensus().ParentChainHasPow()) {
const Sidechain::Bitcoin::CTransactionRef& btc_peg_in_tx = boost::get<Sidechain::Bitcoin::CTransactionRef>(input.peg_in_tx);
const Sidechain::Bitcoin::CMerkleBlock& btc_txout_proof = boost::get<Sidechain::Bitcoin::CMerkleBlock>(input.txout_proof);
pegin_witness = CreatePeginWitness(*input.value, input.asset, input.genesis_hash, input.claim_script, btc_peg_in_tx, btc_txout_proof);
} else {
const CTransactionRef& elem_peg_in_tx = boost::get<CTransactionRef>(input.peg_in_tx);
const CMerkleBlock& elem_txout_proof = boost::get<CMerkleBlock>(input.txout_proof);
pegin_witness = CreatePeginWitness(*input.value, input.asset, input.genesis_hash, input.claim_script, elem_peg_in_tx, elem_txout_proof);
}
tx.vin[i].m_is_pegin = true;
tx.witness.vtxinwit[i].m_pegin_witness = pegin_witness;
// Set the witness utxo
input.witness_utxo = GetPeginOutputFromWitness(tx.witness.vtxinwit[i].m_pegin_witness);
}
}
// This is a convenience/usability check -- it's not invalid to sign an unbalanced transaction, but it's easy to shoot yourself in the foot.
if (!imbalance_ok) {
// Get UTXOs for all inputs, to check that amounts balance before signing.

View file

@ -83,6 +83,7 @@ class FedPegTest(BitcoinTestFramework):
"-keypool=1",
"-listenonion=0",
"-addresstype=legacy", # To make sure bitcoind gives back p2pkh no matter version
"-fallbackfee=0.0002"
])
else:
extra_args.extend([
@ -290,6 +291,31 @@ class FedPegTest(BitcoinTestFramework):
# Check the createrawtransaction makes the same unsigned peg-in transaction
raw_pegin2 = sidechain.createrawtransaction([{"txid":txid1, "vout": vout, "pegin_bitcoin_tx": raw, "pegin_txout_proof": proof, "pegin_claim_script": addrs["claim_script"]}], outputs)
assert_equal(raw_pegin, raw_pegin2)
# Check that createpsbt makes the correct unsigned peg-in
pegin_psbt = sidechain.createpsbt([{"txid":txid1, "vout": vout, "pegin_bitcoin_tx": raw, "pegin_txout_proof": proof, "pegin_claim_script": addrs["claim_script"]}], outputs)
decoded_psbt = sidechain.decodepsbt(pegin_psbt)
# Check that pegin_bitcoin_tx == raw, but due to stripping witnesses, we need to compare their txids
txid1 = parent.decoderawtransaction(decoded_psbt['inputs'][0]['pegin_bitcoin_tx'])['txid']
txid2 = parent.decoderawtransaction(raw)['txid']
assert_equal(txid1, txid2)
# Check the rest
assert_equal(decoded_psbt['inputs'][0]['pegin_claim_script'], addrs["claim_script"])
assert_equal(decoded_psbt['inputs'][0]['pegin_txout_proof'], proof)
assert_equal(decoded_psbt['inputs'][0]['pegin_genesis_hash'], parent.getblockhash(0))
# Make a psbt without those peg-in data and merge them
merge_pegin_psbt = sidechain.createpsbt([{"txid":txid1, "vout": vout}], outputs)
decoded_psbt = sidechain.decodepsbt(merge_pegin_psbt)
assert 'pegin_bitcoin_tx' not in decoded_psbt['inputs'][0]
assert 'pegin_claim_script' not in decoded_psbt['inputs'][0]
assert 'pegin_txout_proof' not in decoded_psbt['inputs'][0]
assert 'pegin_genesis_hash' not in decoded_psbt['inputs'][0]
merged_pegin_psbt = sidechain.combinepsbt([pegin_psbt, merge_pegin_psbt])
assert_equal(pegin_psbt, merged_pegin_psbt)
# Now sign the psbt
signed_psbt = sidechain.walletsignpsbt(pegin_psbt)
# Finalize and extract and compare
fin_psbt = sidechain.finalizepsbt(signed_psbt['psbt'])
assert_equal(fin_psbt, signed_pegin)
sample_pegin_struct = FromHex(CTransaction(), signed_pegin["hex"])
# Round-trip peg-in transaction using python serialization

View file

@ -524,10 +524,7 @@ class PSBTTest(BitcoinTestFramework):
# Some Confidential-Assets-specific tests
self.run_ca_tests()
# Check that peg-ins are disallowed
assert_raises_rpc_error(-8, 'pegin_ arguments provided but this command does not support peg-ins', self.nodes[0].createpsbt, [{"txid": "0000000000000000000000000000000000000000000000000000000000000000", "vout": 0, "pegin_bitcoin_tx": "00"}], [{self.nodes[0].getnewaddress(): 1}])
assert_raises_rpc_error(-8, 'pegin_ arguments provided but this command does not support peg-ins', self.nodes[0].createpsbt, [{"txid": "0000000000000000000000000000000000000000000000000000000000000000", "vout": 0, "pegin_txout_proof": "00"}], [{self.nodes[0].getnewaddress(): 1}])
assert_raises_rpc_error(-8, 'pegin_ arguments provided but this command does not support peg-ins', self.nodes[0].createpsbt, [{"txid": "0000000000000000000000000000000000000000000000000000000000000000", "vout": 0, "pegin_claim_script": "00"}], [{self.nodes[0].getnewaddress(): 1}])
# Check that peg-ins are disallowed for walletcreatefundedpsbt
assert_raises_rpc_error(-8, 'pegin_ arguments provided but this command does not support peg-ins', self.nodes[0].walletcreatefundedpsbt, [{"txid": "0000000000000000000000000000000000000000000000000000000000000000", "vout": 0, "pegin_bitcoin_tx": "00"}], [{self.nodes[0].getnewaddress(): 1}])
assert_raises_rpc_error(-8, 'pegin_ arguments provided but this command does not support peg-ins', self.nodes[0].walletcreatefundedpsbt, [{"txid": "0000000000000000000000000000000000000000000000000000000000000000", "vout": 0, "pegin_txout_proof": "00"}], [{self.nodes[0].getnewaddress(): 1}])
assert_raises_rpc_error(-8, 'pegin_ arguments provided but this command does not support peg-ins', self.nodes[0].walletcreatefundedpsbt, [{"txid": "0000000000000000000000000000000000000000000000000000000000000000", "vout": 0, "pegin_claim_script": "00"}], [{self.nodes[0].getnewaddress(): 1}])