From d3ab44de4458386cdd8386b40e973499c76dafd9 Mon Sep 17 00:00:00 2001 From: Steven Roose Date: Wed, 20 Mar 2019 11:01:36 +0000 Subject: [PATCH] [BROKEN] Adapt wallet to CA --- src/interfaces/wallet.cpp | 8 +- src/rpc/client.cpp | 3 + src/wallet/coincontrol.cpp | 2 +- src/wallet/coincontrol.h | 3 +- src/wallet/coinselection.cpp | 72 ++ src/wallet/coinselection.h | 26 +- src/wallet/feebumper.cpp | 53 +- src/wallet/rpcdump.cpp | 24 + src/wallet/rpcwallet.cpp | 503 ++++++++++--- src/wallet/wallet.cpp | 1376 +++++++++++++++++++++++++++++----- src/wallet/wallet.h | 213 ++++-- src/wallet/walletdb.cpp | 36 + src/wallet/walletdb.h | 3 +- 13 files changed, 1962 insertions(+), 360 deletions(-) diff --git a/src/interfaces/wallet.cpp b/src/interfaces/wallet.cpp index 72b0bfa99e..74c03c1399 100644 --- a/src/interfaces/wallet.cpp +++ b/src/interfaces/wallet.cpp @@ -31,7 +31,7 @@ namespace { class PendingWalletTxImpl : public PendingWalletTx { public: - explicit PendingWalletTxImpl(CWallet& wallet) : m_wallet(wallet), m_key(&wallet) {} + explicit PendingWalletTxImpl(CWallet& wallet) : m_wallet(wallet) { m_keys.reserve(1); m_keys.emplace_back(new CReserveKey(&wallet)); } const CTransaction& get() override { return *m_tx; } @@ -43,7 +43,7 @@ class PendingWalletTxImpl : public PendingWalletTx { LOCK2(cs_main, m_wallet.cs_wallet); CValidationState state; - if (!m_wallet.CommitTransaction(m_tx, std::move(value_map), std::move(order_form), m_key, g_connman.get(), state)) { + if (!m_wallet.CommitTransaction(m_tx, std::move(value_map), std::move(order_form), m_keys, g_connman.get(), state)) { reject_reason = state.GetRejectReason(); return false; } @@ -52,7 +52,7 @@ class PendingWalletTxImpl : public PendingWalletTx CTransactionRef m_tx; CWallet& m_wallet; - CReserveKey m_key; + std::vector> m_keys; }; //! Construct wallet tx struct. @@ -224,7 +224,7 @@ class WalletImpl : public Wallet { LOCK2(cs_main, m_wallet.cs_wallet); auto pending = MakeUnique(m_wallet); - if (!m_wallet.CreateTransaction(recipients, pending->m_tx, pending->m_key, fee, change_pos, + if (!m_wallet.CreateTransaction(recipients, pending->m_tx, pending->m_keys, fee, change_pos, fail_reason, coin_control, sign)) { return {}; } diff --git a/src/rpc/client.cpp b/src/rpc/client.cpp index 5d817dffe4..3746b654ae 100644 --- a/src/rpc/client.cpp +++ b/src/rpc/client.cpp @@ -174,6 +174,9 @@ static const CRPCConvertParam vRPCConvertParams[] = { "rawblindrawtransaction", 3, "inputasset" }, { "rawblindrawtransaction", 4, "inputassetblinder" }, { "rawblindrawtransaction", 6, "ignoreblindfail" }, + { "sendmany", 7 , "output_assets" }, + { "sendmany", 8 , "ignoreblindfail" }, + { "sendtoaddress", 9 , "ignoreblindfail" }, { "createrawtransaction", 4, "output_assets" }, }; diff --git a/src/wallet/coincontrol.cpp b/src/wallet/coincontrol.cpp index 645981faa4..e12678cca1 100644 --- a/src/wallet/coincontrol.cpp +++ b/src/wallet/coincontrol.cpp @@ -8,7 +8,7 @@ void CCoinControl::SetNull() { - destChange = CNoDestination(); + destChange.clear(); m_change_type.reset(); fAllowOtherInputs = false; fAllowWatchOnly = false; diff --git a/src/wallet/coincontrol.h b/src/wallet/coincontrol.h index 48a924abfb..b9aeb7b08f 100644 --- a/src/wallet/coincontrol.h +++ b/src/wallet/coincontrol.h @@ -5,6 +5,7 @@ #ifndef BITCOIN_WALLET_COINCONTROL_H #define BITCOIN_WALLET_COINCONTROL_H +#include #include #include #include @@ -17,7 +18,7 @@ class CCoinControl { public: //! Custom change destination, if not set an address is generated - CTxDestination destChange; + std::map destChange; //! Override the default change type if set, ignored if destChange is set boost::optional m_change_type; //! If false, allows unselected inputs, but requires all selected inputs be used diff --git a/src/wallet/coinselection.cpp b/src/wallet/coinselection.cpp index fdeb89553b..cb3f7e7d32 100644 --- a/src/wallet/coinselection.cpp +++ b/src/wallet/coinselection.cpp @@ -3,12 +3,28 @@ // file COPYING or http://www.opensource.org/licenses/mit-license.php. #include +#include #include #include #include +CInputCoin::CInputCoin(const CWalletTx* wtx, unsigned int i) { + if (!wtx || !wtx->tx) + throw std::invalid_argument("tx should not be null"); + if (i >= wtx->tx->vout.size()) + throw std::out_of_range("The output index is out of range"); + + outpoint = COutPoint(wtx->tx->GetHash(), i); + txout = wtx->tx->vout[i]; + effective_value = std::max(0, wtx->GetOutputValueOut(i)); + value = wtx->GetOutputValueOut(i); + asset = wtx->GetOutputAsset(i); + bf_value = wtx->GetOutputAmountBlindingFactor(i); + bf_asset = wtx->GetOutputAssetBlindingFactor(i); +} + // Descending order comparator struct { bool operator()(const OutputGroup& a, const OutputGroup& b) const @@ -213,6 +229,62 @@ static void ApproximateBestSubset(const std::vector& groups, const } } +// ELEMENTS: +bool KnapsackSolver(const CAmountMap& mapTargetValue, std::vector& groups, std::set& setCoinsRet, CAmountMap& mapValueRet) { + setCoinsRet.clear(); + mapValueRet.clear(); + + std::vector inner_groups; + std::set inner_coinsret; + // Perform the standard Knapsack solver for every asset individually. + for(std::map::const_iterator it = mapTargetValue.begin(); it != mapTargetValue.end(); ++it) { + inner_groups.clear(); + inner_coinsret.clear(); + + if (it->second == 0) { + continue; + } + + // We filter the groups on two conditions: + // - only groups that have (exclusively) coins of the asset we're solving for + // - no groups that are already used in setCoinsRet + for (const OutputGroup& g : groups) { + bool add = true; + for (const CInputCoin& c : g.m_outputs) { + if (setCoinsRet.find(c) != setCoinsRet.end()) { + add = false; + break; + } + + if (c.asset != it->first) { + add = false; + break; + } + } + + if (add) { + inner_groups.push_back(g); + } + } + + if (inner_groups.size() == 0) { + // No output groups for this asset. + return false; + } + + CAmount outValue; + if (!KnapsackSolver(it->second, inner_groups, inner_coinsret, outValue)) { + return false; + } + mapValueRet[it->first] = outValue; + for (const CInputCoin& ic : inner_coinsret) { + setCoinsRet.insert(ic); + } + } + + return true; +} + bool KnapsackSolver(const CAmount& nTargetValue, std::vector& groups, std::set& setCoinsRet, CAmount& nValueRet) { setCoinsRet.clear(); diff --git a/src/wallet/coinselection.h b/src/wallet/coinselection.h index 6d755d0969..2d4cf3a9ca 100644 --- a/src/wallet/coinselection.h +++ b/src/wallet/coinselection.h @@ -14,21 +14,14 @@ static const CAmount MIN_CHANGE = CENT; //! final minimum change amount after paying for fees static const CAmount MIN_FINAL_CHANGE = MIN_CHANGE/2; +class CWalletTx; +class uint256; + class CInputCoin { public: - CInputCoin(const CTransactionRef& tx, unsigned int i) - { - if (!tx) - throw std::invalid_argument("tx should not be null"); - if (i >= tx->vout.size()) - throw std::out_of_range("The output index is out of range"); - - outpoint = COutPoint(tx->GetHash(), i); - txout = tx->vout[i]; - effective_value = txout.nValue; - } + CInputCoin(const CWalletTx* wtx, unsigned int i); - CInputCoin(const CTransactionRef& tx, unsigned int i, int input_bytes) : CInputCoin(tx, i) + CInputCoin(const CWalletTx* wtx, unsigned int i, int input_bytes) : CInputCoin(wtx, i) { m_input_bytes = input_bytes; } @@ -36,6 +29,11 @@ class CInputCoin { COutPoint outpoint; CTxOut txout; CAmount effective_value; + // ELEMENTS: + CAmount value; + CAsset asset; + uint256 bf_value; + uint256 bf_asset; /** Pre-computed estimated size of this output as a fully-signed input in a transaction. Can be -1 if it could not be calculated */ int m_input_bytes{-1}; @@ -98,4 +96,8 @@ bool SelectCoinsBnB(std::vector& utxo_pool, const CAmount& target_v // Original coin selection algorithm as a fallback bool KnapsackSolver(const CAmount& nTargetValue, std::vector& groups, std::set& setCoinsRet, CAmount& nValueRet); +// ELEMENTS: +// Knapsack that delegates for every asset individually. +bool KnapsackSolver(const CAmountMap& mapTargetValue, std::vector& groups, std::set& setCoinsRet, CAmountMap& mapValueRet); + #endif // BITCOIN_WALLET_COINSELECTION_H diff --git a/src/wallet/feebumper.cpp b/src/wallet/feebumper.cpp index d023786948..9e1bf312c9 100644 --- a/src/wallet/feebumper.cpp +++ b/src/wallet/feebumper.cpp @@ -94,6 +94,10 @@ Result CreateTransaction(const CWallet* wallet, const uint256& txid, const CCoin // if there was no change output or multiple change outputs, fail int nOutput = -1; for (size_t i = 0; i < wtx.tx->vout.size(); ++i) { + if (wtx.GetOutputAsset(i) != ::policyAsset) { + continue; + } + if (wallet->IsChange(wtx.tx->vout[i])) { if (nOutput != -1) { errors.push_back("Transaction has multiple change outputs"); @@ -107,8 +111,22 @@ Result CreateTransaction(const CWallet* wallet, const uint256& txid, const CCoin return Result::WALLET_ERROR; } + // Find the fee output. + int nFeeOutput = -1; + for (int i = (int)wtx.tx->vout.size()-1; i >= 0; --i) { + if (wtx.GetOutputAsset(i) == ::policyAsset && wtx.tx->vout[i].IsFee()) { + nFeeOutput = i; + break; + } + } + // Calculate the expected size of the new transaction. int64_t txSize = GetVirtualTransactionSize(*(wtx.tx)); + if (g_con_elementsmode && nFeeOutput == -1) { + CMutableTransaction with_fee_output = CMutableTransaction{*wtx.tx}; + with_fee_output.vout.push_back(CTxOut(::policyAsset, 0, CScript())); + txSize = GetVirtualTransactionSize(with_fee_output); + } const int64_t maxNewTxSize = CalculateMaximumSignedTxSize(*wtx.tx, wallet); if (maxNewTxSize < 0) { errors.push_back("Transaction contains inputs that cannot be signed"); @@ -116,7 +134,10 @@ Result CreateTransaction(const CWallet* wallet, const uint256& txid, const CCoin } // calculate the old fee and fee-rate - old_fee = wtx.GetDebit(ISMINE_SPENDABLE) - wtx.tx->GetValueOut(); + old_fee = wtx.GetDebit(ISMINE_SPENDABLE)[::policyAsset] - wtx.tx->GetValueOutMap()[::policyAsset]; + if (g_con_elementsmode) { + old_fee = GetFeeMap(*wtx.tx)[::policyAsset]; + } CFeeRate nOldFeeRate(old_fee, txSize); CFeeRate nNewFeeRate; // The wallet uses a conservative WALLET_INCREMENTAL_RELAY_FEE value to @@ -187,20 +208,33 @@ Result CreateTransaction(const CWallet* wallet, const uint256& txid, const CCoin assert(nDelta > 0); mtx = CMutableTransaction{*wtx.tx}; CTxOut* poutput = &(mtx.vout[nOutput]); - if (poutput->nValue < nDelta) { + // TODO CA: Decrypt output amount using wallet + if (!poutput->nValue.IsExplicit() || poutput->nValue.GetAmount() < nDelta) { errors.push_back("Change output is too small to bump the fee"); return Result::WALLET_ERROR; } // If the output would become dust, discard it (converting the dust to fee) - poutput->nValue -= nDelta; - if (poutput->nValue <= GetDustThreshold(*poutput, GetDiscardRate(*wallet, ::feeEstimator))) { + poutput->nValue = poutput->nValue.GetAmount() - nDelta; + if (poutput->nValue.GetAmount() <= GetDustThreshold(*poutput, GetDiscardRate(*wallet, ::feeEstimator))) { wallet->WalletLogPrintf("Bumping fee and discarding dust output\n"); - new_fee += poutput->nValue; + new_fee += poutput->nValue.GetAmount(); mtx.vout.erase(mtx.vout.begin() + nOutput); if (mtx.witness.vtxoutwit.size() > (size_t) nOutput) { mtx.witness.vtxoutwit.erase(mtx.witness.vtxoutwit.begin() + nOutput); } + if (nFeeOutput > nOutput) { + --nFeeOutput; + } + } + + // Update fee output or add one. + if (g_con_elementsmode) { + if (nFeeOutput >= 0) { + mtx.vout[nFeeOutput].nValue.SetToAmount(new_fee); + } else { + mtx.vout.push_back(CTxOut(::policyAsset, new_fee, CScript())); + } } // Mark new tx not replaceable, if requested. @@ -242,10 +276,15 @@ Result CommitTransaction(CWallet* wallet, const uint256& txid, CMutableTransacti CTransactionRef tx = MakeTransactionRef(std::move(mtx)); mapValue_t mapValue = oldWtx.mapValue; mapValue["replaces_txid"] = oldWtx.GetHash().ToString(); + // wipe blinding details to not store old information + mapValue["blindingdata"] = ""; + // TODO CA: store new blinding data to remember otherwise unblindable outputs - CReserveKey reservekey(wallet); + std::vector> reservekeys; + reservekeys.push_back(std::unique_ptr(new CReserveKey(wallet))); + //reservekeys.push_back(std::unique_ptr(wallet)); CValidationState state; - if (!wallet->CommitTransaction(tx, std::move(mapValue), oldWtx.vOrderForm, reservekey, g_connman.get(), state)) { + if (!wallet->CommitTransaction(tx, std::move(mapValue), oldWtx.vOrderForm, reservekeys, g_connman.get(), state)) { // NOTE: CommitTransaction never returns false, so this should never happen. errors.push_back(strprintf("The transaction was rejected: %s", FormatStateMessage(state))); return Result::WALLET_ERROR; diff --git a/src/wallet/rpcdump.cpp b/src/wallet/rpcdump.cpp index 76fe4ada93..58881687ad 100644 --- a/src/wallet/rpcdump.cpp +++ b/src/wallet/rpcdump.cpp @@ -26,6 +26,7 @@ #include #include