]> Git Repo - VerusCoin.git/commitdiff
Fix advanced, multi-currency operators and UTXO selection
authormiketout <[email protected]>
Thu, 26 Nov 2020 20:29:12 +0000 (12:29 -0800)
committermiketout <[email protected]>
Thu, 26 Nov 2020 20:29:12 +0000 (12:29 -0800)
src/pbaas/crosschainrpc.h
src/pbaas/pbaas.cpp
src/pbaas/pbaas.h
src/pbaas/reserves.cpp
src/pbaas/reserves.h
src/script/script.cpp
src/transaction_builder.cpp
src/wallet/asyncrpcoperation_sendmany.cpp
src/wallet/wallet.cpp

index 1ef0dce61dcdc637a1b7d5447581c833579fcf2d..aa4bf4748cd4fa17cbbadafa6314832382c6c150 100644 (file)
@@ -170,6 +170,7 @@ public:
     std::map<uint160, int64_t> valueMap;
 
     CCurrencyValueMap() {}
+    CCurrencyValueMap(const CCurrencyValueMap &operand) : valueMap(operand.valueMap) {}
     CCurrencyValueMap(const std::map<uint160, int64_t> &vMap) : valueMap(vMap) {}
     CCurrencyValueMap(const std::vector<uint160> &currencyIDs, const std::vector<int64_t> &amounts);
     CCurrencyValueMap(const UniValue &uni);
@@ -210,6 +211,11 @@ public:
     friend CCurrencyValueMap operator-(const CCurrencyValueMap& a, int b);
     friend CCurrencyValueMap operator*(const CCurrencyValueMap& a, int b);
 
+    const CCurrencyValueMap &operator=(const CCurrencyValueMap& operand)
+    {
+        valueMap = operand.valueMap;
+        return *this;
+    }
     const CCurrencyValueMap &operator-=(const CCurrencyValueMap& operand);
     const CCurrencyValueMap &operator+=(const CCurrencyValueMap& operand);
 
index dbf489f55c0521053234bf443ad08f20c50442c7..a5a63524dbb92c49a6d669c93ca166a78f3279ae 100644 (file)
@@ -1399,17 +1399,16 @@ CCurrencyDefinition CConnectedChains::UpdateCachedCurrency(const uint160 &curren
 }
 
 bool CConnectedChains::CreateNextExport(const CCurrencyDefinition &_curDef,
+                                        const std::pair<int, CInputDescriptor> &lastExportInput,
+                                        const std::vector<CInputDescriptor> &reserveDeposits,
                                         const std::vector<ChainTransferData> _txInputs,
+                                        const CTxDestination &feeOutput,
                                         uint32_t nHeight,
                                         TransactionBuilder &exportBuilder,
                                         int32_t &inputsConsumed,
                                         const CPBaaSNotarization &lastNotarization,
                                         CPBaaSNotarization &newNotarization,
-                                        CCurrencyValueMap *arbitragePrices,
-                                        CCurrencyValueMap *arbitrageFunds,
-                                        CCurrencyValueMap *arbitrageThresholds,
-                                        bool *inputTxAdded,
-                                        CTransaction *newInputTx)
+                                        const ChainTransferData *addInputTx)
 {
     // this function accepts all reserve transfer inputs to a particular currency destination,
     // from a particular block as well as a height, which is used as a limiting factor in the
@@ -1428,9 +1427,255 @@ bool CConnectedChains::CreateNextExport(const CCurrencyDefinition &_curDef,
     //    blocks, it must be selected from the first block containing chain transfers, 
     //    following the last block from which chain transfers, are present in the export.
     //
-    // If the input tx boolean, new input tx pointer, and arbitrage information is included,
-    // this function may create a new transaction and conversion input, which is added to the
-    // export transaction created.
+    // If the addInputTx is included, this function will add it to the export transaction created.
+
+    uint160 currencyID = _curDef.GetID();
+
+    // get the inputs to include
+    std::vector<ChainTransferData> txInputs;
+    uint32_t curHeight = _txInputs.size() ? std::get<0>(_txInputs[0]) : nHeight;
+    for (int inputNum = 0; inputNum < _txInputs.size(); inputNum++)
+    {
+        uint32_t nextHeight = std::get<0>(_txInputs[inputNum]);
+        if (curHeight != nextHeight)
+        {
+            // if we have skipped to the next block, and we have enough to make an export, we cannot take any more
+            // except the optional one to add
+            if (inputNum >= CCrossChainExport::MIN_INPUTS)
+            {
+                break;
+            }
+            txInputs.push_back(_txInputs[inputNum]);
+        }
+    }
+
+    // we will consume this many inputs from the list
+    int numInputs = txInputs.size();
+
+    // check to see if we need to add the optional input, not counted as "consumed"
+    if (addInputTx)
+    {
+        uint32_t rtHeight = std::get<0>(*addInputTx);
+        CReserveTransfer reserveTransfer = std::get<2>(*addInputTx);
+        // ensure that any pre-conversions or conversions are all valid, based on mined height and
+        if (reserveTransfer.IsPreConversion())
+        {
+            printf("%s: Invalid optional pre-conversion\n", __func__);
+            LogPrintf("%s: Invalid optional pre-conversion\n", __func__);
+        }
+        else if (reserveTransfer.IsConversion() && rtHeight < _curDef.startBlock)
+        {
+            printf("%s: Invalid optional conversion added before start block\n", __func__);
+            LogPrintf("%s: Invalid optional conversion added before start block\n", __func__);
+        }
+        else
+        {
+            txInputs.push_back(*addInputTx);
+        }
+    }
+
+    // if we have no inputs, including the optional one, we are done
+    if (txInputs.size() == 0)
+    {
+        return false;
+    }
+
+    // now we have all inputs to use. make an export and new notarization from the old notarization
+
+    // inputs can be:
+    // 1. transfers of reserve or tokens for fractional reserve chains
+    // 2. pre-conversions for pre-launch participation in the premine
+    // 3. reserve market conversions
+    //
+    CAmount exportOutVal = lastExportInput.second.nValue; // native export output amount
+
+    // add the last export input, then reserve deposits, then txInputs to the new export
+    exportBuilder.AddTransparentInput(COutPoint(lastExportInput.second.txIn.prevout.hash, lastExportInput.second.txIn.prevout.n), 
+                                      lastExportInput.second.scriptPubKey,
+                                      lastExportInput.second.nValue);
+
+    // combine any reserve deposits from the last export
+    // into the reserve deposit inputs for this export and spend them
+    CCurrencyValueMap currentReserveDeposits;
+    CAmount nativeReserveDeposit = 0;
+
+    for (auto &oneReserveDeposit : reserveDeposits)
+    {
+        COptCCParams p;
+        currentReserveDeposits += oneReserveDeposit.scriptPubKey.ReserveOutValue(p);
+        assert(p.evalCode == EVAL_RESERVE_DEPOSIT);
+        if (oneReserveDeposit.nValue)
+        {
+            nativeReserveDeposit += oneReserveDeposit.nValue;
+        }
+        exportBuilder.AddTransparentInput(COutPoint(oneReserveDeposit.txIn.prevout.hash, oneReserveDeposit.txIn.prevout.n), 
+                                          oneReserveDeposit.scriptPubKey,
+                                          oneReserveDeposit.nValue);
+    }
+
+    CCurrencyValueMap totalTxFees;                  // all fees taken from reserveTransfers
+    CCurrencyValueMap totalAmounts;                 // total amounts exported
+    std::vector<CBaseChainObject *> chainObjects;   // exports that will be part of the opRet
+    std::vector<CReserveTransfer *> preConversions; // if we have pre-conversions, store them here
+
+    for (int i = 0; i < txInputs.size(); i++)
+    {
+        uint32_t rtHeight = std::get<0>(txInputs[i]);
+        CInputDescriptor inputDesc = std::get<1>(txInputs[i]);
+        CReserveTransfer reserveTransfer = std::get<2>(txInputs[i]);
+
+        // ensure that any pre-conversions or conversions are all valid, based on mined height and
+        if (reserveTransfer.IsPreConversion())
+        {
+            //
+            if (rtHeight >= _curDef.startBlock)
+            {
+                printf("%s: Invalid pre-conversion, mined after start block\n", __func__);
+                LogPrintf("%s: Invalid pre-conversion, mined after start block\n", __func__);
+                reserveTransfer = reserveTransfer.GetRefundTransfer();
+            }
+            else
+            {
+                preConversions.push_back(&reserveTransfer);
+            }
+        }
+        else if (reserveTransfer.IsConversion() && rtHeight < _curDef.startBlock)
+        {
+            printf("%s: Invalid conversion, mined before start block\n", __func__);
+            LogPrintf("%s: Invalid conversion, mined before start block\n", __func__);
+            reserveTransfer = reserveTransfer.GetRefundTransfer();
+        }
+
+        exportBuilder.AddTransparentInput(inputDesc.txIn.prevout, inputDesc.scriptPubKey, inputDesc.nValue, inputDesc.txIn.nSequence);
+        CCurrencyValueMap newTransferInput = inputDesc.scriptPubKey.ReserveOutValue();
+        newTransferInput.valueMap[ASSETCHAINS_CHAINID] = inputDesc.nValue;
+
+        totalTxFees += reserveTransfer.CalculateFee(reserveTransfer.flags, reserveTransfer.nValue, _curDef.systemID);
+        totalAmounts += newTransferInput;
+        chainObjects.push_back(new CChainObject<CReserveTransfer>(ObjTypeCode(reserveTransfer), reserveTransfer));
+    }
+
+    // make a fee output as placeholder
+    CReserveTransfer feeOut(CReserveTransfer::VALID + CReserveTransfer::FEE_OUTPUT, 
+                            _curDef.systemID, 0, 0, _curDef.systemID, DestinationToTransferDestination(feeOutput));
+    chainObjects.push_back(new CChainObject<CReserveTransfer>(ObjTypeCode(feeOut), feeOut));
+
+    //printf("%s: total export amounts:\n%s\n", __func__, totalAmounts.ToUniValue().write().c_str());
+    CCrossChainExport ccx(currencyID, txInputs.size(), totalAmounts.CanonicalMap(), totalTxFees.CanonicalMap());
+
+    CAmount nativeExportAmount = 0;
+    if (ccx.totalAmounts.valueMap.count(ASSETCHAINS_CHAINID))
+    {
+        nativeExportAmount = ccx.totalAmounts.valueMap[ASSETCHAINS_CHAINID];
+        nativeReserveDeposit += nativeExportAmount;
+        currentReserveDeposits += ccx.totalAmounts;
+        currentReserveDeposits.valueMap.erase(ASSETCHAINS_CHAINID);
+    }
+    else
+    {
+        currentReserveDeposits += ccx.totalAmounts;
+    }
+
+    if (chainObjects.size())
+    {
+        CScript opRet = StoreOpRetArray(chainObjects);
+        DeleteOpRetObjects(chainObjects);
+
+        CCcontract_info CC;
+        CCcontract_info *cp;
+
+        if (nativeReserveDeposit || currentReserveDeposits.valueMap.size())
+        {
+            CScript opRet = StoreOpRetArray(chainObjects);
+            DeleteOpRetObjects(chainObjects);
+
+            // now send transferred currencies to a reserve deposit
+            cp = CCinit(&CC, EVAL_RESERVE_DEPOSIT);
+
+            // send the entire amount to a reserve deposit output of the specific chain
+            // we receive our fee on the other chain, when it comes back, or if a token,
+            // when it gets imported back to the chain
+            std::vector<CTxDestination> dests({CPubKey(ParseHex(CC.CChexstr))});
+            CReserveDeposit rd = CReserveDeposit(currencyID, currentReserveDeposits);
+            exportBuilder.AddTransparentOutput(MakeMofNCCScript(CConditionObj<CReserveDeposit>(EVAL_RESERVE_DEPOSIT, dests, 1, &rd)), 
+                                    nativeReserveDeposit);
+        }
+
+        cp = CCinit(&CC, EVAL_CROSSCHAIN_EXPORT);
+
+        // send native amount of zero to a cross chain export output of the specific chain
+        std::vector<CTxDestination> dests = std::vector<CTxDestination>({CPubKey(ParseHex(CC.CChexstr)).GetID()});
+
+        exportBuilder.AddTransparentOutput(MakeMofNCCScript(CConditionObj<CCrossChainExport>(EVAL_CROSSCHAIN_EXPORT, dests, 1, &ccx)),
+                                exportOutVal);
+
+        // when exports are confirmed as having been imported, they are finalized
+        // until then, a finalization UTXO enables an index search to only find transactions
+        // that have work to complete on this chain, or have not had their cross-chain import 
+        // acknowledged
+        cp = CCinit(&CC, EVAL_FINALIZE_EXPORT);
+        CTransactionFinalization finalization(CTransactionFinalization::FINALIZE_EXPORT, currencyID, 0);
+
+        //printf("%s: Finalizing export with index dest %s\n", __func__, EncodeDestination(CKeyID(CCrossChainRPCData::GetConditionID(lastChainDef.systemID, EVAL_FINALIZE_EXPORT))).c_str());
+
+        dests = std::vector<CTxDestination>({CPubKey(ParseHex(CC.CChexstr)).GetID()});
+        exportBuilder.AddTransparentOutput(MakeMofNCCScript(CConditionObj<CTransactionFinalization>(EVAL_FINALIZE_EXPORT, dests, 1, &finalization)), 0);
+
+        exportBuilder.AddOpRet(opRet);
+        exportBuilder.SetFee(0);
+
+        CCrossChainExport lastCCX;
+        COptCCParams p;
+
+        if (!(lastExportInput.second.scriptPubKey.IsPayToCryptoCondition(p) && 
+            p.IsValid() && 
+            p.evalCode == EVAL_CROSSCHAIN_EXPORT && 
+            p.vData.size() && 
+            (lastCCX = CCrossChainExport(p.vData[0])).IsValid()))
+        {
+            printf("%s: ERROR: Invalid last export input for %s currency\n", __func__, _curDef.name.c_str());
+            LogPrintf("%s: ERROR: Invalid last export input for %s currency\n", __func__, _curDef.name.c_str());
+            return false;
+        }
+
+        CTransaction lastImport;
+        CPartialTransactionProof exportProof;
+        CCrossChainImport ccImport;
+        CCrossChainExport ccExport;
+        if (!GetLastImport(currencyID, lastImport, exportProof, ccImport, ccExport))
+        {
+            printf("%s: ERROR: Cannot find last import transaction for %s currency\n", __func__, _curDef.name.c_str());
+            LogPrintf("%s: ERROR: Cannot find last import transaction for %s currency\n", __func__, _curDef.name.c_str());
+            return false;
+        }
+
+        //get new notarization, as of when this imports
+        CTransaction simulatedExport(exportBuilder.mtx);
+        CMutableTransaction mtx;
+        CCoinbaseCurrencyState newState;
+        CCoinbaseCurrencyState oldState = lastNotarization.currencyState;
+        CPBaaSNotarization newImportNotarization;
+        if (NewImportNotarization(_curDef, nHeight, lastImport, nHeight, simulatedExport, mtx, oldState, newState))
+        {
+            newImportNotarization = CPBaaSNotarization(mtx);
+            // check if we have gone over the currency limit or maximum pre-conversion contribution
+            // if over on conversion or pre-conversion contribution, determine how much we need to remove
+            // and loop through to remove it
+
+            // if we are still calculating pre-conversion, check against pre-conversion amounts
+
+            // if post start, ensure that resulting currency supply is at or under the maximum
+            // if not, loop through to remove what is needed, largest first
+        }
+
+        /* UniValue uni(UniValue::VOBJ);
+        TxToUniv(exportBuilder.mtx, uint256(), uni);
+        printf("%s: about to send reserve deposits with tx:\n%s\n", __func__, uni.write(1,2).c_str()); */
+
+        // we have build a transaction builder and provided a new notarization for analysis or use
+        return true;
+    }
+
     return false;
 }
 
index 0cbaf116e734c15299f383b4b303868b1937bcf0..a25d701f4cd50b51c91046daa340385e519613d4 100644 (file)
@@ -855,17 +855,16 @@ public:
                        CCrossChainExport &ccCrossExport);
 
     bool CreateNextExport(const CCurrencyDefinition &_curDef,
-                          const std::vector<ChainTransferData> _txInputs,
+                          const std::pair<int, CInputDescriptor> &lastExportInput,
+                          const std::vector<CInputDescriptor> &reserveDeposits,
+                          const std::vector<ChainTransferData> txInputs,
+                          const CTxDestination &feeOutput,
                           uint32_t nHeight,
                           TransactionBuilder &exportBuilder,
                           int32_t &inputsConsumed,
                           const CPBaaSNotarization &lastNotarization,
                           CPBaaSNotarization &newNotarization,
-                          CCurrencyValueMap *arbitragePrices=nullptr,
-                          CCurrencyValueMap *arbitrageFunds=nullptr,
-                          CCurrencyValueMap *arbitrageThresholds=nullptr,
-                          bool *inputTxAdded=nullptr,
-                          CTransaction *newInputTx=nullptr);
+                          const ChainTransferData *addInputTx=nullptr);
 
     // returns newly created import transactions to the specified chain from exports on this chain specified chain
     bool CreateLatestImports(const CCurrencyDefinition &chainDef, 
index 9df9da6f26fee809d7cdf50dcf05e7e17ed4d5a3..a1c08db186118189aad42d4cf27f71ba124485b6 100644 (file)
@@ -1416,29 +1416,34 @@ CCurrencyValueMap CReserveTransactionDescriptor::GeneratedImportCurrency(const u
     return retVal;
 }
 
-CReserveTransfer RefundExport(const CBaseChainObject *objPtr)
+CReserveTransfer CReserveTransfer::GetRefundTransfer() const
 {
-    if (objPtr->objectType == CHAINOBJ_RESERVETRANSFER)
+    CReserveTransfer rt = *this;
+
+    // convert full ID destinations to normal ID outputs, since it's refund, full ID will be on this chain already
+    if (rt.destination.type == CTransferDestination::DEST_FULLID)
     {
-        CReserveTransfer &rt = ((CChainObject<CReserveTransfer> *)objPtr)->object;
+        CIdentity(rt.destination.destination);
+        rt.destination = CTransferDestination(CTransferDestination::DEST_ID, rt.destination.destination);
+    }
 
-        // convert full ID destinations to normal ID outputs, since it's refund, full ID will be on this chain already
-        if (rt.destination.type == CTransferDestination::DEST_FULLID)
-        {
-            CIdentity(rt.destination.destination);
-            rt.destination = CTransferDestination(CTransferDestination::DEST_ID, rt.destination.destination);
-        }
+    // turn it into a normal transfer, which will create an unconverted output
+    rt.flags &= ~(CReserveTransfer::SEND_BACK | CReserveTransfer::PRECONVERT | CReserveTransfer::CONVERT);
 
-        // turn it into a normal transfer, which will create an unconverted output
-        rt.flags &= ~(CReserveTransfer::SEND_BACK | CReserveTransfer::PRECONVERT | CReserveTransfer::CONVERT);
+    if (rt.flags & (CReserveTransfer::PREALLOCATE | CReserveTransfer::MINT_CURRENCY))
+    {
+        rt.flags &= ~(CReserveTransfer::PREALLOCATE | CReserveTransfer::MINT_CURRENCY);
+        rt.nValue = 0;
+    }
+    rt.destCurrencyID = rt.currencyID;
+    return rt;
+}
 
-        if (rt.flags & (CReserveTransfer::PREALLOCATE | CReserveTransfer::MINT_CURRENCY))
-        {
-            rt.flags &= ~(CReserveTransfer::PREALLOCATE | CReserveTransfer::MINT_CURRENCY);
-            rt.nValue = 0;
-        }
-        rt.destCurrencyID = rt.currencyID;
-        return rt;
+CReserveTransfer RefundExport(const CBaseChainObject *objPtr)
+{
+    if (objPtr->objectType == CHAINOBJ_RESERVETRANSFER)
+    {
+        return ((CChainObject<CReserveTransfer> *)objPtr)->object.GetRefundTransfer();
     }
     return CReserveTransfer();
 }
index c0a7ba423996f09fc7a81ad1cd28d132d4471c95..d22bd7e13963a9e00e8a65e9c9476d57fe7b8314 100644 (file)
@@ -147,8 +147,9 @@ public:
         BURN_CHANGE_WEIGHT = 0x100,         // this output is being burned on import and will change the reserve ratio
         IMPORT_TO_SOURCE = 0x200,           // set when the source currency, not destination is the import currency
         RESERVE_TO_RESERVE = 0x400,         // for arbitrage or transient conversion, 2 stage solving (2nd from new fractional to reserves)
-        FEE_SOURCE_NATIVE = 0x800,          // for arbitrage or transient conversion, 2 stage solving (2nd from new fractional to reserves)
-        FEE_DEST_NATIVE = 0x1000            // for arbitrage or transient conversion, 2 stage solving (2nd from new fractional to reserves)
+        FEE_SOURCE_NATIVE = 0x800,          // fee is paid in native currency of source system
+        FEE_DEST_NATIVE = 0x1000,           // fee is paid in native currency of destination system
+        REFUND = 0x2000                     // this transfer should be refunded, individual property when conversions exceed limits
     };
 
     enum EConstants
@@ -283,6 +284,8 @@ public:
     {
         return flags & RESERVE_TO_RESERVE;
     }
+
+    CReserveTransfer GetRefundTransfer() const;
 };
 
 class CReserveDeposit : public CMultiOutput
index 17e30137e3722f90fae3f2cc5a3e6fb58a9bd00b..9cebee581991ef1a113869faedd5c22045237c36 100644 (file)
@@ -1329,7 +1329,10 @@ CCurrencyValueMap::CCurrencyValueMap(const std::vector<uint160> &currencyIDs, co
     int commonNum = currencyIDs.size() >= amounts.size() ? amounts.size() : currencyIDs.size();
     for (int i = 0; i < commonNum; i++)
     {
-        valueMap[currencyIDs[i]] = amounts[i];
+        if (amounts[i])
+        {
+            valueMap[currencyIDs[i]] = amounts[i];
+        }
     }
 }
 
@@ -1337,25 +1340,42 @@ bool operator<(const CCurrencyValueMap& a, const CCurrencyValueMap& b)
 {
     // to be less than means, in this order:
     // 1. To have fewer non-zero currencies.
-    // 2. If not fewer currencies, to be unable to be subtracted from the one being checked
-    //    without creating negative values
+    // 2. If not fewer currencies, all present currencies must be less in a than b
     if (!a.valueMap.size() && !b.valueMap.size())
     {
         return false;
     }
+
     bool isaltb = false;
 
+    // ensure that we are smaller than all those present in b
     for (auto &oneVal : b.valueMap)
     {
         if (oneVal.second)
         {
             auto it = a.valueMap.find(oneVal.first);
-            if (it == a.valueMap.end() || it->second < oneVal.second)
+
+            // negative is less than not present, which is equivalent to 0
+            if ((it == a.valueMap.end() && oneVal.second > 0) || (it != a.valueMap.end() && it->second < oneVal.second))
             {
                 isaltb = true;
             }
         }
     }
+
+    // ensure that for all the currencies we have, b does not have less
+    for (auto &oneVal : a.valueMap)
+    {
+        if (oneVal.second)
+        {
+            auto it = b.valueMap.find(oneVal.first);
+
+            if ((it == b.valueMap.end() && oneVal.second > 0) || (it != b.valueMap.end() && it->second < oneVal.second))
+            {
+                isaltb = false;
+            }
+        }
+    }
     return isaltb;
 }
 
@@ -1391,7 +1411,44 @@ bool operator!=(const CCurrencyValueMap& a, const CCurrencyValueMap& b)
 
 bool operator<=(const CCurrencyValueMap& a, const CCurrencyValueMap& b)
 {
-    return (a < b) || (a == b);
+    // to be less or equal to than means, in this order:
+    // 1. To have fewer non-zero or both zero currencies.
+    // 2. If not fewer or both zero currencies, all present currencies must be less or equal to in a with respect to b
+    if (!a.valueMap.size() && !b.valueMap.size())
+    {
+        return true;
+    }
+
+    bool isalteb = false;
+
+    // ensure that we are smaller than all those present in b
+    for (auto &oneVal : b.valueMap)
+    {
+        if (oneVal.second)
+        {
+            auto it = a.valueMap.find(oneVal.first);
+
+            if ((it == a.valueMap.end() && oneVal.second >= 0) || (it != a.valueMap.end() && it->second <= oneVal.second))
+            {
+                isalteb = true;
+            }
+        }
+    }
+
+    // ensure that for all the currencies we have, b has equal or more
+    for (auto &oneVal : a.valueMap)
+    {
+        if (oneVal.second)
+        {
+            auto it = b.valueMap.find(oneVal.first);
+
+            if ((it == b.valueMap.end() && oneVal.second > 0) || (it != b.valueMap.end() && it->second < oneVal.second))
+            {
+                isalteb = false;
+            }
+        }
+    }
+    return isalteb;
 }
 
 bool operator>=(const CCurrencyValueMap& a, const CCurrencyValueMap& b)
@@ -1539,20 +1596,24 @@ CCurrencyValueMap CCurrencyValueMap::CanonicalMap() const
 
 CCurrencyValueMap CCurrencyValueMap::NonIntersectingValues(const CCurrencyValueMap& operand) const
 {
-    CCurrencyValueMap retVal = operand;
+    CCurrencyValueMap retVal = *this;
 
     if (valueMap.size() && operand.valueMap.size())
     {
         for (auto &oneVal : valueMap)
         {
-            auto it = operand.valueMap.find(oneVal.first);
-            if (it != operand.valueMap.end())
+            if (oneVal.second)
             {
-                if (it->second > 0 && oneVal.second > 0)
+                auto it = operand.valueMap.find(oneVal.first);
+                if (it != operand.valueMap.end() && it->second != 0)
                 {
-                    retVal.valueMap.erase(it);
+                    retVal.valueMap.erase(it->first);
                 }
             }
+            else
+            {
+                retVal.valueMap.erase(oneVal.first);
+            }
         }
     }
     return retVal;
@@ -1585,7 +1646,7 @@ bool CCurrencyValueMap::HasNegative() const
 // subtract, but do not subtract to negative values
 CCurrencyValueMap CCurrencyValueMap::SubtractToZero(const CCurrencyValueMap& operand) const
 {
-    CCurrencyValueMap retVal = *this;
+    CCurrencyValueMap retVal(*this);
     std::vector<uint160> toRemove;
     if (valueMap.size() && operand.valueMap.size())
     {
@@ -1594,7 +1655,7 @@ CCurrencyValueMap CCurrencyValueMap::SubtractToZero(const CCurrencyValueMap& ope
             auto it = operand.valueMap.find(oneVal.first);
             if (it != operand.valueMap.end())
             {
-                oneVal.second = oneVal.second - it->second;
+                oneVal.second -= it->second;
                 if (oneVal.second <= 0)
                 {
                     toRemove.push_back(oneVal.first);
index e3452170c207b75af266332e816d554707a873a3..2f90f675ac00d1d6e6ec68dfa1ac7f771cf9de65 100644 (file)
@@ -255,6 +255,7 @@ TransactionBuilderResult TransactionBuilder::Build()
     }
     for (auto tIn : tIns) {
         change += tIn.value;
+        printf("tIn.scriptPubKey.ReserveOutValue():\n%s\n", tIn.scriptPubKey.ReserveOutValue().ToUniValue().write(1,2).c_str());
         reserveChange += tIn.scriptPubKey.ReserveOutValue();
     }
     if (reserveChange.valueMap.size())
index 433d2f1c586dfea5adafd973386f383cbf0218b6..47d0a7f2697fd54c51c6ba6229838c00d0418e5a 100644 (file)
@@ -327,6 +327,8 @@ bool AsyncRPCOperation_sendmany::main_impl() {
         }
     }
 
+    // printf("Transparent funds, have %s, need %s", t_all_inputs_total.ToUniValue().write(1,2).c_str(), targetAllAmounts.ToUniValue().write(1,2).c_str());
+
     t_all_inputs_total.valueMap[ASSETCHAINS_CHAINID] = t_inputs_total;
     t_all_inputs_total = t_all_inputs_total.CanonicalMap();
 
@@ -385,7 +387,8 @@ bool AsyncRPCOperation_sendmany::main_impl() {
         // sanity check, since we don't always hold locks
         for (int i = 0 ; i < t_inputs_.size(); i++)
         {
-            if (!pwalletMain->mapWallet.count(t_inputs_txids_[i]))
+            auto pit = pwalletMain->mapWallet.find(t_inputs_txids_[i]);
+            if (pit == pwalletMain->mapWallet.end() || t_inputs_[i].tx != &pit->second)
             {
                 throw JSONRPCError(RPC_INTERNAL_ERROR, "Wallet transaction was deleted from wallet after selection and before tx was made.");
             }
@@ -409,6 +412,23 @@ bool AsyncRPCOperation_sendmany::main_impl() {
 
             success = 
               pwalletMain->SelectReserveCoinsMinConf(targetReserveAmounts, targetNativeAmount, 0, 0, t_inputs_, setCoinsRet, reserveValueRet, nativeValueRet);
+
+            /* if (success)
+            {
+                printf("value returned:\n%s, native: %s\n", reserveValueRet.ToUniValue().write(1,2).c_str(), ValueFromAmount(nativeValueRet).write(1,2).c_str());
+                for (auto &oneOutput : setCoinsRet)
+                {
+                    printf("Output %s : %d, for native: %s\n    reserve: %s\n\n", 
+                           oneOutput.first->GetHash().GetHex().c_str(), 
+                           oneOutput.second, 
+                           ValueFromAmount(oneOutput.first->vout[oneOutput.second].nValue).write().c_str(), 
+                           oneOutput.first->vout[oneOutput.second].ReserveOutValue().ToUniValue().write(1,2).c_str());
+                }
+            }
+            else
+            {
+                printf("%s: selection failed\n", __func__);
+            } */
         }
 
         if (!success)
@@ -435,9 +455,6 @@ bool AsyncRPCOperation_sendmany::main_impl() {
             if (selectedUTXOAmount >= targetNativeAmount) {
                 // Select another utxo if there is change less than the dust threshold.
                 dustChange = selectedUTXOAmount - targetNativeAmount;
-                if (dustChange == 0 || dustChange >= dustThreshold) {
-                    break;
-                }
             }
         }
 
@@ -1147,10 +1164,7 @@ bool AsyncRPCOperation_sendmany::find_utxos(bool fAcceptProtectedCoinbase=false)
             }
         }
 
-        const CScript &scriptPubKey = out.tx->vout[out.i].scriptPubKey;
-
         t_inputs_.push_back(out);
-        t_inputs_txids_.push_back(out.tx->GetHash());
     }
 
     // sort in ascending order, so smaller utxos appear first
@@ -1158,6 +1172,12 @@ bool AsyncRPCOperation_sendmany::find_utxos(bool fAcceptProtectedCoinbase=false)
         return ( i.tx->vout[i.i].nValue < j.tx->vout[j.i].nValue );
     });
 
+    // get txids for verification between holding locks
+    for (auto &out : t_inputs_)
+    {
+        t_inputs_txids_.push_back(out.tx->GetHash());
+    }
+
     return t_inputs_.size() > 0;
 }
 
index 4cbd1c342f87588d75537aed849ebc0f03d8c46e..87db343aca9b98136b8249a07316b569892b111a 100644 (file)
@@ -82,58 +82,49 @@ struct CompareValueOnly
     }
 };
 
-struct CompareValueMap
+class CompareValueMap
 {
-    bool operator()(const CCurrencyValueMap &m1,
-                    const CCurrencyValueMap &m2) const
+public:
+    CCurrencyValueMap totalTargetValues;
+    CompareValueMap() {}
+    CompareValueMap(const CCurrencyValueMap &targetValues) : totalTargetValues(targetValues) {}
+    bool CompareMaps(const CCurrencyValueMap &m1,
+                     const CCurrencyValueMap &m2) const
     {
-        if (m1 < m2 && m2 < m1)
+        // if we have a target to compare against,
+        // check to see if one leaves less change after meeting the target
+        if (totalTargetValues.valueMap.size())
         {
-            // this is used for sorting
-            // what we care about most in this case is that we always give the same answer,
-            // so, run a repeatable check, regardless of the order of operands. we'd also want
-            // to be as close to right as possible.
-            CCurrencyValueMap checkMap1 = m1.IntersectingValues(m2);
-            CCurrencyValueMap checkMap2;
-            // where they intersect, they are empty, no way to know which is less for sorting
-            if (!(checkMap2 < checkMap1))
-            {
-                return false;
-            }
-            checkMap2 = checkMap1 - m2.IntersectingValues(m1);
-            CAmount total = 0;
-            for (auto &oneCur : checkMap2.valueMap)
-            {
-                total += oneCur.second;
-            }
-            if (total < 0)
-            {
-                return true;
-            }
-            else
+            CCurrencyValueMap leftover1 = m1.SubtractToZero(totalTargetValues);
+            CCurrencyValueMap leftover2 = m2.SubtractToZero(totalTargetValues);
+
+            if (leftover1 < leftover2 && leftover2 < leftover1)
             {
-                return false;
+                if (leftover1.valueMap.size() < leftover2.valueMap.size())
+                {
+                    return true;
+                }
+                else if (leftover2.valueMap.size() < leftover1.valueMap.size())
+                {
+                    return false;
+                }
             }
+            return leftover1 < leftover2;
         }
-        return m1 < m2;
-    }
-    bool operator()(const pair<CCurrencyValueMap, pair<const CWalletTx*, unsigned int> >& t1,
-                    const pair<CCurrencyValueMap, pair<const CWalletTx*, unsigned int> >& t2) const
-    {
-        if (t1.first < t2.first && t2.first < t1.first)
+        else if (m1 < m2 && m2 < m1)
         {
             // this is used for sorting
             // what we care about most in this case is that we always give the same answer,
             // so, run a repeatable check, regardless of the order of operands. we'd also want
             // to be as close to right as possible.
-            CCurrencyValueMap checkMap1 = t1.first.IntersectingValues(t2.first);
+            CCurrencyValueMap checkMap1 = m1.IntersectingValues(m2);
             CCurrencyValueMap checkMap2;
             // where they intersect, they are empty, no way to know which is less for sorting
             if (!(checkMap2 < checkMap1))
             {
                 return false;
             }
-            checkMap2 = checkMap1 - t2.first.IntersectingValues(t1.first);
+            checkMap2 = checkMap1 - m2.IntersectingValues(m1);
             CAmount total = 0;
             for (auto &oneCur : checkMap2.valueMap)
             {
@@ -148,7 +139,7 @@ struct CompareValueMap
                 return false;
             }
         }
-        return t1.first < t2.first;
+        return m1 < m2;
     }
 };
 
@@ -5216,8 +5207,6 @@ static void ApproximateBestSubset(vector<pair<CAmount, pair<const CWalletTx*,uns
 bool CloserToTarget(const CCurrencyValueMap &target, const CCurrencyValueMap &current, const CCurrencyValueMap &candidate)
 {
     CCurrencyValueMap workingTarget = target.SubtractToZero(current);   // whatever is left is what we still need
-    //printf("with candidate: \n%s\nprior target: %s\n", workingTarget.SubtractToZero(candidate).ToUniValue().write(1,2).c_str(),
-    //                                                   workingTarget.ToUniValue().write(1,2).c_str());
     return workingTarget.SubtractToZero(candidate) < workingTarget;
 }
 
@@ -5239,9 +5228,12 @@ static void ApproximateBestReserveSubset(vector<pair<CCurrencyValueMap, pair<con
     {
         vfIncluded.assign(vValue.size(), false);
         CCurrencyValueMap totals;
+        std::set<uint160> satisfied;
         bool fReachedTarget = false;
         for (int nPass = 0; nPass < 2 && !fReachedTarget; nPass++)
         {
+            CCurrencyValueMap adjustedTarget(targetValues);
+            CCurrencyValueMap presentValues;
             for (unsigned int i = 0; i < vValue.size(); i++)
             {
                 //The solver here uses a randomized algorithm,
@@ -5256,16 +5248,26 @@ static void ApproximateBestReserveSubset(vector<pair<CCurrencyValueMap, pair<con
                                                                               vValue[i].first.ToUniValue().write(1,2).c_str());
                 printf("iscloser: %d\n", CloserToTarget(targetValues, totals, vValue[i].first));
                 */
+
                 if ((nPass == 0 ? insecure_rand()&1 : !vfIncluded[i]) && CloserToTarget(targetValues, totals, vValue[i].first))
                 {
-                    totals += vValue[i].first;
+                    totals += vValue[i].first.IntersectingValues(targetValues);
                     vfIncluded[i] = true;
                     // we reached the target if we fulfill all currencies
-                    CCurrencyValueMap curLeft = totals - targetValues;
-                    if (!curLeft.HasNegative())
+
+                    adjustedTarget = targetValues.SubtractToZero(totals);
+
+                    // loop through all those that have been zeroed in the adjusted target, and mark as satisfied
+                    for (auto &oneCur : targetValues.NonIntersectingValues(adjustedTarget).valueMap)
+                    {
+                        satisfied.insert(oneCur.first);
+                    }
+
+                    if (satisfied.size() == targetValues.valueMap.size())
                     {
                         fReachedTarget = true;
-                        if (CompareValueMap()(totals, bestTotals))
+                        CompareValueMap comparator(targetValues);
+                        if (comparator.CompareMaps(totals, bestTotals))
                         {
                             bestTotals = totals;
                             vfBest = vfIncluded;
@@ -5529,11 +5531,11 @@ bool CWallet::SelectReserveCoinsMinConf(const CCurrencyValueMap& targetValues,
     // for each currency type being looked for, store the lowest larger outputs found in order, up to a maximum of the number of
     // different currencies being looked for
     std::map<uint160, std::multimap<CAmount, CReserveOutSelectionInfo>> coinsLowestLarger;
-    std::set<const COutput *> largerOuts;       // all those that are >= than amount requested in at least one currency
+    std::map<std::pair<const CWalletTx *, int>, CCurrencyValueMap> largerOuts;       // all those that are >= than amount requested in at least one currency
     std::multimap<int, std::pair<std::vector<uint160>, CReserveOutSelectionInfo>> multiSatisfy;  // for outputs that satisfy >= one currency
     CCurrencyValueMap largerTotal;
     std::map<uint160, std::multimap<CAmount, CReserveOutSelectionInfo>> coinsLargestLower;
-    std::set<const COutput *> lowerOuts;        // all those that are lower in all requested amounts
+    std::map<std::pair<const CWalletTx *, int>, CCurrencyValueMap> lowerOuts;        // all those that are lower or unneeded for larger and helpful
     CCurrencyValueMap lowerTotal;
 
     CCurrencyValueMap nativeCent(std::vector<uint160>({ASSETCHAINS_CHAINID}), std::vector<CAmount>({CENT}));
@@ -5547,7 +5549,11 @@ bool CWallet::SelectReserveCoinsMinConf(const CCurrencyValueMap& targetValues,
 
     //printf("totaltarget: %s\n", nTotalTarget.ToUniValue().write().c_str());
 
-    BOOST_FOREACH(const COutput &output, vCoins)
+    // currencies in the target that are satisfied x4 in the lower list
+    std::set<uint160> satisfied_x4;
+    CCurrencyValueMap targetx4(nTotalTarget * 4 + nativeCent);
+
+    for (const COutput &output : vCoins)
     {
         if (!output.fSpendable)
             continue;
@@ -5559,8 +5565,7 @@ bool CWallet::SelectReserveCoinsMinConf(const CCurrencyValueMap& targetValues,
 
         int i = output.i;
         CCurrencyValueMap nAll(pcoin->vout[i].scriptPubKey.ReserveOutValue());  // all currencies, whether in target or not
-        CCurrencyValueMap n = nAll.IntersectingValues(targetValues);            // get only those reserve currencies that are also in target
-        CCurrencyValueMap nTotal = n;                                           // nTotal will be all currencies, including native, that are also in target
+        CCurrencyValueMap nTotal = nAll.IntersectingValues(targetValues); // nTotal will be all currencies, including native, that are also in target
         CAmount nativeN = pcoin->vout[i].nValue;
         if (nativeN)
         {
@@ -5579,13 +5584,11 @@ bool CWallet::SelectReserveCoinsMinConf(const CCurrencyValueMap& targetValues,
 
         //printf("nTotal: %s\n", nTotal.ToUniValue().write().c_str());
 
-        vOutputsToOptimize.push_back(std::make_pair(nAll, std::make_pair(output.tx, output.i)));
-
         CReserveOutSelectionInfo coin(pcoin, i, nAll);
 
         // if all values are equivalent to targets, we've found the perfect output, no more searching needed
         // TODO: should we early out, even if we have extra currencies? If so, use nTotal to commpare
-        if (nAll == nTotalTarget)
+        if (nTotal == nTotalTarget)
         {
             setCoinsRet.insert(std::make_pair(coin.pWtx, coin.n));
             valueRet = pcoin->vout[i].scriptPubKey.ReserveOutValue();
@@ -5593,86 +5596,111 @@ bool CWallet::SelectReserveCoinsMinConf(const CCurrencyValueMap& targetValues,
             return true;
         }
 
+        CCurrencyValueMap subtractedFromTarget(nTotalTarget.SubtractToZero(nTotal));
+
         // now, we need to loop through all targets to see if this satisfies any single currency requirement completely
         // if so, we will include it in the largest lower list for that currency
         int numLarger = 0;
         std::vector<uint160> multiCurrencies;
-        for (auto &oneCur : nTotal.valueMap)
+
+        COutput sanitizedOutput(output.tx, output.i, 0, true);
+
+        // if we have some entries larger than target
+        if (subtractedFromTarget.valueMap.size() < nTotalTarget.valueMap.size())
         {
-            // see if we are larger than or equal to at least one required currency,
-            // we can get duplicates in multiple currency lists this way, and we will reconcile that later
-            if (oneCur.second >= nTotalTarget.valueMap[oneCur.first])
+            //printf("subtractedFromTarget:\n%s\nnTotalTarget:\n%s\nnTotal.NonIntersectingValues(subtractedFromTarget):\n%s\n", subtractedFromTarget.ToUniValue().write().c_str(), nTotal.ToUniValue().write().c_str(), nTotalTarget.NonIntersectingValues(subtractedFromTarget).ToUniValue().write().c_str());
+            for (auto oneCur : nTotal.NonIntersectingValues(subtractedFromTarget).valueMap)
             {
-                if (coinsLowestLarger.count(oneCur.first))
-                {
-                    coinsLowestLarger[oneCur.first].emplace(std::make_pair(oneCur.second, coin));
-                }
-                else
-                {
-                    coinsLowestLarger.emplace(std::make_pair(oneCur.first, 
-                                                             std::multimap<CAmount, CReserveOutSelectionInfo>({{oneCur.second, coin}})));
-                }
-                numLarger++;
+                coinsLowestLarger[oneCur.first].insert(std::make_pair(oneCur.second, CReserveOutSelectionInfo(output.tx, output.i, nAll)));
                 multiCurrencies.push_back(oneCur.first);
+                numLarger++;
             }
         }
-
-        // if any currencies here are larger than or equal to what is needed for one currency
         if (numLarger)
         {
-            largerOuts.insert(&output);
+            largerOuts.insert(std::make_pair(std::make_pair(output.tx, output.i), nAll));
             largerTotal += nTotal;
             multiSatisfy.insert(std::make_pair(numLarger, std::make_pair(multiCurrencies, coin)));
         }
         else
         {
+            bool neededCurrency = false;
             for (auto &oneCur : nTotal.valueMap)
             {
-                if (coinsLargestLower.count(oneCur.first))
-                {
-                    coinsLargestLower[oneCur.first].emplace(std::make_pair(oneCur.second, coin));
-                }
-                else
+                if (satisfied_x4.count(oneCur.first))
                 {
-                    coinsLargestLower.emplace(std::make_pair(oneCur.first, 
-                                                             std::multimap<CAmount, CReserveOutSelectionInfo>({{oneCur.second, coin}})));
+                    continue;
                 }
+                neededCurrency = true;
+                coinsLargestLower[oneCur.first].insert(std::make_pair(oneCur.second, coin));
+            }
+            if (!neededCurrency)
+            {
+                continue;
             }
-            lowerOuts.insert(&output);
+
+            lowerOuts.insert(std::make_pair(std::make_pair(output.tx, output.i), nAll));
             lowerTotal += nTotal;
-            if (lowerTotal > ((nTotalTarget * 4) + nativeCent))
+
+            CCurrencyValueMap adjTargetx4 = targetx4.SubtractToZero(lowerTotal);
+            //printf("targetx4:\n%s\nadjTargetx4:\n%s\n", targetx4.ToUniValue().write().c_str(), adjTargetx4.ToUniValue().write().c_str());
+
+            // loop through all those that have been zeroed in the adjusted target, and mark as satisfied
+            for (auto &oneCur : targetx4.NonIntersectingValues(adjTargetx4).valueMap)
+            {
+                //printf("satisfied x 4: %s\n", EncodeDestination(CIdentityID(oneCur.first)).c_str());
+                satisfied_x4.insert(oneCur.first);
+            }
+
+            if (satisfied_x4.size() == nTotalTarget.valueMap.size())
             {
-                //fprintf(stderr,"why bother with other utxos if we have much more than what is needed?\n");
+                //printf("short circuit lower: lowerTotal:\n%s\nTotalTarget:\n%s\n", lowerTotal.ToUniValue().write().c_str(), nTotalTarget.ToUniValue().write().c_str());
                 break;
             }
         }
     }
 
+    std::set<uint160> satisfied_larger;
+
+    CCurrencyValueMap newLargerTotal;
+    CCurrencyValueMap adjTotalTarget;
+    std::map<std::pair<const CWalletTx *, int>, CCurrencyValueMap> largerCoins; // int is the index into the vOutputsToOptimize to remove
+
     // if our lower total + larger total are not enough, no way we have enough
     if ((lowerTotal + largerTotal) < nTotalTarget)
     {
         return false;
     }
 
-    //printf("lowerTotal:\n%s\nlargerTotal:\n%s\n", lowerTotal.ToUniValue().write().c_str(), largerTotal.ToUniValue().write().c_str());
+    //printf("\nlowerTotal:\n%s\nlargerTotal:\n%s\nnewLargerTotal:\n%s\nTotalTarget:\n%s\n", lowerTotal.ToUniValue().write().c_str(), largerTotal.ToUniValue().write().c_str(), newLargerTotal.ToUniValue().write().c_str(), nTotalTarget.ToUniValue().write().c_str());
+
+    for (auto &lowerOut : lowerOuts)
+    {
+        totalToOptimize += lowerOut.second;
+        vOutputsToOptimize.push_back(std::make_pair(lowerOut.second, std::make_pair(lowerOut.first.first, lowerOut.first.second)));
+    }
 
     // if all the lower amounts are just what we need, and we don't add too many inputs in the process, use them all
     size_t numInputsLimit = (size_t)GetArg("-mempooltxinputlimit", MAX_NUM_INPUTS_LIMIT);
 
     if ((lowerTotal >= nTotalTarget && lowerTotal <= (nTotalTarget + nativeCent)) && lowerOuts.size() <= numInputsLimit)
     {
+        //printf("selecting all lowers\nlowerTotal:\n%s\nTotalTarget:\n%s\n", lowerTotal.ToUniValue().write().c_str(), nTotalTarget.ToUniValue().write().c_str());
+
         for (auto oneOut : lowerOuts)
         {
-            setCoinsRet.insert(std::make_pair(oneOut->tx, oneOut->i));
-            valueRet += oneOut->tx->vout[oneOut->i].ReserveOutValue();
-            nativeValueRet += oneOut->tx->vout[oneOut->i].nValue;
+            setCoinsRet.insert(std::make_pair(oneOut.first.first, oneOut.first.second));
+            valueRet += oneOut.first.first->vout[oneOut.first.second].ReserveOutValue();
+            nativeValueRet += oneOut.first.first->vout[oneOut.first.second].nValue;
         }
         return true;
     }
 
+    //printf("\nlowerTotal:\n%s\nlargerTotal:\n%s\nTotalTarget:\n%s\n", lowerTotal.ToUniValue().write().c_str(), largerTotal.ToUniValue().write().c_str(), nTotalTarget.ToUniValue().write().c_str());
+
     std::map<std::pair<const CWalletTx *, int>, CReserveOutSelectionInfo> added;
-    CCurrencyValueMap totalAdded;
-    CCurrencyValueMap adjustedTarget = nTotalTarget;
+    largerTotal.valueMap.clear();
+    CCurrencyValueMap adjustedTarget;
     std::set<uint160> satisfied;
 
     // short circuit best fit check with any exact amounts we may have
@@ -5685,8 +5713,9 @@ bool CWallet::SelectReserveCoinsMinConf(const CCurrencyValueMap& targetValues,
         for (auto multiIt = multiSatisfy.rbegin(); multiIt != multiSatisfy.rend(); multiIt++)
         {
             // if we have 0 left, we're done
-            if ((nTotalTarget.valueMap.size() - satisfied.size()) == 0)
+            if (nTotalTarget.valueMap.size() == satisfied.size())
             {
+                //printf("satisfied all currencies. lowerTotal:\n%s\n", largerTotal.ToUniValue().write().c_str());
                 break;
             }
 
@@ -5697,28 +5726,39 @@ bool CWallet::SelectReserveCoinsMinConf(const CCurrencyValueMap& targetValues,
                 if (!satisfied.count(oneCurID) &&
                     multiIt->second.second.outVal.valueMap[oneCurID] == adjustedTarget.valueMap[oneCurID])
                 {
-                    satisfied.insert(oneCurID);
                     newFound++;
                 }
             }
 
+            std::pair<const CWalletTx *, unsigned int> outPair({multiIt->second.second.pWtx, multiIt->second.second.n});
+
             // if we don't satisfy any new currency with this output, don't add it as we care more if singles are lower as a priotity
-            if (!newFound)
+            if (!newFound || added.count(outPair))
             {
                 continue;
             }
 
             // this satisfies at least 1 new currency, so use it and also reduce other currencies by all amounts that it includes
             // don't check it again when looking later
-            added.insert(std::make_pair(std::make_pair(multiIt->second.second.pWtx, multiIt->second.second.n), multiIt->second.second));
+            added.insert(std::make_pair(outPair, multiIt->second.second));
 
             // add all currency values in the transaction, as some may partially satisfy, and we should early out when we have enough
-            totalAdded += multiIt->second.second.outVal.IntersectingValues(nTotalTarget);
-            
-            adjustedTarget.SubtractToZero(totalAdded);
+            // printf("multiIt->second.second.outVal:\n%s\n", multiIt->second.second.outVal.ToUniValue().write().c_str());
+            CCurrencyValueMap newAdded(multiIt->second.second.outVal.IntersectingValues(nTotalTarget));
+            largerTotal += newAdded;
+            largerOuts.erase(outPair);
+
+            // printf("adjustedTarget:\n%s\ntotalAdded:\n%s\n", adjustedTarget.ToUniValue().write().c_str(), totalAdded.ToUniValue().write().c_str());
+
+            // printf("adjustedTarget:\n%s\n", adjustedTarget.ToUniValue().write().c_str());
+            // printf("nTotalTarget.NonIntersectingValues(adjustedTarget):\n%s\n", nTotalTarget.NonIntersectingValues(adjustedTarget).ToUniValue().write().c_str());
+
+            adjustedTarget = nTotalTarget.SubtractToZero(largerTotal);
+
             // loop through all those that have been zeroed in the adjusted target, and mark as satisfied
             for (auto &oneCur : nTotalTarget.NonIntersectingValues(adjustedTarget).valueMap)
             {
+                //printf("satisfied: %s\n", EncodeDestination(CIdentityID(oneCur.first)).c_str());
                 satisfied.insert(oneCur.first);
             }
         }
@@ -5741,15 +5781,91 @@ bool CWallet::SelectReserveCoinsMinConf(const CCurrencyValueMap& targetValues,
         return true;
     }
 
+    // fill up lower outputs with larger as well to ensure fill
+    // those we add from multisatisfy check will be removed from optimized selection
+    for (auto &oneCurID : satisfied)
+    {
+        satisfied_x4.insert(oneCurID);
+    }
+    for (auto &largerOut : largerOuts)
+    {
+        COutput thisOutput(largerOut.first.first, largerOut.first.second, 0, true);
+        if (lowerOuts.count(std::make_pair(largerOut.first.first, largerOut.first.second)))
+        {
+            continue;
+        }
+        // if we have more, they only go into the lower, if they have
+        // coins in the currencies where we are not satisfied
+
+        //printf("targetx4:\n%s\nlowerTotal:\n%s\nlargerOut.second:\n%s\n", targetx4.ToUniValue().write().c_str(), lowerTotal.ToUniValue().write().c_str(), largerOut.second.ToUniValue().write().c_str());
+
+        bool useThis = false;
+        for (auto &oneCur : largerOut.second.IntersectingValues(nTotalTarget).valueMap)
+        {
+            if (!satisfied.count(oneCur.first) && !satisfied_x4.count(oneCur.first))
+            {
+                useThis = true;
+            }
+        }
+
+        if (useThis)
+        {
+            CReserveOutSelectionInfo coin(largerOut.first.first, largerOut.first.second, largerOut.second);
+
+            for (auto &oneCur : largerOut.second.valueMap)
+            {
+                coinsLargestLower[oneCur.first].insert(std::make_pair(oneCur.second, coin));
+            }
+
+            lowerOuts.insert(std::make_pair(std::make_pair(largerOut.first.first, largerOut.first.second), largerOut.second));
+
+            lowerTotal += largerOut.second;
+
+            CCurrencyValueMap adjTargetx4 = targetx4.SubtractToZero(lowerTotal);
+            //printf("targetx4:\n%s\nadjTargetx4:\n%s\n", targetx4.ToUniValue().write().c_str(), adjTargetx4.ToUniValue().write().c_str());
+
+            // loop through all those that have been zeroed in the adjusted target, and mark as satisfied
+            for (auto &oneCur : targetx4.NonIntersectingValues(adjTargetx4).valueMap)
+            {
+                // don't consider it satisfied x4, unless we have at least 4 entries to choose from
+                if (coinsLargestLower.count(oneCur.first) && coinsLargestLower[oneCur.first].size() >= 4)
+                {
+                    //printf("satisfied x 4: %s\n", EncodeDestination(CIdentityID(oneCur.first)).c_str());
+                    satisfied_x4.insert(oneCur.first);
+                }
+            }
+            totalToOptimize += largerOut.second;
+            vOutputsToOptimize.push_back(std::make_pair(largerOut.second, std::make_pair(largerOut.first.first, largerOut.first.second)));
+        }
+    }
+
+    //printf("\nlargerTotal:\n%s\n", largerTotal.ToUniValue().write().c_str());
+    // printf("adjustedTarget:\n%s\n", adjustedTarget.ToUniValue().write().c_str());
+
     // make new vector without those we have added due to exact fit, and use remaining and adjusted target to satisfy requests
     std::vector<int> vOutputsToRemove;
     CCurrencyValueMap removedValue;
-    for (int i = 0; i < vOutputsToOptimize.size(); i++)
+    if (added.size())
     {
-        if (added.count(vOutputsToOptimize[i].second))
+        for (int i = 0; i < vOutputsToOptimize.size(); i++)
+        {
+            if (added.count(vOutputsToOptimize[i].second))
+            {
+                vOutputsToRemove.push_back(i);
+                removedValue += vOutputsToOptimize[i].first;
+            }
+        }
+
+        for (auto &oneOutput : added)
         {
-            vOutputsToRemove.push_back(i);
-            removedValue += vOutputsToOptimize[i].first;
+            setCoinsRet.insert(std::make_pair(oneOutput.second.pWtx, oneOutput.second.n));
+            valueRet += oneOutput.second.outVal;
+        }
+        auto vRetIt = valueRet.valueMap.find(ASSETCHAINS_CHAINID);
+        if (vRetIt != valueRet.valueMap.end())
+        {
+            nativeValueRet = vRetIt->second;
+            valueRet.valueMap.erase(vRetIt);
         }
     }
 
@@ -5759,11 +5875,11 @@ bool CWallet::SelectReserveCoinsMinConf(const CCurrencyValueMap& targetValues,
         vOutputsToOptimize.erase(vOutputsToOptimize.begin() + vOutputsToRemove[i]);
     }
 
-    totalToOptimize = largerTotal.SubtractToZero(removedValue) + lowerTotal;
-    CCurrencyValueMap newOptimizationTarget = nTotalTarget.SubtractToZero(removedValue);
+    totalToOptimize = totalToOptimize.SubtractToZero(removedValue);
+    CCurrencyValueMap newOptimizationTarget = nTotalTarget.SubtractToZero(largerTotal);
 
-    /printf("totalToOptimize:\n%s\nnewOptimizationTarget:\n%s\n", totalToOptimize.ToUniValue().write().c_str(), newOptimizationTarget.ToUniValue().write().c_str());
-    for (int i = 0; i < vOutputsToOptimize.size(); i++)
+    //printf("totalToOptimize:\n%s\nnewOptimizationTarget:\n%s\n", totalToOptimize.ToUniValue().write().c_str(), newOptimizationTarget.ToUniValue().write().c_str());
+    /* for (int i = 0; i < vOutputsToOptimize.size(); i++)
     {
         printf("output #%d:\nreserves:\n%s\nnative:\n%s\n", 
             i, 
@@ -5775,8 +5891,9 @@ bool CWallet::SelectReserveCoinsMinConf(const CCurrencyValueMap& targetValues,
     CCurrencyValueMap bestTotals;
 
     ApproximateBestReserveSubset(vOutputsToOptimize, totalToOptimize, newOptimizationTarget, vfBest, bestTotals, 1000);
-    if (bestTotals != targetValues && totalToOptimize >= targetValues + nativeCent)
+    if (bestTotals != newOptimizationTarget && totalToOptimize >= newOptimizationTarget + nativeCent)
     {
+        //printf("bestTotals:\n%s\ntotalToOptimize:\n%s\nnewOptimizationTarget:\n%s\n", bestTotals.ToUniValue().write().c_str(), totalToOptimize.ToUniValue().write().c_str(), (newOptimizationTarget + nativeCent).ToUniValue().write().c_str());
         ApproximateBestReserveSubset(vOutputsToOptimize, totalToOptimize, newOptimizationTarget + nativeCent, vfBest, bestTotals, 1000);
     }
 
@@ -5787,14 +5904,16 @@ bool CWallet::SelectReserveCoinsMinConf(const CCurrencyValueMap& targetValues,
             setCoinsRet.insert(vOutputsToOptimize[i].second);
             valueRet += vOutputsToOptimize[i].second.first->vout[vOutputsToOptimize[i].second.second].ReserveOutValue();
             nativeValueRet += vOutputsToOptimize[i].second.first->vout[vOutputsToOptimize[i].second.second].nValue;
+            /* printf("one selected\ntxid: %s, output: %d\nvalueOut: %s\n", 
+                    vOutputsToOptimize[i].second.first->GetHash().GetHex().c_str(), 
+                    vOutputsToOptimize[i].second.second, 
+                    vOutputsToOptimize[i].first.ToUniValue().write(1,2).c_str()); */
         }
     }
 
     CCurrencyValueMap checkReturn(valueRet);
     checkReturn.valueMap[ASSETCHAINS_CHAINID] = nativeValueRet;
 
-    //printf("valueRet:\n%s\n", checkReturn.ToUniValue().write().c_str());
-
     if (checkReturn.IntersectingValues(nTotalTarget) < nTotalTarget)
     {
         return false;
This page took 0.068986 seconds and 4 git commands to generate.