diff --git a/release/version_history.txt b/release/version_history.txt index 6f41567..2fc7645 100755 --- a/release/version_history.txt +++ b/release/version_history.txt @@ -46,3 +46,6 @@ # - ver:1.09.0 # 20181221 - changed Block reward value from 2500 to 500. + +# - ver:1.10.0 +# 20190906 - add rpc command fundrawtransaction diff --git a/src/rpc/rpcclient.cpp b/src/rpc/rpcclient.cpp index c5b27fa..c153f60 100755 --- a/src/rpc/rpcclient.cpp +++ b/src/rpc/rpcclient.cpp @@ -251,6 +251,10 @@ static const CRPCConvertParam vRPCConvertParams[] = { "signrawtransaction", 1 }, { "signrawtransaction", 2 }, { "sendrawtransaction", 1 }, + #ifdef FEATURE_HPAY_FUNDRAWTX + { "fundrawtransaction", 1}, + { "fundrawtransaction", 2}, + #endif { "gettxout", 1 }, { "gettxout", 2 }, { "lockunspent", 0 }, diff --git a/src/rpc/rpcclient.h b/src/rpc/rpcclient.h index 88e16e5..7ad2c69 100755 --- a/src/rpc/rpcclient.h +++ b/src/rpc/rpcclient.h @@ -6,6 +6,7 @@ #ifndef BITCOIN_RPCCLIENT_H #define BITCOIN_RPCCLIENT_H +#define FEATURE_HPAY_FUNDRAWTX #include "json/json_spirit_reader_template.h" #include "json/json_spirit_utils.h" diff --git a/src/rpc/rpchelp.cpp b/src/rpc/rpchelp.cpp index dfee253..2ebc5ea 100755 --- a/src/rpc/rpchelp.cpp +++ b/src/rpc/rpchelp.cpp @@ -1216,7 +1216,56 @@ void mc_InitRPCHelpMap05() + HelpExampleCli("signrawtransaction", "\"myhex\"") + HelpExampleRpc("signrawtransaction", "\"myhex\"") )); - + +#ifdef FEATURE_HPAY_FUNDRAWTX + mapHelpStrings.insert(std::make_pair("fundrawtransaction", + "fundrawtransaction \"hexstring\" ( options iswitness )\n" + "\nAdd inputs to a transaction until it has enough in value to meet its out value.\n" + "This will not modify existing inputs, and will add at most one change output to the outputs.\n" + "No existing outputs will be modified unless \"subtractFeeFromOutputs\" is specified.\n" + "Note that inputs which were signed may need to be resigned after completion since in/outputs have been added.\n" + "The inputs added will not be signed, use signrawtransaction for that.\n" + "Note that all existing inputs must have their previous output transaction be in the wallet.\n" + "Note that all inputs selected must be of standard form and P2SH scripts must be\n" + "in the wallet using importaddress or addmultisigaddress (to calculate fees).\n" + "You can see whether this is the case by checking the \"solvable\" field in the listunspent output.\n" + "Only pay-to-pubkey, multisig, and P2SH versions thereof are currently supported for watch-only\n" + "\nArguments:\n" + "1. \"hexstring\" (string, required) The hex string of the raw transaction\n" + "2. options (object, optional)\n" + " {\n" + " \"includeWatching\" (boolean, optional, default false) Also select inputs which are watch only\n" + " \"changeAddress\" (string, optional, default pool address) The hdac address to receive the change\n" + " \"changePosition\" (numeric, optional, default random) The index of the change output\n" + " \"feeRate\" (numeric, optional, default not set: makes wallet determine the fee) Set a specific fee rate 0.01 HDAC/kB\n" + " \"subtractFeeFromOutputs\" (array, optional) A json array of integers.\n" + " The fee will be equally deducted from the amount of each specified output.\n" + " The outputs are specified by their zero-based index, before any change output is added.\n" + " Those recipients will receive less bitcoins than you enter in their corresponding amount field.\n" + " If no outputs are specified here, the sender pays the fee.\n" + " [vout_index,...]\n" + + " }\n" + " for backward compatibility: passing in a true instead of an object will result in {\"includeWatching\":true}\n" + + "\nResult:\n" + "{\n" + " \"hex\": \"value\", (string) The resulting raw transaction (hex-encoded string)\n" + " \"fee\": n, (numeric) Fee in 0.01 the resulting transaction pays\n" + " \"changepos\": n (numeric) The position of the added change output, or -1\n" + "}\n" + "\nExamples:\n" + "\nCreate a transaction with no inputs\n" + + HelpExampleCli("createrawtransaction", "\"[]\" \"{\\\"myaddress\\\":0.01}\"") + + "\nAdd sufficient unsigned inputs to meet the output value\n" + + HelpExampleCli("fundrawtransaction", "\"rawtransactionhex\"") + + "\nSign the transaction\n" + + HelpExampleCli("signrawtransaction", "\"fundedtransactionhex\"") + + "\nSend the transaction\n" + + HelpExampleCli("sendrawtransaction", "\"signedtransactionhex\"") + )); +#endif /* FEATURE_HPAY_FUNDRAWTX */ + mapHelpStrings.insert(std::make_pair("createkeypairs", "createkeypairs ( count )\n" "\nCreates public/private key pairs. These key pairs are not stored in the wallet.\n" diff --git a/src/rpc/rpclist.cpp b/src/rpc/rpclist.cpp index 8feb35f..9c8876b 100755 --- a/src/rpc/rpclist.cpp +++ b/src/rpc/rpclist.cpp @@ -120,6 +120,9 @@ static const CRPCCommand vRPCCommands[] = { "rawtransactions", "getrawtransaction", &getrawtransaction, true, false, false }, { "rawtransactions", "sendrawtransaction", &sendrawtransaction, false, false, false }, { "rawtransactions", "signrawtransaction", &signrawtransaction, false, false, false }, /* uses wallet if enabled */ +#ifdef FEATURE_HPAY_FUNDRAWTX + { "rawtransactions", "fundrawtransaction", &fundrawtransaction, false, false, false }, /* uses wallet if enabled */ +#endif /* FEATURE_HPAY_FUNDRAWTX */ { "rawtransactions", "appendrawchange", &appendrawchange, false, false, true }, { "hidden", "appendrawmetadata", &appendrawmetadata, false, false, true }, { "rawtransactions", "appendrawdata", &appendrawmetadata, false, false, true }, diff --git a/src/rpc/rpcrawtransaction.cpp b/src/rpc/rpcrawtransaction.cpp index 4a7612a..4ca7bb9 100755 --- a/src/rpc/rpcrawtransaction.cpp +++ b/src/rpc/rpcrawtransaction.cpp @@ -39,6 +39,9 @@ using namespace std; #include "utils/util.h" #include "utils/utilmoneystr.h" #include "wallet/wallettxs.h" +#ifdef FEATURE_HPAY_FUNDRAWTX +#include "wallet/coincontrol.h" +#endif /* FEATURE_HPAY_FUNDRAWTX */ bool OutputCanSend(COutput out); uint32_t mc_CheckSigScriptForMutableTx(const unsigned char *src,int size); @@ -1659,6 +1662,162 @@ Value decodescript(const Array& params, bool fHelp) return r; } +#ifdef FEATURE_HPAY_FUNDRAWTX +Value fundrawtransaction(const Array& params, bool fHelp) +{ + if (fHelp || params.size() < 2) + throw runtime_error("Help message not found\n"); + + RPCTypeCheck(params, list_of(str_type)(obj_type)); + + CCoinControl coinControl; + bool changeAddress = 0; + int changePosition = -1; + bool lockUnspents = false; + Array subtractFeeFromOutputs; + std::set setSubtractFeeFromOutputs; + + if (params.size() > 1) + { + if (params[1].type() == bool_type) + { + // backward compatibility bool only fallback + coinControl.fAllowWatchOnly = params[1].get_bool(); + } + else + { + if (params.size() > 1 && params[1].type() != null_type) + { + if(params[1].type() == obj_type) + { + Object objParams = params[1].get_obj(); + BOOST_FOREACH(const Pair& s, objParams) + { + if(s.name_ == "includeWatching") + { + if(s.value_.type() == bool_type) + { + coinControl.fAllowWatchOnly = s.value_.get_bool(); + } + else + { + throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid value for 'includeWatching' field, should be boolean"); + } + } + if(s.name_ == "changeAddress") + { + if(s.value_.type() == str_type) + { + CBitcoinAddress address(s.value_.get_str()); + coinControl.destChange = address.Get(); + if (!address.IsValid()) + throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, string("Invalid address: ")+s.value_.get_str()); + if(!AddressCanReceive(address.Get())) + { + throw JSONRPCError(RPC_INSUFFICIENT_PERMISSIONS, "address doesn't have receive permission"); + } + changeAddress = 1; + } + else + { + throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid value for 'changeAddress' field, should be stribg"); + } + } + if(s.name_ == "changePosition") + { + if(s.value_.type() == int_type) + { + changePosition = s.value_.get_int(); + coinControl.fChangePosition = changePosition; + } + else + { + throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid value for 'changePosition' field, should be integer"); + } + } + if(s.name_ == "feeRate") + { + if(s.value_.type() == real_type) + { + if(s.value_.get_real() == 0) + { + // normal fee + } + else + { + CAmount nAmount = AmountFromValue(s.value_.get_real()); + coinControl.nFeeRate = CFeeRate(nAmount, 1000); + coinControl.fOverrideFeeRate = true; + if(fDebug>1)LogPrintf("fundrawtransaction set %d feeRate %d \n",nAmount,coinControl.nFeeRate.ToString()); + } + } + else + { + throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid value for 'feeRate' field, should be real"); + } + } + if(s.name_ == "subtractFeeFromOutputs") + { + if(s.value_.type() == array_type) + { + subtractFeeFromOutputs = s.value_.get_array(); + } + else + { + throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid value for 'subtractFeeFromOutputs' field, should be array"); + } + } + } + } + } + } + } + + // parse hex string from parameter + CTransaction tx; + if (!DecodeHexTx(tx, params[0].get_str())) { + throw JSONRPCError(RPC_DESERIALIZATION_ERROR, "TX decode failed"); + } + + if (tx.vout.size() == 0) { + throw JSONRPCError(RPC_INVALID_PARAMETER, "TX must have at least one output"); + } + + if (changePosition != -1 && + (changePosition < 0 || (unsigned int)changePosition > tx.vout.size())) { + throw JSONRPCError(RPC_INVALID_PARAMETER, "changePosition out of bounds"); + } + + for (unsigned int idx = 0; idx < subtractFeeFromOutputs.size(); idx++) + { + int pos = subtractFeeFromOutputs[idx].get_int(); + if (setSubtractFeeFromOutputs.count(pos)) + throw JSONRPCError(RPC_INVALID_PARAMETER, strprintf("Invalid parameter, duplicated position: %d", pos)); + if (pos < 0) + throw JSONRPCError(RPC_INVALID_PARAMETER, strprintf("Invalid parameter, negative position: %d", pos)); + if (pos >= int(tx.vout.size())) + throw JSONRPCError(RPC_INVALID_PARAMETER, strprintf("Invalid parameter, position too large: %d", pos)); + setSubtractFeeFromOutputs.insert(pos); + } + + if(fDebug>1)LogPrintf("Befor fund pos %d %s\n",changePosition,tx.ToString()); + CAmount nFeeOut; + string strFailReason; + if(!pwalletMain->FundTransaction(tx, nFeeOut, changePosition, strFailReason, lockUnspents, setSubtractFeeFromOutputs, coinControl)) + { + throw JSONRPCError(RPC_WALLET_ERROR, strFailReason); + } + if(fDebug>1)LogPrintf("After fund pos %d %s\n",changePosition,tx.ToString()); + + Object result; + result.push_back(Pair("hex", EncodeHexTx(CTransaction(tx)))); + result.push_back(Pair("changepos", changePosition)); + result.push_back(Pair("fee", ValueFromAmount(nFeeOut))); + return result; + +} +#endif /* FEATURE_HPAY_FUNDRAWTX */ + Value signrawtransaction(const Array& params, bool fHelp) { if (fHelp || params.size() < 1 || params.size() > 4) diff --git a/src/rpc/rpcserver.h b/src/rpc/rpcserver.h index c2c4391..fc4358b 100755 --- a/src/rpc/rpcserver.h +++ b/src/rpc/rpcserver.h @@ -9,6 +9,8 @@ #ifndef BITCOIN_RPCSERVER_H #define BITCOIN_RPCSERVER_H +#define FEATURE_HPAY_FUNDRAWTX + #include "structs/amount.h" #include "rpc/rpcprotocol.h" #include "structs/uint256.h" @@ -307,6 +309,9 @@ extern json_spirit::Value createrawtransaction(const json_spirit::Array& params, extern json_spirit::Value decoderawtransaction(const json_spirit::Array& params, bool fHelp); extern json_spirit::Value decodescript(const json_spirit::Array& params, bool fHelp); extern json_spirit::Value signrawtransaction(const json_spirit::Array& params, bool fHelp); +#ifdef FEATURE_HPAY_FUNDRAWTX +extern json_spirit::Value fundrawtransaction(const json_spirit::Array& params, bool fHelp); +#endif /* FEATURE_HPAY_FUNDRAWTX */ extern json_spirit::Value sendrawtransaction(const json_spirit::Array& params, bool fHelp); extern json_spirit::Value getblockcount(const json_spirit::Array& params, bool fHelp); // in rpcblockchain.cpp diff --git a/src/rpc/rpcwallet.cpp b/src/rpc/rpcwallet.cpp index 36eb20b..3c17f40 100755 --- a/src/rpc/rpcwallet.cpp +++ b/src/rpc/rpcwallet.cpp @@ -1053,6 +1053,14 @@ Value sendmany(const Array& params, bool fHelp) if (params.size() > 3 && params[3].type() != null_type && !params[3].get_str().empty()) wtx.mapValue["comment"] = params[3].get_str(); +#if defined (FEATURE_HPAY_SENDMANY_DEDUCT_FEE) || defined (FEATURE_HPAY_FUNDRAWTX) + vector addrStrings; + std::set setSubtractFeeFromOutputs; + int subtract_idx=0; + if (params.size() > 4) + addrStrings = ParseStringList(params[4]); +#endif /* FEATURE_HPAY_SENDMANY_DEDUCT_FEE */ + set setAddress; vector > vecSend; @@ -1087,7 +1095,12 @@ Value sendmany(const Array& params, bool fHelp) CReserveKey keyChange(pwalletMain); CAmount nFeeRequired = 0; string strFailReason; +#if defined (FEATURE_HPAY_SENDMANY_DEDUCT_FEE) || defined (FEATURE_HPAY_FUNDRAWTX) + int nChangePosRet = -1; + bool fCreated = pwalletMain->CreateTransaction(vecSend, wtx, keyChange, nFeeRequired, nChangePosRet, setSubtractFeeFromOutputs, strFailReason); +#else bool fCreated = pwalletMain->CreateTransaction(vecSend, wtx, keyChange, nFeeRequired, strFailReason); +#endif /* FEATURE_HPAY_FUNDRAWTX */ if (!fCreated) throw JSONRPCError(RPC_WALLET_INSUFFICIENT_FUNDS, strFailReason); diff --git a/src/version/hdacversion.h b/src/version/hdacversion.h index 0bbc0b0..78d8fdf 100755 --- a/src/version/hdacversion.h +++ b/src/version/hdacversion.h @@ -15,7 +15,7 @@ #endif #define HDAC_BUILD_MAJOR 1 -#define HDAC_BUILD_MINOR 09 +#define HDAC_BUILD_MINOR 10 #define HDAC_BUILD_REVISION 0 // Build version is major.minor.revision ( 1.08.0 ) diff --git a/src/wallet/coincontrol.h b/src/wallet/coincontrol.h index dcaff79..e0d6e19 100755 --- a/src/wallet/coincontrol.h +++ b/src/wallet/coincontrol.h @@ -6,6 +6,8 @@ #ifndef BITCOIN_COINCONTROL_H #define BITCOIN_COINCONTROL_H +#define FEATURE_HPAY_FUNDRAWTX + #include "primitives/transaction.h" /** Coin Control Features. */ @@ -13,7 +15,13 @@ class CCoinControl { public: CTxDestination destChange; - +#ifdef FEATURE_HPAY_FUNDRAWTX + bool fAllowWatchOnly; + bool fAllowOtherInputs; + bool fOverrideFeeRate; + CFeeRate nFeeRate; + int fChangePosition; +#endif /* FEATURE_HPAY_FUNDRAWTX */ CCoinControl() { SetNull(); @@ -22,6 +30,12 @@ class CCoinControl void SetNull() { destChange = CNoDestination(); +#ifdef FEATURE_HPAY_FUNDRAWTX + fAllowWatchOnly = false; + fOverrideFeeRate = false; + nFeeRate = CFeeRate(0); + fChangePosition = -1; +#endif /* FEATURE_HPAY_FUNDRAWTX */ setSelected.clear(); } @@ -50,12 +64,17 @@ class CCoinControl { setSelected.clear(); } - +#ifdef FEATURE_HPAY_FUNDRAWTX + void ListSelected(std::vector& vOutpoints) const + { + vOutpoints.assign(setSelected.begin(), setSelected.end()); + } +#else void ListSelected(std::vector& vOutpoints) { vOutpoints.assign(setSelected.begin(), setSelected.end()); } - +#endif private: std::set setSelected; }; diff --git a/src/wallet/wallet.cpp b/src/wallet/wallet.cpp index 9644fa8..b62bd41 100755 --- a/src/wallet/wallet.cpp +++ b/src/wallet/wallet.cpp @@ -1986,7 +1986,11 @@ void CWallet::AvailableCoins(vector& vCoins, bool fOnlyConfirmed, const (coin.BlocksToMaturity() <= 0) && (mine != ISMINE_NO) && (!fOnlyUnlocked || !IsLockedCoin(txid, vout)) && - (!coinControl || !coinControl->HasSelected() || coinControl->IsSelected(txid, vout))) + (!coinControl || !coinControl->HasSelected() + #ifdef FEATURE_HPAY_FUNDRAWTX + || coinControl->fAllowOtherInputs + #endif /* FEATURE_HPAY_FUNDRAWTX */ + || coinControl->IsSelected(txid, vout))) { const CWalletTx& wtx=pwalletTxsMain->GetInternalWalletTx(txid,NULL,NULL); std::map::const_iterator itold = pwalletTxsMain->vAvailableCoins.find(txid); @@ -2006,7 +2010,13 @@ void CWallet::AvailableCoins(vector& vCoins, bool fOnlyConfirmed, const uint32_t vout=coin.m_OutPoint.n; std::map::const_iterator itold = pwalletTxsMain->vAvailableCoins.find(txid); const CWalletTx* pwtx=&(*itold).second; + #ifdef FEATURE_HPAY_FUNDRAWTX + bool fundOrNot = false; + fundOrNot = (coinControl && coinControl->fAllowWatchOnly) ? true : false; + vCoins.push_back(COutput(pwtx, vout, nDepth, (fundOrNot ? (fundOrNot && (mine & ISMINE_SPENDABLE) != ISMINE_NO):(fundOrNot || (mine & ISMINE_SPENDABLE) != ISMINE_NO)))); + #else vCoins.push_back(COutput(pwtx, vout, nDepth, (mine & ISMINE_SPENDABLE) != ISMINE_NO)); + #endif } } pwalletTxsMain->UnLock(); @@ -2263,7 +2273,11 @@ bool CWallet::SelectCoins(const CAmount& nTargetValue, set return all selected outputs (we want all selected to go into the transaction for sure) - if (coinControl && coinControl->HasSelected()) + if (coinControl && coinControl->HasSelected() + #ifdef FEATURE_HPAY_FUNDRAWTX + && !coinControl->fAllowOtherInputs + #endif /* FEATURE_HPAY_FUNDRAWTX */ + ) { BOOST_FOREACH(const COutput& out, vCoins) { @@ -2275,16 +2289,438 @@ bool CWallet::SelectCoins(const CAmount& nTargetValue, set= nTargetValue); } +#ifdef FEATURE_HPAY_FUNDRAWTX + // Calculate value from preset inputs and store them. + CAmount nValueFromPresetInputs=0; + std::set > setPresetCoins; + std::vector vPresetInputs; + if(coinControl) + { + coinControl->ListSelected(vPresetInputs); + } + BOOST_FOREACH(const COutPoint& out, vPresetInputs) + { + std::map::const_iterator it = pwalletTxsMain->vAvailableCoins.find(out.hash); + if (it != pwalletTxsMain->vAvailableCoins.end()) + { + const CWalletTx* pcoin = &it->second; + if (pcoin->vout.size() <= out.n) + { + if(fDebug>0)LogPrintf("fundtx invalid input\n"); + return false; + } + //check tx vout idx's matching Should need ?? + bool find_valid_out = false; + BOOST_FOREACH(const COutput& outc, vCoins) + { + if(out.hash == outc.tx->GetHash()) + { + if((out.n == outc.i) && (pcoin->vout[out.n].nValue == outc.tx->vout[outc.i].nValue)) + { + find_valid_out = true; + break; + } + } + } + if(!find_valid_out) + { + if(fDebug>0)LogPrintf("fundtx invalid vout index %d\n",out.n); + return false; + } + // Just to calculate the marginal byte size + nValueFromPresetInputs += pcoin->vout[out.n].nValue; + setPresetCoins.insert(std::make_pair(pcoin, out.n)); + } + else + { + if(fDebug>0)LogPrintf("fundtx invalid txid\n"); + return false; + } + } + + // Remove preset inputs from vCoins. + for(std::vector::iterator it = vCoins.begin(); it != vCoins.end() && coinControl && coinControl->HasSelected();) + { + if (setPresetCoins.count(std::make_pair(it->tx, it->i))) + { + it = vCoins.erase(it); + } + else + { + ++it; + } + } + + bool res = nTargetValue <= nValueFromPresetInputs || + SelectCoinsMinConf(nTargetValue-nValueFromPresetInputs, 1, 6, vCoins, setCoinsRet, nValueRet) || + SelectCoinsMinConf(nTargetValue-nValueFromPresetInputs, 1, 1, vCoins, setCoinsRet, nValueRet) || + (bSpendZeroConfChange && SelectCoinsMinConf(nTargetValue-nValueFromPresetInputs, 0, 1, vCoins, setCoinsRet, nValueRet)); + + // because SelectCoinsMinConf clears the setCoinsRet, we now add the possible inputs to the coinset + setCoinsRet.insert(setPresetCoins.begin(), setPresetCoins.end()); + + // add preset inputs to the total value selected + nValueRet += nValueFromPresetInputs; + + return res; +#else return (SelectCoinsMinConf(nTargetValue, 1, 6, vCoins, setCoinsRet, nValueRet) || SelectCoinsMinConf(nTargetValue, 1, 1, vCoins, setCoinsRet, nValueRet) || (bSpendZeroConfChange && SelectCoinsMinConf(nTargetValue, 0, 1, vCoins, setCoinsRet, nValueRet))); +#endif /* FEATURE_HPAY_FUNDRAWTX */ + +} + +#ifdef FEATURE_HPAY_FUNDRAWTX +bool CWallet::FundTransaction(CTransaction &tx, CAmount &nFeeRet, int &nChangePosInOut, std::string &strFailReason, bool lockUnspents, + const std::set &setSubtractFeeFromOutputs, CCoinControl coinControl) +{ + vector > vecSend; + for(size_t idx=0; idx >& vecSend, + CWalletTx& wtxNew, CReserveKey& reservekey, CAmount& nFeeRet, int& nChangePosInOut, const std::set &setSubtractFeeFromOutputs,std::string& strFailReason, const CCoinControl* coinControl) +{ + CAmount nValue = 0; + int nChangePosRequest = nChangePosInOut; + CAmount send_amount; + int nDeductFeeAmount = 0; + int deduct_fee_which = 0; + int subtract_idx=0; + BOOST_FOREACH (const PAIRTYPE(CScript, CAmount)& s, vecSend) + { + if (nValue < 0) + { + strFailReason = _("Transaction amounts must be positive"); + return false; + } + nValue += s.second; + if(setSubtractFeeFromOutputs.count(subtract_idx) == 1) + { + nDeductFeeAmount++; + } + subtract_idx++; + } + if (vecSend.empty() || nValue < 0) + { + strFailReason = _("Transaction must have at least one recipient"); + return false; + } + + wtxNew.fTimeReceivedIsTxTime = true; + wtxNew.BindWallet(this); + CMutableTransaction txNew; + + { + LOCK2(cs_main, cs_wallet); + { + nFeeRet = 0; + while (true) + { + txNew.vin.clear(); + txNew.vout.clear(); + wtxNew.fFromMe = true; + nChangePosInOut = nChangePosRequest; + + CAmount nTotalValue = (deduct_fee_which != 0) ? nValue : nValue + nFeeRet; + double dPriority = 0; + subtract_idx=0; + bool first = true; + // vouts to the payees + BOOST_FOREACH (const PAIRTYPE(CScript, CAmount)& s, vecSend) + { + if(setSubtractFeeFromOutputs.count(subtract_idx) == 1) + { + if((nDeductFeeAmount != 0) && (deduct_fee_which != 0)) + { + send_amount = s.second; + send_amount -= (nFeeRet/nDeductFeeAmount); + if(first && (nFeeRet%nDeductFeeAmount != 0)) + { + first = false; + send_amount -= (nFeeRet%nDeductFeeAmount); + } + } + else + { + send_amount = s.second; + } + } + else + { + send_amount = s.second; + } + CTxOut txout(send_amount, s.first); + + uint32_t type,from,to,timestamp,type_ored,no_dust_check; + + mc_gState->m_TmpScript->Clear(); + + const CScript& script1 = txout.scriptPubKey; + CScript::const_iterator pc1 = script1.begin(); + + mc_gState->m_TmpScript->SetScript((unsigned char*)(&pc1[0]),(size_t)(script1.end()-pc1),MC_SCR_TYPE_SCRIPTPUBKEY); + + type_ored=0; + no_dust_check=mc_gState->m_TmpScript->IsOpReturnScript(); + for (int e = 0; e < mc_gState->m_TmpScript->GetNumElements(); e++) + { + mc_gState->m_TmpScript->SetElement(e); + if(mc_gState->m_TmpScript->GetPermission(&type,&from,&to,×tamp) == 0) + { + if(from >= to) + { + no_dust_check=1; + } + type_ored |= type; + } + } + + + if((type_ored == 0))// || (type_ored & MC_PTP_RECEIVE)) + { + if(no_dust_check == 0) + { + ::minRelayTxFee = CFeeRate(MIN_RELAY_TX_FEE); + + if (txout.IsDust(::minRelayTxFee)) + { + strFailReason = _("Transaction amount too small"); + return false; + } + } + } + txNew.vout.push_back(txout); + subtract_idx++; + } + // Choose coins to use + set > setCoins; + CAmount nValueIn = 0; + if (!SelectCoins(nTotalValue, setCoins, nValueIn, coinControl)) + { + strFailReason = _("Insufficient funds"); + return false; + } + BOOST_FOREACH(PAIRTYPE(const CWalletTx*, unsigned int) pcoin, setCoins) + { + CAmount nCredit = pcoin.first->vout[pcoin.second].nValue; + //The coin age after the next block (depth+1) is used instead of the current, + //reflecting an assumption the user would accept a bit more delay for + //a chance at a free transaction. + //But mempool inputs might still be in the mempool, so their age stays 0 + int age = pcoin.first->GetDepthInMainChain(); + if (age != 0) + age += 1; + dPriority += (double)nCredit * age; + } + + //CAmount nChange = nValueIn - nValue - nFeeRet; + CAmount nChange = (deduct_fee_which != 0) ? nValueIn - nValue : nValueIn - nValue - nFeeRet; + if (nChange > 0) + { + // Fill a vout to ourself + // TODO: pass in scriptChange instead of reservekey so + // change transaction isn't always pay-to-bitcoin-address + CScript scriptChange; + + // coin control: send change to custom address + if (coinControl && !boost::get(&coinControl->destChange)) + { + scriptChange = GetScriptForDestination(coinControl->destChange); + } + // no coin control: send change to newly generated address + else + { + // Note: We use a new key here to keep it from being obvious which side is the change. + // The drawback is that by not reusing a previous key, the change may be lost if a + // backup is restored, if the backup doesn't have the new private key for the change. + // If we reused the old key, it would be possible to add code to look for and + // rediscover unknown transactions that were written with keys of ours to recover + // post-backup change. + + // Reserve a new key pair from key pool + CPubKey vchPubKey; + bool ret; + ret = reservekey.GetReservedKey(vchPubKey); + assert(ret); // should never fail, as we just unlocked + // Using default key - cannot use other keys from pool as they don't have permission + //vchPubKey=vchDefaultKey; + + if(!GetKeyFromAddressBook(vchPubKey,MC_PTP_RECEIVE)) + { + if(fDebug>0)LogPrintf("hdac: Internal error: Cannot find address for change having receive permission\n"); + strFailReason = _("Change address not found"); + return false; + } + + scriptChange = GetScriptForDestination(vchPubKey.GetID()); + } + + CTxOut newTxOut(nChange, scriptChange); + + // Never create dust outputs; if we would, just + // add the dust to the fee. + if (newTxOut.IsDust(::minRelayTxFee)) + { + nChangePosInOut = -1; + nFeeRet += nChange; + reservekey.ReturnKey(); + } + else + { + // Insert change txn at random position: + if(nChangePosInOut == -1) + { + nChangePosInOut = GetRandInt(txNew.vout.size()+1); + } + else if((unsigned int)nChangePosInOut > txNew.vout.size()) + { + strFailReason = _("Change index out of range"); + return false; + } + vector::iterator position = txNew.vout.begin()+nChangePosInOut; + txNew.vout.insert(position, newTxOut); + } + } + else + reservekey.ReturnKey(); + + // Fill vin + BOOST_FOREACH(const PAIRTYPE(const CWalletTx*,unsigned int)& coin, setCoins) + txNew.vin.push_back(CTxIn(coin.first->GetHash(),coin.second)); + + // Sign + if(coinControl == NULL) + { + int nIn = 0; + BOOST_FOREACH(const PAIRTYPE(const CWalletTx*,unsigned int)& coin, setCoins) + if (!SignSignature(*this, *coin.first, txNew, nIn++)) + { + strFailReason = _("Signing transaction failed"); + return false; + } + } + + // Embed the constructed transaction data in wtxNew. + *static_cast(&wtxNew) = CTransaction(txNew); + + // Limit size + unsigned int nBytes = ::GetSerializeSize(*(CTransaction*)&wtxNew, SER_NETWORK, PROTOCOL_VERSION); + if (nBytes >= MAX_STANDARD_TX_SIZE) + { + strFailReason = _("Transaction too large"); + return false; + } + dPriority = wtxNew.ComputePriority(dPriority, nBytes); + + // Can we complete this as a free transaction? + if (fSendFreeTransactions && nBytes <= MAX_FREE_TRANSACTION_CREATE_SIZE) + { + // Not enough fee: enough priority? + double dPriorityNeeded = mempool.estimatePriority(nTxConfirmTarget); + // Not enough mempool history to estimate: use hard-coded AllowFree. + if (dPriorityNeeded <= 0 && AllowFree(dPriority)) + break; + + // Small enough, and priority high enough, to send for free + if (dPriorityNeeded > 0 && dPriority >= dPriorityNeeded) + break; + } + + CAmount nFeeNeeded = GetMinimumFee(nBytes, nTxConfirmTarget, mempool); + #ifdef FEATURE_HPAY_FUNDRAWTX + if(coinControl && coinControl->fOverrideFeeRate) + { + nFeeNeeded = std::max(coinControl->nFeeRate.GetFee(nBytes), ::minRelayTxFee.GetFeePerK()); + if(fDebug>1)LogPrintf("fundtx Fee nFeeNeeded %d (%d) size %d\n",nFeeNeeded,coinControl->nFeeRate.GetFee(nBytes),nBytes); + } + #endif /* FEATURE_HPAY_FUNDRAWTX */ + // If we made it here and we aren't even able to meet the relay fee on the next pass, give up + // because we must be at the maximum allowed fee. + //if (nFeeNeeded < ::minRelayTxFee.GetFee(nBytes)) + if (nFeeNeeded < ::minRelayTxFee.GetFeePerK()) // HDAC_sk_20180126 + { + strFailReason = _("Transaction too large for fee policy"); + return false; + } + + if (nFeeRet >= nFeeNeeded) + { + if(deduct_fee_which != 0) deduct_fee_which = 0; + break; // Done, enough fee included. + } + if(nDeductFeeAmount > 0) + { + deduct_fee_which = 1; + } + + // Include more fee and try again. + nFeeRet = nFeeNeeded; + continue; + } + } + } + return true; +} +#else bool CWallet::CreateTransaction(const vector >& vecSend, CWalletTx& wtxNew, CReserveKey& reservekey, CAmount& nFeeRet, std::string& strFailReason, const CCoinControl* coinControl) { @@ -2508,13 +2944,22 @@ bool CWallet::CreateTransaction(const vector >& vecSend, } return true; } +#endif /* defined (FEATURE_HPAY_SENDMANY_DEDUCT_FEE) || defined (FEATURE_HPAY_FUNDRAWTX) */ bool CWallet::CreateTransaction(CScript scriptPubKey, const CAmount& nValue, CWalletTx& wtxNew, CReserveKey& reservekey, CAmount& nFeeRet, std::string& strFailReason, const CCoinControl* coinControl) { + #if defined (FEATURE_HPAY_SENDMANY_DEDUCT_FEE) || defined (FEATURE_HPAY_FUNDRAWTX) + int nChangePosRet = -1; + std::set setSubtractFeeFromOutputs; + #endif /* defined (FEATURE_HPAY_SENDMANY_DEDUCT_FEE) || defined (FEATURE_HPAY_FUNDRAWTX) */ vector< pair > vecSend; vecSend.push_back(make_pair(scriptPubKey, nValue)); + #if defined (FEATURE_HPAY_SENDMANY_DEDUCT_FEE) || defined (FEATURE_HPAY_FUNDRAWTX) + return CreateTransaction(vecSend, wtxNew, reservekey, nFeeRet, nChangePosRet, setSubtractFeeFromOutputs,strFailReason, coinControl); + #else return CreateTransaction(vecSend, wtxNew, reservekey, nFeeRet, strFailReason, coinControl); + #endif /* defined (FEATURE_HPAY_SENDMANY_DEDUCT_FEE) || defined (FEATURE_HPAY_FUNDRAWTX) */ } bool CWallet::CreateTransaction(CScript scriptPubKey, const CAmount& nValue, CScript scriptOpReturn, diff --git a/src/wallet/wallet.h b/src/wallet/wallet.h index 0b9008d..1ab9cd9 100755 --- a/src/wallet/wallet.h +++ b/src/wallet/wallet.h @@ -9,6 +9,8 @@ #ifndef BITCOIN_WALLET_H #define BITCOIN_WALLET_H +#define FEATURE_HPAY_FUNDRAWTX + #include "structs/amount.h" #include "primitives/block.h" #include "primitives/transaction.h" @@ -449,8 +451,17 @@ class CWallet : public CCryptoKeyStore, public CValidationInterface CAmount GetWatchOnlyBalance() const; CAmount GetUnconfirmedWatchOnlyBalance() const; CAmount GetImmatureWatchOnlyBalance() const; + #ifdef FEATURE_HPAY_FUNDRAWTX + bool FundTransaction(CTransaction &tx, CAmount &nFeeRet, int &nChangePosInOut, std::string &strFailReason, bool lockUnspents, + const std::set &setSubtractFeeFromOutputs, CCoinControl coinControl); + #endif + #if defined (FEATURE_HPAY_SENDMANY_DEDUCT_FEE) || defined (FEATURE_HPAY_FUNDRAWTX) + bool CreateTransaction(const std::vector >& vecSend, + CWalletTx& wtxNew, CReserveKey& reservekey, CAmount& nFeeRet, int& nChangePosInOut, const std::set &setSubtractFeeFromOutputs, std::string& strFailReason, const CCoinControl *coinControl = NULL); + #else bool CreateTransaction(const std::vector >& vecSend, CWalletTx& wtxNew, CReserveKey& reservekey, CAmount& nFeeRet, std::string& strFailReason, const CCoinControl *coinControl = NULL); + #endif bool CreateTransaction(CScript scriptPubKey, const CAmount& nValue, CWalletTx& wtxNew, CReserveKey& reservekey, CAmount& nFeeRet, std::string& strFailReason, const CCoinControl *coinControl = NULL); int SelectHdacCombineCoinsMinConf(int nConfMine, int nConfTheirs, std::vector vCoins, mc_Buffer *in_map, mc_Buffer *in_amounts,