txdb.cpp raw
1 // Copyright (c) 2009-2010 Satoshi Nakamoto
2 // Copyright (c) 2009-2022 The Limenka developers
3 // Distributed under the MIT software license, see the accompanying
4 // file COPYING or http://www.opensource.org/licenses/mit-license.php.
5
6 #include <txdb.h>
7
8 #include <coins.h>
9 #include <dbwrapper.h>
10 #include <logging.h>
11 #include <primitives/transaction.h>
12 #include <random.h>
13 #include <serialize.h>
14 #include <uint256.h>
15 #include <util/vector.h>
16
17 #include <cassert>
18 #include <cstdlib>
19 #include <iterator>
20 #include <utility>
21
22 static constexpr uint8_t DB_COIN{'C'};
23 static constexpr uint8_t DB_BEST_BLOCK{'B'};
24 static constexpr uint8_t DB_HEAD_BLOCKS{'H'};
25 // Keys used in previous version that might still be found in the DB:
26 static constexpr uint8_t DB_COINS{'c'};
27
28 bool CCoinsViewDB::NeedsUpgrade()
29 {
30 std::unique_ptr<CDBIterator> cursor{m_db->NewIterator()};
31 // DB_COINS was deprecated in v0.15.0, commit
32 // 1088b02f0ccd7358d2b7076bb9e122d59d502d02
33 cursor->Seek(std::make_pair(DB_COINS, uint256{}));
34 return cursor->Valid();
35 }
36
37 namespace {
38
39 struct CoinEntry {
40 COutPoint* outpoint;
41 uint8_t key;
42 explicit CoinEntry(const COutPoint* ptr) : outpoint(const_cast<COutPoint*>(ptr)), key(DB_COIN) {}
43
44 SERIALIZE_METHODS(CoinEntry, obj) { READWRITE(obj.key, obj.outpoint->hash, VARINT(obj.outpoint->n)); }
45 };
46
47 } // namespace
48
49 CCoinsViewDB::CCoinsViewDB(DBParams db_params, CoinsViewOptions options) :
50 m_db_params{std::move(db_params)},
51 m_options{std::move(options)},
52 m_db{std::make_unique<CDBWrapper>(m_db_params)} { }
53
54 void CCoinsViewDB::ResizeCache(size_t new_cache_size)
55 {
56 // We can't do this operation with an in-memory DB since we'll lose all the coins upon
57 // reset.
58 if (!m_db_params.memory_only) {
59 // Have to do a reset first to get the original `m_db` state to release its
60 // filesystem lock.
61 m_db.reset();
62 m_db_params.cache_bytes = new_cache_size;
63 m_db_params.wipe_data = false;
64 m_db = std::make_unique<CDBWrapper>(m_db_params);
65 }
66 }
67
68 std::optional<Coin> CCoinsViewDB::GetCoin(const COutPoint& outpoint) const
69 {
70 if (Coin coin; m_db->Read(CoinEntry(&outpoint), coin)) return coin;
71 return std::nullopt;
72 }
73
74 bool CCoinsViewDB::HaveCoin(const COutPoint &outpoint) const {
75 return m_db->Exists(CoinEntry(&outpoint));
76 }
77
78 uint256 CCoinsViewDB::GetBestBlock() const {
79 uint256 hashBestChain;
80 if (!m_db->Read(DB_BEST_BLOCK, hashBestChain))
81 return uint256();
82 return hashBestChain;
83 }
84
85 std::vector<uint256> CCoinsViewDB::GetHeadBlocks() const {
86 std::vector<uint256> vhashHeadBlocks;
87 if (!m_db->Read(DB_HEAD_BLOCKS, vhashHeadBlocks)) {
88 return std::vector<uint256>();
89 }
90 return vhashHeadBlocks;
91 }
92
93 bool CCoinsViewDB::BatchWrite(CoinsViewCacheCursor& cursor, const uint256 &hashBlock) {
94 CDBBatch batch(*m_db);
95 size_t count = 0;
96 size_t changed = 0;
97 assert(!hashBlock.IsNull());
98
99 uint256 old_tip = GetBestBlock();
100 if (old_tip.IsNull()) {
101 // We may be in the middle of replaying.
102 std::vector<uint256> old_heads = GetHeadBlocks();
103 if (old_heads.size() == 2) {
104 if (old_heads[0] != hashBlock) {
105 LogPrintLevel(BCLog::COINDB, BCLog::Level::Error, "The coins database detected an inconsistent state, likely due to a previous crash or shutdown. You will need to restart limenkad with the -reindex-chainstate or -reindex configuration option.\n");
106 }
107 assert(old_heads[0] == hashBlock);
108 old_tip = old_heads[1];
109 }
110 }
111
112 // In the first batch, mark the database as being in the middle of a
113 // transition from old_tip to hashBlock.
114 // A vector is used for future extensibility, as we may want to support
115 // interrupting after partial writes from multiple independent reorgs.
116 batch.Erase(DB_BEST_BLOCK);
117 batch.Write(DB_HEAD_BLOCKS, Vector(hashBlock, old_tip));
118
119 for (auto it{cursor.Begin()}; it != cursor.End();) {
120 if (it->second.IsDirty()) {
121 CoinEntry entry(&it->first);
122 if (it->second.coin.IsSpent())
123 batch.Erase(entry);
124 else
125 batch.Write(entry, it->second.coin);
126 changed++;
127 }
128 count++;
129 it = cursor.NextAndMaybeErase(*it);
130 if (batch.SizeEstimate() > m_options.batch_write_bytes) {
131 LogDebug(BCLog::COINDB, "Writing partial batch of %.2f MiB\n", batch.SizeEstimate() * (1.0 / 1048576.0));
132 m_db->WriteBatch(batch);
133 batch.Clear();
134 if (m_options.simulate_crash_ratio) {
135 static FastRandomContext rng;
136 if (rng.randrange(m_options.simulate_crash_ratio) == 0) {
137 LogError("Simulating a crash. Goodbye.");
138 _Exit(0);
139 }
140 }
141 }
142 }
143
144 // In the last batch, mark the database as consistent with hashBlock again.
145 batch.Erase(DB_HEAD_BLOCKS);
146 batch.Write(DB_BEST_BLOCK, hashBlock);
147
148 LogDebug(BCLog::COINDB, "Writing final batch of %.2f MiB\n", batch.SizeEstimate() * (1.0 / 1048576.0));
149 bool ret = m_db->WriteBatch(batch);
150 LogDebug(BCLog::COINDB, "Committed %u changed transaction outputs (out of %u) to coin database...\n", (unsigned int)changed, (unsigned int)count);
151 return ret;
152 }
153
154 size_t CCoinsViewDB::EstimateSize() const
155 {
156 return m_db->EstimateSize(DB_COIN, uint8_t(DB_COIN + 1));
157 }
158
159 /** Specialization of CCoinsViewCursor to iterate over a CCoinsViewDB */
160 class CCoinsViewDBCursor: public CCoinsViewCursor
161 {
162 public:
163 // Prefer using CCoinsViewDB::Cursor() since we want to perform some
164 // cache warmup on instantiation.
165 CCoinsViewDBCursor(CDBIterator* pcursorIn, const uint256&hashBlockIn):
166 CCoinsViewCursor(hashBlockIn), pcursor(pcursorIn) {}
167 ~CCoinsViewDBCursor() = default;
168
169 bool GetKey(COutPoint &key) const override;
170 bool GetValue(Coin &coin) const override;
171
172 bool Valid() const override;
173 void Next() override;
174
175 private:
176 std::unique_ptr<CDBIterator> pcursor;
177 std::pair<char, COutPoint> keyTmp;
178
179 friend class CCoinsViewDB;
180 };
181
182 std::unique_ptr<CCoinsViewCursor> CCoinsViewDB::Cursor() const
183 {
184 auto i = std::make_unique<CCoinsViewDBCursor>(
185 const_cast<CDBWrapper&>(*m_db).NewIterator(), GetBestBlock());
186 /* It seems that there are no "const iterators" for LevelDB. Since we
187 only need read operations on it, use a const-cast to get around
188 that restriction. */
189 i->pcursor->Seek(DB_COIN);
190 // Cache key of first record
191 if (i->pcursor->Valid()) {
192 CoinEntry entry(&i->keyTmp.second);
193 i->pcursor->GetKey(entry);
194 i->keyTmp.first = entry.key;
195 } else {
196 i->keyTmp.first = 0; // Make sure Valid() and GetKey() return false
197 }
198 return i;
199 }
200
201 bool CCoinsViewDBCursor::GetKey(COutPoint &key) const
202 {
203 // Return cached key
204 if (keyTmp.first == DB_COIN) {
205 key = keyTmp.second;
206 return true;
207 }
208 return false;
209 }
210
211 bool CCoinsViewDBCursor::GetValue(Coin &coin) const
212 {
213 return pcursor->GetValue(coin);
214 }
215
216 bool CCoinsViewDBCursor::Valid() const
217 {
218 return keyTmp.first == DB_COIN;
219 }
220
221 void CCoinsViewDBCursor::Next()
222 {
223 pcursor->Next();
224 CoinEntry entry(&keyTmp.second);
225 if (!pcursor->Valid() || !pcursor->GetKey(entry)) {
226 keyTmp.first = 0; // Invalidate cached key after last record so that Valid() and GetKey() return false
227 } else {
228 keyTmp.first = entry.key;
229 }
230 }
231