Mempool fixes
This commit is contained in:
parent
f238353454
commit
1b5878a81f
@ -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<uint64_t>::max())), k1(GetRand(std::numeric_limits<uint64_t>::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<mw::Hash>(index));
|
||||
const mw::Hash& output_id = boost::get<mw::Hash>(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<COutPoint>(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<UTXO::CPtr> 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<mw::Hash>(index));
|
||||
} else {
|
||||
if (index.type() == typeid(COutPoint)) {
|
||||
CCoinsMap::const_iterator it = cacheCoins.find(boost::get<COutPoint>(index));
|
||||
return (it != cacheCoins.end() && !it->second.coin.IsSpent());
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
uint256 CCoinsViewCache::GetBestBlock() const {
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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__));
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -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";
|
||||
|
||||
@ -40,7 +40,7 @@ public:
|
||||
virtual bool IsCache() const noexcept = 0;
|
||||
|
||||
// Virtual functions
|
||||
virtual std::vector<UTXO::CPtr> 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:
|
||||
/// </summary>
|
||||
/// <param name="output_id">The output ID of the UTXO to look for.</param>
|
||||
/// <returns>True if there's a matching unspent coin. Otherwise, false.</returns>
|
||||
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; }
|
||||
|
||||
/// <summary>
|
||||
/// 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<UTXO::CPtr> GetUTXOs(const mw::Hash& output_id) const noexcept final;
|
||||
UTXO::CPtr GetUTXO(const mw::Hash& output_id) const noexcept final;
|
||||
|
||||
/// <summary>
|
||||
/// Validates and connects the block to the end of the chain.
|
||||
@ -96,6 +96,8 @@ public:
|
||||
/// <throws>ValidationException if consensus rules are not met.</throws>
|
||||
mw::BlockUndo::CPtr ApplyBlock(const mw::Block::CPtr& pBlock);
|
||||
|
||||
void AddTx(const mw::Transaction::CPtr& pTx);
|
||||
|
||||
/// <summary>
|
||||
/// Removes a block from the end of the chain.
|
||||
/// </summary>
|
||||
@ -119,6 +121,7 @@ public:
|
||||
mw::Block::Ptr BuildNextBlock(const uint64_t height, const std::vector<mw::Transaction::CPtr>& 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<UTXO::CPtr> 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<UTXO::CPtr> 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;
|
||||
|
||||
@ -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;
|
||||
}
|
||||
|
||||
@ -16,21 +16,22 @@ CoinsViewCache::CoinsViewCache(const ICoinsView::Ptr& pBase)
|
||||
m_pOutputPMMR(std::make_unique<PMMRCache>(pBase->GetOutputPMMR())),
|
||||
m_pUpdates(std::make_shared<CoinsViewUpdates>()) {}
|
||||
|
||||
std::vector<UTXO::CPtr> CoinsViewCache::GetUTXOs(const mw::Hash& output_id) const noexcept
|
||||
UTXO::CPtr CoinsViewCache::GetUTXO(const mw::Hash& output_id) const noexcept
|
||||
{
|
||||
std::vector<UTXO::CPtr> utxos = m_pBase->GetUTXOs(output_id);
|
||||
UTXO::CPtr pUTXO = m_pBase->GetUTXO(output_id);
|
||||
|
||||
std::vector<CoinAction> 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<mw::BlockUndo>(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<CoinAction> 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<CoinAction> 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<UTXO::CPtr> 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<mw::DBBatch>&, 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 {
|
||||
|
||||
@ -26,20 +26,20 @@ CoinsViewDB::Ptr CoinsViewDB::Open(
|
||||
return std::shared_ptr<CoinsViewDB>(pView);
|
||||
}
|
||||
|
||||
std::vector<UTXO::CPtr> 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<UTXO::CPtr> 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<uint8_t> 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<UTXO::CPtr> utxos = GetUTXOs(coinDB, pUTXO->GetOutputID());
|
||||
utxos.push_back(pUTXO);
|
||||
|
||||
coinDB.AddUTXOs(std::vector<UTXO::CPtr>{ pUTXO });
|
||||
}
|
||||
|
||||
void CoinsViewDB::SpendUTXO(CoinDB& coinDB, const mw::Hash& output_id)
|
||||
{
|
||||
std::vector<UTXO::CPtr> utxos = GetUTXOs(coinDB, output_id);
|
||||
if (utxos.empty()) {
|
||||
UTXO::CPtr pUTXO = GetUTXO(coinDB, output_id);
|
||||
if (pUTXO == nullptr) {
|
||||
ThrowValidation(EConsensusError::UTXO_MISSING);
|
||||
}
|
||||
|
||||
|
||||
@ -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()
|
||||
@ -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()
|
||||
@ -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));
|
||||
|
||||
@ -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.
|
||||
|
||||
@ -509,8 +509,13 @@ void RPCRunLater(const std::string& name, std::function<void()> 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;
|
||||
}
|
||||
|
||||
|
||||
@ -17,7 +17,7 @@
|
||||
|
||||
#include <univalue.h>
|
||||
|
||||
static const unsigned int DEFAULT_RPC_SERIALIZE_VERSION = 1;
|
||||
static const unsigned int DEFAULT_RPC_SERIALIZE_VERSION = 2;
|
||||
|
||||
class CRPCCommand;
|
||||
|
||||
|
||||
10
src/txdb.cpp
10
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))
|
||||
|
||||
@ -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();
|
||||
|
||||
@ -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<txiter> piter = GetIter(tx.vin[i].prevout.hash);
|
||||
for (const CTxInput& txin : tx.GetInputs()) {
|
||||
Optional<txiter> 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::txiter> CTxMemPool::GetIter(const uint256& txid) const
|
||||
{
|
||||
auto it = mapTx.find(txid);
|
||||
if (it != mapTx.end()) return it;
|
||||
|
||||
return Optional<txiter>{};
|
||||
}
|
||||
|
||||
Optional<CTxMemPool::txiter> 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<txiter>{};
|
||||
}
|
||||
|
||||
@ -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<mw::Hash>(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<mw::Hash>(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.
|
||||
|
||||
@ -683,6 +683,9 @@ public:
|
||||
/** Returns an iterator to the given hash, if found */
|
||||
Optional<txiter> GetIter(const uint256& txid) const EXCLUSIVE_LOCKS_REQUIRED(cs);
|
||||
|
||||
/** Returns an iterator to the transaction the input spends from, if found */
|
||||
Optional<txiter> 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<uint256>& 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;
|
||||
};
|
||||
|
||||
/**
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -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']
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user