Skip to content

Commit bf78a85

Browse files
committed
qml: adapt wallet transaction creation API
1 parent 9493163 commit bf78a85

6 files changed

Lines changed: 182 additions & 159 deletions

File tree

qml/models/psbtqmlmodel.cpp

Lines changed: 21 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
#include <script/script.h>
1919
#include <script/solver.h>
2020
#include <streams.h>
21+
#include <util/result.h>
2122
#include <util/strencodings.h>
2223
#include <util/translation.h>
2324

@@ -76,19 +77,14 @@ std::optional<std::pair<int, int>> ExtractMultisigSigInfo(const CScript& script)
7677
return std::nullopt;
7778
}
7879

79-
bool IsMultisigScript(const CScript& script)
80-
{
81-
return ExtractMultisigSigInfo(script).has_value();
82-
}
83-
84-
bool DecodePsbtFromBytes(const QByteArray& bytes, PartiallySignedTransaction& psbt, std::string& error)
80+
util::Result<PartiallySignedTransaction> DecodePsbtFromBytes(const QByteArray& bytes)
8581
{
8682
std::vector<std::byte> raw;
8783
raw.reserve(bytes.size());
8884
for (const char ch : bytes) {
8985
raw.push_back(static_cast<std::byte>(static_cast<unsigned char>(ch)));
9086
}
91-
return DecodeRawPSBT(psbt, std::span<const std::byte>{raw.data(), raw.size()}, error);
87+
return DecodeRawPSBT(std::span<const std::byte>{raw.data(), raw.size()});
9288
}
9389

9490
} // namespace
@@ -113,19 +109,22 @@ QString PsbtQmlModel::LoadPsbtFromFile(const QString& path, PartiallySignedTrans
113109
}
114110

115111
const QByteArray bytes{file.readAll()};
116-
std::string error;
117-
if (DecodePsbtFromBytes(bytes, psbt, error)) {
112+
const auto raw_result{DecodePsbtFromBytes(bytes)};
113+
if (raw_result) {
114+
psbt = *raw_result;
118115
return {};
119116
}
120117

121-
psbt = PartiallySignedTransaction{};
122-
std::string base64_error;
123-
if (DecodeBase64PSBT(psbt, QString::fromUtf8(bytes).trimmed().toStdString(), base64_error)) {
118+
const auto base64_result{DecodeBase64PSBT(QString::fromUtf8(bytes).trimmed().toStdString())};
119+
if (base64_result) {
120+
psbt = *base64_result;
124121
return {};
125122
}
126123

127-
const std::string& decode_error{base64_error == "invalid base64" ? error : base64_error};
128-
return tr("Could not decode PSBT: %1").arg(QString::fromStdString(decode_error));
124+
const bilingual_str raw_error{util::ErrorString(raw_result)};
125+
const bilingual_str base64_error{util::ErrorString(base64_result)};
126+
const bilingual_str& decode_error{base64_error.original == "invalid base64" ? raw_error : base64_error};
127+
return tr("Could not decode PSBT: %1").arg(QString::fromStdString(decode_error.translated));
129128
}
130129

131130
QByteArray PsbtQmlModel::SerializePsbtRaw(const PartiallySignedTransaction& psbt)
@@ -171,7 +170,7 @@ std::optional<std::pair<int, int>> PsbtQmlModel::MultisigPsbtInputSigInfo(const
171170
if (auto info{ExtractMultisigSigInfo(input.witness_script)}) return info;
172171

173172
CTxOut utxo;
174-
if (psbt.GetInputUTXO(utxo, index)) {
173+
if (input.GetUTXO(utxo)) {
175174
if (auto info{ExtractMultisigSigInfo(utxo.scriptPubKey)}) return info;
176175
}
177176
return std::nullopt;
@@ -222,7 +221,8 @@ void PsbtQmlModel::setMatchedTxid(const QString& txid)
222221

223222
QString PsbtQmlModel::loadFromFile(const QString& path)
224223
{
225-
PartiallySignedTransaction psbt;
224+
CMutableTransaction empty_tx;
225+
PartiallySignedTransaction psbt{empty_tx};
226226
const QString error{LoadPsbtFromFile(path, psbt)};
227227
if (!error.isEmpty()) {
228228
setError(error);
@@ -248,7 +248,7 @@ void PsbtQmlModel::sign()
248248
bool complete{false};
249249
size_t signed_inputs{0};
250250
const std::optional<common::PSBTError> error{
251-
m_wallet->fillPSBT(std::nullopt, /*sign=*/true, /*bip32derivs=*/true, &signed_inputs, *m_psbt, complete)};
251+
m_wallet->fillPSBT({.sign = true, .bip32_derivs = true}, &signed_inputs, *m_psbt, complete)};
252252
if (error) {
253253
refreshState(tr("Could not sign PSBT: %1").arg(PsbtErrorText(*error)));
254254
return;
@@ -328,7 +328,7 @@ void PsbtQmlModel::refreshState(const QString& status_override)
328328
size_t could_sign{0};
329329
std::optional<common::PSBTError> fill_error;
330330
if (m_wallet) {
331-
fill_error = m_wallet->fillPSBT(std::nullopt, /*sign=*/false, /*bip32derivs=*/true, &could_sign, *m_psbt, complete);
331+
fill_error = m_wallet->fillPSBT({.sign = false, .bip32_derivs = true}, &could_sign, *m_psbt, complete);
332332
}
333333

334334
m_error = fill_error ? PsbtErrorText(*fill_error) : QString();
@@ -361,13 +361,14 @@ void PsbtQmlModel::refreshState(const QString& status_override)
361361
QStringList PsbtQmlModel::buildSummary(const PartiallySignedTransaction& psbt) const
362362
{
363363
QStringList lines;
364-
if (!psbt.tx) {
364+
const auto unsigned_tx{psbt.GetUnsignedTx()};
365+
if (!unsigned_tx) {
365366
lines << tr("PSBT does not contain an unsigned transaction.");
366367
return lines;
367368
}
368369

369370
CAmount total{0};
370-
for (const CTxOut& output : psbt.tx->vout) {
371+
for (const CTxOut& output : unsigned_tx->vout) {
371372
total += output.nValue;
372373
CTxDestination destination;
373374
const QString address{ExtractDestination(output.scriptPubKey, destination) ? QString::fromStdString(EncodeDestination(destination)) : tr("unknown destination")};

qml/models/walletqmlmodel.cpp

Lines changed: 33 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -192,14 +192,12 @@ std::optional<CAmount> TryPreviewFee(interfaces::Wallet& wallet,
192192
const std::vector<wallet::CRecipient>& recipients,
193193
const wallet::CCoinControl& coin_control)
194194
{
195-
int change_position{-1};
196-
CAmount fee{0};
197-
const auto result = wallet.createTransaction(recipients, coin_control, /*sign=*/false, change_position, fee);
195+
const auto result = wallet.createTransaction(recipients, coin_control, /*sign=*/false, /*change_pos=*/std::nullopt);
198196
if (!result) {
199197
return std::nullopt;
200198
}
201199

202-
return fee;
200+
return result->fee;
203201
}
204202

205203
std::optional<std::vector<wallet::CRecipient>> WithLargestRecipientPayingFee(const std::vector<wallet::CRecipient>& recipients)
@@ -1597,25 +1595,24 @@ bool WalletQmlModel::prepareTransactionInternal(std::optional<SecureString> pass
15971595
return false;
15981596
}
15991597

1600-
int nChangePosRet = -1;
1601-
CAmount nFeeRequired = 0;
16021598
const bool sign = !m_wallet->privateKeysDisabled();
1603-
const auto& result = m_wallet->createTransaction(*vec_send, coin_control, sign, nChangePosRet, nFeeRequired);
1599+
const auto& result = m_wallet->createTransaction(*vec_send, coin_control, sign, /*change_pos=*/std::nullopt);
16041600
if (result) {
16051601
if (m_current_transaction) {
16061602
delete m_current_transaction;
16071603
}
1608-
const CTransactionRef& newTx = *result;
1604+
const CTransactionRef& newTx = result->tx;
16091605
m_current_transaction = new WalletQmlModelTransaction(m_send_recipients, this);
16101606
m_current_psbt.reset();
16111607
m_current_transaction_source = CurrentTransactionSource::SendDraft;
16121608
m_current_transaction_can_send = true;
16131609
m_current_transaction_can_broadcast = false;
16141610
m_current_transaction_review_message.clear();
16151611
m_current_transaction->setWtx(newTx);
1616-
m_current_transaction->setTransactionFee(nFeeRequired);
1612+
m_current_transaction->setTransactionFee(result->fee);
16171613
if (subtract_fee_from_amount) {
1618-
m_current_transaction->reassignAmounts(nChangePosRet);
1614+
m_current_transaction->reassignAmounts(
1615+
result->change_pos ? static_cast<int>(*result->change_pos) : -1);
16191616
}
16201617
m_current_transaction->setDisplayUnit(m_display_unit);
16211618
relock_guard.relock();
@@ -1642,7 +1639,8 @@ void WalletQmlModel::approveExternalSignerTransaction()
16421639
}
16431640

16441641
try {
1645-
PartiallySignedTransaction psbtx;
1642+
CMutableTransaction empty_tx;
1643+
PartiallySignedTransaction psbtx{empty_tx};
16461644
if (m_current_psbt) {
16471645
psbtx = *m_current_psbt;
16481646
} else {
@@ -1652,7 +1650,7 @@ void WalletQmlModel::approveExternalSignerTransaction()
16521650
}
16531651

16541652
bool complete{false};
1655-
const auto draft_err = m_wallet->fillPSBT(std::nullopt, /*sign=*/false, /*bip32derivs=*/true,
1653+
const auto draft_err = m_wallet->fillPSBT({.sign = false, .bip32_derivs = true},
16561654
/*n_signed=*/nullptr, psbtx, complete);
16571655
if (draft_err) {
16581656
Q_EMIT externalSignerApprovalFailed(
@@ -1662,7 +1660,7 @@ void WalletQmlModel::approveExternalSignerTransaction()
16621660
}
16631661

16641662
if (!complete) {
1665-
const auto sign_err = m_wallet->fillPSBT(std::nullopt, /*sign=*/true, /*bip32derivs=*/true,
1663+
const auto sign_err = m_wallet->fillPSBT({.sign = true, .bip32_derivs = true},
16661664
/*n_signed=*/nullptr, psbtx, complete);
16671665
if (sign_err) {
16681666
const bool signer_not_found = *sign_err == common::PSBTError::EXTERNAL_SIGNER_NOT_FOUND;
@@ -1813,7 +1811,7 @@ bool WalletQmlModel::sendTransactionInternal(std::optional<SecureString> passphr
18131811

18141812
size_t signed_inputs{0};
18151813
const std::optional<common::PSBTError> fill_error{
1816-
m_wallet->fillPSBT(std::nullopt, /*sign=*/true, /*bip32derivs=*/true, &signed_inputs, psbt, complete)};
1814+
m_wallet->fillPSBT({.sign = true, .bip32_derivs = true}, &signed_inputs, psbt, complete)};
18171815
if (fill_error) {
18181816
setTransactionStatus(PsbtQmlModel::PsbtErrorText(*fill_error));
18191817
return false;
@@ -1876,15 +1874,17 @@ WalletQmlModel::PsbtImportResult WalletQmlModel::importPsbtFromFile(const QStrin
18761874
return PsbtImportResult::PsbtUnsupported;
18771875
}
18781876

1879-
PartiallySignedTransaction psbt;
1877+
CMutableTransaction empty_tx;
1878+
PartiallySignedTransaction psbt{empty_tx};
18801879
const QString load_err{PsbtQmlModel::LoadPsbtFromFile(path, psbt)};
18811880
if (!load_err.isEmpty()) {
18821881
m_imported_psbt_model->setError(load_err);
18831882
return PsbtImportResult::PsbtUnsupported;
18841883
}
18851884

1886-
if (m_wallet && psbt.tx) {
1887-
const Txid psbt_txid{psbt.tx->GetHash()};
1885+
const auto unsigned_tx{psbt.GetUnsignedTx()};
1886+
if (m_wallet && unsigned_tx) {
1887+
const Txid psbt_txid{unsigned_tx->GetHash()};
18881888
interfaces::WalletTxStatus tx_status;
18891889
int num_blocks{0};
18901890
int64_t block_time{0};
@@ -1915,7 +1915,8 @@ QString WalletQmlModel::saveCurrentTransactionAsPsbt(const QString& path)
19151915
return tr("No transaction is prepared.");
19161916
}
19171917

1918-
PartiallySignedTransaction psbtx;
1918+
CMutableTransaction empty_tx;
1919+
PartiallySignedTransaction psbtx{empty_tx};
19191920
try {
19201921
if (m_current_psbt) {
19211922
psbtx = *m_current_psbt;
@@ -1932,7 +1933,7 @@ QString WalletQmlModel::saveCurrentTransactionAsPsbt(const QString& path)
19321933
}
19331934

19341935
bool complete{false};
1935-
const auto err{m_wallet->fillPSBT(std::nullopt, /*sign=*/false, /*bip32derivs=*/true,
1936+
const auto err{m_wallet->fillPSBT({.sign = false, .bip32_derivs = true},
19361937
/*n_signed=*/nullptr, psbtx, complete)};
19371938
if (err) {
19381939
return PsbtQmlModel::PsbtErrorText(*err);
@@ -1950,33 +1951,39 @@ bool WalletQmlModel::tryImportPsbtToReview(const PartiallySignedTransaction& psb
19501951
reason = tr("No wallet is loaded.");
19511952
return false;
19521953
}
1953-
if (!psbt.tx) {
1954+
const auto unsigned_tx{psbt.GetUnsignedTx()};
1955+
if (!unsigned_tx) {
19541956
reason = tr("The PSBT does not contain an unsigned transaction.");
19551957
return false;
19561958
}
1957-
if (psbt.tx->vin.empty() || psbt.tx->vout.empty()) {
1959+
if (unsigned_tx->vin.empty() || unsigned_tx->vout.empty()) {
19581960
reason = tr("The PSBT has no inputs or outputs.");
19591961
return false;
19601962
}
1961-
if (psbt.inputs.size() != psbt.tx->vin.size() || psbt.outputs.size() != psbt.tx->vout.size()) {
1963+
if (psbt.inputs.size() != unsigned_tx->vin.size() || psbt.outputs.size() != unsigned_tx->vout.size()) {
19621964
reason = tr("The PSBT is malformed.");
19631965
return false;
19641966
}
19651967

1966-
const bool spends_only_wallet_inputs{std::all_of(psbt.tx->vin.begin(), psbt.tx->vin.end(), [this](const CTxIn& input) {
1968+
const bool spends_only_wallet_inputs{std::all_of(unsigned_tx->vin.begin(), unsigned_tx->vin.end(), [this](const CTxIn& input) {
19671969
return m_wallet->txinIsMine(input);
19681970
})};
19691971

19701972
PartiallySignedTransaction analysis_psbt{psbt};
19711973
bool complete{FinalizePSBT(analysis_psbt)};
19721974
size_t could_sign{0};
19731975
const std::optional<common::PSBTError> fill_error{
1974-
m_wallet->fillPSBT(std::nullopt, /*sign=*/false, /*bip32derivs=*/false, &could_sign, analysis_psbt, complete)};
1976+
m_wallet->fillPSBT({.sign = false, .bip32_derivs = false}, &could_sign, analysis_psbt, complete)};
19751977
if (fill_error) {
19761978
reason = PsbtQmlModel::PsbtErrorText(*fill_error);
19771979
return false;
19781980
}
19791981
complete = FinalizePSBT(analysis_psbt);
1982+
const auto analysis_tx{analysis_psbt.GetUnsignedTx()};
1983+
if (!analysis_tx) {
1984+
reason = tr("The PSBT does not contain an unsigned transaction.");
1985+
return false;
1986+
}
19801987
std::optional<std::pair<int, int>> multisig_sig_info;
19811988
if (!complete) {
19821989
for (size_t i{0}; i < analysis_psbt.inputs.size(); ++i) {
@@ -2009,7 +2016,7 @@ bool WalletQmlModel::tryImportPsbtToReview(const PartiallySignedTransaction& psb
20092016
};
20102017
std::vector<DraftRecipient> draft_recipients;
20112018
CAmount recipient_total{0};
2012-
for (const CTxOut& output : analysis_psbt.tx->vout) {
2019+
for (const CTxOut& output : analysis_tx->vout) {
20132020
CTxDestination destination;
20142021
if (!ExtractDestination(output.scriptPubKey, destination)) {
20152022
if (output.nValue == 0 && output.scriptPubKey.IsUnspendable()) {
@@ -2056,7 +2063,7 @@ bool WalletQmlModel::tryImportPsbtToReview(const PartiallySignedTransaction& psb
20562063
delete m_current_transaction;
20572064
}
20582065
m_current_transaction = new WalletQmlModelTransaction(m_send_recipients, this);
2059-
m_current_transaction->setWtx(MakeTransactionRef(*analysis_psbt.tx));
2066+
m_current_transaction->setWtx(MakeTransactionRef(*analysis_tx));
20602067
if (analysis.fee) {
20612068
m_current_transaction->setTransactionFee(*analysis.fee);
20622069
}

test/mocks/mockwallet.h

Lines changed: 15 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ class StubWallet : public interfaces::Wallet
5353
bool unlockCoin(const COutPoint&) override { return false; }
5454
bool isLockedCoin(const COutPoint&) override { return false; }
5555
void listLockedCoins(std::vector<COutPoint>&) override {}
56-
util::Result<CTransactionRef> createTransaction(const std::vector<wallet::CRecipient>&, const wallet::CCoinControl&, bool, int&, CAmount&) override { return util::Error{Untranslated("not implemented")}; }
56+
util::Result<wallet::CreatedTransactionResult> createTransaction(const std::vector<wallet::CRecipient>&, const wallet::CCoinControl&, bool, std::optional<unsigned int>) override { return util::Error{Untranslated("not implemented")}; }
5757
void commitTransaction(CTransactionRef, interfaces::WalletValueMap, interfaces::WalletOrderForm) override {}
5858
bool transactionCanBeAbandoned(const Txid&) override { return false; }
5959
bool abandonTransaction(const Txid&) override { return false; }
@@ -66,7 +66,7 @@ class StubWallet : public interfaces::Wallet
6666
std::set<interfaces::WalletTx> getWalletTxs() override { return {}; }
6767
bool tryGetTxStatus(const Txid&, interfaces::WalletTxStatus&, int&, int64_t&) override { return false; }
6868
interfaces::WalletTx getWalletTxDetails(const Txid&, interfaces::WalletTxStatus&, interfaces::WalletOrderForm&, bool&, int&) override { return {}; }
69-
std::optional<common::PSBTError> fillPSBT(std::optional<int>, bool, bool, size_t*, PartiallySignedTransaction&, bool&) override { return std::nullopt; }
69+
std::optional<common::PSBTError> fillPSBT(const common::PSBTFillOptions&, size_t*, PartiallySignedTransaction&, bool&) override { return std::nullopt; }
7070
interfaces::WalletBalances getBalances() override { return {}; }
7171
bool tryGetBalances(interfaces::WalletBalances&, uint256&) override { return false; }
7272
CAmount getBalance() override { return 0; }
@@ -106,14 +106,23 @@ class MockWallet : public StubWallet
106106
return getNewDestinationValue(type, label);
107107
}
108108

109-
util::Result<CTransactionRef> createTransaction(const std::vector<wallet::CRecipient>& recipients,
109+
util::Result<wallet::CreatedTransactionResult> createTransaction(const std::vector<wallet::CRecipient>& recipients,
110110
const wallet::CCoinControl& coin_control,
111111
bool sign,
112-
int& change_pos,
113-
CAmount& fee) override
112+
std::optional<unsigned int>) override
114113
{
115114
if (createTransactionHandler) {
116-
return createTransactionHandler(recipients, coin_control, sign, change_pos, fee);
115+
int change_pos{-1};
116+
CAmount fee{0};
117+
auto result = createTransactionHandler(recipients, coin_control, sign, change_pos, fee);
118+
if (!result) {
119+
return util::Error{util::ErrorString(result)};
120+
}
121+
return wallet::CreatedTransactionResult{
122+
*result,
123+
fee,
124+
change_pos >= 0 ? std::optional<unsigned int>{static_cast<unsigned int>(change_pos)} : std::nullopt,
125+
FeeCalculation{}};
117126
}
118127
return util::Error{Untranslated("no createTransactionHandler installed")};
119128
}

0 commit comments

Comments
 (0)