Mempool fixes

This commit is contained in:
David Burkett 2022-03-10 11:16:37 -05:00 committed by Loshan T
parent f238353454
commit 1b5878a81f
21 changed files with 242 additions and 78 deletions

View File

@ -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 {

View File

@ -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

View File

@ -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__));

View File

@ -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);

View File

@ -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";

View File

@ -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;

View File

@ -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;
}

View File

@ -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 {

View File

@ -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);
}

View File

@ -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()

View File

@ -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()

View File

@ -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));

View File

@ -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.

View File

@ -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;
}

View File

@ -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;

View File

@ -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))

View File

@ -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();

View File

@ -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.

View File

@ -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;
};
/**

View File

@ -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)

View File

@ -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']