From: Michael Toutonghi <23489320+miketout@users.noreply.github.com> Date: Sun, 4 Aug 2019 00:47:51 +0000 (-0700) Subject: Create import transactions with all outputs X-Git-Url: https://repo.jachan.dev/VerusCoin.git/commitdiff_plain/c3250dcd49e21206b2a50af4406561f577ea54d6?hp=88bc6df5a8d2aa0ddcce5d8c3b15405a15d9b650 Create import transactions with all outputs --- diff --git a/src/miner.cpp b/src/miner.cpp index 923edf14a..b39f8e578 100644 --- a/src/miner.cpp +++ b/src/miner.cpp @@ -248,26 +248,26 @@ CBlockTemplate* CreateNewBlock(const CScript& _scriptPubKeyIn, int32_t gpucount, std::vector> minerOutputs({make_pair((int)1, scriptPubKeyIn)}); // TODO: when we accept a parameter of the minerOutputs vector, remove this comment but not the check - int64_t shareCheck = 0; - for (auto output : minerOutputs) - { - shareCheck += output.first; - } - if (shareCheck < 0 || shareCheck > INT_MAX) + CTxDestination firstDestination; + if (!ConnectedChains.SetLatestMiningOutputs(minerOutputs, firstDestination)) { - fprintf(stderr,"Invalid miner outputs share specifications\n"); + fprintf(stderr,"%s: Must have valid miner outputs, including script with valid PK or PKH destination.\n"); return NULL; } - CPubKey pk = CPubKey(); - std::vector> vAddrs; - txnouttype txT; - if (minerOutputs.size() && Solver(minerOutputs[0].second, txT, vAddrs)) + int64_t shareCheck = 0; + for (auto output : minerOutputs) { - if (txT == TX_PUBKEY) - pk = CPubKey(vAddrs[0]); + shareCheck += output.first; + if (shareCheck < 0 || shareCheck > INT_MAX) + { + fprintf(stderr,"Invalid miner outputs share specifications\n"); + return NULL; + } } + CPubKey pk = boost::apply_visitor(GetPubKeyForPubKey(), firstDestination); + uint64_t deposits; int32_t isrealtime,kmdheight; uint32_t blocktime; const CChainParams& chainparams = Params(); //fprintf(stderr,"create new block\n"); // Create new block @@ -460,190 +460,7 @@ CBlockTemplate* CreateNewBlock(const CScript& _scriptPubKeyIn, int32_t gpucount, } } - // all chains aggregate reserve transfer transactions, so aggregate and add all necessary export transactions to the mem pool - { - multimap> transferOutputs; - - CKeyID exportKeyID(CCrossChainRPCData::GetConditionID(ConnectedChains.ThisChain().GetChainID(), EVAL_CROSSCHAIN_EXPORT)); - - // get all available transfer outputs to aggregate into export transactions - if (GetUnspentChainTransfers(transferOutputs)) - { - std::vector> txInputs; - std::multimap> exportOutputs; - - // we need unspent export outputs to export - if (GetUnspentChainExports(exportOutputs)) - { - uint160 lastChain; - - // merge all of the common chainID outputs into common export transactions if either MIN_BLOCKS blocks have passed since the last - // export of that type, or there are MIN_INPUTS or more outputs to aggregate - for (auto it = transferOutputs.begin(); it != transferOutputs.end(); it++) - { - // get chain target and see if it is the same - if (lastChain.IsNull() || it->first == lastChain) - { - txInputs.push_back(it->second); - lastChain = it->first; - } - else - { - auto recentExportIt = exportOutputs.find(lastChain); - - if (recentExportIt != exportOutputs.end() && - ((nHeight - recentExportIt->second.first) >= CCrossChainExport::MIN_BLOCKS) || - (txInputs.size() >= CCrossChainExport::MIN_INPUTS)) - { - // make one or more transactions that spends the last export and all possible cross chain transfers - while (txInputs.size()) - { - TransactionBuilder tb(consensusParams, nHeight); - boost::optional oneExport; - - int numInputs = (txInputs.size() < CCrossChainExport::MAX_EXPORT_INPUTS) ? txInputs.size() : CCrossChainExport::MAX_EXPORT_INPUTS; - - int inputsLeft = txInputs.size() - numInputs; - - if (inputsLeft > 0 && inputsLeft < CCrossChainExport::MIN_INPUTS) - { - inputsLeft += CCrossChainExport::MIN_INPUTS - inputsLeft; - numInputs -= CCrossChainExport::MIN_INPUTS - inputsLeft; - assert(numInputs > 0); - } - - // each time through, we make one export transaction with the remainder or a subset of the - // reserve transfer inputs. inputs can be: - // 1. transfers of reserve for fractional reserve chains - // 2. pre-conversions for pre-launch participation in the premine - // 3. reserve market conversions to send between Verus and a fractional reserve chain and always output the native coin - // - // If we are on the Verus chain, all inputs will include native coins. On a PBaaS chain, inputs can either be native - // or reserve token inputs. - // - // On the Verus chain, total native amount, minus the fee, must be sent to the reserve address of the specific chain - // as reserve deposit with native coin equivalent. Pre-conversions and conversions will be realized on the PBaaS chain - // as part of the import process - // - // If we are on the PBaaS chain, conversions must happen before coins are sent this way back to the reserve chain. - // Verus reserve outputs can be directly aggregated and transferred, with fees paid through conversion and the - // remaining Verus reserve coin considered burned. - // - CAmount totalTxFees = 0; - CAmount totalAmount = 0; - std::vector chainObjects; - - // first, we must add the export output from the current export thread to this chain - if (oneExport.has_value()) - { - // spend the last export transaction output - CTransaction &tx = oneExport.get(); - COptCCParams p; - int j; - for (j = 0; j < tx.vout.size(); j++) - { - if (::IsPayToCryptoCondition(tx.vout[j].scriptPubKey, p) && p.evalCode == EVAL_CROSSCHAIN_EXPORT) - { - break; - } - } - - // had to be found and valid if we made the tx - assert(j < tx.vout.size() && p.IsValid()); - - tb.AddTransparentInput(COutPoint(tx.GetHash(), j), tx.vout[j].scriptPubKey, tx.vout[j].nValue); - } - else - { - // spend the recentExportIt output - tb.AddTransparentInput(recentExportIt->second.second.txIn.prevout, - recentExportIt->second.second.scriptPubKey, - recentExportIt->second.second.nValue); - } - - for (int j = 0; j < numInputs; j++) - { - tb.AddTransparentInput(txInputs[j].first.txIn.prevout, txInputs[j].first.scriptPubKey, txInputs[j].first.nValue, txInputs[j].first.txIn.nSequence); - totalTxFees += txInputs[j].second.nFees; - totalAmount += txInputs[j].second.nValue; - chainObjects.push_back(new CChainObject(ObjTypeCode(txInputs[j].second), txInputs[j].second)); - } - - CScript opRet = StoreOpRetArray(chainObjects); - DeleteOpRetObjects(chainObjects); - - CCcontract_info CC; - CCcontract_info *cp; - cp = CCinit(&CC, EVAL_CROSSCHAIN_EXPORT); - - CPubKey pk = CPubKey(ParseHex(CC.CChexstr)); - - // send zero to a cross chain export output of the specific chain - std::vector dests = std::vector({CKeyID(CCrossChainRPCData::GetConditionID(lastChain, EVAL_CROSSCHAIN_EXPORT))}); - - CCrossChainExport ccx(lastChain, numInputs, totalAmount, totalTxFees); - CAmount exportFees = ccx.CalculateExportFee(); - - CTxOut exportOut = MakeCC1of1Vout(EVAL_CROSSCHAIN_EXPORT, - 0, - pk, - dests, - ccx); - - tb.AddTransparentOutput(exportOut.scriptPubKey, 0); - - // if we are on Verus chain, send all native funds, less fees to reserve deposit CC, which is equivalent to the reserve account - // on a PBaaS reserve chain, input is burned - if (isVerusActive) - { - cp = CCinit(&CC, EVAL_RESERVE_DEPOSIT); - pk = CPubKey(ParseHex(CC.CChexstr)); - - // send the entire amount, less fees taken on this chain only, to a reserve transfer output of the specific chain - dests = std::vector({CKeyID(CCrossChainRPCData::GetConditionID(lastChain, EVAL_RESERVE_DEPOSIT))}); - - CReserveOutput ro(CReserveOutput::VALID, totalAmount - ccx.CalculateExportFee()); - - CTxOut outToReserve = MakeCC1of1Vout(EVAL_RESERVE_DEPOSIT, - ro.nValue, - pk, - dests, - ro); - - tb.AddTransparentOutput(outToReserve.scriptPubKey, ro.nValue); - } - - tb.AddOpRet(opRet); - - boost::optional newExport = tb.Build(); - - if (newExport.has_value()) - { - // replace the last one only if we have a valid new one - oneExport = newExport; - CTransaction &tx = oneExport.get(); - - LOCK2(cs_main, mempool.cs); - - // don't remove conflicts for now - //std::list removed; - //mempool.removeConflicts(tx, removed); - - // add to mem pool and relay - if (myAddtomempool(tx)) - { - RelayTransaction(tx); - } - } - // erase the inputs we've attempted to spend - txInputs.erase(txInputs.begin(), txInputs.begin() + numInputs); - } - } - } - } - } - } - } + ConnectedChains.AggregateChainTransfers(firstDestination, nHeight); // // Now start solving the block @@ -776,7 +593,7 @@ CBlockTemplate* CreateNewBlock(const CScript& _scriptPubKeyIn, int32_t gpucount, CCcontract_info CC; CCcontract_info *cp; vector vKeys; - CPubKey pk; + CPubKey pkCC; // Create coinbase tx and set up the null input with height CMutableTransaction coinbaseTx = CreateNewContextualCMutableTransaction(consensusParams, nHeight); @@ -885,10 +702,10 @@ CBlockTemplate* CreateNewBlock(const CScript& _scriptPubKeyIn, int32_t gpucount, cp = CCinit(&CC, EVAL_PBAASDEFINITION); // send this to EVAL_PBAASDEFINITION address as a destination, locked by the default pubkey - pk = CPubKey(ParseHex(CC.CChexstr)); + pkCC = CPubKey(ParseHex(CC.CChexstr)); vKeys.push_back(CKeyID(CCrossChainRPCData::GetConditionID(thisChainID, EVAL_PBAASDEFINITION))); thisChain.preconverted = currencyState.ReserveIn; // update known, preconverted amount - chainDefinitionOut = MakeCC1of1Vout(EVAL_PBAASDEFINITION, 0, pk, vKeys, thisChain); + chainDefinitionOut = MakeCC1of1Vout(EVAL_PBAASDEFINITION, 0, pkCC, vKeys, thisChain); coinbaseTx.vout.push_back(chainDefinitionOut); // import - only spendable for reserve currency or currency with preconversion to allow import of conversions, this output will include @@ -898,22 +715,25 @@ CBlockTemplate* CreateNewBlock(const CScript& _scriptPubKeyIn, int32_t gpucount, vKeys.clear(); cp = CCinit(&CC, EVAL_CROSSCHAIN_IMPORT); - pk = CPubKey(ParseHex(CC.CChexstr)); - vKeys.push_back(CKeyID(CCrossChainRPCData::GetConditionID(thisChainID, EVAL_CROSSCHAIN_IMPORT))); + pkCC = CPubKey(ParseHex(CC.CChexstr)); + + // import thread is specific to the chain importing from + vKeys.push_back(CKeyID(CCrossChainRPCData::GetConditionID(ConnectedChains.notaryChain.GetChainID(), EVAL_CROSSCHAIN_IMPORT))); importThreadOut = MakeCC1of1Vout(EVAL_CROSSCHAIN_IMPORT, - currencyState.ReserveToNative(thisChain.preconverted, thisChain.conversion), pk, vKeys, + currencyState.ReserveToNative(thisChain.preconverted, thisChain.conversion), pkCC, vKeys, CCrossChainImport(ConnectedChains.NotaryChain().GetChainID(), 0)); + coinbaseTx.vout.push_back(importThreadOut); // export - currently only spendable for reserve currency, but added for future capabilities vKeys.clear(); cp = CCinit(&CC, EVAL_CROSSCHAIN_EXPORT); - pk = CPubKey(ParseHex(CC.CChexstr)); + pkCC = CPubKey(ParseHex(CC.CChexstr)); vKeys.push_back(CKeyID(CCrossChainRPCData::GetConditionID(thisChainID, EVAL_CROSSCHAIN_EXPORT))); - exportThreadOut = MakeCC1of1Vout(EVAL_CROSSCHAIN_EXPORT, 0, pk, vKeys, + exportThreadOut = MakeCC1of1Vout(EVAL_CROSSCHAIN_EXPORT, 0, pkCC, vKeys, CCrossChainExport(ConnectedChains.NotaryChain().GetChainID(), 0, 0, 0)); coinbaseTx.vout.push_back(exportThreadOut); } @@ -1015,7 +835,7 @@ CBlockTemplate* CreateNewBlock(const CScript& _scriptPubKeyIn, int32_t gpucount, cp = CCinit(&CC, EVAL_EARNEDNOTARIZATION); // send this to EVAL_EARNEDNOTARIZATION address as a destination, locked by the default pubkey - CPubKey pk(ParseHex(cp->CChexstr)); + pkCC = CPubKey(ParseHex(cp->CChexstr)); vKeys.push_back(CTxDestination(CKeyID(CCrossChainRPCData::GetConditionID(VERUS_CHAINID, EVAL_EARNEDNOTARIZATION)))); int64_t needed = nHeight == 1 ? PBAAS_MINNOTARIZATIONOUTPUT << 1 : PBAAS_MINNOTARIZATIONOUTPUT; @@ -1023,10 +843,14 @@ CBlockTemplate* CreateNewBlock(const CScript& _scriptPubKeyIn, int32_t gpucount, // output duplicate notarization as coinbase output for instant spend to notarization // the output amount is considered part of the total value of this coinbase CPBaaSNotarization pbn(newNotarizationTx); - notarizationOut = MakeCC1of1Vout(EVAL_EARNEDNOTARIZATION, needed, pk, vKeys, pbn); + notarizationOut = MakeCC1of1Vout(EVAL_EARNEDNOTARIZATION, needed, pkCC, vKeys, pbn); coinbaseTx.vout.push_back(notarizationOut); - // TODO: if we have confirmed our notary chain, we need to create and send any newly confirmed + // we need to create and send any newly confirmed + if (confirmedInput != -1) + { + + } // exports as import transactions to the notary chain - may want to set a flag and do that on submissionthread // } diff --git a/src/pbaas/pbaas.cpp b/src/pbaas/pbaas.cpp index 39276f296..46150dd89 100644 --- a/src/pbaas/pbaas.cpp +++ b/src/pbaas/pbaas.cpp @@ -17,6 +17,7 @@ #include "base58.h" #include "timedata.h" #include "main.h" +#include "transaction_builder.h" using namespace std; @@ -423,16 +424,15 @@ CServiceReward::CServiceReward(const CTransaction &tx, bool validate) CCrossChainImport::CCrossChainImport(const CTransaction &tx) { - for (auto out : tx.vout) + if (tx.vout.size()) { COptCCParams p; - if (IsPayToCryptoCondition(out.scriptPubKey, p)) + if (IsPayToCryptoCondition(tx.vout[0].scriptPubKey, p)) { // always take the first for now if (p.evalCode == EVAL_CROSSCHAIN_IMPORT) { FromVector(p.vData[0], *this); - break; } } } @@ -462,7 +462,7 @@ uint160 CPBaaSChainDefinition::GetChainID(std::string name) return Hash160(chainHash.begin(), chainHash.end()); } -uint160 CPBaaSChainDefinition::GetConditionID(int32_t condition) +uint160 CPBaaSChainDefinition::GetConditionID(int32_t condition) const { return CCrossChainRPCData::GetConditionID(name, condition); } @@ -1051,6 +1051,249 @@ CCoinbaseCurrencyState CConnectedChains::GetCurrencyState(int32_t height) return currencyState; } +bool CConnectedChains::SetLatestMiningOutputs(const std::vector> minerOutputs, CTxDestination &firstDestinationOut) +{ + LOCK(cs_mergemining); + { + txnouttype outType; + std::vector> vSolutions; + + if (!minerOutputs.size() || !Solver(minerOutputs[0].second, outType, vSolutions)) + { + return false; + } + + if (outType == TX_PUBKEY) + { + CPubKey pubKey(vSolutions[0]); + if (!pubKey.IsValid()) + { + return false; + } + firstDestinationOut = CTxDestination(pubKey); + } + else if (outType == TX_PUBKEYHASH) + { + firstDestinationOut = CTxDestination(CKeyID(uint160(vSolutions[0]))); + } + else + { + return false; + } + } + latestMiningOutputs = minerOutputs; + latestDestination = firstDestinationOut; +} + +void CConnectedChains::AggregateChainTransfers(const CTxDestination &feeOutput, uint32_t nHeight) +{ + // all chains aggregate reserve transfer transactions, so aggregate and add all necessary export transactions to the mem pool + { + if (!nHeight) + { + return; + } + + multimap> transferOutputs; + + CKeyID exportKeyID(CCrossChainRPCData::GetConditionID(ThisChain().GetChainID(), EVAL_CROSSCHAIN_EXPORT)); + + // get all available transfer outputs to aggregate into export transactions + if (GetUnspentChainTransfers(transferOutputs)) + { + std::vector> txInputs; + std::multimap> exportOutputs; + + // we need unspent export outputs to export + if (GetUnspentChainExports(exportOutputs)) + { + uint160 lastChain; + + // merge all of the common chainID outputs into common export transactions if either MIN_BLOCKS blocks have passed since the last + // export of that type, or there are MIN_INPUTS or more outputs to aggregate + for (auto it = transferOutputs.begin(); it != transferOutputs.end(); it++) + { + // get chain target and see if it is the same + if (lastChain.IsNull() || it->first == lastChain) + { + txInputs.push_back(it->second); + lastChain = it->first; + } + else + { + auto recentExportIt = exportOutputs.find(lastChain); + // TODO: we cannot have a duplicate here, check for it + + if (recentExportIt != exportOutputs.end() && + ((nHeight - recentExportIt->second.first) >= CCrossChainExport::MIN_BLOCKS) || + (txInputs.size() >= CCrossChainExport::MIN_INPUTS)) + { + boost::optional oneExport; + + // make one or more transactions that spends the last export and all possible cross chain transfers + while (txInputs.size()) + { + TransactionBuilder tb(Params().GetConsensus(), nHeight); + + int numInputs = (txInputs.size() < CCrossChainExport::MAX_EXPORT_INPUTS) ? txInputs.size() : CCrossChainExport::MAX_EXPORT_INPUTS; + + int inputsLeft = txInputs.size() - numInputs; + + if (inputsLeft > 0 && inputsLeft < CCrossChainExport::MIN_INPUTS) + { + inputsLeft += CCrossChainExport::MIN_INPUTS - inputsLeft; + numInputs -= CCrossChainExport::MIN_INPUTS - inputsLeft; + assert(numInputs > 0); + } + + // each time through, we make one export transaction with the remainder or a subset of the + // reserve transfer inputs. inputs can be: + // 1. transfers of reserve for fractional reserve chains + // 2. pre-conversions for pre-launch participation in the premine + // 3. reserve market conversions to send between Verus and a fractional reserve chain and always output the native coin + // + // If we are on the Verus chain, all inputs will include native coins. On a PBaaS chain, inputs can either be native + // or reserve token inputs. + // + // On the Verus chain, total native amount, minus the fee, must be sent to the reserve address of the specific chain + // as reserve deposit with native coin equivalent. Pre-conversions and conversions will be realized on the PBaaS chain + // as part of the import process + // + // If we are on the PBaaS chain, conversions must happen before coins are sent this way back to the reserve chain. + // Verus reserve outputs can be directly aggregated and transferred, with fees paid through conversion and the + // remaining Verus reserve coin considered burned. + // + CAmount totalTxFees = 0; + CAmount totalAmount = 0; + std::vector chainObjects; + + // first, we must add the export output from the current export thread to this chain + if (oneExport.has_value()) + { + // spend the last export transaction output + CTransaction &tx = oneExport.get(); + COptCCParams p; + int j; + for (j = 0; j < tx.vout.size(); j++) + { + if (::IsPayToCryptoCondition(tx.vout[j].scriptPubKey, p) && p.evalCode == EVAL_CROSSCHAIN_EXPORT) + { + break; + } + } + + // had to be found and valid if we made the tx + assert(j < tx.vout.size() && p.IsValid()); + + tb.AddTransparentInput(COutPoint(tx.GetHash(), j), tx.vout[j].scriptPubKey, tx.vout[j].nValue); + } + else + { + // spend the recentExportIt output + tb.AddTransparentInput(recentExportIt->second.second.txIn.prevout, + recentExportIt->second.second.scriptPubKey, + recentExportIt->second.second.nValue); + } + + for (int j = 0; j < numInputs; j++) + { + tb.AddTransparentInput(txInputs[j].first.txIn.prevout, txInputs[j].first.scriptPubKey, txInputs[j].first.nValue, txInputs[j].first.txIn.nSequence); + totalTxFees += txInputs[j].second.nFees; + totalAmount += txInputs[j].second.nValue; + chainObjects.push_back(new CChainObject(ObjTypeCode(txInputs[j].second), txInputs[j].second)); + } + + CCrossChainExport ccx(lastChain, numInputs, totalAmount, totalTxFees); + CAmount exportFees = ccx.CalculateExportFee(); + CReserveTransfer feeOut = CReserveTransfer(CReserveTransfer::VALID + CReserveTransfer::SEND_BACK + CReserveTransfer::FEE_OUTPUT, + exportFees, 0, GetDestinationID(feeOutput)); + chainObjects.push_back(new CChainObject(ObjTypeCode(feeOut), feeOut)); + + CScript opRet = StoreOpRetArray(chainObjects); + DeleteOpRetObjects(chainObjects); + + CCcontract_info CC; + CCcontract_info *cp; + cp = CCinit(&CC, EVAL_CROSSCHAIN_EXPORT); + + CPubKey pk = CPubKey(ParseHex(CC.CChexstr)); + + // send zero to a cross chain export output of the specific chain + std::vector dests = std::vector({CKeyID(CCrossChainRPCData::GetConditionID(lastChain, EVAL_CROSSCHAIN_EXPORT))}); + + CTxOut exportOut = MakeCC1of1Vout(EVAL_CROSSCHAIN_EXPORT, + 0, + pk, + dests, + ccx); + + tb.AddTransparentOutput(exportOut.scriptPubKey, 0); + + // if we are on Verus chain, send all native funds, less fees to reserve deposit CC, which is equivalent to the reserve account + // on a PBaaS reserve chain, input is burned + if (IsVerusActive()) + { + cp = CCinit(&CC, EVAL_RESERVE_DEPOSIT); + pk = CPubKey(ParseHex(CC.CChexstr)); + + // send the entire amount to a reserve transfer output of the specific chain + // we receive our fee on the other chain or when it comes back + dests = std::vector({CKeyID(CCrossChainRPCData::GetConditionID(lastChain, EVAL_RESERVE_DEPOSIT))}); + + CReserveOutput ro(CReserveOutput::VALID, totalAmount - ccx.CalculateExportFee()); + + CTxOut outToReserve = MakeCC1of1Vout(EVAL_RESERVE_DEPOSIT, + ro.nValue, + pk, + dests, + ro); + + tb.AddTransparentOutput(outToReserve.scriptPubKey, ro.nValue); + } + + tb.AddOpRet(opRet); + + boost::optional newExport = tb.Build(); + + if (newExport.has_value()) + { + // replace the last one only if we have a valid new one + oneExport = newExport; + CTransaction &tx = oneExport.get(); + + LOCK2(cs_main, mempool.cs); + static int lastHeight = 0; + // remove conflicts, so that we are the now + std::list removed; + mempool.removeConflicts(tx, removed); + + // add to mem pool and relay + if (myAddtomempool(tx)) + { + RelayTransaction(tx); + } + } + // erase the inputs we've attempted to spend + txInputs.erase(txInputs.begin(), txInputs.begin() + numInputs); + } + } + } + } + } + } + } +} + +// send new imports from this chain to the specified chain, which generally will be the notary chain +void CConnectedChains::SendNewImports(const uint160 &chainID, + const CPBaaSNotarization ¬arization, + const uint256 &lastExportTx, + const CTransaction &lastCrossImport, + const CTransaction &lastExport) +{ + // currently only support sending imports to +} + void CConnectedChains::SubmissionThread() { try @@ -1060,11 +1303,13 @@ void CConnectedChains::SubmissionThread() // wait for something to check on, then submit blocks that should be submitted while (true) { + boost::this_thread::interruption_point(); + if (IsVerusActive()) { // blocks get discarded after no refresh for 5 minutes by default, probably should be more often //printf("SubmissionThread: pruning\n"); - ConnectedChains.PruneOldChains(GetAdjustedTime() - 300); + PruneOldChains(GetAdjustedTime() - 300); bool submit = false; { LOCK(cs_mergemining); diff --git a/src/pbaas/pbaas.h b/src/pbaas/pbaas.h index 0f77474ae..74f6d8ed1 100644 --- a/src/pbaas/pbaas.h +++ b/src/pbaas/pbaas.h @@ -26,6 +26,8 @@ #include +class CPBaaSNotarization; + // these are output cryptoconditions for the Verus reserve liquidity system // VRSC can be proxied to other PBaaS chains and sent back for use with this system // The assumption that Verus is either the proxy on the PBaaS chain or the native @@ -393,6 +395,7 @@ public: CKeyID address; // non-purchased/converted premine and fee recipient address int64_t premine; // initial supply that is distributed to the premine output address, but not purchased int64_t initialcontribution; // optional initial contribution by this transaction. this serves to ensure the option to participate by whoever defines the chain + int64_t minpreconvert; // zero for now, later can be used for Kickstarter-like launch and return all non-network fees upon failure to meet minimum int64_t maxpreconvert; // maximum amount of reserve that can be pre-converted int64_t preconverted; // actual converted amount if known int64_t conversion; // initial reserve ratio, also value in Verus if premine is 0, otherwise value is conversion * maxpreconvert/(maxpreconvert + premine) @@ -422,7 +425,7 @@ public: CPBaaSChainDefinition(const CTransaction &tx, bool validate = false); - CPBaaSChainDefinition(std::string Name, std::string Address, int64_t Premine, int64_t Conversion, int64_t maxPreConvert, int64_t preConverted, int64_t LaunchFee, + CPBaaSChainDefinition(std::string Name, std::string Address, int64_t Premine, int64_t Conversion, int64_t minPreConvert, int64_t maxPreConvert, int64_t preConverted, int64_t LaunchFee, int32_t StartBlock, int32_t EndBlock, int32_t chainEras, const std::vector &chainRewards, const std::vector &chainRewardsDecay, const std::vector &chainHalving, const std::vector &chainEraEnd, std::vector &chainCurrencyOptions, @@ -431,6 +434,7 @@ public: name(Name), premine(Premine), conversion(Conversion), + minpreconvert(minPreConvert), maxpreconvert(maxPreConvert), preconverted(preConverted), launchFee(LaunchFee), @@ -464,6 +468,7 @@ public: READWRITE(address); READWRITE(VARINT(premine)); READWRITE(VARINT(conversion)); + READWRITE(VARINT(minpreconvert)); READWRITE(VARINT(maxpreconvert)); READWRITE(VARINT(preconverted)); READWRITE(VARINT(launchFee)); @@ -480,26 +485,26 @@ public: READWRITE(nodes); } - std::vector AsVector() + std::vector AsVector() const { return ::AsVector(*this); } static uint160 GetChainID(std::string name); - uint160 GetChainID() + uint160 GetChainID() const { return GetChainID(name); } - uint160 GetConditionID(int32_t condition); + uint160 GetConditionID(int32_t condition) const; - bool IsValid() + bool IsValid() const { return (nVersion != PBAAS_VERSION_INVALID) && (name.size() && eras > 0) && (eras <= ASSETCHAINS_MAX_ERAS); } - int32_t ChainOptions() + int32_t ChainOptions() const { return eraOptions.size() ? eraOptions[0] : 0; } @@ -852,6 +857,9 @@ public: CRPCChainData notaryChain; // notary chain information CPBaaSChainDefinition thisChain; + std::vector> latestMiningOutputs; // accessible from all merge miners - can be invalid + CTxDestination latestDestination; // latest destination from miner output 0 - can be invalid + int64_t lastAggregation = 0; // adjusted time of last aggregation int32_t earnedNotarizationHeight; // zero or the height of one or more potential submissions CBlock earnedNotarizationBlock; @@ -891,6 +899,32 @@ public: uint32_t PruneOldChains(uint32_t pruneBefore); uint32_t CombineBlocks(CBlockHeader &bh); + // returns false if destinations are empty or first is not either pubkey or pubkeyhash + bool SetLatestMiningOutputs(const std::vector> minerOutputs, CTxDestination &firstDestinationOut); + void AggregateChainTransfers(const CTxDestination &feeOutput, uint32_t nHeight); + + // send new imports from this chain to the specified chain, which generally will be the notary chain + void SendNewImports(const uint160 &chainID, + const CPBaaSNotarization ¬arization, + const uint256 &lastExportTx, + const CTransaction &lastCrossImport, + const CTransaction &lastExport); + + bool GetLastImport(const uint160 &chainID, + CTransaction &lastImport, + CTransaction &crossChainExport, + CCrossChainImport &ccImport, + CCrossChainExport &ccCrossExport); + + // returns newly created import transactions to the specified chain from exports on this chain specified chain + void CreateLatestImports(const CPBaaSChainDefinition &chainDef, + const CTransaction &lastCrossChainImport, + const CTransaction &lastExport, + const CTransaction &importTxTemplate, + uint32_t confirmedHeight, + std::vector &newImports); + + CRPCChainData &NotaryChain() { return notaryChain; diff --git a/src/pbaas/reserves.cpp b/src/pbaas/reserves.cpp index ee985dee7..1fdf2f961 100644 --- a/src/pbaas/reserves.cpp +++ b/src/pbaas/reserves.cpp @@ -463,7 +463,7 @@ CMutableTransaction &CReserveTransactionDescriptor::AddConversionOutputs(CMutabl CCurrencyState dummy; CCurrencyState ¤cyState = pCurrencyState ? *pCurrencyState : dummy; - // if no exchange rate is specified, it is unity + // if no exchange rate is specified, first from the currency if present, then it is unity if (!exchangeRate) { int64_t price; @@ -517,28 +517,24 @@ CMutableTransaction &CReserveTransactionDescriptor::AddConversionOutputs(CMutabl conversionTx.vout.push_back(MakeCC1of1Vout(EVAL_RESERVE_TRANSFER, 0, pk, dests, rt)); } - else + else if (indexRex.second.flags & indexRex.second.TO_RESERVE) { - // check reserve or normal output - if (indexRex.second.flags & indexRex.second.TO_RESERVE) - { - // convert amount to reserve from native - amount = currencyState.NativeToReserve(amount, exchangeRate); + // convert amount to reserve from native + amount = currencyState.NativeToReserve(amount, exchangeRate); - // send the net amount to the indicated destination - std::vector dests = std::vector({p.vKeys[0]}); + // send the net amount to the indicated destination + std::vector dests = std::vector({p.vKeys[0]}); - // create the output with the unconverted amount less fees - CReserveTransfer rt(CReserveTransfer::VALID, amount, CReserveTransfer::DEFAULT_PER_STEP_FEE << 1, GetDestinationID(p.vKeys[0])); + // create the output with the unconverted amount less fees + CReserveOutput ro(CReserveOutput::VALID, amount); - conversionTx.vout.push_back(MakeCC0of0Vout(EVAL_RESERVE_TRANSFER, 0, dests, rt)); - } - else - { - // convert amount to native from reserve - amount = currencyState.ReserveToNative(amount, exchangeRate); - conversionTx.vout.push_back(CTxOut(amount, GetScriptForDestination(p.vKeys[0]))); - } + conversionTx.vout.push_back(MakeCC0of0Vout(EVAL_RESERVE_OUTPUT, 0, dests, ro)); + } + else + { + // convert amount to native from reserve and send as normal output + amount = currencyState.ReserveToNative(amount, exchangeRate); + conversionTx.vout.push_back(CTxOut(amount, GetScriptForDestination(p.vKeys[0]))); } } return conversionTx; @@ -897,7 +893,7 @@ CAmount CCurrencyState::ReserveToNative(CAmount reserveAmount) const return bigAmount.GetLow64(); } -CAmount CCurrencyState::ReserveToNative(CAmount reserveAmount, CAmount exchangeRate) const +CAmount CCurrencyState::ReserveToNative(CAmount reserveAmount, CAmount exchangeRate) { arith_uint256 bigAmount(reserveAmount); diff --git a/src/pbaas/reserves.h b/src/pbaas/reserves.h index c12423daf..42f284422 100644 --- a/src/pbaas/reserves.h +++ b/src/pbaas/reserves.h @@ -69,11 +69,13 @@ class CReserveTransfer : public CReserveOutput public: static const uint32_t CONVERT = 2; static const uint32_t PRECONVERT = 4; + static const uint32_t FEE_OUTPUT = 8; // one per import, amount must match total percentage of fees for exporter, no pre-convert allowed + static const uint32_t SEND_BACK = 0x10; // fee is sent back immediately to destination on exporting chain - static const CAmount DEFAULT_PER_STEP_FEE = 10000; // default fee for each step of each transfer (initial mining, transfer, mining on new chain) + static const CAmount DEFAULT_PER_STEP_FEE = 10000; // default fee for each step of each transfer (initial mining, transfer, mining on new chain) - CAmount nFees; // cross-chain network fees only, separated out to enable market conversions, conversion fees are additional - CKeyID destination; // transparent address to send funds to on the target chain + CAmount nFees; // cross-chain network fees only, separated out to enable market conversions, conversion fees are additional + CKeyID destination; // transparent address to send funds to on the target chain CReserveTransfer(const std::vector &asVector) { @@ -368,7 +370,7 @@ public: CAmount ReserveFeeToNative(CAmount inputAmount, CAmount outputAmount) const; CAmount ReserveToNative(CAmount reserveAmount) const; - CAmount ReserveToNative(CAmount reserveAmount, CAmount exchangeRate) const; + static CAmount ReserveToNative(CAmount reserveAmount, CAmount exchangeRate); CAmount NativeToReserve(CAmount nativeAmount) const { diff --git a/src/rpc/pbaasrpc.cpp b/src/rpc/pbaasrpc.cpp index 7ce1a8e1c..3f39f15ba 100644 --- a/src/rpc/pbaasrpc.cpp +++ b/src/rpc/pbaasrpc.cpp @@ -175,6 +175,288 @@ void GetDefinedChains(vector &chains, bool includeExpired } } +bool CConnectedChains::GetLastImport(const uint160 &chainID, + CTransaction &lastImport, + CTransaction &crossChainExport, + CCrossChainImport &ccImport, + CCrossChainExport &ccCrossExport) +{ + CKeyID keyID = CCrossChainRPCData::GetConditionID(chainID, EVAL_CROSSCHAIN_IMPORT); + + std::vector > unspentOutputs; + + LOCK2(cs_main, mempool.cs); + + // get last import from the specified chain + if (!GetAddressUnspent(keyID, 1, unspentOutputs)) + { + return false; + } + + // make sure it isn't just a burned transaction to that address, drop out on first match + const std::pair *pOutput = NULL; + COptCCParams p; + for (const auto &output : unspentOutputs) + { + if (output.second.script.IsPayToCryptoCondition(p) && p.IsValid() && p.evalCode == EVAL_CROSSCHAIN_IMPORT) + { + pOutput = &output; + break; + } + } + if (!pOutput) + { + return false; + } + uint256 hashBlk; + if (!myGetTransaction(pOutput->first.txhash, lastImport, hashBlk) || !(lastImport.vout.size() && lastImport.vout.back().scriptPubKey.IsOpReturn())) + { + return false; + } + ccImport = CCrossChainImport(p.vData[0]); + auto opRetArr = RetrieveOpRetArray(lastImport.vout.back().scriptPubKey); + if (!opRetArr.size() || opRetArr[0]->objectType != CHAINOBJ_TRANSACTION) + { + DeleteOpRetObjects(opRetArr); + } + else + { + crossChainExport = ((CChainObject *)opRetArr[0])->object; + if (!(ccCrossExport = CCrossChainExport(crossChainExport)).IsValid()) + { + return false; + } + } + return true; +} + +// returns newly created import transactions to the specified chain from exports on this chain specified chain +// nHeight is the height for which we have an MMR that the chainID chain considers confirmed for this chain. it will +// accept proofs of any transactions with that MMR and height. +// Parameters shuold be validated before this call. +void CConnectedChains::CreateLatestImports(const CPBaaSChainDefinition &chainDef, + const CTransaction &lastCrossChainImport, + const CTransaction &lastExport, + const CTransaction &importTxTemplate, + uint32_t confirmedHeight, + std::vector &newImports) +{ + uint160 chainID = chainDef.GetChainID(); + + // we are passed the latest import transaction from the chain specified, and + // we continue from there, creating import transactions from all of the confirmed exports after the one passed + uint256 lastExportHash = lastExport.GetHash(); + + // which transaction are we in this block? + std::vector> addressIndex; + std::set countedTxes; // don't count twice + + CCrossChainImport lastCCI(lastCrossChainImport); + if (!lastCCI.IsValid()) + { + LogPrintf("%s: Invalid lastCrossChainImport transaction\n", __func__); + printf("%s: Invalid lastCrossChainImport transaction\n", __func__); + return; + } + + // look for the exports + CKeyID keyID = CCrossChainRPCData::GetConditionID(chainID, EVAL_CROSSCHAIN_EXPORT); + + LOCK2(cs_main, mempool.cs); + CTransaction lastExportTx; + uint256 blkHash; + CBlockIndex *pIndex; + BlockMap::iterator blkMapIt; + + // get all export transactions including and since this one up to the confirmed height + if (myGetTransaction(lastExportHash, lastExportTx, blkHash) && + (blkMapIt = mapBlockIndex.find(blkHash)) != mapBlockIndex.end() && + chainActive.Contains(blkMapIt->second) && + blkMapIt->second->GetHeight() <= confirmedHeight && + GetAddressIndex(keyID, 1, addressIndex, blkMapIt->second->GetHeight(), confirmedHeight)) + { + // find this export, then check the next one that spends it and use it if still valid + bool found = false; + uint256 lastHash = lastExportHash; + + // indexed by input hash + std::map> validExports; + + // validate, order, and relate them with their inputs + for (auto utxo : addressIndex) + { + // if current tx spends lastHash, then we have our next valid transaction to create an import with + CTransaction tx, inputtx; + uint256 blkHash1, blkHash2; + BlockMap::iterator blkIt; + CCrossChainExport ccx; + if (myGetTransaction(utxo.first.txhash, tx, blkHash1) && + (ccx = CCrossChainExport(tx)).IsValid() && + (tx.IsCoinBase() && (blkIt = mapBlockIndex.find(blkHash1)) != mapBlockIndex.end() && blkIt->second->GetHeight() == 1) || + (!tx.IsCoinBase() && tx.vin.size() && myGetTransaction(tx.vin[0].prevout.hash, inputtx, blkHash2))) + { + // either this is the first import as a chain definition from the coinbase of block 1, or it must spend a valid import + if (!tx.IsCoinBase()) + { + COptCCParams p; + // validate the input as a chain export input + if (!(inputtx.vout[tx.vin[0].prevout.n].scriptPubKey.IsPayToCryptoCondition(p) && p.IsValid() && p.evalCode == EVAL_CROSSCHAIN_EXPORT)) + { + printf("%s: invalid export: input tx %s is not in valid export thread\n", __func__, inputtx.GetHash().GetHex().c_str()); + continue; + } + validExports.insert(make_pair(tx.vin[0].prevout.hash, make_pair(utxo.first, tx))); + } + } + else + { + printf("%s: cannot retrieve transaction %s or transaction is an invalid export\n", __func__, utxo.first.txhash.GetHex().c_str()); + continue; + } + } + + CTransaction lastImport(lastCrossChainImport); + for (auto aixIt = validExports.find(lastExportHash); + aixIt != validExports.end(); + aixIt = validExports.find(lastExportHash)) + { + // One pass - create an import transaction that spends the last import transaction from a confirmed export transaction that spends the last one of those + // 1. Creates preconvert outputs that spend from the initial supply, which comes from the import transaction thread without fees + // 2. Creates reserve outputs for unconverted imports + // 3. Creates reserveExchange transactions requesting conversion at market for convert transfers + // 4. Creates reserveTransfer outputs for outputs with the SEND_BACK flag set, unless they are under 5x the normal network fee + // 5. Creates a pass-through EVAL_CROSSCHAIN_IMPORT output with the remainder of the non-preconverted coins + // then signs the transaction considers it the latest import and the new export the latest export and + // loops until there are no more confirmed, consecutive, valid export transactions to export + + // aixIt has an input from the export thread of last transaction, an optional deposit to the reserve, and an opret of all outputs + 1 fee + assert(aixIt->second.second.vout.back().scriptPubKey.IsOpReturn()); + + std::vector exportOutputs = RetrieveOpRetArray(aixIt->second.second.vout.back().scriptPubKey); + + CMutableTransaction newImportTx(importTxTemplate); + newImportTx.vin.clear(); + newImportTx.vin.push_back(CTxIn(lastImport.GetHash(), 0)); // must spend output 0 + newImportTx.vout.clear(); + newImportTx.vout.push_back(CTxOut()); // placeholder for the first output + + // emit a reserve exchange output + // we will send using a reserve output, fee will be paid by converting from reserve + CCcontract_info CC; + CCcontract_info *cp; + + CAmount totalPreconvert = 0; + CAmount totalImport = 0; + + for (auto pRT : exportOutputs) + { + if (pRT->objectType != CHAINOBJ_RESERVETRANSFER) + { + LogPrintf("%s: POSSIBLE CORRUPTION bad export opret in transaction %s\n", __func__, aixIt->second.second.GetHash().GetHex().c_str()); + printf("%s: POSSIBLE CORRUPTION bad export opret in transaction %s\n", __func__, aixIt->second.second.GetHash().GetHex().c_str()); + break; + } + CReserveTransfer &curTransfer = ((CChainObject *)pRT)->object; + if (curTransfer.IsValid()) + { + CTxOut newOut; + + totalImport += curTransfer.nFees + curTransfer.nValue; + + if (curTransfer.flags & curTransfer.CONVERT) + { + // emit a reserve exchange output + // we will send using a reserve output, fee will be paid by converting from reserve + cp = CCinit(&CC, EVAL_RESERVE_EXCHANGE); + + CPubKey pk = CPubKey(ParseHex(CC.CChexstr)); + + std::vector dests = std::vector({CTxDestination(curTransfer.destination)}); + CReserveExchange rex = CReserveExchange(CReserveExchange::VALID, curTransfer.nValue); + + newOut = MakeCC1of1Vout(EVAL_RESERVE_EXCHANGE, 0, pk, dests, rex); + } + else if (curTransfer.flags & curTransfer.PRECONVERT) + { + // calculate the amount and generate a normal output that spends the input of the import + CAmount nativeConverted = CCurrencyState::ReserveToNative(curTransfer.nValue, chainDef.conversion); + totalPreconvert += nativeConverted; + newOut = CTxOut(nativeConverted, GetScriptForDestination(curTransfer.destination)); + } + else if (curTransfer.flags & curTransfer.SEND_BACK && curTransfer.nValue > (curTransfer.DEFAULT_PER_STEP_FEE << 2)) + { + // generate a reserve transfer back to the source chain if we have at least double the fee, otherwise leave it on + // this chain to be claimed + cp = CCinit(&CC, EVAL_RESERVE_TRANSFER); + + std::vector dests = std::vector({CTxDestination(curTransfer.destination)}); + CAmount fees = curTransfer.DEFAULT_PER_STEP_FEE << 1; + CReserveTransfer rt = CReserveTransfer(CReserveExchange::VALID, curTransfer.nValue - fees, fees, curTransfer.destination); + + newOut = MakeCC0of0Vout(EVAL_RESERVE_TRANSFER, 0, dests, rt); + } + else + { + // generate a reserve output of the amount indicated, less fees + // emit a reserve exchange output + // we will send using a reserve output, fee will be paid by converting from reserve + cp = CCinit(&CC, EVAL_RESERVE_OUTPUT); + + std::vector dests = std::vector({CTxDestination(curTransfer.destination)}); + CReserveOutput ro = CReserveOutput(CReserveExchange::VALID, curTransfer.nValue); + + newOut = MakeCC0of0Vout(EVAL_RESERVE_OUTPUT, 0, dests, ro); + } + newImportTx.vout.push_back(newOut); + } + } + + // emit a reserve exchange output + // we will send using a reserve output, fee will be paid by converting from reserve + cp = CCinit(&CC, EVAL_CROSSCHAIN_IMPORT); + + CPubKey pk = CPubKey(ParseHex(CC.CChexstr)); + CKeyID dest(); + + std::vector dests = std::vector({CTxDestination(CKeyID(CCrossChainRPCData::GetConditionID(chainDef.GetChainID(), EVAL_CROSSCHAIN_IMPORT)))}); + CCrossChainImport cci = CCrossChainImport(ConnectedChains.ThisChain().GetChainID(), totalImport); + + newImportTx.vout[0] = MakeCC1of1Vout(EVAL_CROSSCHAIN_IMPORT, lastCCI.nValue - totalPreconvert, pk, dests, cci); + + // + // sign the transaction and addto our vector + + CTransaction ntx(newImportTx); + + uint32_t consensusBranchId = CurrentEpochBranchId(chainActive.LastTip()->GetHeight(), Params().GetConsensus()); + + bool signSuccess; + SignatureData sigdata; + CAmount value; + const CScript *pScriptPubKey; + + const CScript virtualCC; + CTxOut virtualCCOut; + + pScriptPubKey = &lastImport.vout[0].scriptPubKey; + value = lastCCI.nValue; + + signSuccess = ProduceSignature( + TransactionSignatureCreator(pwalletMain, &ntx, 0, lastCCI.nValue, SIGHASH_ALL), lastImport.vout[0].scriptPubKey, sigdata, consensusBranchId); + + if (signSuccess) + { + UpdateTransaction(newImportTx, 0, sigdata); + } + + // we now have a signed Import transaction for the chainID chain, it is the latest, and the export we used is now the latest as well + newImports.push_back(newImportTx); + lastImport = newImportTx; + lastExportHash = aixIt->second.first.txhash; + } + } +} + void CheckPBaaSAPIsValid() { if (!chainActive.LastTip() || @@ -1427,7 +1709,7 @@ UniValue getcrossnotarization(const UniValue& params, bool fHelp) else { // get index in the block as our transaction index for proofs - txIndex = addressIndex[txIndex].first.txindex; + txIndex = addressIndex[txIndex].first.index; } // if bock headers are merge mined, keep header refs, not headers @@ -1731,6 +2013,37 @@ UniValue listreservetransactions(const UniValue& params, bool fHelp) // lists all transactions in a wallet that are } +UniValue reserveexchange(const UniValue& params, bool fHelp) +{ + if (fHelp || params.size() != 1) + { + throw runtime_error( + "reserveexchange '[{\"toreserve\": 1, \"recipient\": \"RRehdmUV7oEAqoZnzEGBH34XysnWaBatct\", \"amount\": 5.0}]'\n" + "\nThis sends a Verus output as a JSON object or lists of Verus outputs as a list of objects to an address on the same or another chain.\n" + "\nFunds are sourced automatically from the current wallet, which must be present, as in sendtoaddress.\n" + + "\nArguments\n" + " {\n" + " \"toreserve\" : \"bool\", (bool, optional) if present, conversion is to the underlying reserve (Verus), if false, from Verus\n" + " \"recipient\" : \"Rxxx\", (string, required) recipient of converted funds or funds that failed to convert\n" + " \"amount\" : \"n\", (int64, required) amount of source coins that will be converted, depending on the toreserve flag, the rest is change\n" + " \"limit\" : \"n\", (int64, optional) price in reserve limit, below which for buys and above which for sells, execution will occur\n" + " \"validbefore\" : \"n\", (int, optional) block before which this can execute as a conversion, otherwise, it executes as a send with normal network fee\n" + " \"subtractfee\" : \"bool\", (bool, optional) if true, reduce amount to destination by the fee amount, otherwise, add from inputs to cover fee" + " }\n" + + "\nResult:\n" + " \"txid\" : \"transactionid\" (string) The transaction id.\n" + + "\nExamples:\n" + + HelpExampleCli("reserveexchange", "'[{\"name\": \"PBAASCHAIN\", \"paymentaddress\": \"RRehdmUV7oEAqoZnzEGBH34XysnWaBatct\", \"amount\": 5.0}]'") + + HelpExampleRpc("reserveexchange", "'[{\"name\": \"PBAASCHAIN\", \"paymentaddress\": \"RRehdmUV7oEAqoZnzEGBH34XysnWaBatct\", \"amount\": 5.0}]'") + ); + } + + CheckPBaaSAPIsValid(); +} + UniValue sendreserve(const UniValue& params, bool fHelp) { if (fHelp || params.size() != 1) @@ -1742,12 +2055,12 @@ UniValue sendreserve(const UniValue& params, bool fHelp) "\nArguments\n" " {\n" - " \"chain\" : \"xxxx\", (string, optional) Verus ecosystem-wide name/symbol of chain to send to, if absent, current chain is assumed\n" + " \"name\" : \"xxxx\", (string, optional) Verus ecosystem-wide name/symbol of chain to send to, if absent, current chain is assumed\n" " \"paymentaddress\" : \"Rxxx\", (string, required) premine and launch fee recipient\n" " \"refundaddress\" : \"Rxxx\", (string, required) if a pre-convert is not mined in time, funds can be spent by the owner of this address\n" " \"amount\" : \"n\", (int64, required) amount of coins that will be moved and sent to address on PBaaS chain, network and conversion fees additional\n" " \"convert\" : \"false\", (bool, optional) auto-convert to PBaaS currency at market price\n" - " \"launchonly\" : \"false\", (bool, optional) auto-convert to PBaaS currency at market price, fail if order cannot be placed before launch\n" + " \"preconvert\" : \"false\", (bool, optional) auto-convert to PBaaS currency at market price, fail if order cannot be placed before launch\n" " \"subtractfee\" : \"bool\", (bool, optional) if true, reduce amount to destination by the fee amount, otherwise, add from inputs to cover fee" " }\n" @@ -1789,6 +2102,7 @@ UniValue sendreserve(const UniValue& params, bool fHelp) string refundAddr = uni_get_str(find_value(params[0], "refundaddress"), paymentAddr); CAmount amount = uni_get_int64(find_value(params[0], "amount"), -1); bool convert = uni_get_int(find_value(params[0], "convert"), false); + bool preconvert = uni_get_int(find_value(params[0], "preconvert"), false); bool subtractFee = uni_get_int(find_value(params[0], "subtractfee"), false); uint32_t flags = CReserveOutput::VALID; @@ -1839,8 +2153,6 @@ UniValue sendreserve(const UniValue& params, bool fHelp) int32_t height = chainActive.Height(); bool beforeStart = chainDef.startBlock > height; - bool launchOnly = uni_get_int(find_value(params[0], "launchonly"), beforeStart); - if (isVerusActive) { if (chainID == thisChainID) @@ -1849,7 +2161,7 @@ UniValue sendreserve(const UniValue& params, bool fHelp) } else // ensure the PBaaS chain is a fractional reserve or that it's convertible and this is a conversion { - if (convert) + if (convert || preconvert) { // if chain hasn't started yet, we must use the conversion as a ratio over satoshis to participate in the pre-mine // up to a maximum @@ -1861,9 +2173,9 @@ UniValue sendreserve(const UniValue& params, bool fHelp) } flags |= CReserveTransfer::PRECONVERT; - } else if (!isReserve || launchOnly) + } else if (!isReserve || preconvert) { - throw JSONRPCError(RPC_INVALID_PARAMETER, "Cannot convert " + std::string(ASSETCHAINS_SYMBOL) + " after chain launch"); + throw JSONRPCError(RPC_INVALID_PARAMETER, "Cannot preconvert " + std::string(ASSETCHAINS_SYMBOL) + " after chain launch"); } else {