diff --git a/src/coins.cpp b/src/coins.cpp index 01eefddde..96820b420 100644 --- a/src/coins.cpp +++ b/src/coins.cpp @@ -36,6 +36,7 @@ bool CCoinsViewBacked::BatchWrite(CCoinsMap& mapCoins, const uint256& hashBlock, CCoinsViewCursor *CCoinsViewBacked::Cursor() const { return base->Cursor(); } size_t CCoinsViewBacked::EstimateSize() const { return base->EstimateSize(); } mw::ICoinsView::Ptr CCoinsViewBacked::GetMWEBView() const { return base->GetMWEBView(); } +bool CCoinsViewBacked::GetMWEBCoin(const mw::Hash& output_id, Output& coin) const { return base->GetMWEBCoin(output_id, coin); } SaltedOutpointHasher::SaltedOutpointHasher() : k0(GetRand(std::numeric_limits::max())), k1(GetRand(std::numeric_limits::max())) {} @@ -148,20 +149,44 @@ const Coin& CCoinsViewCache::AccessCoin(const COutPoint &outpoint) const { bool CCoinsViewCache::HaveCoin(const OutputIndex& index) const { if (index.type() == typeid(mw::Hash)) { - return GetMWEBView()->HasCoin(boost::get(index)); + const mw::Hash& output_id = boost::get(index); + if (GetMWEBCacheView()->HasCoinInCache(output_id)) { + return true; + } + + if (GetMWEBCacheView()->HasSpendInCache(output_id)) { + return false; + } + + return base->HaveCoin(index); } else { CCoinsMap::const_iterator it = FetchCoin(boost::get(index)); return (it != cacheCoins.end() && !it->second.coin.IsSpent()); } } +bool CCoinsViewCache::GetMWEBCoin(const mw::Hash& output_id, Output& coin) const { + if (GetMWEBCacheView()->HasCoinInCache(output_id)) { + std::vector utxos = GetMWEBCacheView()->GetUTXOs(output_id); + assert(!utxos.empty()); + coin = utxos.front()->GetOutput(); + return true; + } + + if (GetMWEBCacheView()->HasSpendInCache(output_id)) { + return false; + } + + return base->GetMWEBCoin(output_id, coin); +} + bool CCoinsViewCache::HaveCoinInCache(const OutputIndex& index) const { - if (index.type() == typeid(mw::Hash)) { - return GetMWEBView()->HasCoinInCache(boost::get(index)); - } else { + if (index.type() == typeid(COutPoint)) { CCoinsMap::const_iterator it = cacheCoins.find(boost::get(index)); return (it != cacheCoins.end() && !it->second.coin.IsSpent()); } + + return false; } uint256 CCoinsViewCache::GetBestBlock() const { diff --git a/src/coins.h b/src/coins.h index b67d92e8f..dcd81cfe9 100644 --- a/src/coins.h +++ b/src/coins.h @@ -222,6 +222,8 @@ public: virtual size_t EstimateSize() const { return 0; } virtual mw::ICoinsView::Ptr GetMWEBView() const { return nullptr; } + + virtual bool GetMWEBCoin(const mw::Hash& output_id, Output& coin) const { return false; } }; @@ -242,6 +244,7 @@ public: CCoinsViewCursor *Cursor() const override; size_t EstimateSize() const override; mw::ICoinsView::Ptr GetMWEBView() const override; + bool GetMWEBCoin(const mw::Hash& output_id, Output& coin) const override; }; @@ -283,6 +286,8 @@ public: mw::ICoinsView::Ptr GetMWEBView() const final { return mweb_view; } mw::CoinsViewCache::Ptr GetMWEBCacheView() const { return mweb_view; } + bool GetMWEBCoin(const mw::Hash& output_id, Output& coin) const final; + /** * Check if we have the given utxo already loaded in this cache. * The semantics are the same as HaveCoin(), but no calls to diff --git a/src/consensus/tx_verify.cpp b/src/consensus/tx_verify.cpp index 45afa6e51..d74511d66 100644 --- a/src/consensus/tx_verify.cpp +++ b/src/consensus/tx_verify.cpp @@ -214,13 +214,12 @@ bool Consensus::CheckTxInputs(const CTransaction& tx, TxValidationState& state, // MWEB if (tx.HasMWEBTx()) { for (const Input& input : tx.mweb_tx.m_transaction->GetInputs()) { - auto utxos = inputs.GetMWEBView()->GetUTXOs(input.GetOutputID()); - if (utxos.empty()) { + Output utxo; + if (!inputs.GetMWEBCoin(input.GetOutputID(), utxo)) { return state.Invalid(TxValidationResult::TX_CONSENSUS, "bad-txns-inputs-missing", strprintf("%s: MWEB inputs missing", __func__)); } - const Output& utxo = utxos.front()->GetOutput(); if (utxo.GetReceiverPubKey() != input.GetOutputPubKey() || utxo.GetCommitment() != input.GetCommitment()) { return state.Invalid(TxValidationResult::TX_CONSENSUS, "bad-txns-input-mismatch", strprintf("%s: MWEB input doesn't match UTXO", __func__)); diff --git a/src/init.cpp b/src/init.cpp index 87c512718..6674f20ea 100644 --- a/src/init.cpp +++ b/src/init.cpp @@ -1199,7 +1199,7 @@ bool AppInitParameterInteraction(const ArgsManager& args) if (args.GetArg("-rpcserialversion", DEFAULT_RPC_SERIALIZE_VERSION) < 0) return InitError(Untranslated("rpcserialversion must be non-negative.")); - if (args.GetArg("-rpcserialversion", DEFAULT_RPC_SERIALIZE_VERSION) > 1) + if (args.GetArg("-rpcserialversion", DEFAULT_RPC_SERIALIZE_VERSION) > 2) return InitError(Untranslated("Unknown rpcserialversion requested.")); nMaxTipAge = args.GetArg("-maxtipage", DEFAULT_MAX_TIP_AGE); diff --git a/src/libmw/include/mw/exceptions/ValidationException.h b/src/libmw/include/mw/exceptions/ValidationException.h index 432436a3c..cd70fb8fe 100644 --- a/src/libmw/include/mw/exceptions/ValidationException.h +++ b/src/libmw/include/mw/exceptions/ValidationException.h @@ -19,7 +19,8 @@ enum class EConsensusError MMR_MISMATCH, UTXO_MISSING, UTXO_MISMATCH, - NOT_SORTED + NOT_SORTED, + BAD_STATE }; class ValidationException : public LTCException @@ -67,6 +68,8 @@ private: return "UTXO_MISMATCH"; case EConsensusError::NOT_SORTED: return "NOT_SORTED"; + case EConsensusError::BAD_STATE: + return "BAD_STATE"; } return "UNKNOWN"; diff --git a/src/libmw/include/mw/node/CoinsView.h b/src/libmw/include/mw/node/CoinsView.h index d3825d4a2..f1bbbe7f1 100644 --- a/src/libmw/include/mw/node/CoinsView.h +++ b/src/libmw/include/mw/node/CoinsView.h @@ -40,7 +40,7 @@ public: virtual bool IsCache() const noexcept = 0; // Virtual functions - virtual std::vector GetUTXOs(const mw::Hash& output_id) const = 0; + virtual UTXO::CPtr GetUTXO(const mw::Hash& output_id) const = 0; virtual void WriteBatch( const mw::DBBatch::UPtr& pBatch, const CoinsViewUpdates& updates, @@ -55,7 +55,7 @@ public: /// /// The output ID of the UTXO to look for. /// True if there's a matching unspent coin. Otherwise, false. - bool HasCoin(const mw::Hash& output_id) const noexcept { return !GetUTXOs(output_id).empty(); } + bool HasCoin(const mw::Hash& output_id) const noexcept { return GetUTXO(output_id) != nullptr; } /// /// Checks if there's a unspent coin with a matching commitment in the view that has not been flushed to the parent. @@ -85,7 +85,7 @@ public: bool IsCache() const noexcept final { return true; } - std::vector GetUTXOs(const mw::Hash& output_id) const noexcept final; + UTXO::CPtr GetUTXO(const mw::Hash& output_id) const noexcept final; /// /// Validates and connects the block to the end of the chain. @@ -96,6 +96,8 @@ public: /// ValidationException if consensus rules are not met. mw::BlockUndo::CPtr ApplyBlock(const mw::Block::CPtr& pBlock); + void AddTx(const mw::Transaction::CPtr& pTx); + /// /// Removes a block from the end of the chain. /// @@ -119,6 +121,7 @@ public: mw::Block::Ptr BuildNextBlock(const uint64_t height, const std::vector& transactions); bool HasCoinInCache(const mw::Hash& output_id) const noexcept final; + bool HasSpendInCache(const mw::Hash& output_id) const noexcept; ILeafSet::Ptr GetLeafSet() const noexcept final { return m_pLeafSet; } IMMR::Ptr GetOutputPMMR() const noexcept final { return m_pOutputPMMR; } @@ -148,7 +151,7 @@ public: bool IsCache() const noexcept final { return false; } - std::vector GetUTXOs(const mw::Hash& output_id) const final; + UTXO::CPtr GetUTXO(const mw::Hash& output_id) const final; void WriteBatch( const mw::DBBatch::UPtr& pBatch, const CoinsViewUpdates& updates, @@ -174,7 +177,7 @@ private: void AddUTXO(CoinDB& coinDB, const Output& output); void AddUTXO(CoinDB& coinDB, const UTXO::CPtr& pUTXO); void SpendUTXO(CoinDB& coinDB, const mw::Hash& output_id); - std::vector GetUTXOs(const CoinDB& coinDB, const mw::Hash& output_id) const; + UTXO::CPtr GetUTXO(const CoinDB& coinDB, const mw::Hash& output_id) const; LeafSet::Ptr m_pLeafSet; PMMR::Ptr m_pOutputPMMR; diff --git a/src/libmw/src/node/BlockBuilder.cpp b/src/libmw/src/node/BlockBuilder.cpp index 951c2f90d..9b5cf1b30 100644 --- a/src/libmw/src/node/BlockBuilder.cpp +++ b/src/libmw/src/node/BlockBuilder.cpp @@ -62,7 +62,7 @@ bool BlockBuilder::AddTransaction(const Transaction::CPtr& pTransaction, const s // Make sure all inputs are available. for (const Input& input : pTransaction->GetInputs()) { - if (m_pCoinsView->GetUTXOs(input.GetOutputID()).empty()) { + if (!m_pCoinsView->HasCoin(input.GetOutputID())) { LOG_ERROR_F("Input {} not found on chain", input.GetOutputID()); return false; } @@ -70,7 +70,7 @@ bool BlockBuilder::AddTransaction(const Transaction::CPtr& pTransaction, const s // Make sure no duplicate outputs already on chain. for (const Output& output : pTransaction->GetOutputs()) { - if (!m_pCoinsView->GetUTXOs(output.GetOutputID()).empty()) { + if (m_pCoinsView->HasCoin(output.GetOutputID())) { LOG_ERROR_F("Output {} already on chain", output.GetOutputID()); return false; } diff --git a/src/libmw/src/node/CoinsViewCache.cpp b/src/libmw/src/node/CoinsViewCache.cpp index 7bf004894..9069f8a64 100644 --- a/src/libmw/src/node/CoinsViewCache.cpp +++ b/src/libmw/src/node/CoinsViewCache.cpp @@ -16,21 +16,22 @@ CoinsViewCache::CoinsViewCache(const ICoinsView::Ptr& pBase) m_pOutputPMMR(std::make_unique(pBase->GetOutputPMMR())), m_pUpdates(std::make_shared()) {} -std::vector CoinsViewCache::GetUTXOs(const mw::Hash& output_id) const noexcept +UTXO::CPtr CoinsViewCache::GetUTXO(const mw::Hash& output_id) const noexcept { - std::vector utxos = m_pBase->GetUTXOs(output_id); + UTXO::CPtr pUTXO = m_pBase->GetUTXO(output_id); std::vector actions = m_pUpdates->GetActions(output_id); for (const CoinAction& action : actions) { if (action.pUTXO != nullptr) { - utxos.push_back(action.pUTXO); + // MW: TODO - Don't allow having more than one UTXO with output_id at a time + pUTXO = action.pUTXO; } else { - assert(!utxos.empty()); - utxos.pop_back(); + assert(pUTXO != nullptr); + pUTXO = nullptr; } } - return utxos; + return pUTXO; } mw::BlockUndo::CPtr CoinsViewCache::ApplyBlock(const mw::Block::CPtr& pBlock) @@ -72,6 +73,25 @@ mw::BlockUndo::CPtr CoinsViewCache::ApplyBlock(const mw::Block::CPtr& pBlock) return std::make_shared(pPreviousHeader, std::move(coinsSpent), std::move(coinsAdded)); } +static const uint32_t MEMPOOL_HEIGHT = 0x7FFFFFFF; + +void CoinsViewCache::AddTx(const mw::Transaction::CPtr& pTx) +{ + std::for_each( + pTx->GetInputs().cbegin(), pTx->GetInputs().cend(), + [this](const Input& input) { + SpendUTXO(input.GetOutputID()); + } + ); + + std::for_each( + pTx->GetOutputs().cbegin(), pTx->GetOutputs().cend(), + [this](const Output& output) { + AddUTXO(MEMPOOL_HEIGHT, output); + } + ); +} + void CoinsViewCache::UndoBlock(const mw::BlockUndo::CPtr& pUndo) { assert(pUndo != nullptr); @@ -164,7 +184,17 @@ bool CoinsViewCache::HasCoinInCache(const mw::Hash& output_id) const noexcept { std::vector actions = m_pUpdates->GetActions(output_id); if (!actions.empty()) { - return actions.back().pUTXO != nullptr; + return !actions.back().IsSpend(); + } + + return false; +} + +bool CoinsViewCache::HasSpendInCache(const mw::Hash& output_id) const noexcept +{ + std::vector actions = m_pUpdates->GetActions(output_id); + if (!actions.empty()) { + return actions.back().IsSpend(); } return false; @@ -182,24 +212,25 @@ void CoinsViewCache::AddUTXO(const uint64_t header_height, const Output& output) UTXO CoinsViewCache::SpendUTXO(const mw::Hash& output_id) { - std::vector utxos = GetUTXOs(output_id); - if (utxos.empty() || !m_pLeafSet->Contains(utxos.back()->GetLeafIndex())) { + UTXO::CPtr pUTXO = GetUTXO(output_id); + if (pUTXO == nullptr || !m_pLeafSet->Contains(pUTXO->GetLeafIndex())) { ThrowValidation(EConsensusError::UTXO_MISSING); } - m_pLeafSet->Remove(utxos.back()->GetLeafIndex()); + m_pLeafSet->Remove(pUTXO->GetLeafIndex()); m_pUpdates->SpendUTXO(output_id); - return *utxos.back(); + return *pUTXO; } void CoinsViewCache::WriteBatch(const std::unique_ptr&, const CoinsViewUpdates& updates, const mw::Header::CPtr& pHeader) { SetBestHeader(pHeader); - + for (const auto& actions : updates.GetActions()) { const mw::Hash& output_id = actions.first; for (const auto& action : actions.second) { + // MW: TODO - This is probably a good place to make sure there's no duplicate outputs if (action.IsSpend()) { m_pUpdates->SpendUTXO(output_id); } else { diff --git a/src/libmw/src/node/CoinsViewDB.cpp b/src/libmw/src/node/CoinsViewDB.cpp index 5486673fd..a963d18ec 100644 --- a/src/libmw/src/node/CoinsViewDB.cpp +++ b/src/libmw/src/node/CoinsViewDB.cpp @@ -26,20 +26,20 @@ CoinsViewDB::Ptr CoinsViewDB::Open( return std::shared_ptr(pView); } -std::vector CoinsViewDB::GetUTXOs(const mw::Hash& output_id) const +UTXO::CPtr CoinsViewDB::GetUTXO(const mw::Hash& output_id) const { CoinDB coinDB(GetDatabase().get(), nullptr); - return GetUTXOs(coinDB, output_id); + return GetUTXO(coinDB, output_id); } -std::vector CoinsViewDB::GetUTXOs(const CoinDB& coinDB, const mw::Hash& output_id) const +UTXO::CPtr CoinsViewDB::GetUTXO(const CoinDB& coinDB, const mw::Hash& output_id) const { std::vector value; auto utxos_by_hash = coinDB.GetUTXOs({output_id}); auto iter = utxos_by_hash.find(output_id); if (iter != utxos_by_hash.cend()) { - return { iter->second }; + return iter->second; } return {}; @@ -55,16 +55,13 @@ void CoinsViewDB::AddUTXO(CoinDB& coinDB, const Output& output) void CoinsViewDB::AddUTXO(CoinDB& coinDB, const UTXO::CPtr& pUTXO) { - std::vector utxos = GetUTXOs(coinDB, pUTXO->GetOutputID()); - utxos.push_back(pUTXO); - coinDB.AddUTXOs(std::vector{ pUTXO }); } void CoinsViewDB::SpendUTXO(CoinDB& coinDB, const mw::Hash& output_id) { - std::vector utxos = GetUTXOs(coinDB, output_id); - if (utxos.empty()) { + UTXO::CPtr pUTXO = GetUTXO(coinDB, output_id); + if (pUTXO == nullptr) { ThrowValidation(EConsensusError::UTXO_MISSING); } diff --git a/src/libmw/test/tests/node/Test_MineChain.cpp b/src/libmw/test/tests/node/Test_MineChain.cpp index 8269f01bf..5a6144813 100644 --- a/src/libmw/test/tests/node/Test_MineChain.cpp +++ b/src/libmw/test/tests/node/Test_MineChain.cpp @@ -30,8 +30,8 @@ BOOST_AUTO_TEST_CASE(MineChain) pCachedView->ApplyBlock(block1.GetBlock()); const auto& block1_tx1_output1 = block1_tx1.GetOutputs()[0]; - BOOST_REQUIRE(pDBView->GetUTXOs(block1_tx1_output1.GetOutputID()).empty()); - BOOST_REQUIRE(pCachedView->GetUTXOs(block1_tx1_output1.GetOutputID()).size() == 1); + BOOST_REQUIRE(pDBView->GetUTXO(block1_tx1_output1.GetOutputID()) == nullptr); + BOOST_REQUIRE(pCachedView->GetUTXO(block1_tx1_output1.GetOutputID()) != nullptr); /////////////////////// // Mine Block 2 @@ -42,8 +42,8 @@ BOOST_AUTO_TEST_CASE(MineChain) pCachedView->ApplyBlock(block2.GetBlock()); const auto& block2_tx1_output1 = block2_tx1.GetOutputs()[0]; - BOOST_REQUIRE(pDBView->GetUTXOs(block2_tx1_output1.GetOutputID()).empty()); - BOOST_REQUIRE(pCachedView->GetUTXOs(block2_tx1_output1.GetOutputID()).size() == 1); + BOOST_REQUIRE(pDBView->GetUTXO(block2_tx1_output1.GetOutputID()) == nullptr); + BOOST_REQUIRE(pCachedView->GetUTXO(block2_tx1_output1.GetOutputID()) != nullptr); /////////////////////// // Flush View @@ -52,10 +52,10 @@ BOOST_AUTO_TEST_CASE(MineChain) pCachedView->Flush(pBatch); pBatch->Commit(); - BOOST_REQUIRE(pDBView->GetUTXOs(block1_tx1_output1.GetOutputID()).size() == 1); - BOOST_REQUIRE(pCachedView->GetUTXOs(block1_tx1_output1.GetOutputID()).size() == 1); - BOOST_REQUIRE(pDBView->GetUTXOs(block2_tx1_output1.GetOutputID()).size() == 1); - BOOST_REQUIRE(pCachedView->GetUTXOs(block2_tx1_output1.GetOutputID()).size() == 1); + BOOST_REQUIRE(pDBView->GetUTXO(block1_tx1_output1.GetOutputID()) != nullptr); + BOOST_REQUIRE(pCachedView->GetUTXO(block1_tx1_output1.GetOutputID()) != nullptr); + BOOST_REQUIRE(pDBView->GetUTXO(block2_tx1_output1.GetOutputID()) != nullptr); + BOOST_REQUIRE(pCachedView->GetUTXO(block2_tx1_output1.GetOutputID()) != nullptr); } BOOST_AUTO_TEST_SUITE_END() \ No newline at end of file diff --git a/src/libmw/test/tests/node/Test_Reorg.cpp b/src/libmw/test/tests/node/Test_Reorg.cpp index 3505f3382..2e21633e9 100644 --- a/src/libmw/test/tests/node/Test_Reorg.cpp +++ b/src/libmw/test/tests/node/Test_Reorg.cpp @@ -30,8 +30,8 @@ BOOST_AUTO_TEST_CASE(ReorgChain) pCachedView->ApplyBlock(block1.GetBlock()); const auto& block1_tx1_output1 = block1_tx1.GetOutputs()[0]; - BOOST_REQUIRE(pDBView->GetUTXOs(block1_tx1_output1.GetOutputID()).empty()); - BOOST_REQUIRE(pCachedView->GetUTXOs(block1_tx1_output1.GetOutputID()).size() == 1); + BOOST_REQUIRE(pDBView->GetUTXO(block1_tx1_output1.GetOutputID()) == nullptr); + BOOST_REQUIRE(pCachedView->GetUTXO(block1_tx1_output1.GetOutputID()) != nullptr); /////////////////////// // Mine Block 2 @@ -42,15 +42,15 @@ BOOST_AUTO_TEST_CASE(ReorgChain) mw::BlockUndo::CPtr undoBlock2 = pCachedView->ApplyBlock(block2.GetBlock()); const auto& block2_tx1_output1 = block2_tx1.GetOutputs()[0]; - BOOST_REQUIRE(pDBView->GetUTXOs(block2_tx1_output1.GetOutputID()).empty()); - BOOST_REQUIRE(pCachedView->GetUTXOs(block2_tx1_output1.GetOutputID()).size() == 1); + BOOST_REQUIRE(pDBView->GetUTXO(block2_tx1_output1.GetOutputID()) == nullptr); + BOOST_REQUIRE(pCachedView->GetUTXO(block2_tx1_output1.GetOutputID()) != nullptr); /////////////////////// // Disconnect Block 2 /////////////////////// pCachedView->UndoBlock(undoBlock2); - BOOST_REQUIRE(pCachedView->GetUTXOs(block1_tx1_output1.GetOutputID()).size() == 1); - BOOST_REQUIRE(pCachedView->GetUTXOs(block2_tx1_output1.GetOutputID()).empty()); + BOOST_REQUIRE(pCachedView->GetUTXO(block1_tx1_output1.GetOutputID()) != nullptr); + BOOST_REQUIRE(pCachedView->GetUTXO(block2_tx1_output1.GetOutputID()) == nullptr); miner.Rewind(1); /////////////////////// @@ -62,8 +62,8 @@ BOOST_AUTO_TEST_CASE(ReorgChain) pCachedView->ApplyBlock(block3.GetBlock()); const auto& block3_tx1_output1 = block3_tx1.GetOutputs()[0]; - BOOST_REQUIRE(pDBView->GetUTXOs(block3_tx1_output1.GetOutputID()).empty()); - BOOST_REQUIRE(pCachedView->GetUTXOs(block3_tx1_output1.GetOutputID()).size() == 1); + BOOST_REQUIRE(pDBView->GetUTXO(block3_tx1_output1.GetOutputID()) == nullptr); + BOOST_REQUIRE(pCachedView->GetUTXO(block3_tx1_output1.GetOutputID()) != nullptr); /////////////////////// // Flush View @@ -72,12 +72,12 @@ BOOST_AUTO_TEST_CASE(ReorgChain) pCachedView->Flush(pBatch); pBatch->Commit(); - BOOST_REQUIRE(pDBView->GetUTXOs(block1_tx1_output1.GetOutputID()).size() == 1); - BOOST_REQUIRE(pCachedView->GetUTXOs(block1_tx1_output1.GetOutputID()).size() == 1); - BOOST_REQUIRE(pDBView->GetUTXOs(block2_tx1_output1.GetOutputID()).empty()); - BOOST_REQUIRE(pCachedView->GetUTXOs(block2_tx1_output1.GetOutputID()).empty()); - BOOST_REQUIRE(pDBView->GetUTXOs(block3_tx1_output1.GetOutputID()).size() == 1); - BOOST_REQUIRE(pCachedView->GetUTXOs(block3_tx1_output1.GetOutputID()).size() == 1); + BOOST_REQUIRE(pDBView->GetUTXO(block1_tx1_output1.GetOutputID()) != nullptr); + BOOST_REQUIRE(pCachedView->GetUTXO(block1_tx1_output1.GetOutputID()) != nullptr); + BOOST_REQUIRE(pDBView->GetUTXO(block2_tx1_output1.GetOutputID()) == nullptr); + BOOST_REQUIRE(pCachedView->GetUTXO(block2_tx1_output1.GetOutputID()) == nullptr); + BOOST_REQUIRE(pDBView->GetUTXO(block3_tx1_output1.GetOutputID()) != nullptr); + BOOST_REQUIRE(pCachedView->GetUTXO(block3_tx1_output1.GetOutputID()) != nullptr); } BOOST_AUTO_TEST_SUITE_END() \ No newline at end of file diff --git a/src/mweb/mweb_models.h b/src/mweb/mweb_models.h index 6e53eff2f..9d472d9bc 100644 --- a/src/mweb/mweb_models.h +++ b/src/mweb/mweb_models.h @@ -195,6 +195,22 @@ struct Tx { return IsNull() ? 0 : m_transaction->GetLockHeight(); } + bool GetOutput(const mw::Hash& output_id, Output& output) const noexcept + { + if (IsNull()) { + return false; + } + + for (const Output& o : m_transaction->GetOutputs()) { + if (o.GetOutputID() == output_id) { + output = o; + return true; + } + } + + return false; + } + SERIALIZE_METHODS(Tx, obj) { READWRITE(WrapOptionalPtr(obj.m_transaction)); diff --git a/src/node/transaction.cpp b/src/node/transaction.cpp index 97d5aad8e..b244bf3ba 100644 --- a/src/node/transaction.cpp +++ b/src/node/transaction.cpp @@ -42,11 +42,8 @@ TransactionError BroadcastTransaction(NodeContext& node, const CTransactionRef t // If the transaction is already confirmed in the chain, don't do anything // and return early. CCoinsViewCache &view = ::ChainstateActive().CoinsTip(); - for (size_t o = 0; o < tx->vout.size(); o++) { - const Coin& existingCoin = view.AccessCoin(COutPoint(hashTx, o)); - // IsSpent doesn't mean the coin is spent, it means the output doesn't exist. - // So if the output does exist, then this transaction exists in the chain. - if (!existingCoin.IsSpent()) return TransactionError::ALREADY_IN_CHAIN; + for (const CTxOutput& o : tx->GetOutputs()) { + if (view.HaveCoin(o.GetIndex())) return TransactionError::ALREADY_IN_CHAIN; } if (!node.mempool->exists(hashTx)) { // Transaction is not already in the mempool. diff --git a/src/rpc/server.cpp b/src/rpc/server.cpp index 953e76734..f45da07e3 100644 --- a/src/rpc/server.cpp +++ b/src/rpc/server.cpp @@ -509,8 +509,13 @@ void RPCRunLater(const std::string& name, std::function func, int64_t nS int RPCSerializationFlags() { int flag = 0; - if (gArgs.GetArg("-rpcserialversion", DEFAULT_RPC_SERIALIZE_VERSION) == 0) + unsigned int rpc_serialize_version = gArgs.GetArg("-rpcserialversion", DEFAULT_RPC_SERIALIZE_VERSION); + if (rpc_serialize_version == 0) { flag |= SERIALIZE_TRANSACTION_NO_WITNESS | SERIALIZE_NO_MWEB; + } else if (rpc_serialize_version == 1) { + flag |= SERIALIZE_NO_MWEB; + } + return flag; } diff --git a/src/rpc/server.h b/src/rpc/server.h index b2358ac5b..4b3102cd6 100644 --- a/src/rpc/server.h +++ b/src/rpc/server.h @@ -17,7 +17,7 @@ #include -static const unsigned int DEFAULT_RPC_SERIALIZE_VERSION = 1; +static const unsigned int DEFAULT_RPC_SERIALIZE_VERSION = 2; class CRPCCommand; diff --git a/src/txdb.cpp b/src/txdb.cpp index ace536515..ca20ac5c7 100644 --- a/src/txdb.cpp +++ b/src/txdb.cpp @@ -67,6 +67,16 @@ bool CCoinsViewDB::HaveCoin(const OutputIndex& index) const { } } +bool CCoinsViewDB::GetMWEBCoin(const mw::Hash& output_id, Output& coin) const { + UTXO::CPtr pUTXO = GetMWEBView()->GetUTXO(output_id); + if (pUTXO != nullptr) { + coin = pUTXO->GetOutput(); + return true; + } + + return false; +} + uint256 CCoinsViewDB::GetBestBlock() const { uint256 hashBestChain; if (!m_db->Read(DB_BEST_BLOCK, hashBestChain)) diff --git a/src/txdb.h b/src/txdb.h index a267559a9..4e850185e 100644 --- a/src/txdb.h +++ b/src/txdb.h @@ -66,6 +66,7 @@ public: CDBWrapper* GetDB() noexcept { return m_db.get(); } void SetMWEBView(const mw::ICoinsView::Ptr& view) { mweb_view = view; } mw::ICoinsView::Ptr GetMWEBView() const final { return mweb_view; } + bool GetMWEBCoin(const mw::Hash& output_id, Output& coin) const final; //! Attempt to update from an older database format. Returns whether an error occurred. bool Upgrade(); diff --git a/src/txmempool.cpp b/src/txmempool.cpp index f9cee41ba..d9c8e3872 100644 --- a/src/txmempool.cpp +++ b/src/txmempool.cpp @@ -165,8 +165,8 @@ bool CTxMemPool::CalculateMemPoolAncestors(const CTxMemPoolEntry &entry, setEntr // Get parents of this transaction that are in the mempool // GetMemPoolParents() is only valid for entries in the mempool, so we // iterate mapTx to find parents. - for (unsigned int i = 0; i < tx.vin.size(); i++) { - Optional piter = GetIter(tx.vin[i].prevout.hash); + for (const CTxInput& txin : tx.GetInputs()) { + Optional piter = GetIter(txin); if (piter) { staged_ancestors.insert(**piter); if (staged_ancestors.size() + 1 > limitAncestorCount) { @@ -721,21 +721,22 @@ void CTxMemPool::check(const CCoinsViewCache *pcoins) const innerUsage += memusage::DynamicUsage(it->GetMemPoolParentsConst()) + memusage::DynamicUsage(it->GetMemPoolChildrenConst()); bool fDependsWait = false; CTxMemPoolEntry::Parents setParentCheck; - for (const CTxIn &txin : tx.vin) { + for (const CTxInput& input : tx.GetInputs()) { // Check that every mempool transaction's inputs refer to available coins, or other mempool tx's. - indexed_transaction_set::const_iterator it2 = mapTx.find(txin.prevout.hash); - if (it2 != mapTx.end()) { - const CTransaction& tx2 = it2->GetTx(); - assert(tx2.vout.size() > txin.prevout.n && !tx2.vout[txin.prevout.n].IsNull()); + auto opt_it2 = GetIter(input); + if (opt_it2) { + auto it2 = *opt_it2; + //const CTransaction& tx2 = it2->GetTx(); + //assert(tx2.vout.size() > txin.prevout.n && !tx2.vout[txin.prevout.n].IsNull()); // MW: TODO - fDependsWait = true; setParentCheck.insert(*it2); } else { - assert(pcoins->HaveCoin(txin.prevout)); + assert(pcoins->HaveCoin(input.GetIndex())); } // Check whether its inputs are marked in mapNextTx. - auto it3 = mapNextTx.find(txin.prevout); + auto it3 = mapNextTx.find(input.GetIndex()); assert(it3 != mapNextTx.end()); - assert(it3->first == OutputIndex(txin.prevout)); + assert(it3->first == input.GetIndex()); assert(it3->second == &tx); i++; } @@ -967,6 +968,21 @@ Optional CTxMemPool::GetIter(const uint256& txid) const { auto it = mapTx.find(txid); if (it != mapTx.end()) return it; + + return Optional{}; +} + +Optional CTxMemPool::GetIter(const CTxInput& input) const +{ + if (input.IsMWEB()) { + auto iter = mapTxOutputs_MWEB.find(input.ToMWEB()); + if (iter != mapTxOutputs_MWEB.end()) { + return GetIter(iter->second->GetHash()); + } + } else { + return GetIter(input.GetTxIn().prevout.hash); + } + return Optional{}; } @@ -1013,6 +1029,50 @@ bool CCoinsViewMemPool::GetCoin(const COutPoint &outpoint, Coin &coin) const { return base->GetCoin(outpoint, coin); } +bool CCoinsViewMemPool::HaveCoin(const OutputIndex& index) const +{ + if (index.type() == typeid(mw::Hash)) { + LogPrintf("Checking mempool\n"); + if (mempool.mapNextTx.find(index) != mempool.mapNextTx.end()) { + LogPrintf("Spend found in mempool!\n"); + return false; + } + + auto iter = mempool.mapTxOutputs_MWEB.find(boost::get(index)); + if (iter != mempool.mapTxOutputs_MWEB.end()) { + LogPrintf("Coin in mempool!\n"); + assert(mempool.mapTx.count(iter->second->GetHash()) > 0); + return true; + } + + return GetMWEBView()->HasCoin(boost::get(index)); + } else { + return base->HaveCoin(index); + } +} + +bool CCoinsViewMemPool::GetMWEBCoin(const mw::Hash& output_id, Output& coin) const +{ + if (mempool.mapNextTx.find(output_id) != mempool.mapNextTx.end()) { + return false; + } + + auto iter = mempool.mapTxOutputs_MWEB.find(output_id); + if (iter != mempool.mapTxOutputs_MWEB.end()) { + //assert(mempool.mapTx.count(iter->second->GetHash()) > 0); + //assert(!iter->second->mweb_tx.IsNull()); + return iter->second->mweb_tx.GetOutput(output_id, coin); + } + + UTXO::CPtr pUTXO = GetMWEBView()->GetUTXO(output_id); + if (pUTXO) { + coin = pUTXO->GetOutput(); + return true; + } + + return false; +} + size_t CTxMemPool::DynamicMemoryUsage() const { LOCK(cs); // Estimate the overhead of mapTx to be 15 pointers + an allocation, as no exact formula for boost::multi_index_contained is implemented. diff --git a/src/txmempool.h b/src/txmempool.h index 0c9cac126..8cddf3f25 100644 --- a/src/txmempool.h +++ b/src/txmempool.h @@ -683,6 +683,9 @@ public: /** Returns an iterator to the given hash, if found */ Optional GetIter(const uint256& txid) const EXCLUSIVE_LOCKS_REQUIRED(cs); + /** Returns an iterator to the transaction the input spends from, if found */ + Optional GetIter(const CTxInput& input) const EXCLUSIVE_LOCKS_REQUIRED(cs); + /** Translate a set of hashes into a set of pool iterators to avoid repeated lookups */ setEntries GetIterSet(const std::set& hashes) const EXCLUSIVE_LOCKS_REQUIRED(cs); @@ -940,6 +943,8 @@ protected: public: CCoinsViewMemPool(CCoinsView* baseIn, const CTxMemPool& mempoolIn); bool GetCoin(const COutPoint &outpoint, Coin &coin) const override; + bool HaveCoin(const OutputIndex& index) const override; + bool GetMWEBCoin(const mw::Hash& output_id, Output& coin) const override; }; /** diff --git a/src/validation.cpp b/src/validation.cpp index 365feae4c..6932df4d1 100644 --- a/src/validation.cpp +++ b/src/validation.cpp @@ -1504,6 +1504,10 @@ void UpdateCoins(const CTransaction& tx, CCoinsViewCache& inputs, CTxUndo &txund } // add outputs AddCoins(inputs, tx, nHeight); + + if (!tx.mweb_tx.IsNull()) { + inputs.GetMWEBCacheView()->AddTx(tx.mweb_tx.m_transaction); + } } void UpdateCoins(const CTransaction& tx, CCoinsViewCache& inputs, int nHeight) diff --git a/test/functional/mweb_wallet_basic.py b/test/functional/mweb_wallet_basic.py index cd3e67999..9a1cee7cf 100644 --- a/test/functional/mweb_wallet_basic.py +++ b/test/functional/mweb_wallet_basic.py @@ -100,8 +100,11 @@ class MWEBWalletBasicTest(BitcoinTestFramework): assert tx3_id in node1.getrawmempool() self.log.info("Mine next block so node2 sees the transactions") - node0.generate(1) + node0.generate(2) self.sync_all() + + assert tx2_id not in node1.getrawmempool() + assert tx3_id not in node1.getrawmempool() self.log.info("Verify node2's wallet receives the first pegout transaction") n2_addr_coins = node2.listreceivedbyaddress(minconf=0, address_filter=n2_addr) @@ -113,7 +116,7 @@ class MWEBWalletBasicTest(BitcoinTestFramework): n2_addr2_coins = node2.listreceivedbyaddress(minconf=0) self.log.info(n2_addr2_coins) assert_equal(len(n2_addr2_coins), 1) - assert n2_addr2_coins['amount'] < 5 and n2_addr2_coins['amount'] > 4.9 + assert n2_addr2_coins[0]['amount'] < 5 and n2_addr2_coins[0]['amount'] > 4.9 assert_equal(n2_addr2_coins[0]['confirmations'], 1) n2_balances = node2.getbalances()['mine']