Skip to content

Commit

Permalink
[BROKEN] Adapt wallet to CA
Browse files Browse the repository at this point in the history
  • Loading branch information
stevenroose committed Mar 20, 2019
1 parent 49e8083 commit d3ab44d
Show file tree
Hide file tree
Showing 13 changed files with 1,962 additions and 360 deletions.
8 changes: 4 additions & 4 deletions src/interfaces/wallet.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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; }

Expand All @@ -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;
}
Expand All @@ -52,7 +52,7 @@ class PendingWalletTxImpl : public PendingWalletTx

CTransactionRef m_tx;
CWallet& m_wallet;
CReserveKey m_key;
std::vector<std::unique_ptr<CReserveKey>> m_keys;
};

//! Construct wallet tx struct.
Expand Down Expand Up @@ -224,7 +224,7 @@ class WalletImpl : public Wallet
{
LOCK2(cs_main, m_wallet.cs_wallet);
auto pending = MakeUnique<PendingWalletTxImpl>(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 {};
}
Expand Down
3 changes: 3 additions & 0 deletions src/rpc/client.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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" },

};
Expand Down
2 changes: 1 addition & 1 deletion src/wallet/coincontrol.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@

void CCoinControl::SetNull()
{
destChange = CNoDestination();
destChange.clear();
m_change_type.reset();
fAllowOtherInputs = false;
fAllowWatchOnly = false;
Expand Down
3 changes: 2 additions & 1 deletion src/wallet/coincontrol.h
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
#ifndef BITCOIN_WALLET_COINCONTROL_H
#define BITCOIN_WALLET_COINCONTROL_H

#include <asset.h>
#include <policy/feerate.h>
#include <policy/fees.h>
#include <primitives/transaction.h>
Expand All @@ -17,7 +18,7 @@ class CCoinControl
{
public:
//! Custom change destination, if not set an address is generated
CTxDestination destChange;
std::map<CAsset, CTxDestination> destChange;
//! Override the default change type if set, ignored if destChange is set
boost::optional<OutputType> m_change_type;
//! If false, allows unselected inputs, but requires all selected inputs be used
Expand Down
72 changes: 72 additions & 0 deletions src/wallet/coinselection.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,28 @@
// file COPYING or http://www.opensource.org/licenses/mit-license.php.

#include <wallet/coinselection.h>
#include <wallet/wallet.h>

#include <util.h>
#include <utilmoneystr.h>

#include <boost/optional.hpp>

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<CAmount>(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
Expand Down Expand Up @@ -213,6 +229,62 @@ static void ApproximateBestSubset(const std::vector<OutputGroup>& groups, const
}
}

// ELEMENTS:
bool KnapsackSolver(const CAmountMap& mapTargetValue, std::vector<OutputGroup>& groups, std::set<CInputCoin>& setCoinsRet, CAmountMap& mapValueRet) {
setCoinsRet.clear();
mapValueRet.clear();

std::vector<OutputGroup> inner_groups;
std::set<CInputCoin> inner_coinsret;
// Perform the standard Knapsack solver for every asset individually.
for(std::map<CAsset, CAmount>::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<OutputGroup>& groups, std::set<CInputCoin>& setCoinsRet, CAmount& nValueRet)
{
setCoinsRet.clear();
Expand Down
26 changes: 14 additions & 12 deletions src/wallet/coinselection.h
Original file line number Diff line number Diff line change
Expand Up @@ -14,28 +14,26 @@ 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;
}

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};
Expand Down Expand Up @@ -98,4 +96,8 @@ bool SelectCoinsBnB(std::vector<OutputGroup>& utxo_pool, const CAmount& target_v
// Original coin selection algorithm as a fallback
bool KnapsackSolver(const CAmount& nTargetValue, std::vector<OutputGroup>& groups, std::set<CInputCoin>& setCoinsRet, CAmount& nValueRet);

// ELEMENTS:
// Knapsack that delegates for every asset individually.
bool KnapsackSolver(const CAmountMap& mapTargetValue, std::vector<OutputGroup>& groups, std::set<CInputCoin>& setCoinsRet, CAmountMap& mapValueRet);

#endif // BITCOIN_WALLET_COINSELECTION_H
53 changes: 46 additions & 7 deletions src/wallet/feebumper.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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");
Expand All @@ -107,16 +111,33 @@ 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");
return Result::INVALID_ADDRESS_OR_KEY;
}

// 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
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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<std::unique_ptr<CReserveKey>> reservekeys;
reservekeys.push_back(std::unique_ptr<CReserveKey>(new CReserveKey(wallet)));
//reservekeys.push_back(std::unique_ptr<CReserveKey>(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;
Expand Down
24 changes: 24 additions & 0 deletions src/wallet/rpcdump.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
#include <univalue.h>

#include <script/descriptor.h> // getwalletpakinfo
#include <rpc/util.h> // IsBlindDestination


int64_t static DecodeDumpTime(const std::string &str) {
Expand Down Expand Up @@ -761,6 +762,10 @@ UniValue dumpwallet(const JSONRPCRequest& request)
file << "# extended private masterkey: " << EncodeExtKey(masterKey) << "\n\n";
}
}
// ELEMENTS: Dump the master blinding key in hex as well
if (!pwallet->blinding_derivation_key.IsNull()) {
file << ("# Master private blinding key: " + pwallet->blinding_derivation_key.GetHex() + "\n\n");
}
for (std::vector<std::pair<int64_t, CKeyID> >::const_iterator it = vKeyBirth.begin(); it != vKeyBirth.end(); it++) {
const CKeyID &keyid = it->second;
std::string strTime = FormatISO8601DateTime(it->first);
Expand Down Expand Up @@ -825,6 +830,7 @@ static UniValue ProcessImport(CWallet * const pwallet, const UniValue& data, con
// Optional fields.
const std::string& strRedeemScript = data.exists("redeemscript") ? data["redeemscript"].get_str() : "";
const UniValue& pubKeys = data.exists("pubkeys") ? data["pubkeys"].get_array() : UniValue();
const std::string& str_blinding_key = data.exists("blinding_privkey") ? data["blinding_privkey"].get_str() : "";
const UniValue& keys = data.exists("keys") ? data["keys"].get_array() : UniValue();
const bool internal = data.exists("internal") ? data["internal"].get_bool() : false;
const bool watchOnly = data.exists("watchonly") ? data["watchonly"].get_bool() : false;
Expand Down Expand Up @@ -878,6 +884,18 @@ static UniValue ProcessImport(CWallet * const pwallet, const UniValue& data, con

// Process. //

// Get blinding privkey
if (!str_blinding_key.empty() &&
(!IsHex(str_blinding_key) || str_blinding_key.size() != 64)) {
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid blinding privkey");
}

uint256 blinding_privkey;
if (!str_blinding_key.empty()) {
std::vector<unsigned char> blind_bytes = ParseHex(str_blinding_key);
memcpy(blinding_privkey.begin(), blind_bytes.data(), 32);
}

// P2SH
if (isP2SH) {
// Import redeem script.
Expand Down Expand Up @@ -1067,6 +1085,11 @@ static UniValue ProcessImport(CWallet * const pwallet, const UniValue& data, con
}
}

// Lastly, if dest import is valid, import blinding key
if (!str_blinding_key.empty() && !pwallet->AddSpecificBlindingKey(CScriptID(GetScriptForDestination(dest)), blinding_privkey)) {
throw JSONRPCError(RPC_WALLET_ERROR, "Error adding blinding key to wallet");
}

UniValue result = UniValue(UniValue::VOBJ);
result.pushKV("success", UniValue(success));
return result;
Expand Down Expand Up @@ -1123,6 +1146,7 @@ UniValue importmulti(const JSONRPCRequest& mainRequest)
" creation time of all keys being imported by the importmulti call will be scanned.\n"
" \"redeemscript\": \"<script>\" , (string, optional) Allowed only if the scriptPubKey is a P2SH address or a P2SH scriptPubKey\n"
" \"pubkeys\": [\"<pubKey>\", ... ] , (array, optional) Array of strings giving pubkeys that must occur in the output or redeemscript\n"
" \"blinding_privkey\": \"<privkey>\" , (string, optional) String giving blinding key in hex that is used to unblind outputs going to `scriptPubKey`\n"
" \"keys\": [\"<key>\", ... ] , (array, optional) Array of strings giving private keys whose corresponding public keys must occur in the output or redeemscript\n"
" \"internal\": <true> , (boolean, optional, default: false) Stating whether matching outputs should be treated as not incoming payments\n"
" \"watchonly\": <true> , (boolean, optional, default: false) Stating whether matching outputs should be considered watched even when they're not spendable, only allowed if keys are empty\n"
Expand Down
Loading

0 comments on commit d3ab44d

Please sign in to comment.