mirror of
https://github.com/ElementsProject/elements.git
synced 2026-08-20 13:37:28 +02:00
Merge 316afb1eca into merged_master (Bitcoin PR bitcoin/bitcoin#25218)
This commit is contained in:
commit
e22bfb3011
22 changed files with 248 additions and 240 deletions
|
|
@ -292,6 +292,7 @@ BITCOIN_CORE_H = \
|
|||
util/overloaded.h \
|
||||
util/rbf.h \
|
||||
util/readwritefile.h \
|
||||
util/result.h \
|
||||
util/serfloat.h \
|
||||
util/settings.h \
|
||||
util/sock.h \
|
||||
|
|
|
|||
7
src/bench/wallet_loading.cpp
Normal file → Executable file
7
src/bench/wallet_loading.cpp
Normal file → Executable file
|
|
@ -47,12 +47,11 @@ static void BenchUnloadWallet(std::shared_ptr<CWallet>&& wallet)
|
|||
|
||||
static void AddTx(CWallet& wallet)
|
||||
{
|
||||
bilingual_str error;
|
||||
CTxDestination dest;
|
||||
wallet.GetNewDestination(OutputType::BECH32, "", dest, error);
|
||||
const auto& dest = wallet.GetNewDestination(OutputType::BECH32, "");
|
||||
assert(dest.HasRes());
|
||||
|
||||
CMutableTransaction mtx;
|
||||
mtx.vout.push_back({::policyAsset, COIN, GetScriptForDestination(dest)});
|
||||
mtx.vout.push_back({::policyAsset, COIN, GetScriptForDestination(dest.GetObj())});
|
||||
mtx.vin.push_back(CTxIn());
|
||||
|
||||
wallet.AddToWallet(MakeTransactionRef(mtx), TxStateInactive{});
|
||||
|
|
|
|||
8
src/interfaces/wallet.h
Normal file → Executable file
8
src/interfaces/wallet.h
Normal file → Executable file
|
|
@ -13,6 +13,7 @@
|
|||
#include <script/standard.h> // For CTxDestination
|
||||
#include <support/allocators/secure.h> // For SecureString
|
||||
#include <util/message.h>
|
||||
#include <util/result.h>
|
||||
#include <util/ui_change_type.h>
|
||||
|
||||
#include <cstdint>
|
||||
|
|
@ -89,7 +90,7 @@ public:
|
|||
virtual std::string getWalletName() = 0;
|
||||
|
||||
// Get a new address.
|
||||
virtual bool getNewDestination(const OutputType type, const std::string label, CTxDestination& dest, bool add_blinding_key = false) = 0;
|
||||
virtual BResult<CTxDestination> getNewDestination(const OutputType type, const std::string label, bool add_blinding_key = false) = 0;
|
||||
|
||||
//! Get public key.
|
||||
virtual bool getPubKey(const CScript& script, const CKeyID& address, CPubKey& pub_key) = 0;
|
||||
|
|
@ -140,13 +141,12 @@ public:
|
|||
virtual void listLockedCoins(std::vector<COutPoint>& outputs) = 0;
|
||||
|
||||
//! Create transaction.
|
||||
virtual CTransactionRef createTransaction(const std::vector<wallet::CRecipient>& recipients,
|
||||
virtual BResult<CTransactionRef> createTransaction(const std::vector<wallet::CRecipient>& recipients,
|
||||
const wallet::CCoinControl& coin_control,
|
||||
bool sign,
|
||||
int& change_pos,
|
||||
CAmount& fee,
|
||||
wallet::BlindDetails* blind_details,
|
||||
bilingual_str& fail_reason) = 0;
|
||||
wallet::BlindDetails* blind_details) = 0;
|
||||
|
||||
//! Commit transaction.
|
||||
virtual void commitTransaction(CTransactionRef tx,
|
||||
|
|
|
|||
14
src/qt/addresstablemodel.cpp
Normal file → Executable file
14
src/qt/addresstablemodel.cpp
Normal file → Executable file
|
|
@ -370,23 +370,21 @@ QString AddressTableModel::addRow(const QString &type, const QString &label, con
|
|||
else if(type == Receive)
|
||||
{
|
||||
// Generate a new address to associate with given label
|
||||
CTxDestination dest;
|
||||
if(!walletModel->wallet().getNewDestination(address_type, strLabel, dest, true))
|
||||
{
|
||||
auto op_dest = walletModel->wallet().getNewDestination(address_type, strLabel, true);
|
||||
if (!op_dest) {
|
||||
WalletModel::UnlockContext ctx(walletModel->requestUnlock());
|
||||
if(!ctx.isValid())
|
||||
{
|
||||
if (!ctx.isValid()) {
|
||||
// Unlock wallet failed or was cancelled
|
||||
editStatus = WALLET_UNLOCK_FAILURE;
|
||||
return QString();
|
||||
}
|
||||
if(!walletModel->wallet().getNewDestination(address_type, strLabel, dest, true))
|
||||
{
|
||||
op_dest = walletModel->wallet().getNewDestination(address_type, strLabel, true);
|
||||
if (!op_dest) {
|
||||
editStatus = KEY_GENERATION_FAILURE;
|
||||
return QString();
|
||||
}
|
||||
}
|
||||
strAddress = EncodeDestination(dest);
|
||||
strAddress = EncodeDestination(op_dest.GetObj());
|
||||
}
|
||||
else
|
||||
{
|
||||
|
|
|
|||
|
|
@ -236,11 +236,11 @@ WalletModel::SendCoinsReturn WalletModel::prepareTransaction(WalletModelTransact
|
|||
{
|
||||
CAmount nFeeRequired = 0;
|
||||
int nChangePosRet = -1;
|
||||
bilingual_str error;
|
||||
|
||||
auto& newTx = transaction.getWtx();
|
||||
std::vector<CAmount> out_amounts;
|
||||
newTx = m_wallet->createTransaction(vecSend, coinControl, !wallet().privateKeysDisabled() /* sign */, nChangePosRet, nFeeRequired, blind_details, error);
|
||||
const auto& res = m_wallet->createTransaction(vecSend, coinControl, !wallet().privateKeysDisabled() /* sign */, nChangePosRet, nFeeRequired, blind_details);
|
||||
newTx = res ? res.GetObj() : nullptr;
|
||||
transaction.setTransactionFee(nFeeRequired);
|
||||
if (fSubtractFeeFromAmount && newTx) {
|
||||
if(blind_details) {
|
||||
|
|
@ -257,7 +257,7 @@ WalletModel::SendCoinsReturn WalletModel::prepareTransaction(WalletModelTransact
|
|||
{
|
||||
return SendCoinsReturn(AmountWithFeeExceedsBalance);
|
||||
}
|
||||
Q_EMIT message(tr("Send Coins"), QString::fromStdString(error.translated),
|
||||
Q_EMIT message(tr("Send Coins"), QString::fromStdString(res.GetError().translated),
|
||||
CClientUIInterface::MSG_ERROR);
|
||||
return TransactionCreationFailed;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -20,11 +20,10 @@ const std::string ADDRESS_BCRT1_UNSPENDABLE = "bcrt1qqqqqqqqqqqqqqqqqqqqqqqqqqqq
|
|||
std::string getnewaddress(CWallet& w)
|
||||
{
|
||||
constexpr auto output_type = OutputType::BECH32;
|
||||
CTxDestination dest;
|
||||
bilingual_str error;
|
||||
if (!w.GetNewDestination(output_type, "", dest, error)) assert(false);
|
||||
auto op_dest = w.GetNewDestination(output_type, "");
|
||||
assert(op_dest.HasRes());
|
||||
|
||||
return EncodeDestination(dest);
|
||||
return EncodeDestination(op_dest.GetObj());
|
||||
}
|
||||
|
||||
#endif // ENABLE_WALLET
|
||||
|
|
|
|||
43
src/util/result.h
Executable file
43
src/util/result.h
Executable file
|
|
@ -0,0 +1,43 @@
|
|||
// Copyright (c) 2022 The Bitcoin Core developers
|
||||
// Distributed under the MIT software license, see the accompanying
|
||||
// file COPYING or https://www.opensource.org/licenses/mit-license.php.
|
||||
|
||||
#ifndef BITCOIN_UTIL_RESULT_H
|
||||
#define BITCOIN_UTIL_RESULT_H
|
||||
|
||||
#include <util/translation.h>
|
||||
#include <variant>
|
||||
|
||||
/*
|
||||
* 'BResult' is a generic class useful for wrapping a return object
|
||||
* (in case of success) or propagating the error cause.
|
||||
*/
|
||||
template<class T>
|
||||
class BResult {
|
||||
private:
|
||||
std::variant<bilingual_str, T> m_variant;
|
||||
|
||||
public:
|
||||
BResult() : m_variant(Untranslated("")) {}
|
||||
BResult(const T& _obj) : m_variant(_obj) {}
|
||||
BResult(const bilingual_str& error) : m_variant(error) {}
|
||||
|
||||
/* Whether the function succeeded or not */
|
||||
bool HasRes() const { return std::holds_alternative<T>(m_variant); }
|
||||
|
||||
/* In case of success, the result object */
|
||||
const T& GetObj() const {
|
||||
assert(HasRes());
|
||||
return std::get<T>(m_variant);
|
||||
}
|
||||
|
||||
/* In case of failure, the error cause */
|
||||
const bilingual_str& GetError() const {
|
||||
assert(!HasRes());
|
||||
return std::get<bilingual_str>(m_variant);
|
||||
}
|
||||
|
||||
explicit operator bool() const { return HasRes(); }
|
||||
};
|
||||
|
||||
#endif // BITCOIN_UTIL_RESULT_H
|
||||
|
|
@ -236,19 +236,18 @@ Result CreateRateBumpTransaction(CWallet& wallet, const uint256& txid, const CCo
|
|||
new_coin_control.m_min_depth = 1;
|
||||
|
||||
constexpr int RANDOM_CHANGE_POSITION = -1;
|
||||
bilingual_str fail_reason;
|
||||
FeeCalculation fee_calc_out;
|
||||
std::optional<CreatedTransactionResult> txr = CreateTransaction(wallet, recipients, RANDOM_CHANGE_POSITION, fail_reason, new_coin_control, fee_calc_out, false);
|
||||
if (!txr) {
|
||||
errors.push_back(Untranslated("Unable to create transaction.") + Untranslated(" ") + fail_reason);
|
||||
auto res = CreateTransaction(wallet, recipients, RANDOM_CHANGE_POSITION, new_coin_control, false);
|
||||
if (!res) {
|
||||
errors.push_back(Untranslated("Unable to create transaction.") + Untranslated(" ") + res.GetError());
|
||||
return Result::WALLET_ERROR;
|
||||
}
|
||||
|
||||
const auto& txr = res.GetObj();
|
||||
// Write back new fee if successful
|
||||
new_fee = txr->fee;
|
||||
new_fee = txr.fee;
|
||||
|
||||
// Write back transaction
|
||||
mtx = CMutableTransaction(*txr->tx);
|
||||
mtx = CMutableTransaction(*txr.tx);
|
||||
|
||||
return Result::OK;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -158,11 +158,10 @@ public:
|
|||
void abortRescan() override { m_wallet->AbortRescan(); }
|
||||
bool backupWallet(const std::string& filename) override { return m_wallet->BackupWallet(filename); }
|
||||
std::string getWalletName() override { return m_wallet->GetName(); }
|
||||
bool getNewDestination(const OutputType type, const std::string label, CTxDestination& dest, bool add_blinding_key = false) override
|
||||
BResult<CTxDestination> getNewDestination(const OutputType type, const std::string label, bool add_blinding_key = false) override
|
||||
{
|
||||
LOCK(m_wallet->cs_wallet);
|
||||
bilingual_str error;
|
||||
return m_wallet->GetNewDestination(type, label, dest, error, add_blinding_key);
|
||||
return m_wallet->GetNewDestination(type, label, add_blinding_key);
|
||||
}
|
||||
bool getPubKey(const CScript& script, const CKeyID& address, CPubKey& pub_key) override
|
||||
{
|
||||
|
|
@ -262,23 +261,22 @@ public:
|
|||
LOCK(m_wallet->cs_wallet);
|
||||
return m_wallet->ListLockedCoins(outputs);
|
||||
}
|
||||
CTransactionRef createTransaction(const std::vector<CRecipient>& recipients,
|
||||
BResult<CTransactionRef> createTransaction(const std::vector<CRecipient>& recipients,
|
||||
const CCoinControl& coin_control,
|
||||
bool sign,
|
||||
int& change_pos,
|
||||
CAmount& fee,
|
||||
BlindDetails* blind_details,
|
||||
bilingual_str& fail_reason) override
|
||||
BlindDetails* blind_details) override
|
||||
{
|
||||
LOCK(m_wallet->cs_wallet);
|
||||
FeeCalculation fee_calc_out;
|
||||
std::optional<CreatedTransactionResult> txr = CreateTransaction(*m_wallet, recipients, change_pos,
|
||||
fail_reason, coin_control, fee_calc_out, sign, blind_details);
|
||||
if (!txr) return {};
|
||||
fee = txr->fee;
|
||||
change_pos = txr->change_pos;
|
||||
const auto& res = CreateTransaction(*m_wallet, recipients, change_pos,
|
||||
coin_control, sign, blind_details);
|
||||
if (!res) return res.GetError();
|
||||
const auto& txr = res.GetObj();
|
||||
fee = txr.fee;
|
||||
change_pos = txr.change_pos;
|
||||
|
||||
return txr->tx;
|
||||
return txr.tx;
|
||||
}
|
||||
void commitTransaction(CTransactionRef tx,
|
||||
WalletValueMap value_map,
|
||||
|
|
|
|||
18
src/wallet/rpc/addresses.cpp
Normal file → Executable file
18
src/wallet/rpc/addresses.cpp
Normal file → Executable file
|
|
@ -70,13 +70,12 @@ RPCHelpMan getnewaddress()
|
|||
output_type = parsed.value();
|
||||
}
|
||||
|
||||
CTxDestination dest;
|
||||
bilingual_str error;
|
||||
if (!pwallet->GetNewDestination(output_type, label, dest, error, add_blinding_key)) {
|
||||
throw JSONRPCError(RPC_WALLET_KEYPOOL_RAN_OUT, error.original);
|
||||
auto op_dest = pwallet->GetNewDestination(output_type, label, add_blinding_key);
|
||||
if (!op_dest) {
|
||||
throw JSONRPCError(RPC_WALLET_KEYPOOL_RAN_OUT, op_dest.GetError().original);
|
||||
}
|
||||
|
||||
return EncodeDestination(dest);
|
||||
return EncodeDestination(op_dest.GetObj());
|
||||
},
|
||||
};
|
||||
}
|
||||
|
|
@ -123,13 +122,12 @@ RPCHelpMan getrawchangeaddress()
|
|||
output_type = parsed.value();
|
||||
}
|
||||
|
||||
CTxDestination dest;
|
||||
bilingual_str error;
|
||||
bool add_blinding_key = force_blind || gArgs.GetBoolArg("-blindedaddresses", g_con_elementsmode);
|
||||
if (!pwallet->GetNewChangeDestination(output_type, dest, error, add_blinding_key)) {
|
||||
throw JSONRPCError(RPC_WALLET_KEYPOOL_RAN_OUT, error.original);
|
||||
auto op_dest = pwallet->GetNewChangeDestination(output_type, add_blinding_key);
|
||||
if (!op_dest) {
|
||||
throw JSONRPCError(RPC_WALLET_KEYPOOL_RAN_OUT, op_dest.GetError().original);
|
||||
}
|
||||
return EncodeDestination(dest);
|
||||
return EncodeDestination(op_dest.GetObj());
|
||||
},
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -189,13 +189,12 @@ RPCHelpMan getpeginaddress()
|
|||
}
|
||||
|
||||
// Use native witness destination
|
||||
CTxDestination dest;
|
||||
bilingual_str error;
|
||||
if (!pwallet->GetNewDestination(OutputType::BECH32, "", dest, error)) {
|
||||
throw JSONRPCError(RPC_WALLET_KEYPOOL_RAN_OUT, error.original);
|
||||
auto dest = pwallet->GetNewDestination(OutputType::BECH32, "");
|
||||
if (!dest) {
|
||||
throw JSONRPCError(RPC_WALLET_KEYPOOL_RAN_OUT, dest.GetError().original);
|
||||
}
|
||||
|
||||
CScript dest_script = GetScriptForDestination(dest);
|
||||
CScript dest_script = GetScriptForDestination(dest.GetObj());
|
||||
|
||||
// Also add raw scripts to index to recognize later.
|
||||
spk_man->AddCScript(dest_script);
|
||||
|
|
@ -833,10 +832,9 @@ static UniValue createrawpegin(const JSONRPCRequest& request, T_tx_ref& txBTCRef
|
|||
pwallet->TopUpKeyPool();
|
||||
|
||||
// Generate a new key that is added to wallet
|
||||
CTxDestination wpkhash;
|
||||
bilingual_str error;
|
||||
if (!pwallet->GetNewDestination(OutputType::BECH32, "", wpkhash, error)) {
|
||||
throw JSONRPCError(RPC_WALLET_KEYPOOL_RAN_OUT, error.original);
|
||||
auto wpkhash = pwallet->GetNewDestination(OutputType::BECH32, "");
|
||||
if (!wpkhash) {
|
||||
throw JSONRPCError(RPC_WALLET_KEYPOOL_RAN_OUT, wpkhash.GetError().original);
|
||||
}
|
||||
|
||||
// Get value for output
|
||||
|
|
@ -846,7 +844,7 @@ static UniValue createrawpegin(const JSONRPCRequest& request, T_tx_ref& txBTCRef
|
|||
}
|
||||
|
||||
// one wallet output and one fee output
|
||||
mtx.vout.push_back(CTxOut(Params().GetConsensus().pegged_asset, value, GetScriptForDestination(wpkhash)));
|
||||
mtx.vout.push_back(CTxOut(Params().GetConsensus().pegged_asset, value, GetScriptForDestination(wpkhash.GetObj())));
|
||||
mtx.vout.push_back(CTxOut(Params().GetConsensus().pegged_asset, 0, CScript()));
|
||||
|
||||
// Estimate fee for transaction, decrement fee output(including witness data)
|
||||
|
|
@ -1379,17 +1377,17 @@ static CTransactionRef SendGenerationTransaction(const CScript& asset_script, co
|
|||
FeeCalculation fee_calc_out;
|
||||
CCoinControl dummy_control;
|
||||
BlindDetails blind_details;
|
||||
std::optional<CreatedTransactionResult> txr = CreateTransaction(*pwallet, vecSend, RANDOM_CHANGE_POSITION,
|
||||
error, dummy_control, fee_calc_out, true, &blind_details, issuance_details);
|
||||
BResult<CreatedTransactionResult> txr = CreateTransaction(*pwallet, vecSend, RANDOM_CHANGE_POSITION,
|
||||
dummy_control, true, &blind_details, issuance_details);
|
||||
if (!txr) {
|
||||
throw JSONRPCError(RPC_WALLET_ERROR, error.original);
|
||||
}
|
||||
nFeeRequired = txr->fee;
|
||||
nFeeRequired = txr.GetObj().fee;
|
||||
|
||||
mapValue_t map_value;
|
||||
pwallet->CommitTransaction(txr->tx, std::move(map_value), {} /* orderForm */, &blind_details);
|
||||
pwallet->CommitTransaction(txr.GetObj().tx, std::move(map_value), {} /* orderForm */, &blind_details);
|
||||
|
||||
return txr->tx;
|
||||
return txr.GetObj().tx;
|
||||
}
|
||||
|
||||
RPCHelpMan issueasset()
|
||||
|
|
@ -1449,29 +1447,35 @@ RPCHelpMan issueasset()
|
|||
// Generate a new key that is added to wallet
|
||||
bilingual_str error;
|
||||
CPubKey newKey;
|
||||
CTxDestination asset_dest;
|
||||
CTxDestination token_dest;
|
||||
BResult<CTxDestination> asset_dest;
|
||||
BResult<CTxDestination> token_dest;
|
||||
CScript asset_script;
|
||||
CScript token_script;
|
||||
CPubKey asset_dest_blindpub;
|
||||
CPubKey token_dest_blindpub;
|
||||
|
||||
if (nAmount > 0) {
|
||||
if (!pwallet->GetNewDestination(OutputType::BECH32, "", asset_dest, error)) {
|
||||
throw JSONRPCError(RPC_WALLET_KEYPOOL_RAN_OUT, error.original);
|
||||
asset_dest = pwallet->GetNewDestination(OutputType::BECH32, "");
|
||||
if (!asset_dest) {
|
||||
throw JSONRPCError(RPC_WALLET_KEYPOOL_RAN_OUT, asset_dest.GetError().original);
|
||||
}
|
||||
asset_dest_blindpub = pwallet->GetBlindingPubKey(GetScriptForDestination(asset_dest));
|
||||
asset_script = GetScriptForDestination(asset_dest.GetObj());
|
||||
asset_dest_blindpub = pwallet->GetBlindingPubKey(GetScriptForDestination(asset_dest.GetObj()));
|
||||
}
|
||||
if (nTokens > 0) {
|
||||
if (!pwallet->GetNewDestination(OutputType::BECH32, "", token_dest, error)) {
|
||||
throw JSONRPCError(RPC_WALLET_KEYPOOL_RAN_OUT, error.original);
|
||||
token_dest = pwallet->GetNewDestination(OutputType::BECH32, "");
|
||||
if (!token_dest) {
|
||||
throw JSONRPCError(RPC_WALLET_KEYPOOL_RAN_OUT, token_dest.GetError().original);
|
||||
}
|
||||
token_dest_blindpub = pwallet->GetBlindingPubKey(GetScriptForDestination(token_dest));
|
||||
token_script = GetScriptForDestination(token_dest.GetObj());
|
||||
token_dest_blindpub = pwallet->GetBlindingPubKey(GetScriptForDestination(token_dest.GetObj()));
|
||||
}
|
||||
|
||||
CAsset dummyasset;
|
||||
IssuanceDetails issuance_details;
|
||||
issuance_details.blind_issuance = blind_issuances;
|
||||
issuance_details.contract_hash = contract_hash;
|
||||
CTransactionRef tx_ref = SendGenerationTransaction(GetScriptForDestination(asset_dest), asset_dest_blindpub, GetScriptForDestination(token_dest), token_dest_blindpub, nAmount, nTokens, &issuance_details, pwallet);
|
||||
CTransactionRef tx_ref = SendGenerationTransaction(asset_script, asset_dest_blindpub, token_script, token_dest_blindpub, nAmount, nTokens, &issuance_details, pwallet);
|
||||
|
||||
// Calculate asset type, assumes first vin is used for issuance
|
||||
CAsset asset;
|
||||
|
|
@ -1556,21 +1560,21 @@ RPCHelpMan reissueasset()
|
|||
|
||||
// Add destination for the to-be-created asset
|
||||
bilingual_str error;
|
||||
CTxDestination asset_dest;
|
||||
if (!pwallet->GetNewDestination(OutputType::BECH32, "", asset_dest, error)) {
|
||||
throw JSONRPCError(RPC_WALLET_KEYPOOL_RAN_OUT, error.original);
|
||||
auto asset_dest = pwallet->GetNewDestination(OutputType::BECH32, "");
|
||||
if (!asset_dest) {
|
||||
throw JSONRPCError(RPC_WALLET_KEYPOOL_RAN_OUT, asset_dest.GetError().original);
|
||||
}
|
||||
CPubKey asset_dest_blindpub = pwallet->GetBlindingPubKey(GetScriptForDestination(asset_dest));
|
||||
CPubKey asset_dest_blindpub = pwallet->GetBlindingPubKey(GetScriptForDestination(asset_dest.GetObj()));
|
||||
|
||||
// Add destination for tokens we are moving
|
||||
CTxDestination token_dest;
|
||||
if (!pwallet->GetNewDestination(OutputType::BECH32, "", token_dest, error)) {
|
||||
throw JSONRPCError(RPC_WALLET_KEYPOOL_RAN_OUT, error.original);
|
||||
auto token_dest = pwallet->GetNewDestination(OutputType::BECH32, "");
|
||||
if (!token_dest) {
|
||||
throw JSONRPCError(RPC_WALLET_KEYPOOL_RAN_OUT, token_dest.GetError().original);
|
||||
}
|
||||
CPubKey token_dest_blindpub = pwallet->GetBlindingPubKey(GetScriptForDestination(token_dest));
|
||||
CPubKey token_dest_blindpub = pwallet->GetBlindingPubKey(GetScriptForDestination(token_dest.GetObj()));
|
||||
|
||||
// Attempt a send.
|
||||
CTransactionRef tx_ref = SendGenerationTransaction(GetScriptForDestination(asset_dest), asset_dest_blindpub, GetScriptForDestination(token_dest), token_dest_blindpub, nAmount, -1, &issuance_details, pwallet);
|
||||
CTransactionRef tx_ref = SendGenerationTransaction(GetScriptForDestination(asset_dest.GetObj()), asset_dest_blindpub, GetScriptForDestination(token_dest.GetObj()), token_dest_blindpub, nAmount, -1, &issuance_details, pwallet);
|
||||
CHECK_NONFATAL(!tx_ref->vin.empty());
|
||||
|
||||
UniValue obj(UniValue::VOBJ);
|
||||
|
|
|
|||
|
|
@ -170,20 +170,18 @@ UniValue SendMoney(CWallet& wallet, const CCoinControl &coin_control, std::vecto
|
|||
|
||||
// Send
|
||||
constexpr int RANDOM_CHANGE_POSITION = -1;
|
||||
bilingual_str error;
|
||||
FeeCalculation fee_calc_out;
|
||||
auto blind_details = g_con_elementsmode ? std::make_unique<BlindDetails>() : nullptr;
|
||||
if (blind_details) blind_details->ignore_blind_failure = ignore_blind_fail;
|
||||
std::optional<CreatedTransactionResult> txr = CreateTransaction(wallet, recipients, RANDOM_CHANGE_POSITION, error, coin_control, fee_calc_out, true, blind_details.get());
|
||||
if (!txr) {
|
||||
throw JSONRPCError(RPC_WALLET_INSUFFICIENT_FUNDS, error.original);
|
||||
auto res = CreateTransaction(wallet, recipients, RANDOM_CHANGE_POSITION, coin_control, true, blind_details.get());
|
||||
if (!res) {
|
||||
throw JSONRPCError(RPC_WALLET_INSUFFICIENT_FUNDS, res.GetError().original);
|
||||
}
|
||||
CTransactionRef tx = txr->tx;
|
||||
const CTransactionRef& tx = res.GetObj().tx;
|
||||
wallet.CommitTransaction(tx, std::move(map_value), {} /* orderForm */, blind_details.get());
|
||||
if (verbose) {
|
||||
UniValue entry(UniValue::VOBJ);
|
||||
entry.pushKV("txid", tx->GetHash().GetHex());
|
||||
entry.pushKV("fee_reason", StringForFeeReason(fee_calc_out.reason));
|
||||
entry.pushKV("fee_reason", StringForFeeReason(res.GetObj().fee_calc.reason));
|
||||
return entry;
|
||||
}
|
||||
return tx->GetHash().GetHex();
|
||||
|
|
|
|||
36
src/wallet/scriptpubkeyman.cpp
Normal file → Executable file
36
src/wallet/scriptpubkeyman.cpp
Normal file → Executable file
|
|
@ -21,27 +21,22 @@ namespace wallet {
|
|||
//! Value for the first BIP 32 hardened derivation. Can be used as a bit mask and as a value. See BIP 32 for more details.
|
||||
const uint32_t BIP32_HARDENED_KEY_LIMIT = 0x80000000;
|
||||
|
||||
bool LegacyScriptPubKeyMan::GetNewDestination(const OutputType type, CTxDestination& dest, bilingual_str& error)
|
||||
BResult<CTxDestination> LegacyScriptPubKeyMan::GetNewDestination(const OutputType type)
|
||||
{
|
||||
if (LEGACY_OUTPUT_TYPES.count(type) == 0) {
|
||||
error = _("Error: Legacy wallets only support the \"legacy\", \"p2sh-segwit\", and \"bech32\" address types");
|
||||
return false;
|
||||
return _("Error: Legacy wallets only support the \"legacy\", \"p2sh-segwit\", and \"bech32\" address types");;
|
||||
}
|
||||
assert(type != OutputType::BECH32M);
|
||||
|
||||
LOCK(cs_KeyStore);
|
||||
error.clear();
|
||||
|
||||
// Generate a new key that is added to wallet
|
||||
CPubKey new_key;
|
||||
if (!GetKeyFromPool(new_key, type)) {
|
||||
error = _("Error: Keypool ran out, please call keypoolrefill first");
|
||||
return false;
|
||||
return _("Error: Keypool ran out, please call keypoolrefill first");
|
||||
}
|
||||
LearnRelatedScripts(new_key, type);
|
||||
dest = GetDestinationForKey(new_key, type);
|
||||
|
||||
return true;
|
||||
return GetDestinationForKey(new_key, type);
|
||||
}
|
||||
|
||||
typedef std::vector<unsigned char> valtype;
|
||||
|
|
@ -1664,12 +1659,11 @@ std::set<CKeyID> LegacyScriptPubKeyMan::GetKeys() const
|
|||
return set_address;
|
||||
}
|
||||
|
||||
bool DescriptorScriptPubKeyMan::GetNewDestination(const OutputType type, CTxDestination& dest, bilingual_str& error)
|
||||
BResult<CTxDestination> DescriptorScriptPubKeyMan::GetNewDestination(const OutputType type)
|
||||
{
|
||||
// Returns true if this descriptor supports getting new addresses. Conditions where we may be unable to fetch them (e.g. locked) are caught later
|
||||
if (!CanGetAddresses()) {
|
||||
error = _("No addresses available");
|
||||
return false;
|
||||
return _("No addresses available");
|
||||
}
|
||||
{
|
||||
LOCK(cs_desc_man);
|
||||
|
|
@ -1687,15 +1681,14 @@ bool DescriptorScriptPubKeyMan::GetNewDestination(const OutputType type, CTxDest
|
|||
std::vector<CScript> scripts_temp;
|
||||
if (m_wallet_descriptor.range_end <= m_max_cached_index && !TopUp(1)) {
|
||||
// We can't generate anymore keys
|
||||
error = _("Error: Keypool ran out, please call keypoolrefill first");
|
||||
return false;
|
||||
return _("Error: Keypool ran out, please call keypoolrefill first");
|
||||
}
|
||||
if (!m_wallet_descriptor.descriptor->ExpandFromCache(m_wallet_descriptor.next_index, m_wallet_descriptor.cache, scripts_temp, out_keys)) {
|
||||
// We can't generate anymore keys
|
||||
error = _("Error: Keypool ran out, please call keypoolrefill first");
|
||||
return false;
|
||||
return _("Error: Keypool ran out, please call keypoolrefill first");
|
||||
}
|
||||
|
||||
CTxDestination dest;
|
||||
std::optional<OutputType> out_script_type = m_wallet_descriptor.descriptor->GetOutputType();
|
||||
if (out_script_type && out_script_type == type) {
|
||||
ExtractDestination(scripts_temp[0], dest);
|
||||
|
|
@ -1704,7 +1697,7 @@ bool DescriptorScriptPubKeyMan::GetNewDestination(const OutputType type, CTxDest
|
|||
}
|
||||
m_wallet_descriptor.next_index++;
|
||||
WalletBatch(m_storage.GetDatabase()).WriteDescriptor(GetID(), m_wallet_descriptor);
|
||||
return true;
|
||||
return dest;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -1775,9 +1768,14 @@ bool DescriptorScriptPubKeyMan::Encrypt(const CKeyingMaterial& master_key, Walle
|
|||
bool DescriptorScriptPubKeyMan::GetReservedDestination(const OutputType type, bool internal, CTxDestination& address, int64_t& index, CKeyPool& keypool, bilingual_str& error)
|
||||
{
|
||||
LOCK(cs_desc_man);
|
||||
bool result = GetNewDestination(type, address, error);
|
||||
auto op_dest = GetNewDestination(type);
|
||||
index = m_wallet_descriptor.next_index - 1;
|
||||
return result;
|
||||
if (op_dest) {
|
||||
address = op_dest.GetObj();
|
||||
} else {
|
||||
error = op_dest.GetError();
|
||||
}
|
||||
return op_dest.HasRes();
|
||||
}
|
||||
|
||||
void DescriptorScriptPubKeyMan::ReturnDestination(int64_t index, bool internal, const CTxDestination& addr)
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@
|
|||
#include <script/standard.h>
|
||||
#include <util/error.h>
|
||||
#include <util/message.h>
|
||||
#include <util/result.h>
|
||||
#include <util/time.h>
|
||||
#include <wallet/crypter.h>
|
||||
#include <wallet/ismine.h>
|
||||
|
|
@ -171,7 +172,7 @@ protected:
|
|||
public:
|
||||
explicit ScriptPubKeyMan(WalletStorage& storage) : m_storage(storage) {}
|
||||
virtual ~ScriptPubKeyMan() {};
|
||||
virtual bool GetNewDestination(const OutputType type, CTxDestination& dest, bilingual_str& error) { return false; }
|
||||
virtual BResult<CTxDestination> GetNewDestination(const OutputType type) { return Untranslated("Not supported"); }
|
||||
virtual isminetype IsMine(const CScript& script) const { return ISMINE_NO; }
|
||||
|
||||
//! Check that the given decryption key is valid for this ScriptPubKeyMan, i.e. it decrypts all of the keys handled by it.
|
||||
|
|
@ -359,7 +360,7 @@ private:
|
|||
public:
|
||||
using ScriptPubKeyMan::ScriptPubKeyMan;
|
||||
|
||||
bool GetNewDestination(const OutputType type, CTxDestination& dest, bilingual_str& error) override;
|
||||
BResult<CTxDestination> GetNewDestination(const OutputType type) override;
|
||||
isminetype IsMine(const CScript& script) const override;
|
||||
|
||||
bool CheckDecryptionKey(const CKeyingMaterial& master_key, bool accept_no_keys = false) override;
|
||||
|
|
@ -573,7 +574,7 @@ public:
|
|||
|
||||
mutable RecursiveMutex cs_desc_man;
|
||||
|
||||
bool GetNewDestination(const OutputType type, CTxDestination& dest, bilingual_str& error) override;
|
||||
BResult<CTxDestination> GetNewDestination(const OutputType type) override;
|
||||
isminetype IsMine(const CScript& script) const override;
|
||||
|
||||
bool CheckDecryptionKey(const CKeyingMaterial& master_key, bool accept_no_keys = false) override;
|
||||
|
|
|
|||
|
|
@ -950,13 +950,11 @@ static bool fillBlindDetails(BlindDetails* det, CWallet* wallet, CMutableTransac
|
|||
return true;
|
||||
}
|
||||
|
||||
static std::optional<CreatedTransactionResult> CreateTransactionInternal(
|
||||
static BResult<CreatedTransactionResult> CreateTransactionInternal(
|
||||
CWallet& wallet,
|
||||
const std::vector<CRecipient>& vecSend,
|
||||
int change_pos,
|
||||
bilingual_str& error,
|
||||
const CCoinControl& coin_control,
|
||||
FeeCalculation& fee_calc_out,
|
||||
bool sign,
|
||||
BlindDetails* blind_details,
|
||||
const IssuanceDetails* issuance_details) EXCLUSIVE_LOCKS_REQUIRED(wallet.cs_wallet)
|
||||
|
|
@ -973,7 +971,6 @@ static std::optional<CreatedTransactionResult> CreateTransactionInternal(
|
|||
AssertLockHeld(wallet.cs_wallet);
|
||||
|
||||
// out variables, to be packed into returned result structure
|
||||
CTransactionRef tx;
|
||||
CAmount nFeeRet;
|
||||
int nChangePosInOut = change_pos;
|
||||
|
||||
|
|
@ -1027,6 +1024,7 @@ static std::optional<CreatedTransactionResult> CreateTransactionInternal(
|
|||
// For manually set change, we need to use the blinding pubkey associated
|
||||
// with the manually-set address rather than generating one from the wallet
|
||||
std::map<CAsset, std::optional<CPubKey>> mapBlindingKeyChange;
|
||||
bilingual_str error; // possible error str
|
||||
|
||||
// coin control: send change to custom address
|
||||
if (coin_control.destChange.size() > 0) {
|
||||
|
|
@ -1098,8 +1096,7 @@ static std::optional<CreatedTransactionResult> CreateTransactionInternal(
|
|||
if (dest_err.empty()) {
|
||||
dest_err = _("Keypool ran out, please call keypoolrefill first");
|
||||
}
|
||||
error = _("Transaction needs a change address, but we can't generate it.") + Untranslated(" ") + dest_err;
|
||||
return std::nullopt;
|
||||
return _("Transaction needs a change address, but we can't generate it.") + Untranslated(" ") + dest_err;
|
||||
}
|
||||
|
||||
CScript scriptChange = GetScriptForDestination(dest);
|
||||
|
|
@ -1149,13 +1146,11 @@ static std::optional<CreatedTransactionResult> CreateTransactionInternal(
|
|||
// Do not, ever, assume that it's fine to change the fee rate if the user has explicitly
|
||||
// provided one
|
||||
if (coin_control.m_feerate && coin_selection_params.m_effective_feerate > *coin_control.m_feerate) {
|
||||
error = strprintf(_("Fee rate (%s) is lower than the minimum fee rate setting (%s)"), coin_control.m_feerate->ToString(FeeEstimateMode::SAT_VB), coin_selection_params.m_effective_feerate.ToString(FeeEstimateMode::SAT_VB));
|
||||
return std::nullopt;
|
||||
return strprintf(_("Fee rate (%s) is lower than the minimum fee rate setting (%s)"), coin_control.m_feerate->ToString(FeeEstimateMode::SAT_VB), coin_selection_params.m_effective_feerate.ToString(FeeEstimateMode::SAT_VB));
|
||||
}
|
||||
if (feeCalc.reason == FeeReason::FALLBACK && !wallet.m_allow_fallback_fee) {
|
||||
// eventually allow a fallback fee
|
||||
error = _("Fee estimation failed. Fallbackfee is disabled. Wait a few blocks or enable -fallbackfee.");
|
||||
return std::nullopt;
|
||||
return _("Fee estimation failed. Fallbackfee is disabled. Wait a few blocks or enable -fallbackfee.");
|
||||
}
|
||||
|
||||
// Calculate the cost of change
|
||||
|
|
@ -1190,8 +1185,7 @@ static std::optional<CreatedTransactionResult> CreateTransactionInternal(
|
|||
|
||||
if (recipient.asset == policyAsset && IsDust(txout, wallet.chain().relayDustFee()))
|
||||
{
|
||||
error = _("Transaction amount too small");
|
||||
return std::nullopt;
|
||||
return _("Transaction amount too small");
|
||||
}
|
||||
txNew.vout.push_back(txout);
|
||||
|
||||
|
|
@ -1257,8 +1251,7 @@ static std::optional<CreatedTransactionResult> CreateTransactionInternal(
|
|||
// Choose coins to use
|
||||
std::optional<SelectionResult> result = SelectCoins(wallet, res_available_coins.coins, /*nTargetValue=*/map_selection_target, coin_control, coin_selection_params);
|
||||
if (!result) {
|
||||
error = _("Insufficient funds");
|
||||
return std::nullopt;
|
||||
return _("Insufficient funds");
|
||||
}
|
||||
TRACE5(coin_selection, selected_coins, wallet.GetName().c_str(), GetAlgorithmName(result->m_algo).c_str(), result->m_target, result->GetWaste(), result->GetSelectedValue());
|
||||
|
||||
|
|
@ -1292,8 +1285,7 @@ static std::optional<CreatedTransactionResult> CreateTransactionInternal(
|
|||
if (nChangePosInOut == -1) {
|
||||
// randomly set policyasset change position
|
||||
} else if ((unsigned int)nChangePosInOut >= fixed_change_pos.size()) {
|
||||
error = _("Transaction change output index out of range");
|
||||
return std::nullopt;
|
||||
return _("Transaction change output index out of range");
|
||||
} else {
|
||||
fixed_change_pos[nChangePosInOut] = policyAsset;
|
||||
}
|
||||
|
|
@ -1329,8 +1321,7 @@ static std::optional<CreatedTransactionResult> CreateTransactionInternal(
|
|||
|
||||
const std::map<CAsset, std::pair<int, CScript>>::const_iterator itScript = mapScriptChange.find(asset);
|
||||
if (itScript == mapScriptChange.end()) {
|
||||
error = Untranslated(strprintf("No change destination provided for asset %s", asset.GetHex()));
|
||||
return std::nullopt;
|
||||
return Untranslated(strprintf("No change destination provided for asset %s", asset.GetHex()));
|
||||
}
|
||||
CTxOut newTxOut(asset, change_and_fee, itScript->second.second);
|
||||
|
||||
|
|
@ -1494,15 +1485,14 @@ static std::optional<CreatedTransactionResult> CreateTransactionInternal(
|
|||
CMutableTransaction tx_blinded = txNew;
|
||||
if (blind_details) {
|
||||
if (!fillBlindDetails(blind_details, &wallet, tx_blinded, selected_coins, error)) {
|
||||
return std::nullopt;
|
||||
return error;
|
||||
}
|
||||
txNew = tx_blinded; // sigh, `fillBlindDetails` may have modified txNew
|
||||
|
||||
int ret = BlindTransaction(blind_details->i_amount_blinds, blind_details->i_asset_blinds, blind_details->i_assets, blind_details->i_amounts, blind_details->o_amount_blinds, blind_details->o_asset_blinds, blind_details->o_pubkeys, issuance_asset_keys, issuance_token_keys, tx_blinded);
|
||||
assert(ret != -1);
|
||||
if (ret != blind_details->num_to_blind) {
|
||||
error = _("Unable to blind the transaction properly. This should not happen.");
|
||||
return std::nullopt;
|
||||
return _("Unable to blind the transaction properly. This should not happen.");
|
||||
}
|
||||
|
||||
tx_sizes = CalculateMaximumSignedTxSize(CTransaction(tx_blinded), &wallet, &coin_control);
|
||||
|
|
@ -1514,8 +1504,7 @@ static std::optional<CreatedTransactionResult> CreateTransactionInternal(
|
|||
// Calculate the transaction fee
|
||||
int nBytes = tx_sizes.vsize;
|
||||
if (nBytes == -1) {
|
||||
error = _("Missing solving data for estimating transaction size");
|
||||
return std::nullopt;
|
||||
return _("Missing solving data for estimating transaction size");
|
||||
}
|
||||
nFeeRet = coin_selection_params.m_effective_feerate.GetFee(nBytes);
|
||||
|
||||
|
|
@ -1569,7 +1558,7 @@ static std::optional<CreatedTransactionResult> CreateTransactionInternal(
|
|||
if (blind_details->num_to_blind < 2) {
|
||||
resetBlindDetails(blind_details, true /* don't wipe output data */);
|
||||
if (!fillBlindDetails(blind_details, &wallet, txNew, selected_coins, error)) {
|
||||
return std::nullopt;
|
||||
return error;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1589,8 +1578,7 @@ static std::optional<CreatedTransactionResult> CreateTransactionInternal(
|
|||
if (!coin_selection_params.m_subtract_fee_outputs && fee_needed > map_change_and_fee.at(policyAsset) - change_amount) {
|
||||
wallet.WalletLogPrintf("ERROR: not enough coins to cover for fee (needed: %d, total: %d, change: %d)\n",
|
||||
fee_needed, map_change_and_fee.at(policyAsset), change_amount);
|
||||
error = _("Could not cover fee");
|
||||
return std::nullopt;
|
||||
return _("Could not cover fee");
|
||||
}
|
||||
|
||||
// Update nFeeRet in case fee_needed changed due to dropping the change output
|
||||
|
|
@ -1614,8 +1602,7 @@ static std::optional<CreatedTransactionResult> CreateTransactionInternal(
|
|||
{
|
||||
CAmount value = txout.nValue.GetAmount();
|
||||
if (recipient.asset != policyAsset) {
|
||||
error = Untranslated(strprintf("Wallet does not support more than one type of fee at a time, therefore can not subtract fee from address amount, which is of a different asset id. fee asset: %s recipient asset: %s", policyAsset.GetHex(), recipient.asset.GetHex()));
|
||||
return std::nullopt;
|
||||
return Untranslated(strprintf("Wallet does not support more than one type of fee at a time, therefore can not subtract fee from address amount, which is of a different asset id. fee asset: %s recipient asset: %s", policyAsset.GetHex(), recipient.asset.GetHex()));
|
||||
}
|
||||
|
||||
value -= to_reduce / outputs_to_subtract_fee_from; // Subtract fee equally from each selected recipient
|
||||
|
|
@ -1629,11 +1616,10 @@ static std::optional<CreatedTransactionResult> CreateTransactionInternal(
|
|||
// Error if this output is reduced to be below dust
|
||||
if (IsDust(txout, wallet.chain().relayDustFee())) {
|
||||
if (value < 0) {
|
||||
error = _("The transaction amount is too small to pay the fee");
|
||||
return _("The transaction amount is too small to pay the fee");
|
||||
} else {
|
||||
error = _("The transaction amount is too small to send after the fee has been deducted");
|
||||
return _("The transaction amount is too small to send after the fee has been deducted");
|
||||
}
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
txout.nValue = value;
|
||||
|
|
@ -1648,7 +1634,7 @@ static std::optional<CreatedTransactionResult> CreateTransactionInternal(
|
|||
if (maybe_change_asset) {
|
||||
auto used = mapScriptChange.extract(*maybe_change_asset);
|
||||
if (used.mapped().second == dummy_script) {
|
||||
return std::nullopt;
|
||||
return error;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1707,8 +1693,7 @@ static std::optional<CreatedTransactionResult> CreateTransactionInternal(
|
|||
assert(ret != -1);
|
||||
if (ret != blind_details->num_to_blind) {
|
||||
wallet.WalletLogPrintf("ERROR: tried to blind %d outputs but only blinded %d\n", (int) blind_details->num_to_blind, (int) ret);
|
||||
error = _("Unable to blind the transaction properly. This should not happen.");
|
||||
return std::nullopt;
|
||||
return _("Unable to blind the transaction properly. This should not happen.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1726,8 +1711,7 @@ static std::optional<CreatedTransactionResult> CreateTransactionInternal(
|
|||
|
||||
if (sign) {
|
||||
if (!wallet.SignTransaction(txNew)) {
|
||||
error = _("Signing transaction failed");
|
||||
return std::nullopt;
|
||||
return _("Signing transaction failed");
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -1737,26 +1721,23 @@ static std::optional<CreatedTransactionResult> CreateTransactionInternal(
|
|||
}
|
||||
|
||||
// Return the constructed transaction data.
|
||||
tx = MakeTransactionRef(std::move(txNew));
|
||||
CTransactionRef tx = MakeTransactionRef(std::move(txNew));
|
||||
|
||||
// Limit size
|
||||
if ((sign && GetTransactionWeight(*tx) > MAX_STANDARD_TX_WEIGHT) ||
|
||||
(!sign && tx_sizes.weight > MAX_STANDARD_TX_WEIGHT))
|
||||
{
|
||||
error = _("Transaction too large");
|
||||
return std::nullopt;
|
||||
return _("Transaction too large");
|
||||
}
|
||||
|
||||
if (nFeeRet > wallet.m_default_max_tx_fee) {
|
||||
error = TransactionErrorString(TransactionError::MAX_FEE_EXCEEDED);
|
||||
return std::nullopt;
|
||||
return TransactionErrorString(TransactionError::MAX_FEE_EXCEEDED);
|
||||
}
|
||||
|
||||
if (gArgs.GetBoolArg("-walletrejectlongchains", DEFAULT_WALLET_REJECT_LONG_CHAINS)) {
|
||||
// Lastly, ensure this tx will pass the mempool's chain limits
|
||||
if (!wallet.chain().checkChainLimits(tx)) {
|
||||
error = _("Transaction has too long of a mempool chain");
|
||||
return std::nullopt;
|
||||
return _("Transaction has too long of a mempool chain");
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -1765,7 +1746,6 @@ static std::optional<CreatedTransactionResult> CreateTransactionInternal(
|
|||
for (auto& reservedest_ : reservedest) {
|
||||
reservedest_->KeepDestination();
|
||||
}
|
||||
fee_calc_out = feeCalc;
|
||||
|
||||
wallet.WalletLogPrintf("Fee Calculation: Fee:%d Bytes:%u Tgt:%d (requested %d) Reason:\"%s\" Decay %.5f: Estimation: (%g - %g) %.2f%% %.1f/(%.1f %d mem %.1f out) Fail: (%g - %g) %.2f%% %.1f/(%.1f %d mem %.1f out)\n",
|
||||
nFeeRet, nBytes, feeCalc.returnedTarget, feeCalc.desiredTarget, StringForFeeReason(feeCalc.reason), feeCalc.est.decay,
|
||||
|
|
@ -1775,69 +1755,66 @@ static std::optional<CreatedTransactionResult> CreateTransactionInternal(
|
|||
feeCalc.est.fail.start, feeCalc.est.fail.end,
|
||||
(feeCalc.est.fail.totalConfirmed + feeCalc.est.fail.inMempool + feeCalc.est.fail.leftMempool) > 0.0 ? 100 * feeCalc.est.fail.withinTarget / (feeCalc.est.fail.totalConfirmed + feeCalc.est.fail.inMempool + feeCalc.est.fail.leftMempool) : 0.0,
|
||||
feeCalc.est.fail.withinTarget, feeCalc.est.fail.totalConfirmed, feeCalc.est.fail.inMempool, feeCalc.est.fail.leftMempool);
|
||||
return CreatedTransactionResult(tx, nFeeRet, nChangePosInOut);
|
||||
return CreatedTransactionResult(tx, nFeeRet, nChangePosInOut, feeCalc);
|
||||
}
|
||||
|
||||
std::optional<CreatedTransactionResult> CreateTransaction(
|
||||
BResult<CreatedTransactionResult> CreateTransaction(
|
||||
CWallet& wallet,
|
||||
const std::vector<CRecipient>& vecSend,
|
||||
int change_pos,
|
||||
bilingual_str& error,
|
||||
const CCoinControl& coin_control,
|
||||
FeeCalculation& fee_calc_out,
|
||||
bool sign,
|
||||
BlindDetails* blind_details,
|
||||
const IssuanceDetails* issuance_details)
|
||||
{
|
||||
if (vecSend.empty()) {
|
||||
error = _("Transaction must have at least one recipient");
|
||||
return std::nullopt;
|
||||
return _("Transaction must have at least one recipient");
|
||||
}
|
||||
|
||||
if (std::any_of(vecSend.cbegin(), vecSend.cend(), [](const auto& recipient){ return recipient.nAmount < 0; })) {
|
||||
error = _("Transaction amounts must not be negative");
|
||||
return std::nullopt;
|
||||
return _("Transaction amounts must not be negative");
|
||||
}
|
||||
|
||||
// ELEMENTS
|
||||
if (g_con_elementsmode) {
|
||||
if (std::any_of(vecSend.cbegin(), vecSend.cend(), [](const auto& recipient){ return recipient.asset.IsNull(); })) {
|
||||
error = _("No asset provided for recipient");
|
||||
return std::nullopt;
|
||||
return _("No asset provided for recipient");
|
||||
}
|
||||
}
|
||||
|
||||
LOCK(wallet.cs_wallet);
|
||||
|
||||
std::optional<CreatedTransactionResult> txr_ungrouped = CreateTransactionInternal(wallet, vecSend, change_pos, error, coin_control, fee_calc_out, sign, blind_details, issuance_details);
|
||||
TRACE4(coin_selection, normal_create_tx_internal, wallet.GetName().c_str(), txr_ungrouped.has_value(),
|
||||
txr_ungrouped.has_value() ? txr_ungrouped->fee : 0, txr_ungrouped.has_value() ? txr_ungrouped->change_pos : 0);
|
||||
if (!txr_ungrouped) return std::nullopt;
|
||||
auto res = CreateTransactionInternal(wallet, vecSend, change_pos, coin_control, sign, blind_details, issuance_details);
|
||||
TRACE4(coin_selection, normal_create_tx_internal, wallet.GetName().c_str(), res.HasRes(),
|
||||
res ? res.GetObj().fee : 0, res ? res.GetObj().change_pos : 0);
|
||||
if (!res) return res;
|
||||
const auto& txr_ungrouped = res.GetObj();
|
||||
// try with avoidpartialspends unless it's enabled already
|
||||
if (txr_ungrouped->fee > 0 /* 0 means non-functional fee rate estimation */ && wallet.m_max_aps_fee > -1 && !coin_control.m_avoid_partial_spends) {
|
||||
if (txr_ungrouped.fee > 0 /* 0 means non-functional fee rate estimation */ && wallet.m_max_aps_fee > -1 && !coin_control.m_avoid_partial_spends) {
|
||||
TRACE1(coin_selection, attempting_aps_create_tx, wallet.GetName().c_str());
|
||||
CCoinControl tmp_cc = coin_control;
|
||||
tmp_cc.m_avoid_partial_spends = true;
|
||||
bilingual_str error2; // fired and forgotten; if an error occurs, we discard the results
|
||||
BlindDetails blind_details2;
|
||||
BlindDetails *blind_details2_ptr = blind_details ? &blind_details2 : nullptr;
|
||||
std::optional<CreatedTransactionResult> txr_grouped = CreateTransactionInternal(wallet, vecSend, change_pos, error2, tmp_cc, fee_calc_out, sign, blind_details2_ptr, issuance_details);
|
||||
auto res_tx_grouped = CreateTransactionInternal(wallet, vecSend, change_pos, tmp_cc, sign, blind_details2_ptr, issuance_details);
|
||||
// Helper optional class for now
|
||||
std::optional<CreatedTransactionResult> txr_grouped{res_tx_grouped.HasRes() ? std::make_optional(res_tx_grouped.GetObj()) : std::nullopt};
|
||||
// if fee of this alternative one is within the range of the max fee, we use this one
|
||||
const bool use_aps{txr_grouped.has_value() ? (txr_grouped->fee <= txr_ungrouped->fee + wallet.m_max_aps_fee) : false};
|
||||
const bool use_aps{txr_grouped.has_value() ? (txr_grouped->fee <= txr_ungrouped.fee + wallet.m_max_aps_fee) : false};
|
||||
TRACE5(coin_selection, aps_create_tx_internal, wallet.GetName().c_str(), use_aps, txr_grouped.has_value(),
|
||||
txr_grouped.has_value() ? txr_grouped->fee : 0, txr_grouped.has_value() ? txr_grouped->change_pos : 0);
|
||||
if (txr_grouped) {
|
||||
wallet.WalletLogPrintf("Fee non-grouped = %lld, grouped = %lld, using %s\n",
|
||||
txr_ungrouped->fee, txr_grouped->fee, use_aps ? "grouped" : "non-grouped");
|
||||
txr_ungrouped.fee, txr_grouped->fee, use_aps ? "grouped" : "non-grouped");
|
||||
if (use_aps) {
|
||||
if (blind_details) { // ELEMENTS FIXME: is this if statement + body still needed?
|
||||
*blind_details = blind_details2;
|
||||
}
|
||||
return txr_grouped;
|
||||
return res_tx_grouped;
|
||||
}
|
||||
}
|
||||
}
|
||||
return txr_ungrouped;
|
||||
return res;
|
||||
}
|
||||
|
||||
bool FundTransaction(CWallet& wallet, CMutableTransaction& tx, CAmount& nFeeRet, int& nChangePosInOut, bilingual_str& error, bool lockUnspents, const std::set<int>& setSubtractFeeFromOutputs, CCoinControl coinControl)
|
||||
|
|
@ -1897,13 +1874,16 @@ bool FundTransaction(CWallet& wallet, CMutableTransaction& tx, CAmount& nFeeRet,
|
|||
}
|
||||
}
|
||||
|
||||
FeeCalculation fee_calc_out;
|
||||
auto blind_details = g_con_elementsmode ? std::make_unique<BlindDetails>() : nullptr;
|
||||
std::optional<CreatedTransactionResult> txr = CreateTransaction(wallet, vecSend, nChangePosInOut, error, coinControl, fee_calc_out, false, blind_details.get());
|
||||
if (!txr) return false;
|
||||
CTransactionRef tx_new = txr->tx;
|
||||
nFeeRet = txr->fee;
|
||||
nChangePosInOut = txr->change_pos;
|
||||
auto res = CreateTransaction(wallet, vecSend, nChangePosInOut, coinControl, false, blind_details.get());
|
||||
if (!res) {
|
||||
error = res.GetError();
|
||||
return false;
|
||||
}
|
||||
const auto& txr = res.GetObj();
|
||||
CTransactionRef tx_new = txr.tx;
|
||||
nFeeRet = txr.fee;
|
||||
nChangePosInOut = txr.change_pos;
|
||||
|
||||
// Wipe outputs and output witness and re-add one by one
|
||||
tx.vout.clear();
|
||||
|
|
|
|||
|
|
@ -7,6 +7,8 @@
|
|||
|
||||
#include <consensus/amount.h>
|
||||
#include <wallet/coincontrol.h>
|
||||
#include <policy/fees.h> // for FeeCalculation
|
||||
#include <util/result.h>
|
||||
#include <wallet/coinselection.h>
|
||||
#include <wallet/transaction.h>
|
||||
#include <wallet/wallet.h>
|
||||
|
|
@ -104,10 +106,11 @@ struct CreatedTransactionResult
|
|||
{
|
||||
CTransactionRef tx;
|
||||
CAmount fee;
|
||||
FeeCalculation fee_calc;
|
||||
int change_pos;
|
||||
|
||||
CreatedTransactionResult(CTransactionRef tx, CAmount fee, int change_pos)
|
||||
: tx(tx), fee(fee), change_pos(change_pos) {}
|
||||
CreatedTransactionResult(CTransactionRef _tx, CAmount _fee, int _change_pos, const FeeCalculation& _fee_calc)
|
||||
: tx(_tx), fee(_fee), fee_calc(_fee_calc), change_pos(_change_pos) {}
|
||||
};
|
||||
|
||||
/**
|
||||
|
|
@ -115,7 +118,7 @@ struct CreatedTransactionResult
|
|||
* selected by SelectCoins(); Also create the change output, when needed
|
||||
* @note passing change_pos as -1 will result in setting a random position
|
||||
*/
|
||||
std::optional<CreatedTransactionResult> CreateTransaction(CWallet& wallet, const std::vector<CRecipient>& vecSend, int change_pos, bilingual_str& error, const CCoinControl& coin_control, FeeCalculation& fee_calc_out, bool sign = true, BlindDetails* blind_details = nullptr, const IssuanceDetails* issuance_details = nullptr);
|
||||
BResult<CreatedTransactionResult> CreateTransaction(CWallet& wallet, const std::vector<CRecipient>& vecSend, int change_pos, const CCoinControl& coin_control, bool sign = true, BlindDetails* blind_details = nullptr, const IssuanceDetails* issuance_details = nullptr);
|
||||
|
||||
/**
|
||||
* Insert additional inputs into the transaction by
|
||||
|
|
|
|||
|
|
@ -82,11 +82,9 @@ static void add_coin(std::vector<COutput>& coins, CWallet& wallet, const CAmount
|
|||
tx.vout.resize(nInput + 1);
|
||||
tx.vout[nInput].nValue = nValue;
|
||||
if (spendable) {
|
||||
CTxDestination dest;
|
||||
bilingual_str error;
|
||||
const bool destination_ok = wallet.GetNewDestination(OutputType::BECH32, "", dest, error);
|
||||
assert(destination_ok);
|
||||
tx.vout[nInput].scriptPubKey = GetScriptForDestination(dest);
|
||||
auto op_dest = wallet.GetNewDestination(OutputType::BECH32, "");
|
||||
assert(op_dest.HasRes());
|
||||
tx.vout[nInput].scriptPubKey = GetScriptForDestination(op_dest.GetObj());
|
||||
}
|
||||
uint256 txid = tx.GetHash();
|
||||
|
||||
|
|
|
|||
|
|
@ -69,15 +69,14 @@ struct FuzzedWallet {
|
|||
CScript GetScriptPubKey(FuzzedDataProvider& fuzzed_data_provider)
|
||||
{
|
||||
auto type{fuzzed_data_provider.PickValueInArray(OUTPUT_TYPES)};
|
||||
CTxDestination dest;
|
||||
bilingual_str error;
|
||||
BResult<CTxDestination> op_dest;
|
||||
if (fuzzed_data_provider.ConsumeBool()) {
|
||||
assert(wallet->GetNewDestination(type, "", dest, error));
|
||||
op_dest = wallet->GetNewDestination(type, "");
|
||||
} else {
|
||||
assert(wallet->GetNewChangeDestination(type, dest, error));
|
||||
op_dest = wallet->GetNewChangeDestination(type);
|
||||
}
|
||||
assert(error.empty());
|
||||
return GetScriptForDestination(dest);
|
||||
assert(op_dest.HasRes());
|
||||
return GetScriptForDestination(op_dest.GetObj());
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -28,19 +28,18 @@ BOOST_FIXTURE_TEST_CASE(SubtractFee, TestChain100Setup)
|
|||
auto check_tx = [&wallet](CAmount leftover_input_amount) {
|
||||
CRecipient recipient{GetScriptForRawPubKey({}), 50 * COIN - leftover_input_amount, CAsset(), CPubKey(), true /* subtract fee */};
|
||||
constexpr int RANDOM_CHANGE_POSITION = -1;
|
||||
bilingual_str error;
|
||||
CCoinControl coin_control;
|
||||
coin_control.m_feerate.emplace(10000);
|
||||
coin_control.fOverrideFeeRate = true;
|
||||
// We need to use a change type with high cost of change so that the leftover amount will be dropped to fee instead of added as a change output
|
||||
coin_control.m_change_type = OutputType::LEGACY;
|
||||
FeeCalculation fee_calc;
|
||||
std::optional<CreatedTransactionResult> txr = CreateTransaction(*wallet, {recipient}, RANDOM_CHANGE_POSITION, error, coin_control, fee_calc);
|
||||
BOOST_CHECK(txr.has_value());
|
||||
BOOST_CHECK_EQUAL(txr->tx->vout.size(), 1);
|
||||
BOOST_CHECK_EQUAL(txr->tx->vout[0].nValue.GetAmount(), recipient.nAmount + leftover_input_amount - txr->fee);
|
||||
BOOST_CHECK_GT(txr->fee, 0);
|
||||
return txr->fee;
|
||||
auto res = CreateTransaction(*wallet, {recipient}, RANDOM_CHANGE_POSITION, coin_control);
|
||||
BOOST_CHECK(res);
|
||||
const auto& txr = res.GetObj();
|
||||
BOOST_CHECK_EQUAL(txr.tx->vout.size(), 1);
|
||||
BOOST_CHECK_EQUAL(txr.tx->vout[0].nValue.GetAmount(), recipient.nAmount + leftover_input_amount - txr.fee);
|
||||
BOOST_CHECK_GT(txr.fee, 0);
|
||||
return txr.fee;
|
||||
};
|
||||
|
||||
// Send full input amount to recipient, check that only nonzero fee is
|
||||
|
|
|
|||
|
|
@ -4,7 +4,6 @@
|
|||
|
||||
#include <wallet/wallet.h>
|
||||
|
||||
#include <any>
|
||||
#include <future>
|
||||
#include <memory>
|
||||
#include <stdint.h>
|
||||
|
|
@ -13,7 +12,6 @@
|
|||
#include <interfaces/chain.h>
|
||||
#include <key_io.h>
|
||||
#include <node/blockstorage.h>
|
||||
#include <node/context.h>
|
||||
#include <policy/policy.h>
|
||||
#include <rpc/server.h>
|
||||
#include <test/util/logging.h>
|
||||
|
|
@ -540,14 +538,12 @@ public:
|
|||
CWalletTx& AddTx(CRecipient recipient)
|
||||
{
|
||||
CTransactionRef tx;
|
||||
bilingual_str error;
|
||||
CCoinControl dummy;
|
||||
FeeCalculation fee_calc_out;
|
||||
{
|
||||
constexpr int RANDOM_CHANGE_POSITION = -1;
|
||||
std::optional<CreatedTransactionResult> txr = CreateTransaction(*wallet, {recipient}, RANDOM_CHANGE_POSITION, error, dummy, fee_calc_out);
|
||||
BOOST_CHECK(txr.has_value());
|
||||
tx = txr->tx;
|
||||
auto res = CreateTransaction(*wallet, {recipient}, RANDOM_CHANGE_POSITION, dummy);
|
||||
BOOST_CHECK(res);
|
||||
tx = res.GetObj().tx;
|
||||
}
|
||||
wallet->CommitTransaction(tx, {}, {});
|
||||
CMutableTransaction blocktx;
|
||||
|
|
@ -634,9 +630,7 @@ BOOST_FIXTURE_TEST_CASE(wallet_disableprivkeys, TestChain100Setup)
|
|||
wallet->SetMinVersion(FEATURE_LATEST);
|
||||
wallet->SetWalletFlag(WALLET_FLAG_DISABLE_PRIVATE_KEYS);
|
||||
BOOST_CHECK(!wallet->TopUpKeyPool(1000));
|
||||
CTxDestination dest;
|
||||
bilingual_str error;
|
||||
BOOST_CHECK(!wallet->GetNewDestination(OutputType::BECH32, "", dest, error));
|
||||
BOOST_CHECK(!wallet->GetNewDestination(OutputType::BECH32, ""));
|
||||
}
|
||||
{
|
||||
const std::shared_ptr<CWallet> wallet = std::make_shared<CWallet>(m_node.chain.get(), "", m_args, CreateDummyWalletDatabase());
|
||||
|
|
@ -644,9 +638,7 @@ BOOST_FIXTURE_TEST_CASE(wallet_disableprivkeys, TestChain100Setup)
|
|||
wallet->SetWalletFlag(WALLET_FLAG_DESCRIPTORS);
|
||||
wallet->SetMinVersion(FEATURE_LATEST);
|
||||
wallet->SetWalletFlag(WALLET_FLAG_DISABLE_PRIVATE_KEYS);
|
||||
CTxDestination dest;
|
||||
bilingual_str error;
|
||||
BOOST_CHECK(!wallet->GetNewDestination(OutputType::BECH32, "", dest, error));
|
||||
BOOST_CHECK(!wallet->GetNewDestination(OutputType::BECH32, ""));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -2536,37 +2536,38 @@ bool CWallet::GetOnlinePakKey(CPubKey& online_pubkey, std::string& error)
|
|||
}
|
||||
/// end ELEMENTS
|
||||
|
||||
bool CWallet::GetNewDestination(const OutputType type, const std::string label, CTxDestination& dest, bilingual_str& error, bool add_blinding_key)
|
||||
BResult<CTxDestination> CWallet::GetNewDestination(const OutputType type, const std::string label, bool add_blinding_key)
|
||||
{
|
||||
LOCK(cs_wallet);
|
||||
error.clear();
|
||||
bool result = false;
|
||||
auto spk_man = GetScriptPubKeyMan(type, false /* internal */);
|
||||
if (spk_man) {
|
||||
spk_man->TopUp();
|
||||
result = spk_man->GetNewDestination(type, dest, error);
|
||||
if (!spk_man) {
|
||||
return strprintf(_("Error: No %s addresses available."), FormatOutputType(type));
|
||||
}
|
||||
|
||||
spk_man->TopUp();
|
||||
auto op_dest = spk_man->GetNewDestination(type);
|
||||
if (op_dest) {
|
||||
if (add_blinding_key) {
|
||||
auto dest = op_dest.GetObj();
|
||||
CPubKey blinding_pubkey = GetBlindingPubKey(GetScriptForDestination(dest));
|
||||
std::visit(SetBlindingPubKeyVisitor(blinding_pubkey), dest);
|
||||
op_dest = dest;
|
||||
}
|
||||
} else {
|
||||
error = strprintf(_("Error: No %s addresses available."), FormatOutputType(type));
|
||||
}
|
||||
if (result) {
|
||||
SetAddressBook(dest, label, "receive");
|
||||
SetAddressBook(op_dest.GetObj(), label, "receive");
|
||||
}
|
||||
|
||||
return result;
|
||||
return op_dest;
|
||||
}
|
||||
|
||||
bool CWallet::GetNewChangeDestination(const OutputType type, CTxDestination& dest, bilingual_str& error, bool add_blinding_key)
|
||||
BResult<CTxDestination> CWallet::GetNewChangeDestination(const OutputType type, bool add_blinding_key)
|
||||
{
|
||||
LOCK(cs_wallet);
|
||||
error.clear();
|
||||
|
||||
CTxDestination dest;
|
||||
bilingual_str error;
|
||||
ReserveDestination reservedest(this, type);
|
||||
if (!reservedest.GetReservedDestination(dest, true, error)) {
|
||||
return false;
|
||||
return error;
|
||||
}
|
||||
if (add_blinding_key) {
|
||||
CPubKey blinding_pubkey = GetBlindingPubKey(GetScriptForDestination(dest));
|
||||
|
|
@ -2574,7 +2575,7 @@ bool CWallet::GetNewChangeDestination(const OutputType type, CTxDestination& des
|
|||
}
|
||||
|
||||
reservedest.KeepDestination();
|
||||
return true;
|
||||
return dest;
|
||||
}
|
||||
|
||||
std::optional<int64_t> CWallet::GetOldestKeyPoolTime() const
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@
|
|||
#include <psbt.h>
|
||||
#include <tinyformat.h>
|
||||
#include <util/message.h>
|
||||
#include <util/result.h>
|
||||
#include <util/strencodings.h>
|
||||
#include <util/string.h>
|
||||
#include <util/system.h>
|
||||
|
|
@ -47,7 +48,6 @@ using LoadWalletFn = std::function<void(std::unique_ptr<interfaces::Wallet> wall
|
|||
|
||||
class CScript;
|
||||
enum class FeeEstimateMode;
|
||||
struct FeeCalculation;
|
||||
struct bilingual_str;
|
||||
|
||||
namespace wallet {
|
||||
|
|
@ -737,8 +737,8 @@ public:
|
|||
void MarkDestinationsDirty(const std::set<CTxDestination>& destinations) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet);
|
||||
|
||||
bool GetOnlinePakKey(CPubKey& online_pubkey, std::string& error);
|
||||
bool GetNewDestination(const OutputType type, const std::string label, CTxDestination& dest, bilingual_str& error, bool add_blinding_key = false);
|
||||
bool GetNewChangeDestination(const OutputType type, CTxDestination& dest, bilingual_str& error, bool add_blinding_key = false);
|
||||
BResult<CTxDestination> GetNewDestination(const OutputType type, const std::string label, bool add_blinding_key = false);
|
||||
BResult<CTxDestination> GetNewChangeDestination(const OutputType type, bool add_blinding_key = false);
|
||||
|
||||
isminetype IsMine(const CTxDestination& dest) const EXCLUSIVE_LOCKS_REQUIRED(cs_wallet);
|
||||
isminetype IsMine(const CScript& script) const EXCLUSIVE_LOCKS_REQUIRED(cs_wallet);
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue