From b4c7dfd76c00fa658567432a8bb66fc4b1625d2d Mon Sep 17 00:00:00 2001 From: Luke Dashjr Date: Tue, 22 Aug 2023 00:58:47 +0000 Subject: [PATCH 01/21] dbwrapper: Restore `CDBIterator::GetValueSize()` This partially reverts commit fb38c6e21f064e23b63a46d15adb873029463cff. Original code written in 3499ce1e1ad87a86598d00b7124072c91ddad833. --- src/dbwrapper.cpp | 5 +++++ src/dbwrapper.h | 3 +++ 2 files changed, 8 insertions(+) diff --git a/src/dbwrapper.cpp b/src/dbwrapper.cpp index 775496e21bd7f..d98d22e28bea5 100644 --- a/src/dbwrapper.cpp +++ b/src/dbwrapper.cpp @@ -411,6 +411,11 @@ Span CDBIterator::GetValueImpl() const return MakeByteSpan(m_impl_iter->iter->value()); } +unsigned int CDBIterator::GetValueSize() const +{ + return m_impl_iter->iter->value().size(); +} + CDBIterator::~CDBIterator() = default; bool CDBIterator::Valid() const { return m_impl_iter->iter->Valid(); } void CDBIterator::SeekToFirst() { m_impl_iter->iter->SeekToFirst(); } diff --git a/src/dbwrapper.h b/src/dbwrapper.h index 63c2f99d2a81c..99bb8300e2d3c 100644 --- a/src/dbwrapper.h +++ b/src/dbwrapper.h @@ -174,6 +174,9 @@ class CDBIterator } return true; } + + unsigned int GetValueSize() const; + }; struct LevelDBContext; From d5a5bb07b294541a7675353edc81950fcc89f8c1 Mon Sep 17 00:00:00 2001 From: Luke Dashjr Date: Wed, 10 May 2023 00:37:17 +0000 Subject: [PATCH 02/21] txdb: Support for flags which prevent opening with incompatible software --- src/node/blockstorage.cpp | 21 +++++++++++++++++++-- src/node/blockstorage.h | 2 +- src/txdb.cpp | 13 +++++++++++-- 3 files changed, 31 insertions(+), 5 deletions(-) diff --git a/src/node/blockstorage.cpp b/src/node/blockstorage.cpp index f8f1aab551b9b..7284ae085b1f7 100644 --- a/src/node/blockstorage.cpp +++ b/src/node/blockstorage.cpp @@ -17,6 +17,7 @@ #include #include #include +#include #include #include #include @@ -41,6 +42,9 @@ static constexpr uint8_t DB_LAST_BLOCK{'l'}; // BlockTreeDB::DB_TXINDEX{'t'} // BlockTreeDB::ReadFlag("txindex") +// Before v0.15.0, this was used for chainstate, but now it is used for versioning +static constexpr uint8_t DB_COINS{'c'}; + bool BlockTreeDB::ReadBlockFileInfo(int nFile, CBlockFileInfo& info) { return Read(std::make_pair(DB_BLOCK_FILES, nFile), info); @@ -78,9 +82,22 @@ bool BlockTreeDB::WriteBatchSync(const std::vector()); + } + return WriteBatch(batch, true); } bool BlockTreeDB::ReadFlag(const std::string& name, bool& fValue) diff --git a/src/node/blockstorage.h b/src/node/blockstorage.h index ac97728c0567e..ce231d80a5735 100644 --- a/src/node/blockstorage.h +++ b/src/node/blockstorage.h @@ -57,7 +57,7 @@ class BlockTreeDB : public CDBWrapper bool ReadLastBlockFile(int& nFile); bool WriteReindexing(bool fReindexing); void ReadReindexing(bool& fReindexing); - bool WriteFlag(const std::string& name, bool fValue); + bool WriteFlag(const std::string& name, bool fValue, bool allow_ignore = true); bool ReadFlag(const std::string& name, bool& fValue); bool LoadBlockIndexGuts(const Consensus::Params& consensusParams, std::function insertBlockIndex, const util::SignalInterrupt& interrupt) EXCLUSIVE_LOCKS_REQUIRED(::cs_main); diff --git a/src/txdb.cpp b/src/txdb.cpp index e4a4b3bf72a01..e125b886bd9f8 100644 --- a/src/txdb.cpp +++ b/src/txdb.cpp @@ -22,7 +22,8 @@ static constexpr uint8_t DB_COIN{'C'}; static constexpr uint8_t DB_BEST_BLOCK{'B'}; static constexpr uint8_t DB_HEAD_BLOCKS{'H'}; -// Keys used in previous version that might still be found in the DB: + +// Before v0.15.0, this was used for chainstate, but now it is used for versioning static constexpr uint8_t DB_COINS{'c'}; bool CCoinsViewDB::NeedsUpgrade() @@ -31,7 +32,15 @@ bool CCoinsViewDB::NeedsUpgrade() // DB_COINS was deprecated in v0.15.0, commit // 1088b02f0ccd7358d2b7076bb9e122d59d502d02 cursor->Seek(std::make_pair(DB_COINS, uint256{})); - return cursor->Valid(); + while (cursor->Valid()) { + std::pair key; + if (cursor->GetKey(key) && key.first == DB_COINS && cursor->GetValueSize() == 0) { + // Versioning entry + // TODO: if (key.second == SUPPORTED) { cursor->Next(); continue; } + } + return true; + } + return false; } namespace { From 835a869dbfa31e0a373e445b12da1dfd50c8224f Mon Sep 17 00:00:00 2001 From: Luke Dashjr Date: Wed, 10 May 2023 16:36:37 +0000 Subject: [PATCH 03/21] Hide COutPoint with n > 0x00ffffff from consensus/transaction handling This should be safe since it is impossible for transactions to have more than ~0x0001c000 outputs. 0xffffffff is used for the coinbase COutPoint n, but wouldn't be looked up in the coins db. --- src/coins.cpp | 26 +++++++++++++------------- src/coins.h | 28 ++++++++++++++++++++-------- src/primitives/transaction.h | 1 + src/rpc/blockchain.cpp | 1 + src/test/coins_tests.cpp | 29 +++++++++++++++++------------ src/test/fuzz/coins_view.cpp | 14 ++++++++++---- src/test/fuzz/coinscache_sim.cpp | 10 +++++----- src/test/fuzz/tx_pool.cpp | 2 +- src/txdb.cpp | 4 ++-- src/txdb.h | 4 ++-- src/txmempool.cpp | 4 ++-- src/txmempool.h | 2 +- src/validation.cpp | 2 +- 13 files changed, 76 insertions(+), 51 deletions(-) diff --git a/src/coins.cpp b/src/coins.cpp index b44d920ee1ffb..364a5de7e3803 100644 --- a/src/coins.cpp +++ b/src/coins.cpp @@ -10,21 +10,21 @@ #include #include -bool CCoinsView::GetCoin(const COutPoint &outpoint, Coin &coin) const { return false; } +bool CCoinsView::GetCoinRaw(const COutPoint &outpoint, Coin &coin) const { return false; } uint256 CCoinsView::GetBestBlock() const { return uint256(); } std::vector CCoinsView::GetHeadBlocks() const { return std::vector(); } bool CCoinsView::BatchWrite(CCoinsMap &mapCoins, const uint256 &hashBlock, bool erase) { return false; } std::unique_ptr CCoinsView::Cursor() const { return nullptr; } -bool CCoinsView::HaveCoin(const COutPoint &outpoint) const +bool CCoinsView::HaveCoinRaw(const COutPoint &outpoint) const { Coin coin; - return GetCoin(outpoint, coin); + return GetCoinRaw(outpoint, coin); } CCoinsViewBacked::CCoinsViewBacked(CCoinsView *viewIn) : base(viewIn) { } -bool CCoinsViewBacked::GetCoin(const COutPoint &outpoint, Coin &coin) const { return base->GetCoin(outpoint, coin); } -bool CCoinsViewBacked::HaveCoin(const COutPoint &outpoint) const { return base->HaveCoin(outpoint); } +bool CCoinsViewBacked::GetCoinRaw(const COutPoint &outpoint, Coin &coin) const { return base->GetCoinRaw(outpoint, coin); } +bool CCoinsViewBacked::HaveCoinRaw(const COutPoint &outpoint) const { return base->HaveCoinRaw(outpoint); } uint256 CCoinsViewBacked::GetBestBlock() const { return base->GetBestBlock(); } std::vector CCoinsViewBacked::GetHeadBlocks() const { return base->GetHeadBlocks(); } void CCoinsViewBacked::SetBackend(CCoinsView &viewIn) { base = &viewIn; } @@ -46,7 +46,7 @@ CCoinsMap::iterator CCoinsViewCache::FetchCoin(const COutPoint &outpoint) const if (it != cacheCoins.end()) return it; Coin tmp; - if (!base->GetCoin(outpoint, tmp)) + if (!base->GetCoinRaw(outpoint, tmp)) return cacheCoins.end(); CCoinsMap::iterator ret = cacheCoins.emplace(std::piecewise_construct, std::forward_as_tuple(outpoint), std::forward_as_tuple(std::move(tmp))).first; if (ret->second.coin.IsSpent()) { @@ -58,7 +58,7 @@ CCoinsMap::iterator CCoinsViewCache::FetchCoin(const COutPoint &outpoint) const return ret; } -bool CCoinsViewCache::GetCoin(const COutPoint &outpoint, Coin &coin) const { +bool CCoinsViewCache::GetCoinRaw(const COutPoint &outpoint, Coin &coin) const { CCoinsMap::const_iterator it = FetchCoin(outpoint); if (it != cacheCoins.end()) { coin = it->second.coin; @@ -119,7 +119,7 @@ void AddCoins(CCoinsViewCache& cache, const CTransaction &tx, int nHeight, bool bool fCoinbase = tx.IsCoinBase(); const uint256& txid = tx.GetHash(); for (size_t i = 0; i < tx.vout.size(); ++i) { - bool overwrite = check_for_overwrite ? cache.HaveCoin(COutPoint(txid, i)) : fCoinbase; + bool overwrite = check_for_overwrite ? cache.HaveCoinRaw(COutPoint(txid, i)) : fCoinbase; // Coinbase transactions can always be overwritten, in order to correctly // deal with the pre-BIP30 occurrences of duplicate coinbase transactions. cache.AddCoin(COutPoint(txid, i), Coin(tx.vout[i], nHeight, fCoinbase), overwrite); @@ -159,7 +159,7 @@ const Coin& CCoinsViewCache::AccessCoin(const COutPoint &outpoint) const { } } -bool CCoinsViewCache::HaveCoin(const COutPoint &outpoint) const { +bool CCoinsViewCache::HaveCoinRaw(const COutPoint &outpoint) const { CCoinsMap::const_iterator it = FetchCoin(outpoint); return (it != cacheCoins.end() && !it->second.coin.IsSpent()); } @@ -371,10 +371,10 @@ static bool ExecuteBackedWrapper(Func func, const std::vector COutPoint::MAX_INDEX) { + return false; + } + return GetCoinRaw(outpoint, coin); + } + virtual bool GetCoinRaw(const COutPoint &outpoint, Coin &coin) const; //! Just check whether a given outpoint is unspent. - virtual bool HaveCoin(const COutPoint &outpoint) const; + bool HaveCoin(const COutPoint &outpoint) const { + if (outpoint.n > COutPoint::MAX_INDEX) { + return false; + } + return HaveCoinRaw(outpoint); + } + virtual bool HaveCoinRaw(const COutPoint &outpoint) const; //! Retrieve the block hash whose state this CCoinsView currently represents virtual uint256 GetBestBlock() const; @@ -214,8 +226,8 @@ class CCoinsViewBacked : public CCoinsView public: CCoinsViewBacked(CCoinsView *viewIn); - bool GetCoin(const COutPoint &outpoint, Coin &coin) const override; - bool HaveCoin(const COutPoint &outpoint) const override; + bool GetCoinRaw(const COutPoint &outpoint, Coin &coin) const override; + bool HaveCoinRaw(const COutPoint &outpoint) const override; uint256 GetBestBlock() const override; std::vector GetHeadBlocks() const override; void SetBackend(CCoinsView &viewIn); @@ -252,8 +264,8 @@ class CCoinsViewCache : public CCoinsViewBacked CCoinsViewCache(const CCoinsViewCache &) = delete; // Standard CCoinsView methods - bool GetCoin(const COutPoint &outpoint, Coin &coin) const override; - bool HaveCoin(const COutPoint &outpoint) const override; + bool GetCoinRaw(const COutPoint &outpoint, Coin &coin) const override; + bool HaveCoinRaw(const COutPoint &outpoint) const override; uint256 GetBestBlock() const override; void SetBestBlock(const uint256 &hashBlock); bool BatchWrite(CCoinsMap &mapCoins, const uint256 &hashBlock, bool erase = true) override; @@ -382,8 +394,8 @@ class CCoinsViewErrorCatcher final : public CCoinsViewBacked m_err_callbacks.emplace_back(std::move(f)); } - bool GetCoin(const COutPoint &outpoint, Coin &coin) const override; - bool HaveCoin(const COutPoint &outpoint) const override; + bool GetCoinRaw(const COutPoint &outpoint, Coin &coin) const override; + bool HaveCoinRaw(const COutPoint &outpoint) const override; private: /** A list of callbacks to execute upon leveldb read error. */ diff --git a/src/primitives/transaction.h b/src/primitives/transaction.h index bd7eb16becf6a..2d9e97e6e16e4 100644 --- a/src/primitives/transaction.h +++ b/src/primitives/transaction.h @@ -38,6 +38,7 @@ class COutPoint uint256 hash; uint32_t n; + static constexpr uint32_t MAX_INDEX = 0x00ffffff; static constexpr uint32_t NULL_INDEX = std::numeric_limits::max(); COutPoint(): n(NULL_INDEX) { } diff --git a/src/rpc/blockchain.cpp b/src/rpc/blockchain.cpp index 6d2b84cb6c107..57c1958ecd0e2 100644 --- a/src/rpc/blockchain.cpp +++ b/src/rpc/blockchain.cpp @@ -2005,6 +2005,7 @@ bool FindScriptPubKey(std::atomic& scan_progress, const std::atomic& COutPoint key; Coin coin; if (!cursor->GetKey(key) || !cursor->GetValue(coin)) return false; + if (key.n > COutPoint::MAX_INDEX) continue; if (++count % 8192 == 0) { interruption_point(); if (should_abort) { diff --git a/src/test/coins_tests.cpp b/src/test/coins_tests.cpp index 12dc4d1ccc478..e8281117a544c 100644 --- a/src/test/coins_tests.cpp +++ b/src/test/coins_tests.cpp @@ -39,7 +39,7 @@ class CCoinsViewTest : public CCoinsView std::map map_; public: - [[nodiscard]] bool GetCoin(const COutPoint& outpoint, Coin& coin) const override + [[nodiscard]] bool GetCoinRaw(const COutPoint& outpoint, Coin& coin) const override { std::map::const_iterator it = map_.find(outpoint); if (it == map_.end()) { @@ -927,11 +927,11 @@ void TestFlushBehavior( }; uint256 txid = InsecureRand256(); - COutPoint outp = COutPoint(txid, 0); + COutPoint outp = COutPoint(txid, 0xff000000); Coin coin = MakeCoin(); // Ensure the coins views haven't seen this coin before. - BOOST_CHECK(!base.HaveCoin(outp)); - BOOST_CHECK(!view->HaveCoin(outp)); + BOOST_CHECK(!base.HaveCoinRaw(outp)); + BOOST_CHECK(!view->HaveCoinRaw(outp)); // --- 1. Adding a random coin to the child cache // @@ -941,8 +941,11 @@ void TestFlushBehavior( cache_size = view->map().size(); // `base` shouldn't have coin (no flush yet) but `view` should have cached it. - BOOST_CHECK(!base.HaveCoin(outp)); - BOOST_CHECK(view->HaveCoin(outp)); + BOOST_CHECK(!base.HaveCoinRaw(outp)); + BOOST_CHECK(view->HaveCoinRaw(outp)); + + // since n is >0x00ffffff, it shouldn't be visible to normal HaveCoin. + BOOST_CHECK(!view->HaveCoin(outp)); GetCoinsMapEntry(view->map(), value, flags, outp); BOOST_CHECK_EQUAL(value, coin.out.nValue); @@ -963,8 +966,10 @@ void TestFlushBehavior( BOOST_CHECK_EQUAL(flags, 0); // Flags should have been wiped. // Both views should now have the coin. - BOOST_CHECK(base.HaveCoin(outp)); - BOOST_CHECK(view->HaveCoin(outp)); + BOOST_CHECK(base.HaveCoinRaw(outp)); + BOOST_CHECK(view->HaveCoinRaw(outp)); + BOOST_CHECK(!base.HaveCoin(outp)); + BOOST_CHECK(!view->HaveCoin(outp)); if (do_erasing_flush) { // --- 4. Flushing the caches again (with erasing) @@ -1002,14 +1007,14 @@ void TestFlushBehavior( GetCoinsMapEntry(view->map(), value, flags, outp); BOOST_CHECK_EQUAL(value, SPENT); BOOST_CHECK_EQUAL(flags, DIRTY); - BOOST_CHECK(!view->HaveCoin(outp)); // Coin should be considered spent in `view`. - BOOST_CHECK(base.HaveCoin(outp)); // But coin should still be unspent in `base`. + BOOST_CHECK(!view->HaveCoinRaw(outp)); // Coin should be considered spent in `view`. + BOOST_CHECK(base.HaveCoinRaw(outp)); // But coin should still be unspent in `base`. flush_all(/*erase=*/ false); // Coin should be considered spent in both views. - BOOST_CHECK(!view->HaveCoin(outp)); - BOOST_CHECK(!base.HaveCoin(outp)); + BOOST_CHECK(!view->HaveCoinRaw(outp)); + BOOST_CHECK(!base.HaveCoinRaw(outp)); // Spent coin should not be spendable. BOOST_CHECK(!view->SpendCoin(outp)); diff --git a/src/test/fuzz/coins_view.cpp b/src/test/fuzz/coins_view.cpp index b088aa0bd7ba9..c882a3d2b30e4 100644 --- a/src/test/fuzz/coins_view.cpp +++ b/src/test/fuzz/coins_view.cpp @@ -146,22 +146,28 @@ FUZZ_TARGET(coins_view, .init = initialize_coins_view) { const Coin& coin_using_access_coin = coins_view_cache.AccessCoin(random_out_point); const bool exists_using_access_coin = !(coin_using_access_coin == EMPTY_COIN); - const bool exists_using_have_coin = coins_view_cache.HaveCoin(random_out_point); + const bool exists_using_have_coin = coins_view_cache.HaveCoinRaw(random_out_point); const bool exists_using_have_coin_in_cache = coins_view_cache.HaveCoinInCache(random_out_point); Coin coin_using_get_coin; - const bool exists_using_get_coin = coins_view_cache.GetCoin(random_out_point, coin_using_get_coin); + const bool exists_using_get_coin = coins_view_cache.GetCoinRaw(random_out_point, coin_using_get_coin); if (exists_using_get_coin) { assert(coin_using_get_coin == coin_using_access_coin); } assert((exists_using_access_coin && exists_using_have_coin_in_cache && exists_using_have_coin && exists_using_get_coin) || (!exists_using_access_coin && !exists_using_have_coin_in_cache && !exists_using_have_coin && !exists_using_get_coin)); + if (exists_using_have_coin && random_out_point.n > 0x00ffffff) { + assert(!coins_view_cache.HaveCoin(random_out_point)); + } // If HaveCoin on the backend is true, it must also be on the cache if the coin wasn't spent. - const bool exists_using_have_coin_in_backend = backend_coins_view.HaveCoin(random_out_point); + const bool exists_using_have_coin_in_backend = backend_coins_view.HaveCoinRaw(random_out_point); if (!coin_using_access_coin.IsSpent() && exists_using_have_coin_in_backend) { assert(exists_using_have_coin); + if (random_out_point.n > 0x00ffffff) { + assert(!backend_coins_view.HaveCoin(random_out_point)); + } } Coin coin_using_backend_get_coin; - if (backend_coins_view.GetCoin(random_out_point, coin_using_backend_get_coin)) { + if (backend_coins_view.GetCoinRaw(random_out_point, coin_using_backend_get_coin)) { assert(exists_using_have_coin_in_backend); // Note we can't assert that `coin_using_get_coin == coin_using_backend_get_coin` because the coin in // the cache may have been modified but not yet flushed. diff --git a/src/test/fuzz/coinscache_sim.cpp b/src/test/fuzz/coinscache_sim.cpp index f350c9d032d1b..c57d6fe7e4ee8 100644 --- a/src/test/fuzz/coinscache_sim.cpp +++ b/src/test/fuzz/coinscache_sim.cpp @@ -145,7 +145,7 @@ class CoinsViewBottom final : public CCoinsView std::map m_data; public: - bool GetCoin(const COutPoint& outpoint, Coin& coin) const final + bool GetCoinRaw(const COutPoint& outpoint, Coin& coin) const final { auto it = m_data.find(outpoint); if (it == m_data.end()) { @@ -160,7 +160,7 @@ class CoinsViewBottom final : public CCoinsView } } - bool HaveCoin(const COutPoint& outpoint) const final + bool HaveCoinRaw(const COutPoint& outpoint) const final { return m_data.count(outpoint); } @@ -269,7 +269,7 @@ FUZZ_TARGET(coinscache_sim) auto sim = lookup(outpointidx); // Look up in real caches. Coin realcoin; - auto real = caches.back()->GetCoin(data.outpoints[outpointidx], realcoin); + auto real = caches.back()->GetCoinRaw(data.outpoints[outpointidx], realcoin); // Compare results. if (!sim.has_value()) { assert(!real || realcoin.IsSpent()); @@ -287,7 +287,7 @@ FUZZ_TARGET(coinscache_sim) // Look up in simulation data. auto sim = lookup(outpointidx); // Look up in real caches. - auto real = caches.back()->HaveCoin(data.outpoints[outpointidx]); + auto real = caches.back()->HaveCoinRaw(data.outpoints[outpointidx]); // Compare results. assert(sim.has_value() == real); }, @@ -464,7 +464,7 @@ FUZZ_TARGET(coinscache_sim) // Compare the bottom coinsview (not a CCoinsViewCache) with sim_cache[0]. for (uint32_t outpointidx = 0; outpointidx < NUM_OUTPOINTS; ++outpointidx) { Coin realcoin; - bool real = bottom.GetCoin(data.outpoints[outpointidx], realcoin); + bool real = bottom.GetCoinRaw(data.outpoints[outpointidx], realcoin); auto sim = lookup(outpointidx, 0); if (!sim.has_value()) { assert(!real || realcoin.IsSpent()); diff --git a/src/test/fuzz/tx_pool.cpp b/src/test/fuzz/tx_pool.cpp index ee73f67f6604b..130e1bb1d8fec 100644 --- a/src/test/fuzz/tx_pool.cpp +++ b/src/test/fuzz/tx_pool.cpp @@ -161,7 +161,7 @@ FUZZ_TARGET(tx_pool_standard, .init = initialize_tx_pool) const CCoinsViewMemPool amount_view{WITH_LOCK(::cs_main, return &chainstate.CoinsTip()), tx_pool}; const auto GetAmount = [&](const COutPoint& outpoint) { Coin c; - Assert(amount_view.GetCoin(outpoint, c)); + Assert(amount_view.GetCoinRaw(outpoint, c)); return c.out.nValue; }; diff --git a/src/txdb.cpp b/src/txdb.cpp index e125b886bd9f8..e090debebde8d 100644 --- a/src/txdb.cpp +++ b/src/txdb.cpp @@ -74,11 +74,11 @@ void CCoinsViewDB::ResizeCache(size_t new_cache_size) } } -bool CCoinsViewDB::GetCoin(const COutPoint &outpoint, Coin &coin) const { +bool CCoinsViewDB::GetCoinRaw(const COutPoint &outpoint, Coin &coin) const { return m_db->Read(CoinEntry(&outpoint), coin); } -bool CCoinsViewDB::HaveCoin(const COutPoint &outpoint) const { +bool CCoinsViewDB::HaveCoinRaw(const COutPoint &outpoint) const { return m_db->Exists(CoinEntry(&outpoint)); } diff --git a/src/txdb.h b/src/txdb.h index c9af0a091ec68..ab698313bc0ef 100644 --- a/src/txdb.h +++ b/src/txdb.h @@ -59,8 +59,8 @@ class CCoinsViewDB final : public CCoinsView public: explicit CCoinsViewDB(DBParams db_params, CoinsViewOptions options); - bool GetCoin(const COutPoint &outpoint, Coin &coin) const override; - bool HaveCoin(const COutPoint &outpoint) const override; + bool GetCoinRaw(const COutPoint &outpoint, Coin &coin) const override; + bool HaveCoinRaw(const COutPoint &outpoint) const override; uint256 GetBestBlock() const override; std::vector GetHeadBlocks() const override; bool BatchWrite(CCoinsMap &mapCoins, const uint256 &hashBlock, bool erase = true) override; diff --git a/src/txmempool.cpp b/src/txmempool.cpp index 461662ad93ae0..bb88a500f911e 100644 --- a/src/txmempool.cpp +++ b/src/txmempool.cpp @@ -993,7 +993,7 @@ bool CTxMemPool::HasNoInputsOf(const CTransaction &tx) const CCoinsViewMemPool::CCoinsViewMemPool(CCoinsView* baseIn, const CTxMemPool& mempoolIn) : CCoinsViewBacked(baseIn), mempool(mempoolIn) { } -bool CCoinsViewMemPool::GetCoin(const COutPoint &outpoint, Coin &coin) const { +bool CCoinsViewMemPool::GetCoinRaw(const COutPoint &outpoint, Coin &coin) const { // Check to see if the inputs are made available by another tx in the package. // These Coins would not be available in the underlying CoinsView. if (auto it = m_temp_added.find(outpoint); it != m_temp_added.end()) { @@ -1014,7 +1014,7 @@ bool CCoinsViewMemPool::GetCoin(const COutPoint &outpoint, Coin &coin) const { return false; } } - return base->GetCoin(outpoint, coin); + return base->GetCoinRaw(outpoint, coin); } void CCoinsViewMemPool::PackageAddTransaction(const CTransactionRef& tx) diff --git a/src/txmempool.h b/src/txmempool.h index cbeabb31fa6ce..038b14f4ab620 100644 --- a/src/txmempool.h +++ b/src/txmempool.h @@ -839,7 +839,7 @@ class CCoinsViewMemPool : public CCoinsViewBacked CCoinsViewMemPool(CCoinsView* baseIn, const CTxMemPool& mempoolIn); /** GetCoin, returning whether it exists and is not spent. Also updates m_non_base_coins if the * coin is not fetched from base. */ - bool GetCoin(const COutPoint &outpoint, Coin &coin) const override; + bool GetCoinRaw(const COutPoint &outpoint, Coin &coin) const override; /** Add the coins created by this transaction. These coins are only temporarily stored in * m_temp_added and cannot be flushed to the back end. Only used for package validation. */ void PackageAddTransaction(const CTransactionRef& tx); diff --git a/src/validation.cpp b/src/validation.cpp index 290db8c9b25b7..d6a04203bc181 100644 --- a/src/validation.cpp +++ b/src/validation.cpp @@ -1956,7 +1956,7 @@ int ApplyTxInUndo(Coin&& undo, CCoinsViewCache& view, const COutPoint& out) { bool fClean = true; - if (view.HaveCoin(out)) fClean = false; // overwriting transaction output + if (view.HaveCoinRaw(out)) fClean = false; // overwriting transaction output if (undo.nHeight == 0) { // Missing undo metadata (height and coinbase). Older versions included this From c4d7865f55d30aaa77e4a1710650e8e030c36dbe Mon Sep 17 00:00:00 2001 From: Luke Dashjr Date: Mon, 10 Jul 2023 22:54:43 +0000 Subject: [PATCH 04/21] WIP Drivechain via UTXOs --- src/Makefile.am | 3 + src/coins.cpp | 2 +- src/sidechain.cpp | 174 +++++++++++++++++++++++++++++++++++++++++++++ src/sidechain.h | 47 ++++++++++++ src/undo.h | 2 +- src/validation.cpp | 34 ++++++++- 6 files changed, 258 insertions(+), 4 deletions(-) create mode 100644 src/sidechain.cpp create mode 100644 src/sidechain.h diff --git a/src/Makefile.am b/src/Makefile.am index 8905c0ad1cd25..98e2c5a0da35f 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -271,6 +271,7 @@ BITCOIN_CORE_H = \ script/signingprovider.h \ script/solver.h \ shutdown.h \ + sidechain.h \ signet.h \ streams.h \ support/allocators/pool.h \ @@ -457,6 +458,7 @@ libbitcoin_node_a_SOURCES = \ rpc/txoutproof.cpp \ script/sigcache.cpp \ shutdown.cpp \ + sidechain.cpp \ signet.cpp \ timedata.cpp \ torcontrol.cpp \ @@ -968,6 +970,7 @@ libbitcoinkernel_la_SOURCES = \ script/script_error.cpp \ script/sigcache.cpp \ script/solver.cpp \ + sidechain.cpp \ signet.cpp \ streams.cpp \ support/cleanse.cpp \ diff --git a/src/coins.cpp b/src/coins.cpp index 364a5de7e3803..a8569b44d9faf 100644 --- a/src/coins.cpp +++ b/src/coins.cpp @@ -69,7 +69,7 @@ bool CCoinsViewCache::GetCoinRaw(const COutPoint &outpoint, Coin &coin) const { void CCoinsViewCache::AddCoin(const COutPoint &outpoint, Coin&& coin, bool possible_overwrite) { assert(!coin.IsSpent()); - if (coin.out.scriptPubKey.IsUnspendable()) return; + if (coin.out.scriptPubKey.IsUnspendable() && outpoint.n <= COutPoint::MAX_INDEX) return; CCoinsMap::iterator it; bool inserted; std::tie(it, inserted) = cacheCoins.emplace(std::piecewise_construct, std::forward_as_tuple(outpoint), std::tuple<>()); diff --git a/src/sidechain.cpp b/src/sidechain.cpp new file mode 100644 index 0000000000000..d6b3ff8633f5e --- /dev/null +++ b/src/sidechain.cpp @@ -0,0 +1,174 @@ +// Copyright (c) 2017-2023 The Bitcoin Core developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#include + +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +void CreateDBUndoData(CTxUndo &txundo, const uint8_t type, const COutPoint& record_id, const Coin& encoded_data) { + Assert(record_id.n <= COutPoint::MAX_INDEX); + Coin& undo = txundo.vprevout.emplace_back(); + undo = encoded_data; + CDataStream s(SER_NETWORK, PROTOCOL_VERSION); + s << record_id; + Assert(s.size() == 0x24); + auto& undo_data = undo.out.scriptPubKey; + undo_data.insert(undo_data.begin(), 1 + s.size(), type); + memcpy(&undo_data[1], &s[0], s.size()); // TODO: figure out how to jump through C++'s hoops to do this right +} + +void CreateDBEntry(CCoinsViewCache& cache, CTxUndo &txundo, const int block_height, const COutPoint& record_id, const Span& record_data) { + CScript scriptPubKey(UCharCast(record_data.begin()), UCharCast(record_data.end())); + cache.AddCoin(record_id, Coin(CTxOut{0, scriptPubKey}, block_height, /*fCoinbase=*/false), /*overwrite=*/false); + + // Create undo data to tell DisconnectBlock to delete it + Coin undo; + CreateDBUndoData(txundo, 1, record_id, undo); +} + +void DeleteDBEntry(CCoinsViewCache& inputs, CTxUndo &txundo, const COutPoint& record_id) { + Coin undo; + bool is_spent = inputs.SpendCoin(record_id, &undo); + assert(is_spent); + CreateDBUndoData(txundo, 0, record_id, undo); +} + +CDataStream GetDBEntry(CCoinsViewCache& inputs, const COutPoint& record_id) { + const Coin& coin = inputs.AccessCoin(record_id); + assert(!coin.IsSpent()); + return CDataStream(MakeByteSpan(coin.out.scriptPubKey), SER_NETWORK, PROTOCOL_VERSION); +} + +void ModifyDBEntry(CCoinsViewCache& view, CTxUndo &txundo, const int block_height, const COutPoint& record_id, const std::function& modify_func) { + CDataStream s = GetDBEntry(view, record_id); + modify_func(s); + DeleteDBEntry(view, txundo, record_id); + CreateDBEntry(view, txundo, block_height, record_id, s); +} + +void IncrementDBEntry(CCoinsViewCache& view, CTxUndo &txundo, const int block_height, const COutPoint& record_id, const int change) { + ModifyDBEntry(view, txundo, block_height, record_id, [change](CDataStream& s){ + uint16_t counter; + s >> counter; + if (change < 0 && !counter) return; // may be surprising if change is <-1 + counter += change; + s.clear(); + s << counter; + }); +} + +void UpdateDrivechains(const CTransaction& tx, CCoinsViewCache& view, CTxUndo &txundo, int block_height) +{ + Assert(tx.IsCoinBase()); + + std::vector sidechain_proposal_list, withdraw_proposal_list; + + for (auto& out : tx.vout) { + if (out.scriptPubKey.size() < 5) continue; + if (out.scriptPubKey[0] != OP_RETURN) continue; + // FIXME: The rest should probably be serialised, but neither BIP300 nor its reference implementation does that + static constexpr uint8_t BIP300_HEADER_SIDECHAIN_PROPOSE[] = {0xd5, 0xe0, 0xc4, 0xaf}; // n.b. 20 sigops + static constexpr uint8_t BIP300_HEADER_SIDECHAIN_ACK[] = {0xd6, 0xe1, 0xc5, 0xbf}; + static constexpr uint8_t BIP300_HEADER_WITHDRAW_PROPOSE[] = {0xd4, 0x5a, 0xa9, 0x43}; // n.b. 67 byte push followed by only 32 bytes + static constexpr uint8_t BIP300_HEADER_WITHDRAW_ACK[] = {0xd7, 0x7d, 0x17, 0x76}; // n.b. 23-byte push followed by variable bytes + if (std::equal(&out.scriptPubKey[1], &out.scriptPubKey[5], BIP300_HEADER_WITHDRAW_ACK)) { + const uint8_t data_format = out.scriptPubKey[6]; + // TODO: Implement formats 3+? Or at least validate + // TODO: How is vote vector actually encoded? + // TODO: Block is invalid if there are no bundles proposed at all + for (int sidechain_id = 0; sidechain_id < 0x100; ++sidechain_id) { + // FIXME: bounds checking + uint16_t vote = out.scriptPubKey[6 + (sidechain_id * data_format)]; + if (data_format == 2) { + vote |= uint16_t{out.scriptPubKey[6 + (sidechain_id * data_format) + 1]} << 8; + } else if (vote >= 0xfe) { + vote |= 0xff00; + } + + if (vote == 0xffff) continue; // abstain + + COutPoint record_id{uint256{sidechain_id}, DBIDX_SIDECHAIN_WITHDRAW_PROPOSAL_LIST}; + // FIXME: what if it's missing? + CDataStream withdraw_proposals = GetDBEntry(view, record_id); + uint256 bundle_hash; + bool found_bundle{false}; + for (uint16_t bundle_hash_num = 0; !withdraw_proposals.eof(); ++bundle_hash_num) { + withdraw_proposals >> bundle_hash; + record_id.hash = bundle_hash; + record_id.n = DBIDX_SIDECHAIN_WITHDRAW_PROPOSAL_ACKS; + if (bundle_hash_num == vote) found_bundle = true; + IncrementDBEntry(view, txundo, block_height, record_id, (bundle_hash_num == vote) ? 1 : -1); + } + // TODO: invalid if ((!found_bundle) && vote != 0xfffe) + } + } else if (std::equal(&out.scriptPubKey[1], &out.scriptPubKey[5], BIP300_HEADER_WITHDRAW_PROPOSE)) { + // FIXME; size check; [at least] 38 bytes + CDataStream s(MakeByteSpan(out.scriptPubKey).subspan(5), SER_NETWORK, PROTOCOL_VERSION); + uint256 bundle_hash; + uint8_t sidechain_id; + s >> bundle_hash; + s >> sidechain_id; + + // FIXME: make sure sidechain_id hasn't been encountered here in this block before + // FIXME: make sure this proposal isn't already listed (check for ACKS existing?) + // FIXME: allow the same bundle for multiple sidechain_id to prevent DoS attacks? + + COutPoint record_id{uint256{sidechain_id}, DBIDX_SIDECHAIN_WITHDRAW_PROPOSAL_LIST}; + ModifyDBEntry(view, txundo, block_height, record_id, [&bundle_hash](CDataStream& withdraw_proposals){ + withdraw_proposals << bundle_hash; + }); + + record_id.hash = bundle_hash; + record_id.n = DBIDX_SIDECHAIN_WITHDRAW_PROPOSAL_ACKS; + s.clear(); + s << uint16_t{0}; + CreateDBEntry(view, txundo, block_height, record_id, s); + + withdraw_proposal_list.resize(withdraw_proposal_list.size() + record_id.hash.size()); + memcpy(&withdraw_proposal_list.data()[withdraw_proposal_list.size() - record_id.hash.size()], record_id.hash.data(), record_id.hash.size()); // FIXME: C++ify + } else if (std::equal(&out.scriptPubKey[1], &out.scriptPubKey[5], BIP300_HEADER_SIDECHAIN_ACK)) { + // FIXME: check size is [at least?] 37 bytes + COutPoint record_id; + memcpy(record_id.hash.data(), &out.scriptPubKey[5], 0x20); + record_id.n = DBIDX_SIDECHAIN_PROPOSAL_ACKS; + + IncrementDBEntry(view, txundo, block_height, record_id, 1); + } else if (std::equal(&out.scriptPubKey[1], &out.scriptPubKey[5], BIP300_HEADER_SIDECHAIN_PROPOSE)) { + CDataStream s(MakeByteSpan(out.scriptPubKey).subspan(5), SER_NETWORK, PROTOCOL_VERSION); + Sidechain proposed; + // FIXME: What happens if parsing fails? + s >> proposed; + + COutPoint record_id; + CSHA256().Write(out.scriptPubKey.data() + 5, out.scriptPubKey.size() - 5).Finalize(record_id.hash.data()); + record_id.n = DBIDX_SIDECHAIN_PROPOSAL; + CreateDBEntry(view, txundo, block_height, record_id, s); + + s.clear(); + s << uint16_t{0}; + record_id.n = DBIDX_SIDECHAIN_PROPOSAL_ACKS; + CreateDBEntry(view, txundo, block_height, record_id, s); + + sidechain_proposal_list.resize(sidechain_proposal_list.size() + record_id.hash.size()); + memcpy(&sidechain_proposal_list.data()[sidechain_proposal_list.size() - record_id.hash.size()], record_id.hash.data(), record_id.hash.size()); // FIXME: C++ify + } + } + + if (sidechain_proposal_list.empty() && withdraw_proposal_list.empty()) return; + + CScript proposal_list; + proposal_list << sidechain_proposal_list; + proposal_list << withdraw_proposal_list; + COutPoint record_id{ArithToUint256(arith_uint256{uint64_t{block_height}}), DBIDX_SIDECHAIN_PROPOSAL_LIST}; + CreateDBEntry(view, txundo, block_height, record_id, MakeByteSpan(proposal_list)); +} diff --git a/src/sidechain.h b/src/sidechain.h new file mode 100644 index 0000000000000..a427d03d403bb --- /dev/null +++ b/src/sidechain.h @@ -0,0 +1,47 @@ +// Copyright (c) 2017-2023 The Bitcoin Core developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#ifndef BITCOIN_SIDECHAIN_H +#define BITCOIN_SIDECHAIN_H + +#include +#include + +#include +#include + +class CTransaction; +class CCoinsViewCache; +class CTxUndo; + +//! The current sidechain version +static constexpr int SIDECHAIN_VERSION_CURRENT{0}; + +// Key is the proposal hash; Data is the proposal itself +static constexpr uint32_t DBIDX_SIDECHAIN_PROPOSAL{0xff010000}; +// Key is the block height; Data is a serialised list of hashes of sidechain proposals in the block, then a serialised list of withdraw proposals in the block +static constexpr uint32_t DBIDX_SIDECHAIN_PROPOSAL_LIST{0xff010001}; +// Key is the proposal hash; Data is a uint16_t with ACK count +static constexpr uint32_t DBIDX_SIDECHAIN_PROPOSAL_ACKS{0xff010002}; +// Key is the sidechain number; Data is a raw list of blinded-hashes of withdraw proposals +static constexpr uint32_t DBIDX_SIDECHAIN_WITHDRAW_PROPOSAL_LIST{0xff010003}; +// Key is a blinded withdrawl hash; Data is a uint16_t with ACK count +static constexpr uint32_t DBIDX_SIDECHAIN_WITHDRAW_PROPOSAL_ACKS{0xff010004}; + +struct Sidechain { + uint8_t idnum{0}; + int32_t version{SIDECHAIN_VERSION_CURRENT}; + std::string title; + std::string description; + uint256 hashID1; + uint160 hashID2; + + SERIALIZE_METHODS(Sidechain, obj) { + READWRITE(obj.idnum, obj.version, obj.title, obj.description, obj.hashID1, obj.hashID2); + } +}; + +void UpdateDrivechains(const CTransaction& tx, CCoinsViewCache& inputs, CTxUndo &txundo, int nHeight); + +#endif // BITCOIN_SIDECHAIN_H diff --git a/src/undo.h b/src/undo.h index a98f046735ce4..5fe1bc9370eee 100644 --- a/src/undo.h +++ b/src/undo.h @@ -63,7 +63,7 @@ class CTxUndo class CBlockUndo { public: - std::vector vtxundo; // for all but the coinbase + std::vector vtxundo; // for all but the coinbase, plus an extra on the end for drivechains SERIALIZE_METHODS(CBlockUndo, obj) { READWRITE(obj.vtxundo); } }; diff --git a/src/validation.cpp b/src/validation.cpp index d6a04203bc181..8d8721f611892 100644 --- a/src/validation.cpp +++ b/src/validation.cpp @@ -41,6 +41,7 @@ #include #include