From f04a42c241ad859368c670e7d616f9831009854e Mon Sep 17 00:00:00 2001 From: timemarkovqtum Date: Fri, 8 Mar 2024 16:03:25 +0100 Subject: [PATCH] Port validation --- src/validation.cpp | 1024 +++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 968 insertions(+), 56 deletions(-) diff --git a/src/validation.cpp b/src/validation.cpp index 75074503ff..f786187c43 100644 --- a/src/validation.cpp +++ b/src/validation.cpp @@ -334,7 +334,7 @@ void Chainstate::MaybeUpdateMempoolForReorg( auto it = queuedTx.rbegin(); while (it != queuedTx.rend()) { // ignore validation errors in resurrected transactions - if (!fAddToMempool || (*it)->IsCoinBase() || + if (!fAddToMempool || (*it)->IsCoinBase() || (*it)->IsCoinStake() || AcceptToMemoryPool(*this, *it, GetTime(), /*bypass_limits=*/true, /*test_accept=*/false).m_result_type != MempoolAcceptResult::ResultType::VALID) { @@ -396,7 +396,7 @@ void Chainstate::MaybeUpdateMempoolForReorg( const Coin& coin{CoinsTip().AccessCoin(txin.prevout)}; assert(!coin.IsSpent()); const auto mempool_spend_height{m_chain.Tip()->nHeight + 1}; - if (coin.IsCoinBase() && mempool_spend_height - coin.nHeight < COINBASE_MATURITY) { + if ((coin.IsCoinBase() || coin.IsCoinStake()) && mempool_spend_height - coin.nHeight < m_chainman.GetParams().GetConsensus().CoinbaseMaturity(mempool_spend_height)) { return true; } } @@ -729,6 +729,7 @@ bool MemPoolAccept::PreChecks(ATMPArgs& args, Workspace& ws) const int64_t nAcceptTime = args.m_accept_time; const bool bypass_limits = args.m_bypass_limits; std::vector& coins_to_uncache = args.m_coins_to_uncache; + const CChainParams& chainparams = args.m_chainparams; // Alias what we need out of ws TxValidationState& state = ws.m_state; @@ -742,6 +743,10 @@ bool MemPoolAccept::PreChecks(ATMPArgs& args, Workspace& ws) if (tx.IsCoinBase()) return state.Invalid(TxValidationResult::TX_CONSENSUS, "coinbase"); + // ppcoin: coinstake is also only valid in a block, not as a loose transaction + if (tx.IsCoinStake()) + return state.Invalid(TxValidationResult::TX_CONSENSUS, "coinstake"); + // Rather not work on nonstandard transactions (unless -testnet/-regtest) std::string reason; if (m_pool.m_require_standard && !IsStandardTx(tx, m_pool.m_max_datacarrier_bytes, m_pool.m_permit_bare_multisig, m_pool.m_dust_relay_feerate, reason)) { @@ -801,6 +806,19 @@ bool MemPoolAccept::PreChecks(ATMPArgs& args, Workspace& ws) m_view.SetBackend(m_viewmempool); const CCoinsViewCache& coins_cache = m_active_chainstate.CoinsTip(); + + // do we already have it? + for (size_t out = 0; out < tx.vout.size(); out++) { + COutPoint outpoint(hash, out); + bool had_coin_in_cache = coins_cache.HaveCoinInCache(outpoint); + if (m_view.HaveCoin(outpoint)) { + if (!had_coin_in_cache) { + coins_to_uncache.push_back(outpoint); + } + return state.Invalid(TxValidationResult::TX_CONFLICT, "txn-already-known"); + } + } + // do all inputs exist? for (const CTxIn& txin : tx.vin) { if (!coins_cache.HaveCoinInCache(txin.prevout)) { @@ -860,16 +878,99 @@ bool MemPoolAccept::PreChecks(ATMPArgs& args, Workspace& ws) int64_t nSigOpsCost = GetTransactionSigOpCost(tx, m_view, STANDARD_SCRIPT_VERIFY_FLAGS); + dev::u256 txMinGasPrice = 0; + + //////////////////////////////////////////////////////////// // qtum + if(!CheckOpSender(tx, chainparams, m_active_chainstate.m_chain.Height() + 1)){ + return state.Invalid(TxValidationResult::TX_CONSENSUS, "bad-txns-invalid-sender"); + } + if(tx.HasCreateOrCall()){ + + if(!CheckSenderScript(m_view, tx)){ + return state.Invalid(TxValidationResult::TX_INVALID_SENDER_SCRIPT, "bad-txns-invalid-sender-script"); + } + + QtumDGP qtumDGP(globalState.get(), m_active_chainstate, fGettingValuesDGP); + uint64_t minGasPrice = qtumDGP.getMinGasPrice(m_active_chainstate.m_chain.Tip()->nHeight + 1); + uint64_t blockGasLimit = qtumDGP.getBlockGasLimit(m_active_chainstate.m_chain.Tip()->nHeight + 1); + size_t count = 0; + for(const CTxOut& o : tx.vout) + count += o.scriptPubKey.HasOpCreate() || o.scriptPubKey.HasOpCall() ? 1 : 0; + unsigned int contractflags = GetContractScriptFlags(m_active_chainstate.m_chain.Height() + 1, chainparams.GetConsensus()); + QtumTxConverter converter(tx, m_active_chainstate, &m_pool, NULL, NULL, contractflags); + ExtractQtumTX resultConverter; + if(!converter.extractionQtumTransactions(resultConverter)){ + return state.Invalid(TxValidationResult::TX_CONSENSUS, "bad-tx-bad-contract-format", "AcceptToMempool(): Contract transaction of the wrong format"); + } + std::vector qtumTransactions = resultConverter.first; + std::vector qtumETP = resultConverter.second; + + dev::u256 sumGas = dev::u256(0); + dev::u256 gasAllTxs = dev::u256(0); + for(QtumTransaction qtumTransaction : qtumTransactions){ + sumGas += qtumTransaction.gas() * qtumTransaction.gasPrice(); + + if(sumGas > dev::u256(INT64_MAX)) { + return state.Invalid(TxValidationResult::TX_CONSENSUS, "bad-tx-gas-stipend-overflow", "AcceptToMempool(): Transaction's gas stipend overflows"); + } + + if(sumGas > dev::u256(ws.m_base_fees)) { + return state.Invalid(TxValidationResult::TX_CONSENSUS, "bad-txns-fee-notenough", "AcceptToMempool(): Transaction fee does not cover the gas stipend"); + } + + if(txMinGasPrice != 0) { + txMinGasPrice = std::min(txMinGasPrice, qtumTransaction.gasPrice()); + } else { + txMinGasPrice = qtumTransaction.gasPrice(); + } + VersionVM v = qtumTransaction.getVersion(); + if(v.format!=0) + return state.Invalid(TxValidationResult::TX_CONSENSUS, "bad-tx-version-format", "AcceptToMempool(): Contract execution uses unknown version format"); + if(v.rootVM != 1) + return state.Invalid(TxValidationResult::TX_CONSENSUS, "bad-tx-version-rootvm", "AcceptToMempool(): Contract execution uses unknown root VM"); + if(v.vmVersion != 0) + return state.Invalid(TxValidationResult::TX_CONSENSUS, "bad-tx-version-vmversion", "AcceptToMempool(): Contract execution uses unknown VM version"); + if(v.flagOptions != 0) + return state.Invalid(TxValidationResult::TX_CONSENSUS, "bad-tx-version-flags", "AcceptToMempool(): Contract execution uses unknown flag options"); + + //check gas limit is not less than minimum mempool gas limit + if(qtumTransaction.gas() < gArgs.GetIntArg("-minmempoolgaslimit", MEMPOOL_MIN_GAS_LIMIT)) + return state.Invalid(TxValidationResult::TX_CONSENSUS, "bad-tx-too-little-mempool-gas", "AcceptToMempool(): Contract execution has lower gas limit than allowed to accept into mempool"); + + //check gas limit is not less than minimum gas limit (unless it is a no-exec tx) + if(qtumTransaction.gas() < MINIMUM_GAS_LIMIT && v.rootVM != 0) + return state.Invalid(TxValidationResult::TX_CONSENSUS, "bad-tx-too-little-gas", "AcceptToMempool(): Contract execution has lower gas limit than allowed"); + + if(qtumTransaction.gas() > UINT32_MAX) + return state.Invalid(TxValidationResult::TX_CONSENSUS, "bad-tx-too-much-gas", "AcceptToMempool(): Contract execution can not specify greater gas limit than can fit in 32-bits"); + + gasAllTxs += qtumTransaction.gas(); + if(gasAllTxs > dev::u256(blockGasLimit)) + return state.Invalid(TxValidationResult::TX_GAS_EXCEEDS_LIMIT, "bad-txns-gas-exceeds-blockgaslimit"); + + //don't allow less than DGP set minimum gas price to prevent MPoS greedy mining/spammers + if(v.rootVM!=0 && (uint64_t)qtumTransaction.gasPrice() < minGasPrice) + return state.Invalid(TxValidationResult::TX_CONSENSUS, "bad-tx-low-gas-price", "AcceptToMempool(): Contract execution has lower gas price than allowed"); + } + + if(!CheckMinGasPrice(qtumETP, minGasPrice)) + return state.Invalid(TxValidationResult::TX_CONSENSUS, "bad-txns-small-gasprice"); + + if(count > qtumTransactions.size()) + return state.Invalid(TxValidationResult::TX_CONSENSUS, "bad-txns-incorrect-format"); + } + //////////////////////////////////////////////////////////// + // ws.m_modified_fees includes any fee deltas from PrioritiseTransaction ws.m_modified_fees = ws.m_base_fees; m_pool.ApplyDelta(hash, ws.m_modified_fees); // Keep track of transactions that spend a coinbase, which we re-scan - // during reorgs to ensure COINBASE_MATURITY is still met. + // during reorgs to ensure coinbaseMaturity is still met. bool fSpendsCoinbase = false; for (const CTxIn &txin : tx.vin) { const Coin &coin = m_view.AccessCoin(txin.prevout); - if (coin.IsCoinBase()) { + if (coin.IsCoinBase() || coin.IsCoinStake()) { fSpendsCoinbase = true; break; } @@ -879,7 +980,7 @@ bool MemPoolAccept::PreChecks(ATMPArgs& args, Workspace& ws) // reorg to be marked earlier than any child txs that were already in the mempool. const uint64_t entry_sequence = bypass_limits ? 0 : m_pool.GetSequence(); entry.reset(new CTxMemPoolEntry(ptx, ws.m_base_fees, nAcceptTime, m_active_chainstate.m_chain.Height(), entry_sequence, - fSpendsCoinbase, nSigOpsCost, lock_points.value())); + fSpendsCoinbase, nSigOpsCost, lock_points.value(), CAmount(txMinGasPrice))); ws.m_vsize = entry->GetTxSize(); if (nSigOpsCost > dgpMaxTxSigOps) @@ -1154,6 +1255,15 @@ bool MemPoolAccept::Finalize(const ATMPArgs& args, Workspace& ws) // transaction has not necessarily been accepted to miners' mempools. bool validForFeeEstimation = !bypass_limits && !args.m_package_submission && IsCurrentForFeeEstimation(m_active_chainstate) && m_pool.HasNoInputsOf(tx); + //////////////////////////////////////////////////////////////// // qtum + // Add memory address index + if (fAddressIndex) + { + m_pool.addAddressIndex(*entry, m_view); + m_pool.addSpentIndex(*entry, m_view); + } + //////////////////////////////////////////////////////////////// + // Store transaction in memory m_pool.addUnchecked(*entry, ws.m_ancestors, validForFeeEstimation); @@ -1674,24 +1784,56 @@ bool IsConfirmedInNPrevBlocks(const CDiskTxPos& txindex, const CBlockIndex* pind return false; } -bool CheckHeaderProof(const CBlockHeader& block, const Consensus::Params& consensusParams, Chainstate& chainstate){ +bool CheckHeaderPoW(const CBlockHeader& block, const Consensus::Params& consensusParams) +{ + // Check for proof of work block header + return CheckProofOfWork(block.GetHash(), block.nBits, consensusParams); +} + +bool CheckHeaderPoS(const CBlockHeader& block, const Consensus::Params& consensusParams, Chainstate& chainstate) +{ return {}; } +bool CheckHeaderProof(const CBlockHeader& block, const Consensus::Params& consensusParams, Chainstate& chainstate){ + if(block.IsProofOfWork()){ + return CheckHeaderPoW(block, consensusParams); + } + if(block.IsProofOfStake()){ + return CheckHeaderPoS(block, consensusParams, chainstate); + } + return false; +} + bool CheckIndexProof(const CBlockIndex& block, const Consensus::Params& consensusParams) { - return {}; + // Get the hash of the proof + // After validating the PoS block the computed hash proof is saved in the block index, which is used to check the index + uint256 hashProof = block.IsProofOfWork() ? block.GetBlockHash() : block.hashProof; + // Check for proof after the hash proof is computed + if(block.IsProofOfStake()){ + //blocks are loaded out of order, so checking PoS kernels here is not practical + return true; //CheckKernel(block.pprev, block.nBits, block.nTime, block.prevoutStake); + }else{ + return CheckProofOfWork(hashProof, block.nBits, consensusParams); + } } CAmount GetBlockSubsidy(int nHeight, const Consensus::Params& consensusParams) { - int halvings = nHeight / consensusParams.nSubsidyHalvingInterval; + if(nHeight <= consensusParams.nLastBigReward) + return 20000 * COIN; + + int subsidyHalvingInterval = consensusParams.SubsidyHalvingInterval(nHeight); + int subsidyHalvingWeight = consensusParams.SubsidyHalvingWeight(nHeight); + int halvings = (subsidyHalvingWeight - 1) / subsidyHalvingInterval; // Force block reward to zero when right shift is undefined. - if (halvings >= 64) + if (halvings >= 7) return 0; - CAmount nSubsidy = 50 * COIN; - // Subsidy is cut in half every 210,000 blocks which will occur approximately every 4 years. + int blocktimeDownscaleFactor = consensusParams.BlocktimeDownscaleFactor(nHeight); + CAmount nSubsidy = 4 * COIN / blocktimeDownscaleFactor; + // Subsidy is cut in half every 985500 blocks which will occur approximately every 4 years. nSubsidy >>= halvings; return nSubsidy; } @@ -1759,6 +1901,10 @@ void Chainstate::InitCoinsCache(size_t cache_size_bytes) // bool ChainstateManager::IsInitialBlockDownload() const { + static bool fForceInitialBlocksDownloadMode = gArgs.GetBoolArg("-forceinitialblocksdownloadmode", DEFAULT_FORCE_INITIAL_BLOCKS_DOWNLOAD_MODE); + if(fForceInitialBlocksDownloadMode) + return true; + // Optimization: pre-test latch before taking the lock. if (m_cached_finished_ibd.load(std::memory_order_relaxed)) return false; @@ -1854,6 +2000,16 @@ void UpdateCoins(const CTransaction& tx, CCoinsViewCache& inputs, CTxUndo &txund } bool CScriptCheck::operator()() { + if(checkOutput()) + { + // Check the sender signature inside the output, used to identify VM sender + CScript senderPubKey, senderSig; + if(!ExtractSenderData(ptxTo->vout[nOut].scriptPubKey, &senderPubKey, &senderSig)) + return false; + return VerifyScript(senderSig, senderPubKey, nullptr, nFlags, CachingTransactionSignatureOutputChecker(ptxTo, nOut, ptxTo->vout[nOut].nValue, cacheStore, *txdata), &error); + } + + // Check the input signature const CScript &scriptSig = ptxTo->vin[nIn].scriptSig; const CScriptWitness *witness = &ptxTo->vin[nIn].scriptWitness; return VerifyScript(scriptSig, m_tx_out.scriptPubKey, witness, nFlags, CachingTransactionSignatureChecker(ptxTo, nIn, m_tx_out.nValue, cacheStore, *txdata), &error); @@ -1978,6 +2134,19 @@ bool CheckInputScripts(const CTransaction& tx, TxValidationState& state, } } + for (unsigned int i = 0; i < tx.vout.size(); i++) { + // Verify sender output signature + if(tx.vout[i].scriptPubKey.HasOpSender()) + { + CScriptCheck check(tx, i, 0, cacheSigStore, &txdata); + if (pvChecks) { + pvChecks->emplace_back(std::move(check)); + } else if (!check()) { + return state.Invalid(TxValidationResult::TX_CONSENSUS, strprintf("sender-output-script-verify-failed (%s)", ScriptErrorString(check.GetScriptError()))); + } + } + } + if (cacheFullScriptStore && !pvChecks) { // We executed all of the provided scripts, and were told to // cache the result. Do so now. @@ -2105,6 +2274,36 @@ DisconnectResult Chainstate::DisconnectBlock(const CBlock& block, const CBlockIn // move best block pointer to prevout block view.SetBestBlock(pindex->pprev->GetBlockHash()); + globalState->setRoot(uintToh256(pindex->pprev->hashStateRoot)); // qtum + globalState->setRootUTXO(uintToh256(pindex->pprev->hashUTXORoot)); // qtum + + if(pfClean == NULL && fLogEvents){ + pstorageresult->deleteResults(block.vtx); + m_blockman.m_block_tree_db->EraseHeightIndex(pindex->nHeight); + } + + // The stake and delegate index is needed for MPoS, update it while MPoS is active + const CChainParams& chainparams{m_chainman.GetParams()}; + if(pindex->nHeight <= chainparams.GetConsensus().nLastMPoSBlock) + { + m_blockman.m_block_tree_db->EraseStakeIndex(pindex->nHeight); + if(pindex->IsProofOfStake() && pindex->HasProofOfDelegation()) + m_blockman.m_block_tree_db->EraseDelegateIndex(pindex->nHeight); + } + + //////////////////////////////////////////////////// // qtum + if (pfClean == NULL && fAddressIndex) { + if (!m_blockman.m_block_tree_db->EraseAddressIndex(addressIndex)) { + error("Failed to delete address index"); + return DISCONNECT_FAILED; + } + if (!m_blockman.m_block_tree_db->UpdateAddressUnspentIndex(addressUnspentIndex)) { + error("Failed to write address unspent index"); + return DISCONNECT_FAILED; + } + } + //////////////////////////////////////////////////// + return fClean ? DISCONNECT_OK : DISCONNECT_UNCLEAN; } @@ -2183,6 +2382,10 @@ static unsigned int GetBlockScriptFlags(const CBlockIndex& block_index, const Ch if (DeploymentActiveAt(block_index, chainman, Consensus::DEPLOYMENT_SEGWIT)) { flags |= SCRIPT_VERIFY_NULLDUMMY; } + // Start support sender address in contract output + if (block_index.nHeight >= consensusparams.QIP5Height) { + flags |= SCRIPT_OUTPUT_SENDER; + } return flags; } @@ -2214,7 +2417,40 @@ bool GetSpentCoinFromBlock(const CBlockIndex* pindex, COutPoint prevout, Coin* c bool GetSpentCoinFromMainChain(const CBlockIndex* pforkPrev, COutPoint prevoutStake, Coin* coin, CChain& chain) { const CBlockIndex* pforkBase = chain.FindFork(pforkPrev); - return {}; + + // If the forkbase is more than coinbaseMaturity blocks in the past, do not attempt to scan the main chain. + int nHeight = chain.Tip()->nHeight; + int coinbaseMaturity = Params().GetConsensus().CoinbaseMaturity(nHeight); + if(nHeight - pforkBase->nHeight > coinbaseMaturity) { + return error("The fork's base is behind by more than 500 blocks"); + } + + // First, we make sure that the prevout has not been spent in any of pforktip's ancestors as the prevoutStake. + // This is done to prevent a single staker building a long chain based on only a single prevout. + { + const CBlockIndex* pindex = pforkPrev; + while(pindex && pindex != pforkBase) { + // The coinstake has already been spent in the fork. + if(pindex->prevoutStake == prevoutStake) { + return error("prevout already spent in the orphan chain"); + } + pindex = pindex->pprev; + } + } + + // Scan through blocks until we reach the forkbase to check if the prevoutStake has been spent in one of those blocks + // If it not in any of those blocks, and not in the utxo set, it can't be spendable in the orphan chain. + { + CBlockIndex* pindex = chain.Tip(); + while(pindex && pindex != pforkBase) { + if(GetSpentCoinFromBlock(pindex, prevoutStake, coin)) { + return true; + } + pindex = pindex->pprev; + } + } + + return false; } bool CheckOpSender(const CTransaction& tx, const CChainParams& chainparams, int nHeight){ @@ -2290,7 +2526,45 @@ bool CheckSenderScript(const CCoinsViewCache& view, const CTransaction& tx){ } std::vector CallContract(const dev::Address& addrContract, std::vector opcode, Chainstate& chainstate, const dev::Address& sender, uint64_t gasLimit, CAmount nAmount){ - return {}; + CBlock block; + CMutableTransaction tx; + + CBlockIndex* pblockindex = &(chainstate.m_blockman.m_block_index[chainstate.m_chain.Tip()->GetBlockHash()]); + chainstate.m_blockman.ReadBlockFromDisk(block, *pblockindex); + block.nTime = GetAdjustedTimeSeconds(); + + if(block.IsProofOfStake()) + block.vtx.erase(block.vtx.begin()+2,block.vtx.end()); + else + block.vtx.erase(block.vtx.begin()+1,block.vtx.end()); + + QtumDGP qtumDGP(globalState.get(), chainstate, fGettingValuesDGP); + uint64_t blockGasLimit = qtumDGP.getBlockGasLimit(chainstate.m_chain.Tip()->nHeight + 1); + + if(gasLimit == 0){ + gasLimit = blockGasLimit - 1; + } + dev::Address senderAddress = sender == dev::Address() ? dev::Address("ffffffffffffffffffffffffffffffffffffffff") : sender; + tx.vout.push_back(CTxOut(nAmount, CScript() << OP_DUP << OP_HASH160 << senderAddress.asBytes() << OP_EQUALVERIFY << OP_CHECKSIG)); + block.vtx.push_back(MakeTransactionRef(CTransaction(tx))); + dev::u256 nonce = globalState->getNonce(senderAddress); + + QtumTransaction callTransaction; + if(addrContract == dev::Address()) + { + callTransaction = QtumTransaction(nAmount, 1, dev::u256(gasLimit), opcode, nonce); + } + else + { + callTransaction = QtumTransaction(nAmount, 1, dev::u256(gasLimit), addrContract, opcode, nonce); + } + callTransaction.forceSender(senderAddress); + callTransaction.setVersion(VersionVM::GetEVMDefault()); + + + ByteCodeExec exec(block, std::vector(1, callTransaction), blockGasLimit, pblockindex, chainstate.m_chain); + exec.performByteCode(dev::eth::Permanence::Reverted); + return exec.getResult(); } bool CheckMinGasPrice(std::vector& etps, const uint64_t& minGasPrice){ @@ -2627,6 +2901,35 @@ bool Chainstate::ConnectBlock(const CBlock& block, BlockValidationState& state, const auto time_start{SteadyClock::now()}; const CChainParams& params{m_chainman.GetParams()}; + ///////////////////////////////////////////////// // qtum + QtumDGP qtumDGP(globalState.get(), *this, fGettingValuesDGP); + globalSealEngine->setQtumSchedule(qtumDGP.getGasSchedule(pindex->nHeight + (pindex->nHeight+1 >= params.GetConsensus().QIP7Height ? 0 : 1) )); + uint32_t sizeBlockDGP = qtumDGP.getBlockSize(pindex->nHeight + (pindex->nHeight+1 >= params.GetConsensus().QIP7Height ? 0 : 1)); + uint64_t minGasPrice = qtumDGP.getMinGasPrice(pindex->nHeight + (pindex->nHeight+1 >= params.GetConsensus().QIP7Height ? 0 : 1)); + uint64_t blockGasLimit = qtumDGP.getBlockGasLimit(pindex->nHeight + (pindex->nHeight+1 >= params.GetConsensus().QIP7Height ? 0 : 1)); + dgpMaxBlockSize = sizeBlockDGP ? sizeBlockDGP : dgpMaxBlockSize; + updateBlockSizeParams(dgpMaxBlockSize); + CBlock checkBlock(block.GetBlockHeader()); + std::vector checkVouts; + + ///////////////////////////////////////////////// + // We recheck the hardened checkpoints here since ContextualCheckBlock(Header) is not called in ConnectBlock. + if(m_chainman.m_options.checkpoints_enabled && !m_blockman.CheckHardened(pindex->nHeight, block.GetHash(), params.Checkpoints())) { + return state.Invalid(BlockValidationResult::BLOCK_CHECKPOINT, "bad-fork-hardened-checkpoint", strprintf("%s: expected hardened checkpoint at height %d", __func__, pindex->nHeight)); + } + + + // Move this check from CheckBlock to ConnectBlock as it depends on DGP values + if (block.vtx.empty() || block.vtx.size() > dgpMaxBlockSize || ::GetSerializeSize(block, PROTOCOL_VERSION | SERIALIZE_TRANSACTION_NO_WITNESS) > dgpMaxBlockSize) // qtum + return state.Invalid(BlockValidationResult::BLOCK_CONSENSUS, "bad-blk-length", "size limits failed"); + + // Move this check from ContextualCheckBlock to ConnectBlock as it depends on DGP values + if (GetBlockWeight(block) > dgpMaxBlockWeight) { + return state.Invalid(BlockValidationResult::BLOCK_CONSENSUS, "bad-blk-weight", strprintf("%s : weight limit failed", __func__)); + } + + bool delegateOutputExist = false; + // Check it again in case a previous version let a bad block in // NOTE: We don't currently (re-)invoke ContextualCheckBlock() or // ContextualCheckBlockHeader() here. This means that if we add a new @@ -2664,6 +2967,11 @@ bool Chainstate::ConnectBlock(const CBlock& block, BlockValidationState& state, return true; } + // State is filled in by UpdateHashProof + if (!UpdateHashProof(block, state, params.GetConsensus(), pindex, view)) { + return error("%s: ConnectBlock(): %s", __func__, state.GetRejectReason().c_str()); + } + bool fScriptChecks = true; if (!m_chainman.AssumedValidBlock().IsNull()) { // We've been configured with the hash of a block which has been externally verified to have a valid history. @@ -2740,7 +3048,7 @@ bool Chainstate::ConnectBlock(const CBlock& block, BlockValidationState& state, // future consensus change to do a new and improved version of BIP34 that // will actually prevent ever creating any duplicate coinbases in the // future. - static constexpr int BIP34_IMPLIES_BIP30_LIMIT = 1983702; + static constexpr int BIP34_IMPLIES_BIP30_LIMIT = 0; // There is no potential to create a duplicate coinbase at block 209,921 // because this is still before the BIP34 height and so explicit BIP30 @@ -2796,6 +3104,7 @@ bool Chainstate::ConnectBlock(const CBlock& block, BlockValidationState& state, // Get the script flags for this block unsigned int flags{GetBlockScriptFlags(*pindex, m_chainman)}; + unsigned int contractflags = GetContractScriptFlags(pindex->nHeight, params.GetConsensus()); const auto time_2{SteadyClock::now()}; time_forks += time_2 - time_1; @@ -2816,9 +3125,34 @@ bool Chainstate::ConnectBlock(const CBlock& block, BlockValidationState& state, std::vector prevheights; CAmount nFees = 0; + CAmount nActualStakeReward = 0; + CAmount nValueCoinPrev = 0; int nInputs = 0; int64_t nSigOpsCost = 0; blockundo.vtxundo.reserve(block.vtx.size() - 1); + + ///////////////////////////////////////////////////////// // qtum + std::vector > addressIndex; + std::vector > addressUnspentIndex; + std::vector > spentIndex; + std::map>> heightIndexes; + ///////////////////////////////////////////////////////// + + uint64_t blockGasUsed = 0; + CAmount gasRefunds=0; + + uint64_t nValueOut=0; + uint64_t nValueIn=0; + + if(block.IsProofOfStake()) + { + Coin coin; + if(!view.GetCoin(block.vtx[1]->vin[0].prevout, coin)){ + return state.Invalid(BlockValidationResult::BLOCK_CONSENSUS, "stake-prevout-not-exist", strprintf("ConnectBlock() : Stake prevout does not exist %s", block.vtx[1]->vin[0].prevout.hash.ToString())); + } + nValueCoinPrev = coin.out.nValue; + } + for (unsigned int i = 0; i < block.vtx.size(); i++) { const CTransaction &tx = *(block.vtx[i]); @@ -2860,17 +3194,22 @@ bool Chainstate::ConnectBlock(const CBlock& block, BlockValidationState& state, // * p2sh (when P2SH enabled in flags and excludes coinbase) // * witness (when witness enabled in flags and excludes coinbase) nSigOpsCost += GetTransactionSigOpCost(tx, view, flags); - if (nSigOpsCost > MAX_BLOCK_SIGOPS_COST) { + if (nSigOpsCost > dgpMaxBlockSigOps) { LogPrintf("ERROR: ConnectBlock(): too many sigops\n"); return state.Invalid(BlockValidationResult::BLOCK_CONSENSUS, "bad-blk-sigops"); } + bool hasOpSpend = tx.HasOpSpend(); + if (!tx.IsCoinBase()) { + if (tx.IsCoinStake()) + nActualStakeReward = tx.GetValueOut()-view.GetValueIn(tx); + std::vector vChecks; bool fCacheResults = fJustCheck; /* Don't cache results if we're actually connecting blocks (still consult the cache, though) */ TxValidationState tx_state; - if (fScriptChecks && !CheckInputScripts(tx, tx_state, view, flags, fCacheResults, fCacheResults, txsdata[i], parallel_script_checks ? &vChecks : nullptr)) { + if (fScriptChecks && !CheckInputScripts(tx, tx_state, view, flags, fCacheResults, fCacheResults, txsdata[i], (hasOpSpend || tx.HasCreateOrCall()) ? nullptr : (parallel_script_checks ? &vChecks : nullptr))) { // Any transaction validation failure in ConnectBlock is a block consensus failure state.Invalid(BlockValidationResult::BLOCK_CONSENSUS, tx_state.GetRejectReason(), tx_state.GetDebugMessage()); @@ -2878,8 +3217,177 @@ bool Chainstate::ConnectBlock(const CBlock& block, BlockValidationState& state, tx.GetHash().ToString(), state.ToString()); } control.Add(std::move(vChecks)); + + for(const CTxIn& j : tx.vin){ + if(!j.scriptSig.HasOpSpend()){ + const CTxOut& prevout = view.AccessCoin(j.prevout).out; + if((prevout.scriptPubKey.HasOpCreate() || prevout.scriptPubKey.HasOpCall())){ + return state.Invalid(BlockValidationResult::BLOCK_CONSENSUS, "bad-txns-invalid-contract-spend", "ConnectBlock(): Contract spend without OP_SPEND in scriptSig"); + } + } + } + } + + if(tx.IsCoinBase()){ + nValueOut += tx.GetValueOut(); + }else{ + int64_t nTxValueIn = view.GetValueIn(tx); + int64_t nTxValueOut = tx.GetValueOut(); + nValueIn += nTxValueIn; + nValueOut += nTxValueOut; } +///////////////////////////////////////////////////////////////////////////////////////// qtum + if(!CheckOpSender(tx, params, pindex->nHeight)){ + return state.Invalid(BlockValidationResult::BLOCK_CONSENSUS, "bad-txns-invalid-sender"); + } + if(!tx.HasOpSpend()){ + checkBlock.vtx.push_back(block.vtx[i]); + } + if(tx.HasCreateOrCall() && !hasOpSpend){ + + if(!CheckSenderScript(view, tx)){ + return state.Invalid(BlockValidationResult::BLOCK_CONSENSUS, "bad-txns-invalid-sender-script"); + } + + QtumTxConverter convert(tx, *this, m_mempool, &view, &block.vtx, contractflags); + + ExtractQtumTX resultConvertQtumTX; + if(!convert.extractionQtumTransactions(resultConvertQtumTX)){ + return state.Invalid(BlockValidationResult::BLOCK_CONSENSUS, "bad-tx-bad-contract-format", "ConnectBlock(): Contract transaction of the wrong format"); + } + if(!CheckMinGasPrice(resultConvertQtumTX.second, minGasPrice)) + return state.Invalid(BlockValidationResult::BLOCK_CONSENSUS, "bad-tx-low-gas-price", "ConnectBlock(): Contract execution has lower gas price than allowed"); + + + dev::u256 gasAllTxs = dev::u256(0); + ByteCodeExec exec(block, resultConvertQtumTX.first, blockGasLimit, pindex->pprev, m_chain); + //validate VM version and other ETH params before execution + //Reject anything unknown (could be changed later by DGP) + //TODO evaluate if this should be relaxed for soft-fork purposes + bool nonZeroVersion=false; + dev::u256 sumGas = dev::u256(0); + CAmount nTxFee = view.GetValueIn(tx)-tx.GetValueOut(); + for(QtumTransaction& qtx : resultConvertQtumTX.first){ + sumGas += qtx.gas() * qtx.gasPrice(); + + if(sumGas > dev::u256(INT64_MAX)) { + return state.Invalid(BlockValidationResult::BLOCK_CONSENSUS, "bad-tx-gas-stipend-overflow", "ConnectBlock(): Transaction's gas stipend overflows"); + } + + if(sumGas > dev::u256(nTxFee)) { + return state.Invalid(BlockValidationResult::BLOCK_CONSENSUS, "bad-txns-fee-notenough", "ConnectBlock(): Transaction fee does not cover the gas stipend"); + } + + VersionVM v = qtx.getVersion(); + if(v.format!=0) + return state.Invalid(BlockValidationResult::BLOCK_CONSENSUS, "bad-tx-version-format", "ConnectBlock(): Contract execution uses unknown version format"); + if(v.rootVM != 0){ + nonZeroVersion=true; + }else{ + if(nonZeroVersion){ + //If an output is version 0, then do not allow any other versions in the same tx + return state.Invalid(BlockValidationResult::BLOCK_CONSENSUS, "bad-tx-mixed-zero-versions", "ConnectBlock(): Contract tx has mixed version 0 and non-0 VM executions"); + } + } + if(!(v.rootVM == 0 || v.rootVM == 1)) + return state.Invalid(BlockValidationResult::BLOCK_CONSENSUS, "bad-tx-version-rootvm", "ConnectBlock(): Contract execution uses unknown root VM"); + if(v.vmVersion != 0) + return state.Invalid(BlockValidationResult::BLOCK_CONSENSUS, "bad-tx-version-vmversion", "ConnectBlock(): Contract execution uses unknown VM version"); + if(v.flagOptions != 0) + return state.Invalid(BlockValidationResult::BLOCK_CONSENSUS, "bad-tx-version-flags", "ConnectBlock(): Contract execution uses unknown flag options"); + + //check gas limit is not less than minimum gas limit (unless it is a no-exec tx) + if(qtx.gas() < MINIMUM_GAS_LIMIT && v.rootVM != 0) + return state.Invalid(BlockValidationResult::BLOCK_CONSENSUS, "bad-tx-too-little-gas", "ConnectBlock(): Contract execution has lower gas limit than allowed"); + + if(qtx.gas() > UINT32_MAX) + return state.Invalid(BlockValidationResult::BLOCK_CONSENSUS, "bad-tx-too-much-gas", "ConnectBlock(): Contract execution can not specify greater gas limit than can fit in 32-bits"); + + gasAllTxs += qtx.gas(); + if(gasAllTxs > dev::u256(blockGasLimit)) + return state.Invalid(BlockValidationResult::BLOCK_GAS_EXCEEDS_LIMIT, "bad-txns-gas-exceeds-blockgaslimit"); + + //don't allow less than DGP set minimum gas price to prevent MPoS greedy mining/spammers + if(v.rootVM!=0 && (uint64_t)qtx.gasPrice() < minGasPrice) + return state.Invalid(BlockValidationResult::BLOCK_CONSENSUS, "bad-tx-low-gas-price", "ConnectBlock(): Contract execution has lower gas price than allowed"); + } + + if(!nonZeroVersion){ + //if tx is 0 version, then the tx must already have been added by a previous contract execution + if(!tx.HasOpSpend()){ + return state.Invalid(BlockValidationResult::BLOCK_CONSENSUS, "bad-tx-improper-version-0", "ConnectBlock(): Version 0 contract executions are not allowed unless created by the AAL"); + } + } + + if(!exec.performByteCode()){ + return state.Invalid(BlockValidationResult::BLOCK_CONSENSUS, "bad-tx-unknown-error", "ConnectBlock(): Unknown error during contract execution"); + } + + std::vector resultExec(exec.getResult()); + ByteCodeExecResult bcer; + if(!exec.processingResults(bcer)){ + return state.Invalid(BlockValidationResult::BLOCK_CONSENSUS, "bad-vm-exec-processing", "ConnectBlock(): Error processing VM execution results"); + } + + std::vector tri; + if (fLogEvents && !fJustCheck) + { + uint64_t countCumulativeGasUsed = blockGasUsed; + for(size_t k = 0; k < resultConvertQtumTX.first.size(); k ++){ + for(auto& log : resultExec[k].txRec.log()) { + if(!heightIndexes.count(log.address)){ + heightIndexes[log.address].first = CHeightTxIndexKey(pindex->nHeight, log.address); + } + heightIndexes[log.address].second.push_back(tx.GetHash()); + } + uint64_t gasUsed = uint64_t(resultExec[k].execRes.gasUsed); + countCumulativeGasUsed += gasUsed; + tri.push_back(TransactionReceiptInfo{ + block.GetHash(), + uint32_t(pindex->nHeight), + tx.GetHash(), + uint32_t(i), + resultConvertQtumTX.first[k].from(), + resultConvertQtumTX.first[k].to(), + countCumulativeGasUsed, + gasUsed, + resultExec[k].execRes.newAddress, + resultExec[k].txRec.log(), + resultExec[k].execRes.excepted, + exceptedMessage(resultExec[k].execRes.excepted, resultExec[k].execRes.output), + resultConvertQtumTX.first[k].getNVout(), + resultExec[k].txRec.bloom(), + resultExec[k].txRec.stateRoot(), + resultExec[k].txRec.utxoRoot(), + }); + } + + pstorageresult->addResult(uintToh256(tx.GetHash()), tri); + } + + blockGasUsed += bcer.usedGas; + if(blockGasUsed > blockGasLimit){ + return state.Invalid(BlockValidationResult::BLOCK_CONSENSUS, "bad-blk-gaslimit", "ConnectBlock(): Block exceeds gas limit"); + } + for(CTxOut refundVout : bcer.refundOutputs){ + gasRefunds += refundVout.nValue; + } + checkVouts.insert(checkVouts.end(), bcer.refundOutputs.begin(), bcer.refundOutputs.end()); + for(CTransaction& t : bcer.valueTransfers){ + checkBlock.vtx.push_back(MakeTransactionRef(std::move(t))); + } + if(fRecordLogOpcodes && !fJustCheck){ + writeVMlog(resultExec, m_chain, tx, block); + } + + for(ResultExecute& re: resultExec){ + if(re.execRes.newAddress != dev::Address() && !fJustCheck) + dev::g_logPost(std::string("Address : " + re.execRes.newAddress.hex()), NULL); + } + } +///////////////////////////////////////////////////////////////////////////////////////// + CTxUndo undoDummy; if (i > 0) { blockundo.vtxundo.emplace_back(); @@ -2894,11 +3402,11 @@ bool Chainstate::ConnectBlock(const CBlock& block, BlockValidationState& state, Ticks(time_connect), Ticks(time_connect) / num_blocks_total); - CAmount blockReward = nFees + GetBlockSubsidy(pindex->nHeight, params.GetConsensus()); - if (block.vtx[0]->GetValueOut() > blockReward) { - LogPrintf("ERROR: ConnectBlock(): coinbase pays too much (actual=%d vs limit=%d)\n", block.vtx[0]->GetValueOut(), blockReward); - return state.Invalid(BlockValidationResult::BLOCK_CONSENSUS, "bad-cb-amount"); + if(nFees < gasRefunds) { //make sure it won't overflow + return state.Invalid(BlockValidationResult::BLOCK_CONSENSUS, "bad-blk-fees-greater-gasrefund", "ConnectBlock(): Less total fees than gas refund fees"); } + if(!CheckReward(block, state, pindex->nHeight, params.GetConsensus(), nFees, gasRefunds, nActualStakeReward, checkVouts, nValueCoinPrev, delegateOutputExist, m_chain, m_blockman)) + return state.Invalid(BlockValidationResult::BLOCK_CONSENSUS, "block-reward-invalid", "ConnectBlock(): Reward check failed"); if (!control.Wait()) { LogPrintf("ERROR: %s: CheckQueue failed\n", __func__); @@ -2912,8 +3420,90 @@ bool Chainstate::ConnectBlock(const CBlock& block, BlockValidationState& state, Ticks(time_verify), Ticks(time_verify) / num_blocks_total); +////////////////////////////////////////////////////////////////// // qtum + if(pindex->nHeight == params.GetConsensus().nOfflineStakeHeight){ + globalState->deployDelegationsContract(); + } + checkBlock.hashMerkleRoot = BlockMerkleRoot(checkBlock); + checkBlock.hashStateRoot = h256Touint(globalState->rootHash()); + checkBlock.hashUTXORoot = h256Touint(globalState->rootHashUTXO()); + + //If this error happens, it probably means that something with AAL created transactions didn't match up to what is expected + if((checkBlock.GetHash() != block.GetHash()) && !fJustCheck) + { + LogPrintf("Actual block data does not match block expected by AAL\n"); + //Something went wrong with AAL, compare different elements and determine what the problem is + if(checkBlock.hashMerkleRoot != block.hashMerkleRoot){ + //there is a mismatched tx, so go through and determine which txs + if(block.vtx.size() > checkBlock.vtx.size()){ + LogPrintf("Unexpected AAL transactions in block. Actual txs: %i, expected txs: %i\n", block.vtx.size(), checkBlock.vtx.size()); + for(size_t i=0;i checkBlock.vtx.size()-1){ + LogPrintf("Unexpected transaction: %s\n", block.vtx[i]->ToString()); + }else { + if (block.vtx[i]->GetHash() != checkBlock.vtx[i]->GetHash()) { + LogPrintf("Mismatched transaction at entry %i\n", i); + LogPrintf("Actual: %s\n", block.vtx[i]->ToString()); + LogPrintf("Expected: %s\n", checkBlock.vtx[i]->ToString()); + } + } + } + }else if(block.vtx.size() < checkBlock.vtx.size()){ + LogPrintf("Actual block is missing AAL transactions. Actual txs: %i, expected txs: %i\n", block.vtx.size(), checkBlock.vtx.size()); + for(size_t i=0;i block.vtx.size()-1){ + LogPrintf("Missing transaction: %s\n", checkBlock.vtx[i]->ToString()); + }else { + if (block.vtx[i]->GetHash() != checkBlock.vtx[i]->GetHash()) { + LogPrintf("Mismatched transaction at entry %i\n", i); + LogPrintf("Actual: %s\n", block.vtx[i]->ToString()); + LogPrintf("Expected: %s\n", checkBlock.vtx[i]->ToString()); + } + } + } + }else{ + //count is correct, but a tx is wrong + for(size_t i=0;iGetHash() != checkBlock.vtx[i]->GetHash()) { + LogPrintf("Mismatched transaction at entry %i\n", i); + LogPrintf("Actual: %s\n", block.vtx[i]->ToString()); + LogPrintf("Expected: %s\n", checkBlock.vtx[i]->ToString()); + } + } + } + } + if(checkBlock.hashUTXORoot != block.hashUTXORoot){ + LogPrintf("Actual block data does not match hashUTXORoot expected by AAL block\n"); + } + if(checkBlock.hashStateRoot != block.hashStateRoot){ + LogPrintf("Actual block data does not match hashStateRoot expected by AAL block\n"); + } + + return state.Invalid(BlockValidationResult::BLOCK_CONSENSUS, "incorrect-transactions-or-hashes-block", "ConnectBlock(): Incorrect AAL transactions or hashes (hashStateRoot, hashUTXORoot)"); + } + if (fJustCheck) + { + dev::h256 prevHashStateRoot(dev::sha3(dev::rlp(""))); + dev::h256 prevHashUTXORoot(dev::sha3(dev::rlp(""))); + if(pindex->pprev->hashStateRoot != uint256() && pindex->pprev->hashUTXORoot != uint256()){ + prevHashStateRoot = uintToh256(pindex->pprev->hashStateRoot); + prevHashUTXORoot = uintToh256(pindex->pprev->hashUTXORoot); + } + globalState->setRoot(prevHashStateRoot); + globalState->setRootUTXO(prevHashUTXORoot); return true; + } +////////////////////////////////////////////////////////////////// + + pindex->nMoneySupply = (pindex->pprev? pindex->pprev->nMoneySupply : 0) + nValueOut - nValueIn; + //only start checking this error after block 5000 and only on testnet and mainnet, not regtest + if(pindex->nHeight > 5000 && !params.MineBlocksOnDemand()) { + //sanity check in case an exploit happens that allows new coins to be minted + if(pindex->nMoneySupply > (uint64_t)(100000000 + ((pindex->nHeight - 5000) * 4)) * COIN){ + return state.Invalid(BlockValidationResult::BLOCK_CONSENSUS, "incorrect-money-supply", "ConnectBlock(): Unknown error caused actual money supply to exceed expected money supply"); + } + } if (!m_blockman.WriteUndoDataForBlock(blockundo, state, *pindex)) { return false; @@ -2931,6 +3521,75 @@ bool Chainstate::ConnectBlock(const CBlock& block, BlockValidationState& state, m_blockman.m_dirty_blockindex.insert(pindex); } + if (fLogEvents) + { + for (const auto& e: heightIndexes) + { + if (!m_blockman.m_block_tree_db->WriteHeightIndex(e.second.first, e.second.second)) + return FatalError(m_chainman.GetNotifications(), state, "Failed to write height index"); + } + } + + // The stake and delegate index is needed for MPoS, update it while MPoS is active + if(pindex->nHeight <= params.GetConsensus().nLastMPoSBlock) + { + if(block.IsProofOfStake()){ + // Read the public key from the second output + std::vector vchPubKey; + uint160 pkh; + if(GetBlockPublicKey(block, vchPubKey)) + { + pkh = uint160(ToByteVector(CPubKey(vchPubKey).GetID())); + m_blockman.m_block_tree_db->WriteStakeIndex(pindex->nHeight, pkh); + }else{ + m_blockman.m_block_tree_db->WriteStakeIndex(pindex->nHeight, uint160()); + } + + if(block.HasProofOfDelegation()) + { + uint160 address; + uint8_t fee = 0; + GetBlockDelegation(block, pkh, address, fee, view, *this); + m_blockman.m_block_tree_db->WriteDelegateIndex(pindex->nHeight, address, fee); + } + }else{ + m_blockman.m_block_tree_db->WriteStakeIndex(pindex->nHeight, uint160()); + } + } + + ///////////////////////////////////////////////////////////// // qtum + if (fAddressIndex) { + if (!m_blockman.m_block_tree_db->WriteAddressIndex(addressIndex)) { + return FatalError(m_chainman.GetNotifications(), state, "Failed to write address index"); + } + if (!m_blockman.m_block_tree_db->UpdateAddressUnspentIndex(addressUnspentIndex)) { + return FatalError(m_chainman.GetNotifications(), state, "Failed to write address unspent index"); + } + + if (!m_blockman.m_block_tree_db->UpdateSpentIndex(spentIndex)) + return FatalError(m_chainman.GetNotifications(), state, "Failed to write transaction index"); + + unsigned int logicalTS = pindex->nTime; + unsigned int prevLogicalTS = 0; + + // retrieve logical timestamp of the previous block + if (pindex->pprev) + if (!m_blockman.m_block_tree_db->ReadTimestampBlockIndex(pindex->pprev->GetBlockHash(), prevLogicalTS)) + LogPrintf("%s: Failed to read previous block's logical timestamp\n", __func__); + + if (logicalTS <= prevLogicalTS) { + logicalTS = prevLogicalTS + 1; + LogPrint(BCLog::INDEX, "%s: Previous logical timestamp is newer Actual[%d] prevLogical[%d] Logical[%d]\n", __func__, pindex->nTime, prevLogicalTS, logicalTS); + } + + if (!m_blockman.m_block_tree_db->WriteTimestampIndex(CTimestampIndexKey(logicalTS, pindex->GetBlockHash()))) + return FatalError(m_chainman.GetNotifications(), state, "Failed to write timestamp index"); + + if (!m_blockman.m_block_tree_db->WriteTimestampBlockIndex(CTimestampBlockIndexKey(pindex->GetBlockHash()), CTimestampBlockIndexValue(logicalTS))) + return FatalError(m_chainman.GetNotifications(), state, "Failed to write blockhash index"); + } + ///////////////////////////////////////////////////////////// + // add this block to the view's block chain view.SetBestBlock(pindex->GetBlockHash()); @@ -2950,6 +3609,9 @@ bool Chainstate::ConnectBlock(const CBlock& block, BlockValidationState& state, time_5 - time_start // in microseconds (µs) ); + if (fLogEvents) + pstorageresult->commitResults(); + return true; } @@ -2967,7 +3629,7 @@ CoinsCacheSizeState Chainstate::GetCoinsCacheSizeState( { AssertLockHeld(::cs_main); const int64_t nMempoolUsage = m_mempool ? m_mempool->DynamicMemoryUsage() : 0; - int64_t cacheSize = CoinsTip().DynamicMemoryUsage(); + int64_t cacheSize = CoinsTip().DynamicMemoryUsage() * DB_PEAK_USAGE_FACTOR; int64_t nTotalSpace = max_coins_cache_size_bytes + std::max(int64_t(max_mempool_size_bytes) - nMempoolUsage, 0); @@ -3376,11 +4038,19 @@ bool Chainstate::ConnectTip(BlockValidationState& state, CBlockIndex* pindexNew, Ticks(time_2 - time_1)); { CCoinsViewCache view(&CoinsTip()); + + dev::h256 oldHashStateRoot(globalState->rootHash()); // qtum + dev::h256 oldHashUTXORoot(globalState->rootHashUTXO()); // qtum + bool rv = ConnectBlock(blockConnecting, state, pindexNew, view); GetMainSignals().BlockChecked(blockConnecting, state); if (!rv) { if (state.IsInvalid()) InvalidBlockFound(pindexNew, state); + + globalState->setRoot(oldHashStateRoot); // qtum + globalState->setRootUTXO(oldHashUTXORoot); // qtum + pstorageresult->clearCacheResult(); return error("%s: ConnectBlock %s failed, %s", __func__, pindexNew->GetBlockHash().ToString(), state.ToString()); } time_3 = SteadyClock::now(); @@ -4148,12 +4818,41 @@ bool GetBlockDelegation(const CBlock& block, const uint160& staker, uint160& add return {}; } -static bool CheckBlockHeader(const CBlockHeader& block, BlockValidationState& state, const Consensus::Params& consensusParams, bool fCheckPOW = true) +bool CheckBlockSignature(const CBlock& block) +{ + std::vector vchBlockSig = block.GetBlockSignature(); + if (block.IsProofOfWork()) + return vchBlockSig.empty(); + + std::vector vchPubKey; + if(!GetBlockPublicKey(block, vchPubKey)) + { + return false; + } + + uint256 hash = block.GetHashWithoutSign(); + + if(vchBlockSig.size() == CPubKey::COMPACT_SIGNATURE_SIZE) + { + CPubKey pubkey; + if(pubkey.RecoverCompact(hash, vchBlockSig) && pubkey == CPubKey(vchPubKey)) + return true; + } + + return CPubKey(vchPubKey).Verify(hash, vchBlockSig); +} + +static bool CheckBlockHeader(const CBlockHeader& block, BlockValidationState& state, const Consensus::Params& consensusParams, Chainstate& chainstate, bool fCheckPOW = true, bool fCheckPOS = true) { // Check proof of work matches claimed amount - if (fCheckPOW && !CheckProofOfWork(block.GetHash(), block.nBits, consensusParams)) + if (fCheckPOW && block.IsProofOfWork() && !CheckHeaderPoW(block, consensusParams)) return state.Invalid(BlockValidationResult::BLOCK_INVALID_HEADER, "high-hash", "proof of work failed"); + // Check proof of stake matches claimed amount + if (fCheckPOS && !chainstate.m_chainman.IsInitialBlockDownload() && block.IsProofOfStake() && !CheckHeaderPoS(block, consensusParams, chainstate)) + // May occur if behind on block chain sync + return state.Invalid(BlockValidationResult::BLOCK_INVALID_HEADER, "bad-cb-header", "proof of stake failed"); + return true; } @@ -4166,9 +4865,12 @@ bool CheckBlock(const CBlock& block, BlockValidationState& state, const Consensu // Check that the header is valid (particularly PoW). This is mostly // redundant with the call in AcceptBlockHeader. - if (!CheckBlockHeader(block, state, consensusParams, fCheckPOW)) + if (!CheckBlockHeader(block, state, consensusParams, chainstate, fCheckPOW, false)) return false; + if (block.IsProofOfStake() && block.GetBlockTime() > FutureDrift(GetAdjustedTimeSeconds(), chainstate.m_chain.Height() + 1, consensusParams)) + return error("CheckBlock() : block timestamp too far in the future"); + // Signet only: check block solution if (consensusParams.signet_blocks && fCheckPOW && !CheckSignetBlockSolution(block, consensusParams)) { return state.Invalid(BlockValidationResult::BLOCK_CONSENSUS, "bad-signet-blksig", "signet block signature validation failure"); @@ -4194,10 +4896,6 @@ bool CheckBlock(const CBlock& block, BlockValidationState& state, const Consensu // Note that witness malleability is checked in ContextualCheckBlock, so no // checks that use witness data may be performed here. - // Size limits - if (block.vtx.empty() || block.vtx.size() * WITNESS_SCALE_FACTOR > MAX_BLOCK_WEIGHT || ::GetSerializeSize(block, PROTOCOL_VERSION | SERIALIZE_TRANSACTION_NO_WITNESS) * WITNESS_SCALE_FACTOR > MAX_BLOCK_WEIGHT) - return state.Invalid(BlockValidationResult::BLOCK_CONSENSUS, "bad-blk-length", "size limits failed"); - // First transaction must be coinbase, the rest must not be if (block.vtx.empty() || !block.vtx[0]->IsCoinBase()) return state.Invalid(BlockValidationResult::BLOCK_CONSENSUS, "bad-cb-missing", "first tx is not coinbase"); @@ -4205,6 +4903,46 @@ bool CheckBlock(const CBlock& block, BlockValidationState& state, const Consensu if (block.vtx[i]->IsCoinBase()) return state.Invalid(BlockValidationResult::BLOCK_CONSENSUS, "bad-cb-multiple", "more than one coinbase"); + //Don't allow contract opcodes in coinbase + if(block.vtx[0]->HasOpSpend() || block.vtx[0]->HasCreateOrCall() || block.vtx[0]->HasOpSender()){ + return state.Invalid(BlockValidationResult::BLOCK_CONSENSUS, "bad-cb-contract", "coinbase must not contain OP_SPEND, OP_CALL, OP_CREATE or OP_SENDER"); + } + + // Second transaction must be coinbase in case of PoS block, the rest must not be + if (block.IsProofOfStake()) + { + // Coinbase output should be empty if proof-of-stake block + if (!CheckFirstCoinstakeOutput(block)) + return state.Invalid(BlockValidationResult::BLOCK_CONSENSUS, "bad-cb-missing", "coinbase output not empty for proof-of-stake block"); + + // Second transaction must be coinstake + if (block.vtx.empty() || block.vtx.size() < 2 || !block.vtx[1]->IsCoinStake()) + return state.Invalid(BlockValidationResult::BLOCK_CONSENSUS, "bad-cs-missing", "second tx is not coinstake"); + + if(!block.HasProofOfDelegation()) + { + //prevoutStake must exactly match the coinstake in the block body + if(block.vtx[1]->vin.empty() || block.prevoutStake != block.vtx[1]->vin[0].prevout){ + return state.Invalid(BlockValidationResult::BLOCK_CONSENSUS, "bad-cs-invalid", "prevoutStake in block header does not match coinstake in block body"); + } + } + //the rest of the transactions must not be coinstake + for (unsigned int i = 2; i < block.vtx.size(); i++) + if (block.vtx[i]->IsCoinStake()) + return state.Invalid(BlockValidationResult::BLOCK_CONSENSUS, "bad-cs-multiple", "more than one coinstake"); + + //Don't allow contract opcodes in coinstake + //We might allow this later, but it hasn't been tested enough to determine if safe + if(block.vtx[1]->HasOpSpend() || block.vtx[1]->HasCreateOrCall() || block.vtx[1]->HasOpSender()){ + return state.Invalid(BlockValidationResult::BLOCK_CONSENSUS, "bad-cs-contract", "coinstake must not contain OP_SPEND, OP_CALL, OP_CREATE or OP_SENDER"); + } + } + + // Check proof-of-stake block signature + if (fCheckSig && !CheckBlockSignature(block)) + return state.Invalid(BlockValidationResult::BLOCK_CONSENSUS, "bad-blk-signature", "bad proof-of-stake block signature"); + + bool lastWasContract=false; // Check transactions // Must check for duplicate inputs (see CVE-2018-17144) for (const auto& tx : block.vtx) { @@ -4216,13 +4954,21 @@ bool CheckBlock(const CBlock& block, BlockValidationState& state, const Consensu return state.Invalid(BlockValidationResult::BLOCK_CONSENSUS, tx_state.GetRejectReason(), strprintf("Transaction check failed (tx hash %s) %s", tx->GetHash().ToString(), tx_state.GetDebugMessage())); } + //OP_SPEND can only exist immediately after a contract tx in a block, or after another OP_SPEND + //So, if the previous tx was not a contract tx, fail it. + if(tx->HasOpSpend()){ + if(!lastWasContract){ + return state.Invalid(BlockValidationResult::BLOCK_CONSENSUS, "bad-opspend-tx", "OP_SPEND transaction without corresponding contract transaction"); + } + } + lastWasContract = tx->HasCreateOrCall() || tx->HasOpSpend(); } unsigned int nSigOps = 0; for (const auto& tx : block.vtx) { nSigOps += GetLegacySigOpCount(*tx); } - if (nSigOps * WITNESS_SCALE_FACTOR > MAX_BLOCK_SIGOPS_COST) + if (nSigOps * WITNESS_SCALE_FACTOR > dgpMaxBlockSigOps) return state.Invalid(BlockValidationResult::BLOCK_CONSENSUS, "bad-blk-sigops", "out-of-bounds SigOpCount"); if (fCheckPOW && fCheckMerkleRoot) @@ -4303,8 +5049,8 @@ static bool ContextualCheckBlockHeader(const CBlockHeader& block, BlockValidatio // Check proof of work const Consensus::Params& consensusParams = chainman.GetConsensus(); - if (block.nBits != GetNextWorkRequired(pindexPrev, &block, consensusParams)) - return state.Invalid(BlockValidationResult::BLOCK_INVALID_HEADER, "bad-diffbits", "incorrect proof of work"); + if (block.nBits != GetNextWorkRequired(pindexPrev, &block, consensusParams, block.IsProofOfStake())) + return state.Invalid(BlockValidationResult::BLOCK_INVALID_HEADER, "bad-diffbits", "incorrect difficulty value"); // Check against checkpoints if (chainman.m_options.checkpoints_enabled) { @@ -4316,14 +5062,22 @@ static bool ContextualCheckBlockHeader(const CBlockHeader& block, BlockValidatio LogPrintf("ERROR: %s: forked chain older than last checkpoint (height %d)\n", __func__, nHeight); return state.Invalid(BlockValidationResult::BLOCK_CHECKPOINT, "bad-fork-prior-to-checkpoint"); } + if(!blockman.CheckHardened(nHeight, block.GetHash(), chainman.GetParams().Checkpoints())) { + return state.Invalid(BlockValidationResult::BLOCK_CHECKPOINT, "bad-fork-hardened-checkpoint", strprintf("%s: expected hardened checkpoint at height %d", __func__, nHeight)); + } } + // Check that the block satisfies synchronized checkpoint + if (!blockman.CheckSync(nHeight, chainman.ActiveTip())) + return state.Invalid(BlockValidationResult::BLOCK_HEADER_SYNC, "bad-fork-prior-to-synch-checkpoint", strprintf("%s: forked chain older than synchronized checkpoint (height %d)", __func__, nHeight)); + // Check timestamp against prev - if (block.GetBlockTime() <= pindexPrev->GetMedianTimePast()) + if (pindexPrev && block.IsProofOfStake() && block.GetBlockTime() <= pindexPrev->GetMedianTimePast()) return state.Invalid(BlockValidationResult::BLOCK_INVALID_HEADER, "time-too-old", "block's timestamp is too early"); // Check timestamp - if (block.Time() > now + std::chrono::seconds{MAX_FUTURE_BLOCK_TIME}) { + int64_t nAdjustedTime = TicksSinceEpoch(now); + if (block.IsProofOfStake() && block.GetBlockTime() > FutureDrift(nAdjustedTime, nHeight, consensusParams)) { return state.Invalid(BlockValidationResult::BLOCK_TIME_FUTURE, "time-too-new", "block timestamp too far in the future"); } @@ -4413,16 +5167,6 @@ static bool ContextualCheckBlock(const CBlock& block, BlockValidationState& stat } } - // After the coinbase witness reserved value and commitment are verified, - // we can check if the block weight passes (before we've checked the - // coinbase witness, it would be possible for the weight to be too - // large by filling up the coinbase witness, which doesn't change - // the block hash, so we couldn't mark the block as permanently - // failed). - if (GetBlockWeight(block) > MAX_BLOCK_WEIGHT) { - return state.Invalid(BlockValidationResult::BLOCK_CONSENSUS, "bad-blk-weight", strprintf("%s : weight limit failed", __func__)); - } - return true; } @@ -4431,11 +5175,42 @@ bool Chainstate::UpdateHashProof(const CBlock& block, BlockValidationState& stat return {}; } +bool CheckPOS(const CBlockHeader& block, CBlockIndex* pindexPrev, Chainstate& chainstate) +{ + // Determining if PoS is possible to be checked in the header + int nHeight = pindexPrev->nHeight + 1; + int coinbaseMaturity = ::Params().GetConsensus().CoinbaseMaturity(nHeight); + int diff = nHeight - chainstate.m_chain.Height(); + if(pindexPrev && block.IsProofOfStake() && !chainstate.m_chainman.IsInitialBlockDownload() + // Additional check if not triggered initial block download, like when PoW blocks were initially created + // CheckPOS is called after ContextualCheckBlockHeader where future block headers are not accepted + && (diff < coinbaseMaturity)) + { + // Old header not child of the Tip + if(diff < -coinbaseMaturity) + return true; + + // New header + // Determining if the header is child of the Tip + CBlockIndex* prev = pindexPrev; + for(int i = 0; i < coinbaseMaturity; i++) + { + if(prev == chainstate.m_chain.Tip()) + return true; + prev = prev->pprev; + } + } + + // PoS header proofs are not validated + return false; +} + bool ChainstateManager::AcceptBlockHeader(const CBlockHeader& block, BlockValidationState& state, CBlockIndex** ppindex, bool min_pow_checked) { AssertLockHeld(cs_main); // Check for duplicate + Chainstate& chainstate = ActiveChainstate(); uint256 hash = block.GetHash(); BlockMap::iterator miSelf{m_blockman.m_block_index.find(hash)}; if (hash != GetConsensus().hashGenesisBlock) { @@ -4451,9 +5226,22 @@ bool ChainstateManager::AcceptBlockHeader(const CBlockHeader& block, BlockValida return true; } - if (!CheckBlockHeader(block, state, GetConsensus())) { - LogPrint(BCLog::VALIDATION, "%s: Consensus::CheckBlockHeader: %s, %s\n", __func__, hash.ToString(), state.ToString()); - return false; + // Check for the checkpoint + if (chainstate.m_chain.Tip() && block.hashPrevBlock != chainstate.m_chain.Tip()->GetBlockHash()) + { + // Extra checks to prevent "fill up memory by spamming with bogus blocks" + const CBlockIndex* pcheckpoint = m_blockman.AutoSelectSyncCheckpoint(chainstate.m_chain.Tip()); + int64_t deltaTime = block.GetBlockTime() - pcheckpoint->nTime; + if (deltaTime < 0) + { + return state.Invalid(BlockValidationResult::BLOCK_HEADER_SYNC, "older-than-checkpoint", "AcceptBlockHeader(): Block with a timestamp before last checkpoint"); + } + } + + // Check for the signiture encoding + if (!CheckCanonicalBlockSignature(&block)) + { + return state.Invalid(BlockValidationResult::BLOCK_INVALID_HEADER, "bad-signature-encoding", "AcceptBlockHeader(): bad block signature encoding"); } // Get prev block index @@ -4544,26 +5332,63 @@ bool ChainstateManager::AcceptBlockHeader(const CBlockHeader& block, BlockValida // Exposed wrapper for AcceptBlockHeader bool ChainstateManager::ProcessNewBlockHeaders(const std::vector& headers, bool min_pow_checked, BlockValidationState& state, const CBlockIndex** ppindex, const CBlockIndex** pindexFirst) { + if(!IsInitialBlockDownload() && headers.size() > 1) { + LOCK(cs_main); + const CBlockHeader last_header = headers[headers.size()-1]; + unsigned int nHeight = ActiveChain().Height() + 1; + if (last_header.IsProofOfStake() && last_header.GetBlockTime() > FutureDrift(GetAdjustedTimeSeconds(), nHeight, GetConsensus())) { + return state.Invalid(BlockValidationResult::BLOCK_TIME_FUTURE, "time-too-new", "block timestamp too far in the future"); + } + } AssertLockNotHeld(cs_main); { LOCK(cs_main); - for (const CBlockHeader& header : headers) { + bool bFirst = true; + bool fInstantBan = false; + for (size_t i = 0; i < headers.size(); ++i) { + const CBlockHeader& header = headers[i]; + + // If the stake has been seen and the header has not yet been seen + if (!m_blockman.LoadingBlocks() && !IsInitialBlockDownload() && header.IsProofOfStake() && setStakeSeen.count(std::make_pair(header.prevoutStake, header.nTime)) && !BlockIndex().count(header.GetHash())) { + // if it is the last header of the list + if(i+1 == headers.size()) { + if(fInstantBan) { + // if we've seen a dupe stake header already in this list, then instaban + return state.Invalid(BlockValidationResult::BLOCK_INVALID_HEADER, "dupe-stake", strprintf("%s: duplicate proof-of-stake instant ban (%s, %d) for header %s", __func__, header.prevoutStake.ToString(), header.nTime, header.GetHash().ToString())); + } else { + // otherwise just reject the block until it is part of a longer list + return state.Invalid(BlockValidationResult::BLOCK_HEADER_REJECT, "dupe-stake", strprintf("%s: duplicate proof-of-stake (%s, %d) for header %s", __func__, header.prevoutStake.ToString(), header.nTime, header.GetHash().ToString())); + } + } else { + // if it is not part of the longest chain, then any error on a subsequent header should result in an instant ban + fInstantBan = true; + } + } CBlockIndex *pindex = nullptr; // Use a temp pindex instead of ppindex to avoid a const_cast bool accepted{AcceptBlockHeader(header, state, &pindex, min_pow_checked)}; CheckBlockIndex(); if (!accepted) { + // if we have seen a duplicate stake in this header list previously, then ban immediately. + if(fInstantBan) { + state.Invalid(BlockValidationResult::BLOCK_INVALID_HEADER, state.GetRejectReason(), "instant ban, due to duplicate header in the chain"); + } return false; } if (ppindex) { *ppindex = pindex; + if(bFirst && pindexFirst) + { + *pindexFirst = pindex; + bFirst = false; + } } } } if (NotifyHeaderTip(*this)) { if (IsInitialBlockDownload() && ppindex && *ppindex) { const CBlockIndex& last_accepted{**ppindex}; - const int64_t blocks_left{(GetTime() - last_accepted.GetBlockTime()) / GetConsensus().nPowTargetSpacing}; + const int64_t blocks_left{(GetTime() - last_accepted.GetBlockTime()) / GetConsensus().TargetSpacing(last_accepted.nHeight)}; const double progress{100.0 * last_accepted.nHeight / (last_accepted.nHeight + blocks_left)}; LogPrintf("Synchronizing blockheaders, height: %d (~%.2f%%)\n", last_accepted.nHeight, progress); } @@ -4797,7 +5622,14 @@ bool TestBlockValidity(BlockValidationState& state, return error("%s: Consensus::CheckBlock: %s", __func__, state.ToString()); if (!ContextualCheckBlock(block, state, chainstate.m_chainman, pindexPrev)) return error("%s: Consensus::ContextualCheckBlock: %s", __func__, state.ToString()); + + dev::h256 oldHashStateRoot(globalState->rootHash()); // qtum + dev::h256 oldHashUTXORoot(globalState->rootHashUTXO()); // qtum + if (!chainstate.ConnectBlock(block, state, &indexDummy, viewNew, true)) { + globalState->setRoot(oldHashStateRoot); // qtum + globalState->setRootUTXO(oldHashUTXORoot); // qtum + pstorageresult->clearCacheResult(); return false; } assert(state.IsValid()); @@ -4880,6 +5712,13 @@ VerifyDBResult CVerifyDB::VerifyDB( int reportDone = 0; bool skipped_no_block_data{false}; bool skipped_l3_checks{false}; + +////////////////////////////////////////////////////////////////////////// // qtum + dev::h256 oldHashStateRoot(globalState->rootHash()); + dev::h256 oldHashUTXORoot(globalState->rootHashUTXO()); + QtumDGP qtumDGP(globalState.get(), chainstate, fGettingValuesDGP); +////////////////////////////////////////////////////////////////////////// + LogPrintf("Verification progress: 0%%\n"); const bool is_snapshot_cs{chainstate.m_from_snapshot_blockhash}; @@ -4902,6 +5741,13 @@ VerifyDBResult CVerifyDB::VerifyDB( skipped_no_block_data = true; break; } + + ///////////////////////////////////////////////////////////////////// // qtum + uint32_t sizeBlockDGP = qtumDGP.getBlockSize(pindex->nHeight); + dgpMaxBlockSize = sizeBlockDGP ? sizeBlockDGP : dgpMaxBlockSize; + updateBlockSizeParams(dgpMaxBlockSize); + ///////////////////////////////////////////////////////////////////// + CBlock block; // check level 0: read from disk if (!chainstate.m_blockman.ReadBlockFromDisk(block, *pindex)) { @@ -4975,12 +5821,22 @@ VerifyDBResult CVerifyDB::VerifyDB( LogPrintf("Verification error: ReadBlockFromDisk failed at %d, hash=%s\n", pindex->nHeight, pindex->GetBlockHash().ToString()); return VerifyDBResult::CORRUPTED_BLOCK_DB; } + + dev::h256 oldHashStateRoot(globalState->rootHash()); // qtum + dev::h256 oldHashUTXORoot(globalState->rootHashUTXO()); // qtum + if (!chainstate.ConnectBlock(block, state, pindex, coins)) { LogPrintf("Verification error: found unconnectable block at %d, hash=%s (%s)\n", pindex->nHeight, pindex->GetBlockHash().ToString(), state.ToString()); + globalState->setRoot(oldHashStateRoot); // qtum + globalState->setRootUTXO(oldHashUTXORoot); // qtum + pstorageresult->clearCacheResult(); return VerifyDBResult::CORRUPTED_BLOCK_DB; } if (chainstate.m_chainman.m_interrupt) return VerifyDBResult::INTERRUPTED; } + } else { + globalState->setRoot(oldHashStateRoot); // qtum + globalState->setRootUTXO(oldHashUTXORoot); // qtum } LogPrintf("Verification: No coin database inconsistencies in last %i blocks (%i transactions)\n", block_count, nGoodTransactions); @@ -5157,6 +6013,13 @@ bool ChainstateManager::LoadBlockIndex() // needs_init. LogPrintf("Initializing databases...\n"); + // Use the provided setting for -logevents in the new database + fLogEvents = gArgs.GetBoolArg("-logevents", DEFAULT_LOGEVENTS); + m_blockman.m_block_tree_db->WriteFlag("logevents", fLogEvents); + /////////////////////////////////////////////////////////////// // qtum + fAddressIndex = gArgs.GetBoolArg("-addrindex", DEFAULT_ADDRINDEX); + m_blockman.m_block_tree_db->WriteFlag("addrindex", fAddressIndex); + /////////////////////////////////////////////////////////////// } return true; } @@ -5181,6 +6044,7 @@ bool Chainstate::LoadGenesisBlock() return error("%s: writing genesis block to disk failed", __func__); } CBlockIndex* pindex = m_blockman.AddToBlockIndex(block, m_chainman.m_best_header); + pindex->hashProof = m_chainman.GetParams().GetConsensus().hashGenesisBlock; m_chainman.ReceivedBlockTransactions(block, pindex, blockPos); } catch (const std::runtime_error& e) { return error("%s: failed to write genesis block: %s", __func__, e.what()); @@ -5202,7 +6066,7 @@ void ChainstateManager::LoadExternalBlockFile( int nLoaded = 0; try { - BufferedFile blkdat{file_in, 2 * MAX_BLOCK_SERIALIZED_SIZE, MAX_BLOCK_SERIALIZED_SIZE + 8}; + BufferedFile blkdat{file_in, 2 * dgpMaxBlockSerSize, dgpMaxBlockSerSize + 8}; // nRewind indicates where to resume scanning in case something goes wrong, // such as a block fails to deserialize. uint64_t nRewind = blkdat.GetPos(); @@ -5224,7 +6088,7 @@ void ChainstateManager::LoadExternalBlockFile( } // read size blkdat >> nSize; - if (nSize < 80 || nSize > MAX_BLOCK_SERIALIZED_SIZE) + if (nSize < 80 || nSize > dgpMaxBlockSerSize) continue; } catch (const std::exception&) { // no valid block header found; don't complain @@ -6440,14 +7304,12 @@ Chainstate& ChainstateManager::ActivateExistingSnapshot(uint256 base_blockhash) bool IsBIP30Repeat(const CBlockIndex& block_index) { - return (block_index.nHeight==91842 && block_index.GetBlockHash() == uint256S("0x00000000000a4d0a398161ffc163c503763b1f4360639393e0e4c8e300e0caec")) || - (block_index.nHeight==91880 && block_index.GetBlockHash() == uint256S("0x00000000000743f190a18c5577a3c2d2a1f610ae9601ac046a38084ccb7cd721")); + return false; } bool IsBIP30Unspendable(const CBlockIndex& block_index) { - return (block_index.nHeight==91722 && block_index.GetBlockHash() == uint256S("0x00000000000271a2dc26e7667f8419f2e15416dc6955e5a6c6cdf3f2574dd08e")) || - (block_index.nHeight==91812 && block_index.GetBlockHash() == uint256S("0x00000000000af0aed4792b1acee3d966af36cf5def14935db8de83d6f9306f2f")); + return false; } static fs::path GetSnapshotCoinsDBPath(Chainstate& cs) EXCLUSIVE_LOCKS_REQUIRED(::cs_main) @@ -6704,12 +7566,62 @@ bool GetTimestampIndex(const unsigned int &high, const unsigned int &low, const CAmount GetTxGasFee(const CMutableTransaction& _tx, const CTxMemPool& mempool, Chainstate& active_chainstate) { - return {}; + CTransaction tx(_tx); + CAmount nGasFee = 0; + if(tx.HasCreateOrCall()) + { + LOCK(cs_main); + const CChainParams& chainparams{active_chainstate.m_chainman.GetParams()}; + unsigned int contractflags = GetContractScriptFlags(active_chainstate.m_chain.Height() + 1, chainparams.GetConsensus()); + QtumTxConverter convert(tx, active_chainstate, &mempool, NULL, NULL, contractflags); + + ExtractQtumTX resultConvertQtumTX; + if(!convert.extractionQtumTransactions(resultConvertQtumTX)){ + return nGasFee; + } + + dev::u256 sumGas = dev::u256(0); + for(QtumTransaction& qtx : resultConvertQtumTX.first){ + sumGas += qtx.gas() * qtx.gasPrice(); + } + + nGasFee = (CAmount) sumGas; + } + return nGasFee; } bool GetAddressWeight(uint256 addressHash, int type, const std::map& immatureStakes, int32_t nHeight, uint64_t& nWeight, node::BlockManager& blockman) { - return {}; + nWeight = 0; + + if (!fAddressIndex) + return error("address index not enabled"); + + // Get address utxos + std::vector > unspentOutputs; + if (!GetAddressUnspent(addressHash, type, unspentOutputs, blockman)) { + throw error("No information available for address"); + } + + // Add the utxos to the list if they are mature + const Consensus::Params& consensusParams = Params().GetConsensus(); + for (std::vector >::const_iterator i=unspentOutputs.begin(); i!=unspentOutputs.end(); i++) { + + int nDepth = nHeight - i->second.blockHeight + 1; + if (nDepth < consensusParams.CoinbaseMaturity(nHeight + 1)) + continue; + + if(i->second.satoshis < 0) + continue; + + COutPoint prevout = COutPoint(i->first.txhash, i->first.index); + if(immatureStakes.find(prevout) == immatureStakes.end()) + { + nWeight+= i->second.satoshis; + } + } + + return true; } std::map GetImmatureStakes(ChainstateManager& chainman)