From e980a26ddd62f795820acb77ae58fd7d46a396a1 Mon Sep 17 00:00:00 2001 From: miketout Date: Fri, 4 May 2018 16:33:34 -0700 Subject: [PATCH] Enable time locked coin bases to be used as normal coinbase transactions with longer maturity, fix max_money --- src/chainparams.cpp | 6 ++++- src/komodo_globals.h | 4 +-- src/komodo_utils.h | 8 +++++- src/main.cpp | 8 ++++++ src/metrics.cpp | 7 ++++- src/primitives/transaction.cpp | 30 +++++++++++++++++++++ src/primitives/transaction.h | 2 ++ src/qt/transactiondesc.cpp | 4 +-- src/script/script.cpp | 31 +++++++++++++++++++++ src/script/script.h | 9 +++++++ src/script/script_ext.cpp | 31 --------------------- src/script/script_ext.h | 8 ------ src/txmempool.cpp | 9 +++++-- src/wallet-utility.cpp | 1 + src/wallet/wallet.cpp | 49 +++++++--------------------------- src/wallet/wallet.h | 6 +---- 16 files changed, 121 insertions(+), 92 deletions(-) diff --git a/src/chainparams.cpp b/src/chainparams.cpp index 1cf2106b0..8c190be2c 100644 --- a/src/chainparams.cpp +++ b/src/chainparams.cpp @@ -85,6 +85,7 @@ extern uint32_t ASSETCHAINS_MAGIC; extern uint64_t ASSETCHAINS_SUPPLY; extern uint64_t ASSETCHAINS_ALGO; extern uint64_t ASSETCHAINS_EQUIHASH; +extern uint64_t ASSETCHAINS_VERUSHASH; const arith_uint256 maxUint = UintToArith256(uint256S("ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff")); @@ -100,7 +101,10 @@ public: consensus.nMajorityEnforceBlockUpgrade = 750; consensus.nMajorityRejectBlockOutdated = 950; consensus.nMajorityWindow = 4000; - consensus.powLimit = uint256S("0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f"); + if (ASSETCHAINS_ALGO == ASSETCHAINS_VERUSHASH) + consensus.powLimit = uint256S("00000f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f"); + else + consensus.powLimit = uint256S("0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f"); consensus.nPowAveragingWindow = 17; consensus.nMaxFutureBlockTime = 7 * 60; // 7 mins diff --git a/src/komodo_globals.h b/src/komodo_globals.h index d57005ec9..7f70c814e 100644 --- a/src/komodo_globals.h +++ b/src/komodo_globals.h @@ -117,14 +117,14 @@ int64_t komodo_current_supply(uint32_t nHeight) int32_t baseid; if ( (baseid = komodo_baseid(ASSETCHAINS_SYMBOL)) >= 0 && baseid < 32 ) - cur_money = ASSETCHAINS_SUPPLY + nHeight * ASSETCHAINS_REWARD[0] / SATOSHIDEN; + cur_money = ASSETCHAINS_GENESISTXVAL + ASSETCHAINS_SUPPLY + nHeight * ASSETCHAINS_REWARD[0] / SATOSHIDEN; else { // figure out max_money by adding up supply to a maximum of 10,000,000 blocks cur_money = (ASSETCHAINS_SUPPLY+1) * SATOSHIDEN + (ASSETCHAINS_MAGIC & 0xffffff) + ASSETCHAINS_GENESISTXVAL; if ( ASSETCHAINS_LASTERA == 0 && ASSETCHAINS_REWARD[0] == 0 ) { - cur_money = ASSETCHAINS_SUPPLY + (nHeight * 10000) / SATOSHIDEN; + cur_money += (nHeight * 10000) / SATOSHIDEN; } else { diff --git a/src/komodo_utils.h b/src/komodo_utils.h index 87cd3490f..fa03f66aa 100644 --- a/src/komodo_utils.h +++ b/src/komodo_utils.h @@ -1733,6 +1733,11 @@ void komodo_args(char *argv0) extralen += iguana_rwnum(1,&extraptr[extralen],sizeof(ASSETCHAINS_DECAY[i]),(void *)&ASSETCHAINS_DECAY[i]); } + if (ASSETCHAINS_LASTERA > 0) + { + extralen += iguana_rwnum(1,&extraptr[extralen],sizeof(ASSETCHAINS_LASTERA),(void *)&ASSETCHAINS_LASTERA); + } + // hash in lock above for time locked coinbase transactions above a certain reward value only if the lock above // param was specified, otherwise, for compatibility, do nothing if ( ASSETCHAINS_TIMELOCKGTE != _ASSETCHAINS_TIMELOCKOFF ) @@ -1777,7 +1782,8 @@ void komodo_args(char *argv0) if ( (port= komodo_userpass(ASSETCHAINS_USERPASS,ASSETCHAINS_SYMBOL)) != 0 ) ASSETCHAINS_RPCPORT = port; else komodo_configfile(ASSETCHAINS_SYMBOL,ASSETCHAINS_P2PPORT + 1); - COINBASE_MATURITY = 1; + if (ASSETCHAINS_LASTERA == 0 && ASSETCHAINS_REWARD[0] == 0) + COINBASE_MATURITY = 1; //fprintf(stderr,"ASSETCHAINS_RPCPORT (%s) %u\n",ASSETCHAINS_SYMBOL,ASSETCHAINS_RPCPORT); } if ( ASSETCHAINS_RPCPORT == 0 ) diff --git a/src/main.cpp b/src/main.cpp index cc8e8995c..82e489269 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -2056,6 +2056,14 @@ namespace Consensus { error("CheckInputs(): tried to spend coinbase at depth %d", nSpendHeight - coins->nHeight), REJECT_INVALID, "bad-txns-premature-spend-of-coinbase"); } + + // ensure that zeroth output of coinbases are not still time locked + uint64_t unlockTime = tx.UnlockTime(0); + if (nSpendHeight >= unlockTime) { + return state.Invalid( + error("CheckInputs(): tried to spend coinbase that is timelocked until block %d", unlockTime), + REJECT_INVALID, "bad-txns-premature-spend-of-coinbase"); + } // Ensure that coinbases cannot be spent to transparent outputs // Disabled on regtest diff --git a/src/metrics.cpp b/src/metrics.cpp index 6c8f80fe5..e10e5cf8d 100644 --- a/src/metrics.cpp +++ b/src/metrics.cpp @@ -24,6 +24,9 @@ #endif #include +extern int64_t ASSETCHAINS_TIMELOCKGTE; +int64_t komodo_block_unlocktime(uint32_t nHeight); + void AtomicTimer::start() { std::unique_lock lock(mtx); @@ -335,7 +338,9 @@ int printMetrics(size_t cols, bool mining) if ((height > 0) && (height <= consensusParams.GetLastFoundersRewardBlockHeight())) { subsidy -= subsidy/5; } - if (std::max(0, COINBASE_MATURITY - (tipHeight - height)) > 0) { + + if ((std::max(0, COINBASE_MATURITY - (tipHeight - height)) > 0) || + (tipHeight < komodo_block_unlocktime(height) && subsidy >= ASSETCHAINS_TIMELOCKGTE)) { immature += subsidy; } else { mature += subsidy; diff --git a/src/primitives/transaction.cpp b/src/primitives/transaction.cpp index d6ad31c3a..c35152df8 100644 --- a/src/primitives/transaction.cpp +++ b/src/primitives/transaction.cpp @@ -266,6 +266,36 @@ unsigned int CTransaction::CalculateModifiedSize(unsigned int nTxSize) const return nTxSize; } +// will return the open time or block if this is a time locked transaction output that we recognize. +// if we can't determine that it has a valid time lock, it returns 0 +int64_t CTransaction::UnlockTime(uint32_t voutNum) const +{ + if (vout.size() > voutNum + 1 && vout[voutNum].scriptPubKey.IsPayToScriptHash()) + { + uint32_t voutNext = voutNum + 1; + + std::vector opretData; + uint160 scriptID = uint160(std::vector(vout[voutNum].scriptPubKey.begin() + 2, vout[voutNum].scriptPubKey.begin() + 22)); + CScript::const_iterator it = vout[voutNext].scriptPubKey.begin() + 1; + + opcodetype op; + if (vout[voutNext].scriptPubKey.GetOp2(it, op, &opretData)) + { + if (opretData.size() > 0 && opretData.data()[0] == OPRETTYPE_TIMELOCK) + { + int64_t unlocktime; + CScript opretScript = CScript(opretData.begin() + 1, opretData.end()); + if (Hash160(opretScript) == scriptID && + opretScript.IsCheckLockTimeVerify(&unlocktime)) + { + return(unlocktime); + } + } + } + } + return(0); +} + std::string CTransaction::ToString() const { std::string str; diff --git a/src/primitives/transaction.h b/src/primitives/transaction.h index 35c12abc8..112906061 100644 --- a/src/primitives/transaction.h +++ b/src/primitives/transaction.h @@ -457,6 +457,8 @@ public: return (vin.size() == 1 && vin[0].prevout.IsNull()); } + int64_t UnlockTime(uint32_t voutNum) const; + friend bool operator==(const CTransaction& a, const CTransaction& b) { return a.hash == b.hash; diff --git a/src/qt/transactiondesc.cpp b/src/qt/transactiondesc.cpp index eba8e8237..6e9c67f7e 100644 --- a/src/qt/transactiondesc.cpp +++ b/src/qt/transactiondesc.cpp @@ -267,8 +267,8 @@ QString TransactionDesc::toHTML(CWallet *wallet, CWalletTx &wtx, TransactionReco extern char ASSETCHAINS_SYMBOL[KOMODO_ASSETCHAIN_MAXLEN]; if ( ASSETCHAINS_SYMBOL[0] == 0 ) COINBASE_MATURITY = _COINBASE_MATURITY; - quint32 numBlocksToMaturity = COINBASE_MATURITY + 1; - strHTML += "
" + tr("Generated coins must mature %1 blocks before they can be spent. When you generated this block, it was broadcast to the network to be added to the block chain. If it fails to get into the chain, its state will change to \"not accepted\" and it won't be spendable. This may occasionally happen if another node generates a block within a few seconds of yours.").arg(QString::number(numBlocksToMaturity)) + "
"; + quint32 numBlocksToMaturity = COINBASE_MATURITY + 1; + strHTML += "
" + tr("Generated coins must mature %1 blocks and have any applicable time locks open before they can be spent. When you generated this block, it was broadcast to the network to be added to the block chain. If it fails to get into the chain, its state will change to \"not accepted\" and it won't be spendable. This may occasionally happen if another node generates a block within a few seconds of yours.").arg(QString::number(numBlocksToMaturity)) + "
"; // we need to display any possible CLTV lock time } diff --git a/src/script/script.cpp b/src/script/script.cpp index a130df4aa..19d5a6159 100644 --- a/src/script/script.cpp +++ b/src/script/script.cpp @@ -284,6 +284,37 @@ bool CScript::IsPushOnly() const return true; } +// if the front of the script has check lock time verify. this is a fairly simple check. +// accepts NULL as parameter if unlockTime is not needed. +bool CScript::IsCheckLockTimeVerify(int64_t *unlockTime) const +{ + opcodetype op; + std::vector unlockTimeParam = std::vector(); + CScript::const_iterator it = this->begin(); + + if (this->GetOp2(it, op, &unlockTimeParam)) + { + if (unlockTimeParam.size() >= 0 && unlockTimeParam.size() < 6 && + *(this->data() + unlockTimeParam.size() + 1) == OP_CHECKLOCKTIMEVERIFY) + { + int i = unlockTimeParam.size() - 1; + for (*unlockTime = 0; i >= 0; i--) + { + *unlockTime <<= 8; + *unlockTime |= *((unsigned char *)unlockTimeParam.data() + i); + } + return true; + } + } + return false; +} + +bool CScript::IsCheckLockTimeVerify() const +{ + int64_t ult; + return this->IsCheckLockTimeVerify(&ult); +} + std::string CScript::ToString() const { std::string str; diff --git a/src/script/script.h b/src/script/script.h index e5328cf06..89499b978 100644 --- a/src/script/script.h +++ b/src/script/script.h @@ -17,6 +17,8 @@ #include #include +#define OPRETTYPE_TIMELOCK 1 + static const unsigned int MAX_SCRIPT_ELEMENT_SIZE = 520; // bytes // Max size of pushdata in a CC sig in bytes @@ -575,6 +577,13 @@ public: /** Called by IsStandardTx and P2SH/BIP62 VerifyScript (which makes it consensus-critical). */ bool IsPushOnly() const; + /** if the front of the script has check lock time verify. this is a fairly simple check. + * accepts NULL as parameter if unlockTime is not needed. + */ + bool IsCheckLockTimeVerify(int64_t *unlockTime) const; + + bool IsCheckLockTimeVerify() const; + /** * Returns whether the script is guaranteed to fail at execution, * regardless of the initial stack. This allows outputs to be pruned diff --git a/src/script/script_ext.cpp b/src/script/script_ext.cpp index 655d256c2..8c74426a6 100644 --- a/src/script/script_ext.cpp +++ b/src/script/script_ext.cpp @@ -74,34 +74,3 @@ const CScriptExt &CScriptExt::TimeLockSpend(const CKeyID &key, int64_t unlocktim return *this; } -// if the front of the script has check lock time verify. this is a fairly simple check. -// accepts NULL as parameter if unlockTime is not needed. -bool CScriptExt::IsCheckLockTimeVerify(int64_t *unlockTime) const -{ - opcodetype op; - std::vector unlockTimeParam = std::vector(); - CScript::const_iterator it = this->begin(); - - if (this->GetOp2(it, op, &unlockTimeParam)) - { - if (unlockTimeParam.size() >= 0 && unlockTimeParam.size() < 6 && - *(this->data() + unlockTimeParam.size() + 1) == OP_CHECKLOCKTIMEVERIFY) - { - int i = unlockTimeParam.size() - 1; - for (*unlockTime = 0; i >= 0; i--) - { - *unlockTime <<= 8; - *unlockTime |= *((unsigned char *)unlockTimeParam.data() + i); - } - return true; - } - } - return false; -} - -bool CScriptExt::IsCheckLockTimeVerify() const -{ - int64_t ult; - return this->IsCheckLockTimeVerify(&ult); -} - diff --git a/src/script/script_ext.h b/src/script/script_ext.h index ece0cf594..a42616669 100644 --- a/src/script/script_ext.h +++ b/src/script/script_ext.h @@ -11,8 +11,6 @@ #include "standard.h" #include "pubkey.h" -#define OPRETTYPE_TIMELOCK 1 - class CScriptExt : public CScript { public: @@ -38,12 +36,6 @@ class CScriptExt : public CScript // combined CLTV script and P2PKH const CScriptExt &TimeLockSpend(const CKeyID &key, int64_t unlocktime) const; - - // if the front of the script has check lock time verify. this is a fairly simple check. - // accepts NULL as parameter if unlockTime is not needed. - bool IsCheckLockTimeVerify(int64_t *unlockTime) const; - - bool IsCheckLockTimeVerify() const; }; #endif diff --git a/src/txmempool.cpp b/src/txmempool.cpp index dd985f500..3dafc2353 100644 --- a/src/txmempool.cpp +++ b/src/txmempool.cpp @@ -312,6 +312,9 @@ void CTxMemPool::remove(const CTransaction &origTx, std::list& rem } } +extern int64_t ASSETCHAINS_TIMELOCKGTE; +int64_t komodo_block_unlocktime(uint32_t nHeight); + void CTxMemPool::removeForReorg(const CCoinsViewCache *pcoins, unsigned int nMemPoolHeight, int flags) { // Remove transactions spending a coinbase which are now immature @@ -331,8 +334,10 @@ void CTxMemPool::removeForReorg(const CCoinsViewCache *pcoins, unsigned int nMem if (it2 != mapTx.end()) continue; const CCoins *coins = pcoins->AccessCoins(txin.prevout.hash); - if (nCheckFrequency != 0) assert(coins); - if (!coins || (coins->IsCoinBase() && ((signed long)nMemPoolHeight) - coins->nHeight < COINBASE_MATURITY)) { + if (nCheckFrequency != 0) assert(coins); + if (!coins || (coins->IsCoinBase() && (((signed long)nMemPoolHeight) - coins->nHeight < COINBASE_MATURITY) && + ((signed long)nMemPoolHeight < komodo_block_unlocktime(coins->nHeight) && + coins->IsAvailable(0) && coins->vout[0].nValue >= ASSETCHAINS_TIMELOCKGTE))) { transactionsToRemove.push_back(tx); break; } diff --git a/src/wallet-utility.cpp b/src/wallet-utility.cpp index 9e326ea78..3d3327139 100644 --- a/src/wallet-utility.cpp +++ b/src/wallet-utility.cpp @@ -17,6 +17,7 @@ uint16_t ASSETCHAINS_P2PPORT,ASSETCHAINS_RPCPORT; uint32_t ASSETCHAIN_INIT,ASSETCHAINS_CC; uint32_t ASSETCHAINS_MAGIC = 2387029918; uint32_t ASSETCHAINS_EQUIHASH = 0; +uint32_t ASSETCHAINS_VERUSHASH = 1; uint32_t ASSETCHAINS_ALGO = 0; unsigned int MAX_BLOCK_SIGOPS = 20000; diff --git a/src/wallet/wallet.cpp b/src/wallet/wallet.cpp index 60b996821..110cc69ee 100644 --- a/src/wallet/wallet.cpp +++ b/src/wallet/wallet.cpp @@ -1206,7 +1206,7 @@ bool CWallet::AddToWalletIfInvolvingMe(const CTransaction& tx, const CBlock* pbl bool fExisted = mapWallet.count(tx.GetHash()) != 0; if (fExisted && !fUpdate) return false; auto noteData = FindMyNotes(tx); - if (fExisted || IsMineOrWatch(tx) || IsFromMe(tx) || noteData.size() > 0) + if (fExisted || IsMine(tx) || IsFromMe(tx) || noteData.size() > 0) { CWalletTx wtx(this,tx); @@ -1468,49 +1468,18 @@ bool CWallet::IsMine(const CTransaction& tx) return false; } -bool CWallet::IsMineOrWatch(const CTransaction& tx) -{ - for (int i = 0; i < tx.vout.size(); i++) - { - if (IsMine(tx, i) & ISMINE_ALL) - return true; - } - return false; -} - // special case handling for CLTV scripts, this does not error check to ensure the script is CLTV and is -// only internal to the wallet for that reason. if it is the first time we see this script, we add it to the wallet. -isminetype CWallet::IsCLTVMine(CScriptExt &script, CScriptID &scriptID, int64_t locktime) +// only internal to the wallet for that reason. +isminetype CWallet::IsCLTVMine(CScript &script, CScriptID &scriptID, int64_t locktime) const { uint8_t pushOp = script.data()[0]; uint32_t scriptStart = pushOp + 3; // check post CLTV script - CScriptExt postfix = CScriptExt(script.size() > scriptStart ? script.begin() + scriptStart : script.end(), script.end()); + CScript postfix = CScript(script.size() > scriptStart ? script.begin() + scriptStart : script.end(), script.end()); // check again with postfix subscript - isminetype ret = ::IsMine(*this, postfix); - if (ret == ISMINE_SPENDABLE) - { - // once we get here, we should have this script in our - // wallet, either as watch only if still time locked, or spendable - CBlockIndex &tip = *(chainActive.Tip()); - if (!(locktime < LOCKTIME_THRESHOLD ? tip.nHeight >= locktime : tip.GetBlockTime() >= locktime)) - { - ret = ISMINE_WATCH_ONLY; - if (!this->HaveWatchOnly(script)) - { - this->AddWatchOnly(script); - } - } else - { - if (this->HaveWatchOnly(script)) - this->RemoveWatchOnly(script); - if (!this->HaveCScript(scriptID)) - this->AddCScript(script); - } - } - return ret; + return(::IsMine(*this, postfix)); } typedef vector valtype; @@ -1584,7 +1553,7 @@ isminetype CWallet::IsMine(const CTransaction& tx, uint32_t voutNum) if (opretData.size() > 0 && opretData.data()[0] == OPRETTYPE_TIMELOCK) { int64_t unlocktime; - CScriptExt opretScript = CScriptExt(opretData.begin() + 1, opretData.end()); + CScript opretScript = CScriptExt(opretData.begin() + 1, opretData.end()); if (CScriptID(opretScript) == scriptID && opretScript.IsCheckLockTimeVerify(&unlocktime)) @@ -3952,10 +3921,12 @@ int CMerkleTx::GetBlocksToMaturity() const COINBASE_MATURITY = _COINBASE_MATURITY; if (!IsCoinBase()) return 0; - return max(0, (COINBASE_MATURITY+1) - GetDepthInMainChain()); + uint32_t depth = GetDepthInMainChain(); + uint32_t ui; + uint32_t toMaturity = (ui = UnlockTime(0) - chainActive.Height()) < 0 ? 0 : ui; + return((ui = COINBASE_MATURITY - depth) < toMaturity ? toMaturity : ui); } - bool CMerkleTx::AcceptToMemoryPool(bool fLimitFree, bool fRejectAbsurdFee) { CValidationState state; diff --git a/src/wallet/wallet.h b/src/wallet/wallet.h index bacdd0308..033bc47ae 100644 --- a/src/wallet/wallet.h +++ b/src/wallet/wallet.h @@ -277,7 +277,6 @@ struct CNotePlaintextEntry }; - /** A transaction with a merkle branch linking it to the block chain. */ class CMerkleTx : public CTransaction { @@ -762,7 +761,7 @@ protected: private: template void SyncMetaData(std::pair::iterator, typename TxSpendMap::iterator>); - isminetype IsCLTVMine(CScriptExt &script, CScriptID &scriptID, int64_t locktime); + isminetype IsCLTVMine(CScript &script, CScriptID &scriptID, int64_t locktime) const; protected: bool UpdatedNoteData(const CWalletTx& wtxIn, CWalletTx& wtx); @@ -904,14 +903,12 @@ public: void UnlockAllCoins(); void ListLockedCoins(std::vector& vOutpts); - bool IsLockedNote(uint256 hash, size_t js, uint8_t n) const; void LockNote(JSOutPoint& output); void UnlockNote(JSOutPoint& output); void UnlockAllNotes(); std::vector ListLockedNotes(); - /** * keystore implementation * Generate a new key @@ -1056,7 +1053,6 @@ public: bool IsChange(const CTxOut& txout) const; CAmount GetChange(const CTxOut& txout) const; bool IsMine(const CTransaction& tx); - bool IsMineOrWatch(const CTransaction& tx); /** should probably be renamed to IsRelevantToMe */ bool IsFromMe(const CTransaction& tx) const; CAmount GetDebit(const CTransaction& tx, const isminefilter& filter) const;