mirror of
https://github.com/ElementsProject/elements.git
synced 2026-08-16 13:01:19 +02:00
Allow Coin Selection be able to take external inputs
This commit is contained in:
parent
698340b62a
commit
8258aaa733
4 changed files with 79 additions and 29 deletions
|
|
@ -19,5 +19,7 @@ void CCoinControl::SetNull()
|
||||||
m_confirm_target.reset();
|
m_confirm_target.reset();
|
||||||
m_signal_bip125_rbf.reset();
|
m_signal_bip125_rbf.reset();
|
||||||
m_fee_mode = FeeEstimateMode::UNSET;
|
m_fee_mode = FeeEstimateMode::UNSET;
|
||||||
|
m_external_txouts.clear();
|
||||||
|
m_external_provider = FlatSigningProvider();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -37,6 +37,8 @@ public:
|
||||||
bool m_avoid_partial_spends;
|
bool m_avoid_partial_spends;
|
||||||
//! Fee estimation mode to control arguments to estimateSmartFee
|
//! Fee estimation mode to control arguments to estimateSmartFee
|
||||||
FeeEstimateMode m_fee_mode;
|
FeeEstimateMode m_fee_mode;
|
||||||
|
//! SigningProvider that has pubkeys and scripts to do spend size estimation for external inputs
|
||||||
|
FlatSigningProvider m_external_provider;
|
||||||
|
|
||||||
CCoinControl()
|
CCoinControl()
|
||||||
{
|
{
|
||||||
|
|
@ -55,11 +57,32 @@ public:
|
||||||
return (setSelected.count(output) > 0);
|
return (setSelected.count(output) > 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool IsExternalSelected(const COutPoint& output) const
|
||||||
|
{
|
||||||
|
return (m_external_txouts.count(output) > 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool GetExternalOutput(const COutPoint& outpoint, CTxOut& txout) const
|
||||||
|
{
|
||||||
|
const auto ext_it = m_external_txouts.find(outpoint);
|
||||||
|
if (ext_it == m_external_txouts.end()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
txout = ext_it->second;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
void Select(const COutPoint& output)
|
void Select(const COutPoint& output)
|
||||||
{
|
{
|
||||||
setSelected.insert(output);
|
setSelected.insert(output);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void SelectExternal(const COutPoint& outpoint, const CTxOut& txout)
|
||||||
|
{
|
||||||
|
setSelected.insert(outpoint);
|
||||||
|
m_external_txouts.emplace(outpoint, txout);
|
||||||
|
}
|
||||||
|
|
||||||
void UnSelect(const COutPoint& output)
|
void UnSelect(const COutPoint& output)
|
||||||
{
|
{
|
||||||
setSelected.erase(output);
|
setSelected.erase(output);
|
||||||
|
|
@ -77,6 +100,7 @@ public:
|
||||||
|
|
||||||
private:
|
private:
|
||||||
std::set<COutPoint> setSelected;
|
std::set<COutPoint> setSelected;
|
||||||
|
std::map<COutPoint, CTxOut> m_external_txouts;
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // BITCOIN_WALLET_COINCONTROL_H
|
#endif // BITCOIN_WALLET_COINCONTROL_H
|
||||||
|
|
|
||||||
|
|
@ -1626,13 +1626,13 @@ int64_t CWalletTx::GetTxTime() const
|
||||||
|
|
||||||
// Helper for producing a max-sized low-S low-R signature (eg 71 bytes)
|
// Helper for producing a max-sized low-S low-R signature (eg 71 bytes)
|
||||||
// or a max-sized low-S signature (e.g. 72 bytes) if use_max_sig is true
|
// or a max-sized low-S signature (e.g. 72 bytes) if use_max_sig is true
|
||||||
bool CWallet::DummySignInput(CMutableTransaction& tx, const size_t nIn, const CTxOut& txout, bool use_max_sig) const
|
static bool DummySignInput(const SigningProvider* provider, CMutableTransaction& tx, const size_t nIn, const CTxOut& txout, bool use_max_sig)
|
||||||
{
|
{
|
||||||
// Fill in dummy signatures for fee calculation.
|
// Fill in dummy signatures for fee calculation.
|
||||||
const CScript& scriptPubKey = txout.scriptPubKey;
|
const CScript& scriptPubKey = txout.scriptPubKey;
|
||||||
SignatureData sigdata;
|
SignatureData sigdata;
|
||||||
|
|
||||||
if (!ProduceSignature(*this, use_max_sig ? DUMMY_MAXIMUM_SIGNATURE_CREATOR : DUMMY_SIGNATURE_CREATOR, scriptPubKey, sigdata)) {
|
if (!ProduceSignature(*provider, use_max_sig ? DUMMY_MAXIMUM_SIGNATURE_CREATOR : DUMMY_SIGNATURE_CREATOR, scriptPubKey, sigdata)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
UpdateTransaction(tx, nIn, sigdata);
|
UpdateTransaction(tx, nIn, sigdata);
|
||||||
|
|
@ -1640,14 +1640,18 @@ bool CWallet::DummySignInput(CMutableTransaction& tx, const size_t nIn, const CT
|
||||||
}
|
}
|
||||||
|
|
||||||
// Helper for producing a bunch of max-sized low-S low-R signatures (eg 71 bytes)
|
// Helper for producing a bunch of max-sized low-S low-R signatures (eg 71 bytes)
|
||||||
bool CWallet::DummySignTx(CMutableTransaction &txNew, const std::vector<CTxOut> &txouts, bool use_max_sig) const
|
bool CWallet::DummySignTx(CMutableTransaction &txNew, const std::vector<CTxOut> &txouts, const CCoinControl* coin_control) const
|
||||||
{
|
{
|
||||||
// Fill in dummy signatures for fee calculation.
|
// Fill in dummy signatures for fee calculation.
|
||||||
int nIn = 0;
|
int nIn = 0;
|
||||||
for (const auto& txout : txouts)
|
for (const auto& txout : txouts)
|
||||||
{
|
{
|
||||||
if (!DummySignInput(txNew, nIn, txout, use_max_sig)) {
|
// Use max sig if watch only inputs were used or if this particular input is an external input
|
||||||
return false;
|
bool use_max_sig = coin_control && (coin_control->fAllowWatchOnly || (coin_control && coin_control->IsExternalSelected(txNew.vin[nIn].prevout)));
|
||||||
|
if (!DummySignInput(this, txNew, nIn, txout, use_max_sig)) {
|
||||||
|
if (!coin_control || !DummySignInput(&coin_control->m_external_provider, txNew, nIn, txout, use_max_sig)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
nIn++;
|
nIn++;
|
||||||
|
|
@ -1655,28 +1659,33 @@ bool CWallet::DummySignTx(CMutableTransaction &txNew, const std::vector<CTxOut>
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
int64_t CalculateMaximumSignedTxSize(const CTransaction &tx, const CWallet *wallet, bool use_max_sig)
|
int64_t CalculateMaximumSignedTxSize(const CTransaction &tx, const CWallet *wallet, const CCoinControl* coin_control)
|
||||||
{
|
{
|
||||||
std::vector<CTxOut> txouts;
|
std::vector<CTxOut> txouts;
|
||||||
// Look up the inputs. We should have already checked that this transaction
|
// Look up the inputs. The inputs are either in the wallet, or in coin_control.
|
||||||
// IsAllFromMe(ISMINE_SPENDABLE), so every input should already be in our
|
|
||||||
// wallet, with a valid index into the vout array, and the ability to sign.
|
|
||||||
for (const CTxIn& input : tx.vin) {
|
for (const CTxIn& input : tx.vin) {
|
||||||
const auto mi = wallet->mapWallet.find(input.prevout.hash);
|
const auto mi = wallet->mapWallet.find(input.prevout.hash);
|
||||||
if (mi == wallet->mapWallet.end()) {
|
if (mi != wallet->mapWallet.end()) {
|
||||||
|
assert(input.prevout.n < mi->second.tx->vout.size());
|
||||||
|
txouts.emplace_back(mi->second.tx->vout[input.prevout.n]);
|
||||||
|
} else if (coin_control) {
|
||||||
|
CTxOut txout;
|
||||||
|
if (!coin_control->GetExternalOutput(input.prevout, txout)) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
txouts.emplace_back(txout);
|
||||||
|
} else {
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
assert(input.prevout.n < mi->second.tx->vout.size());
|
|
||||||
txouts.emplace_back(mi->second.tx->vout[input.prevout.n]);
|
|
||||||
}
|
}
|
||||||
return CalculateMaximumSignedTxSize(tx, wallet, txouts, use_max_sig);
|
return CalculateMaximumSignedTxSize(tx, wallet, txouts, coin_control);
|
||||||
}
|
}
|
||||||
|
|
||||||
// txouts needs to be in the order of tx.vin
|
// txouts needs to be in the order of tx.vin
|
||||||
int64_t CalculateMaximumSignedTxSize(const CTransaction &tx, const CWallet *wallet, const std::vector<CTxOut>& txouts, bool use_max_sig)
|
int64_t CalculateMaximumSignedTxSize(const CTransaction &tx, const CWallet *wallet, const std::vector<CTxOut>& txouts, const CCoinControl* coin_control)
|
||||||
{
|
{
|
||||||
CMutableTransaction txNew(tx);
|
CMutableTransaction txNew(tx);
|
||||||
if (!wallet->DummySignTx(txNew, txouts, use_max_sig)) {
|
if (!wallet->DummySignTx(txNew, txouts, coin_control)) {
|
||||||
// This should never happen, because IsAllFromMe(ISMINE_SPENDABLE)
|
// This should never happen, because IsAllFromMe(ISMINE_SPENDABLE)
|
||||||
// implies that we can sign for every input.
|
// implies that we can sign for every input.
|
||||||
return -1;
|
return -1;
|
||||||
|
|
@ -1688,7 +1697,7 @@ int CalculateMaximumSignedInputSize(const CTxOut& txout, const CWallet* wallet,
|
||||||
{
|
{
|
||||||
CMutableTransaction txn;
|
CMutableTransaction txn;
|
||||||
txn.vin.push_back(CTxIn(COutPoint()));
|
txn.vin.push_back(CTxIn(COutPoint()));
|
||||||
if (!wallet->DummySignInput(txn, 0, txout, use_max_sig)) {
|
if (!DummySignInput(wallet, txn, 0, txout, use_max_sig)) {
|
||||||
// This should never happen, because IsAllFromMe(ISMINE_SPENDABLE)
|
// This should never happen, because IsAllFromMe(ISMINE_SPENDABLE)
|
||||||
// implies that we can sign for every input.
|
// implies that we can sign for every input.
|
||||||
return -1;
|
return -1;
|
||||||
|
|
@ -2690,8 +2699,18 @@ bool CWallet::SelectCoins(const std::vector<COutput>& vAvailableCoins, const CAm
|
||||||
}
|
}
|
||||||
mapValueFromPresetInputs[pcoin->GetOutputAsset(outpoint.n)] += amt;
|
mapValueFromPresetInputs[pcoin->GetOutputAsset(outpoint.n)] += amt;
|
||||||
setPresetCoins.insert(CInputCoin(pcoin, outpoint.n));
|
setPresetCoins.insert(CInputCoin(pcoin, outpoint.n));
|
||||||
} else
|
} else {
|
||||||
return false; // TODO: Allow non-wallet inputs
|
CTxOut txout;
|
||||||
|
if (coin_control.GetExternalOutput(outpoint, txout)) {
|
||||||
|
if (!txout.nValue.IsExplicit() || !txout.nAsset.IsExplicit()) {
|
||||||
|
return false; // We can't get its value, so abort
|
||||||
|
}
|
||||||
|
mapValueFromPresetInputs[txout.nAsset.GetAsset()] += txout.nValue.GetAmount();
|
||||||
|
setPresetCoins.insert(CInputCoin(outpoint, txout));
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// remove preset inputs from vCoins
|
// remove preset inputs from vCoins
|
||||||
|
|
@ -2824,8 +2843,11 @@ bool CWallet::FundTransaction(CMutableTransaction& tx, CAmount& nFeeRet, int& nC
|
||||||
coinControl.ListSelected(vPresetInputs);
|
coinControl.ListSelected(vPresetInputs);
|
||||||
for (const COutPoint& presetInput : vPresetInputs) {
|
for (const COutPoint& presetInput : vPresetInputs) {
|
||||||
std::map<uint256, CWalletTx>::const_iterator it = mapWallet.find(presetInput.hash);
|
std::map<uint256, CWalletTx>::const_iterator it = mapWallet.find(presetInput.hash);
|
||||||
|
CTxOut txout;
|
||||||
if (it != mapWallet.end()) {
|
if (it != mapWallet.end()) {
|
||||||
setAssets.insert(it->second.GetOutputAsset(presetInput.n));
|
setAssets.insert(it->second.GetOutputAsset(presetInput.n));
|
||||||
|
} else if (coinControl.GetExternalOutput(presetInput, txout)) {
|
||||||
|
setAssets.insert(txout.nAsset.GetAsset());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -3162,13 +3184,18 @@ bool CWallet::CreateTransaction(interfaces::Chain::Lock& locked_chain, const std
|
||||||
std::vector<COutPoint> vPresetInputs;
|
std::vector<COutPoint> vPresetInputs;
|
||||||
coin_control.ListSelected(vPresetInputs);
|
coin_control.ListSelected(vPresetInputs);
|
||||||
for (const COutPoint& presetInput : vPresetInputs) {
|
for (const COutPoint& presetInput : vPresetInputs) {
|
||||||
|
CAsset asset;
|
||||||
std::map<uint256, CWalletTx>::const_iterator it = mapWallet.find(presetInput.hash);
|
std::map<uint256, CWalletTx>::const_iterator it = mapWallet.find(presetInput.hash);
|
||||||
if (it == mapWallet.end()) {
|
CTxOut txout;
|
||||||
|
if (it != mapWallet.end()) {
|
||||||
|
asset = it->second.GetOutputAsset(presetInput.n);
|
||||||
|
} else if (coin_control.GetExternalOutput(presetInput, txout)) {
|
||||||
|
asset = txout.nAsset.GetAsset();
|
||||||
|
} else {
|
||||||
// Ignore this here, will fail more gracefully later.
|
// Ignore this here, will fail more gracefully later.
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
CAsset asset = it->second.GetOutputAsset(presetInput.n);
|
|
||||||
if (mapScriptChange.find(asset) != mapScriptChange.end()) {
|
if (mapScriptChange.find(asset) != mapScriptChange.end()) {
|
||||||
// This asset already has a change script.
|
// This asset already has a change script.
|
||||||
continue;
|
continue;
|
||||||
|
|
@ -3509,7 +3536,7 @@ bool CWallet::CreateTransaction(interfaces::Chain::Lock& locked_chain, const std
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
nBytes = CalculateMaximumSignedTxSize(CTransaction(txNew), this, coin_control.fAllowWatchOnly);
|
nBytes = CalculateMaximumSignedTxSize(CTransaction(txNew), this, &coin_control);
|
||||||
if (nBytes < 0) {
|
if (nBytes < 0) {
|
||||||
strFailReason = _("Signing transaction failed");
|
strFailReason = _("Signing transaction failed");
|
||||||
return false;
|
return false;
|
||||||
|
|
|
||||||
|
|
@ -1084,14 +1084,13 @@ public:
|
||||||
std::string& strFailReason, const CCoinControl& coin_control, bool sign = true, BlindDetails* blind_details = nullptr, const IssuanceDetails* issuance_details = nullptr);
|
std::string& strFailReason, const CCoinControl& coin_control, bool sign = true, BlindDetails* blind_details = nullptr, const IssuanceDetails* issuance_details = nullptr);
|
||||||
bool CommitTransaction(CTransactionRef tx, mapValue_t mapValue, std::vector<std::pair<std::string, std::string>> orderForm, std::vector<std::unique_ptr<CReserveKey>>& reservekey, CConnman* connman, CValidationState& state, const BlindDetails* blind_details = nullptr);
|
bool CommitTransaction(CTransactionRef tx, mapValue_t mapValue, std::vector<std::pair<std::string, std::string>> orderForm, std::vector<std::unique_ptr<CReserveKey>>& reservekey, CConnman* connman, CValidationState& state, const BlindDetails* blind_details = nullptr);
|
||||||
|
|
||||||
bool DummySignTx(CMutableTransaction &txNew, const std::set<CTxOut> &txouts, bool use_max_sig = false) const
|
bool DummySignTx(CMutableTransaction &txNew, const std::set<CTxOut> &txouts, const CCoinControl* coin_control = nullptr) const
|
||||||
{
|
{
|
||||||
std::vector<CTxOut> v_txouts(txouts.size());
|
std::vector<CTxOut> v_txouts(txouts.size());
|
||||||
std::copy(txouts.begin(), txouts.end(), v_txouts.begin());
|
std::copy(txouts.begin(), txouts.end(), v_txouts.begin());
|
||||||
return DummySignTx(txNew, v_txouts, use_max_sig);
|
return DummySignTx(txNew, v_txouts, coin_control);
|
||||||
}
|
}
|
||||||
bool DummySignTx(CMutableTransaction &txNew, const std::vector<CTxOut> &txouts, bool use_max_sig = false) const;
|
bool DummySignTx(CMutableTransaction &txNew, const std::vector<CTxOut> &txouts, const CCoinControl* coin_control = nullptr) const;
|
||||||
bool DummySignInput(CMutableTransaction &tx, const size_t nIn, const CTxOut &txout, bool use_max_sig = false) const;
|
|
||||||
|
|
||||||
CFeeRate m_pay_tx_fee{DEFAULT_PAY_TX_FEE};
|
CFeeRate m_pay_tx_fee{DEFAULT_PAY_TX_FEE};
|
||||||
unsigned int m_confirm_target{DEFAULT_TX_CONFIRM_TARGET};
|
unsigned int m_confirm_target{DEFAULT_TX_CONFIRM_TARGET};
|
||||||
|
|
@ -1445,8 +1444,6 @@ public:
|
||||||
|
|
||||||
// Calculate the size of the transaction assuming all signatures are max size
|
// Calculate the size of the transaction assuming all signatures are max size
|
||||||
// Use DummySignatureCreator, which inserts 71 byte signatures everywhere.
|
// Use DummySignatureCreator, which inserts 71 byte signatures everywhere.
|
||||||
// NOTE: this requires that all inputs must be in mapWallet (eg the tx should
|
int64_t CalculateMaximumSignedTxSize(const CTransaction &tx, const CWallet *wallet, const CCoinControl* coin_control = nullptr) EXCLUSIVE_LOCKS_REQUIRED(wallet->cs_wallet);
|
||||||
// be IsAllFromMe).
|
int64_t CalculateMaximumSignedTxSize(const CTransaction &tx, const CWallet *wallet, const std::vector<CTxOut>& txouts, const CCoinControl* coin_control = nullptr);
|
||||||
int64_t CalculateMaximumSignedTxSize(const CTransaction &tx, const CWallet *wallet, bool use_max_sig = false) EXCLUSIVE_LOCKS_REQUIRED(wallet->cs_wallet);
|
|
||||||
int64_t CalculateMaximumSignedTxSize(const CTransaction &tx, const CWallet *wallet, const std::vector<CTxOut>& txouts, bool use_max_sig = false);
|
|
||||||
#endif // BITCOIN_WALLET_WALLET_H
|
#endif // BITCOIN_WALLET_WALLET_H
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue