Skip to content

Commit 92fee79

Browse files
committed
Merge #19806: validation: UTXO snapshot activation
1afc0e4 doc: remove potentially confusing ChainstateManager comment (James O'Beirne) 769a1ef test: Add tests with maleated snapshot data (Fabian Jahr) 4d8de04 tests: add snapshot activation test (James O'Beirne) 31d2252 tests: add deterministic chain generation unittest fixture (James O'Beirne) 6606a4f move-onlyish: break out CreateUTXOSnapshot from dumptxoutset (James O'Beirne) ad949ba txdb: don't reset during in-memory cache resize (James O'Beirne) f6e2da5 simplify ChainstateManager::SnapshotBlockhash() return semantics (James O'Beirne) 7a6c46b chainparams: add allowed assumeutxo values (James O'Beirne) Pull request description: This is part of the [assumeutxo project](https://github.com/bitcoin/bitcoin/projects/11): Parent PR: #15606 Issue: #15605 Specification: https://github.com/jamesob/assumeutxo-docs/tree/master/proposal --- This change proposes logic for activating UTXO snapshots, which is unused at the moment aside from an included unittest. There are a few moveonyish/refactoring commits to allow for halfway decent unittests. Basic structure is included for specifying and checking the assumeutxo hash values used to validate activated snapshots. Initially I had specified a few height/hash pairs for mainnet in this change, but because of the security-critical nature of those parameters, I figured it was better to leave their inclusion to a future PR that includes only that change - my intent being that reviewers will be more likely to verify those parameters firsthand in a dedicated PR. Aside from that and the snapshot activation logic, there are a few related changes: - ~~allow caching the `nChainTx` value in the CCoinsViewDB; this is set during snapshot activation. Because we don't necessarily have access to the full chain at the time of snapshot load, this value is communicated through the snapshot metadata and must be cached within the chainstate to survive restarts.~~ - break out `CreateUTXOSnapshot()` from dumptxoutset. This is essentially a move-only change to allow the reuse of snapshot creation logic from within unittests. - ...and a few other misc. changes that are solely related to unittests. The move-onlyish commit is most easily reviewed with `--color-moved=zebra`. ACKs for top commit: fjahr: Code review ACK 1afc0e4 laanwj: Code review ACK 1afc0e4 Tree-SHA512: a4e4f0698f00a53ec298b5e8b7ef1c9fdf0185f95139d1b1f63cfdf6cbbd6d17b8c6e51bbf1de2e5f1a946bf49f8466232698ef55acce5a012c80b067da366ea
2 parents 3c9d9d2 + 1afc0e4 commit 92fee79

17 files changed

+715
-30
lines changed

src/chain.h

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -163,14 +163,27 @@ class CBlockIndex
163163

164164
//! Number of transactions in this block.
165165
//! Note: in a potential headers-first mode, this number cannot be relied upon
166+
//! Note: this value is faked during UTXO snapshot load to ensure that
167+
//! LoadBlockIndex() will load index entries for blocks that we lack data for.
168+
//! @sa ActivateSnapshot
166169
unsigned int nTx{0};
167170

168171
//! (memory only) Number of transactions in the chain up to and including this block.
169172
//! This value will be non-zero only if and only if transactions for this block and all its parents are available.
170173
//! Change to 64-bit type when necessary; won't happen before 2030
174+
//!
175+
//! Note: this value is faked during use of a UTXO snapshot because we don't
176+
//! have the underlying block data available during snapshot load.
177+
//! @sa AssumeutxoData
178+
//! @sa ActivateSnapshot
171179
unsigned int nChainTx{0};
172180

173181
//! Verification status of this block. See enum BlockStatus
182+
//!
183+
//! Note: this value is modified to show BLOCK_OPT_WITNESS during UTXO snapshot
184+
//! load to avoid the block index being spuriously rewound.
185+
//! @sa RewindBlockIndex
186+
//! @sa ActivateSnapshot
174187
uint32_t nStatus{0};
175188

176189
//! block header

src/chainparams.cpp

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@
88
#include <chainparamsseeds.h>
99
#include <consensus/merkle.h>
1010
#include <hash.h> // for signet block challenge hash
11-
#include <tinyformat.h>
1211
#include <util/system.h>
1312
#include <util/strencodings.h>
1413
#include <versionbitsinfo.h>
@@ -161,6 +160,10 @@ class CMainParams : public CChainParams {
161160
}
162161
};
163162

163+
m_assumeutxo_data = MapAssumeutxo{
164+
// TODO to be specified in a future patch.
165+
};
166+
164167
chainTxData = ChainTxData{
165168
// Data from RPC: getchaintxstats 4096 0000000000000000000b9d2ec5a352ecba0592946514a92f14319dc2b367fc72
166169
/* nTime */ 1603995752,
@@ -250,6 +253,10 @@ class CTestNetParams : public CChainParams {
250253
}
251254
};
252255

256+
m_assumeutxo_data = MapAssumeutxo{
257+
// TODO to be specified in a future patch.
258+
};
259+
253260
chainTxData = ChainTxData{
254261
// Data from RPC: getchaintxstats 4096 000000000000006433d1efec504c53ca332b64963c425395515b01977bd7b3b0
255262
/* nTime */ 1603359686,
@@ -431,6 +438,17 @@ class CRegTestParams : public CChainParams {
431438
}
432439
};
433440

441+
m_assumeutxo_data = MapAssumeutxo{
442+
{
443+
110,
444+
{uint256S("0x76fd7334ac7c1baf57ddc0c626f073a655a35d98a4258cd1382c8cc2b8392e10"), 110},
445+
},
446+
{
447+
210,
448+
{uint256S("0x9c5ed99ef98544b34f8920b6d1802f72ac28ae6e2bd2bd4c316ff10c230df3f2"), 210},
449+
},
450+
};
451+
434452
chainTxData = ChainTxData{
435453
0,
436454
0,
@@ -526,3 +544,9 @@ void SelectParams(const std::string& network)
526544
SelectBaseParams(network);
527545
globalChainParams = CreateChainParams(gArgs, network);
528546
}
547+
548+
std::ostream& operator<<(std::ostream& o, const AssumeutxoData& aud)
549+
{
550+
o << strprintf("AssumeutxoData(%s, %s)", aud.hash_serialized.ToString(), aud.nChainTx);
551+
return o;
552+
}

src/chainparams.h

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,26 @@ struct CCheckpointData {
3030
}
3131
};
3232

33+
/**
34+
* Holds configuration for use during UTXO snapshot load and validation. The contents
35+
* here are security critical, since they dictate which UTXO snapshots are recognized
36+
* as valid.
37+
*/
38+
struct AssumeutxoData {
39+
//! The expected hash of the deserialized UTXO set.
40+
const uint256 hash_serialized;
41+
42+
//! Used to populate the nChainTx value, which is used during BlockManager::LoadBlockIndex().
43+
//!
44+
//! We need to hardcode the value here because this is computed cumulatively using block data,
45+
//! which we do not necessarily have at the time of snapshot load.
46+
const unsigned int nChainTx;
47+
};
48+
49+
std::ostream& operator<<(std::ostream& o, const AssumeutxoData& aud);
50+
51+
using MapAssumeutxo = std::map<int, const AssumeutxoData>;
52+
3353
/**
3454
* Holds various statistics on transactions within a chain. Used to estimate
3555
* verification progress during chain sync.
@@ -90,6 +110,11 @@ class CChainParams
90110
const std::string& Bech32HRP() const { return bech32_hrp; }
91111
const std::vector<SeedSpec6>& FixedSeeds() const { return vFixedSeeds; }
92112
const CCheckpointData& Checkpoints() const { return checkpointData; }
113+
114+
//! Get allowed assumeutxo configuration.
115+
//! @see ChainstateManager
116+
const MapAssumeutxo& Assumeutxo() const { return m_assumeutxo_data; }
117+
93118
const ChainTxData& TxData() const { return chainTxData; }
94119
protected:
95120
CChainParams() {}
@@ -111,6 +136,7 @@ class CChainParams
111136
bool m_is_test_chain;
112137
bool m_is_mockable_chain;
113138
CCheckpointData checkpointData;
139+
MapAssumeutxo m_assumeutxo_data;
114140
ChainTxData chainTxData;
115141
};
116142

src/coins.cpp

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,14 @@ void CCoinsViewCache::AddCoin(const COutPoint &outpoint, Coin&& coin, bool possi
9797
cachedCoinsUsage += it->second.coin.DynamicMemoryUsage();
9898
}
9999

100+
void CCoinsViewCache::EmplaceCoinInternalDANGER(COutPoint&& outpoint, Coin&& coin) {
101+
cachedCoinsUsage += coin.DynamicMemoryUsage();
102+
cacheCoins.emplace(
103+
std::piecewise_construct,
104+
std::forward_as_tuple(std::move(outpoint)),
105+
std::forward_as_tuple(std::move(coin), CCoinsCacheEntry::DIRTY));
106+
}
107+
100108
void AddCoins(CCoinsViewCache& cache, const CTransaction &tx, int nHeight, bool check_for_overwrite) {
101109
bool fCoinbase = tx.IsCoinBase();
102110
const uint256& txid = tx.GetHash();

src/coins.h

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@
2020
#include <functional>
2121
#include <unordered_map>
2222

23+
class ChainstateManager;
24+
2325
/**
2426
* A UTXO entry.
2527
*
@@ -125,6 +127,7 @@ struct CCoinsCacheEntry
125127

126128
CCoinsCacheEntry() : flags(0) {}
127129
explicit CCoinsCacheEntry(Coin&& coin_) : coin(std::move(coin_)), flags(0) {}
130+
CCoinsCacheEntry(Coin&& coin_, unsigned char flag) : coin(std::move(coin_)), flags(flag) {}
128131
};
129132

130133
typedef std::unordered_map<COutPoint, CCoinsCacheEntry, SaltedOutpointHasher> CCoinsMap;
@@ -262,6 +265,15 @@ class CCoinsViewCache : public CCoinsViewBacked
262265
*/
263266
void AddCoin(const COutPoint& outpoint, Coin&& coin, bool possible_overwrite);
264267

268+
/**
269+
* Emplace a coin into cacheCoins without performing any checks, marking
270+
* the emplaced coin as dirty.
271+
*
272+
* NOT FOR GENERAL USE. Used only when loading coins from a UTXO snapshot.
273+
* @sa ChainstateManager::PopulateAndValidateSnapshot()
274+
*/
275+
void EmplaceCoinInternalDANGER(COutPoint&& outpoint, Coin&& coin);
276+
265277
/**
266278
* Spend a coin. Pass moveto in order to get the deleted data.
267279
* If no unspent output exists for the passed outpoint, this call

src/node/coinstats.cpp

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,18 @@ static void ApplyHash(CCoinsStats& stats, MuHash3072& muhash, const uint256& has
5555
muhash.Insert(MakeUCharSpan(ss));
5656
}
5757

58+
//! Warning: be very careful when changing this! assumeutxo and UTXO snapshot
59+
//! validation commitments are reliant on the hash constructed by this
60+
//! function.
61+
//!
62+
//! If the construction of this hash is changed, it will invalidate
63+
//! existing UTXO snapshots. This will not result in any kind of consensus
64+
//! failure, but it will force clients that were expecting to make use of
65+
//! assumeutxo to do traditional IBD instead.
66+
//!
67+
//! It is also possible, though very unlikely, that a change in this
68+
//! construction could cause a previously invalid (and potentially malicious)
69+
//! UTXO snapshot to be considered valid.
5870
template <typename T>
5971
static void ApplyStats(CCoinsStats& stats, T& hash_obj, const uint256& hash, const std::map<uint32_t, Coin>& outputs)
6072
{

src/rpc/blockchain.cpp

Lines changed: 16 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2411,10 +2411,21 @@ static RPCHelpMan dumptxoutset()
24112411

24122412
FILE* file{fsbridge::fopen(temppath, "wb")};
24132413
CAutoFile afile{file, SER_DISK, CLIENT_VERSION};
2414+
NodeContext& node = EnsureNodeContext(request.context);
2415+
UniValue result = CreateUTXOSnapshot(node, node.chainman->ActiveChainstate(), afile);
2416+
fs::rename(temppath, path);
2417+
2418+
result.pushKV("path", path.string());
2419+
return result;
2420+
},
2421+
};
2422+
}
2423+
2424+
UniValue CreateUTXOSnapshot(NodeContext& node, CChainState& chainstate, CAutoFile& afile)
2425+
{
24142426
std::unique_ptr<CCoinsViewCursor> pcursor;
24152427
CCoinsStats stats;
24162428
CBlockIndex* tip;
2417-
NodeContext& node = EnsureNodeContext(request.context);
24182429

24192430
{
24202431
// We need to lock cs_main to ensure that the coinsdb isn't written to
@@ -2431,13 +2442,13 @@ static RPCHelpMan dumptxoutset()
24312442
//
24322443
LOCK(::cs_main);
24332444

2434-
::ChainstateActive().ForceFlushStateToDisk();
2445+
chainstate.ForceFlushStateToDisk();
24352446

2436-
if (!GetUTXOStats(&::ChainstateActive().CoinsDB(), stats, CoinStatsHashType::NONE, node.rpc_interruption_point)) {
2447+
if (!GetUTXOStats(&chainstate.CoinsDB(), stats, CoinStatsHashType::NONE, node.rpc_interruption_point)) {
24372448
throw JSONRPCError(RPC_INTERNAL_ERROR, "Unable to read UTXO set");
24382449
}
24392450

2440-
pcursor = std::unique_ptr<CCoinsViewCursor>(::ChainstateActive().CoinsDB().Cursor());
2451+
pcursor = std::unique_ptr<CCoinsViewCursor>(chainstate.CoinsDB().Cursor());
24412452
tip = g_chainman.m_blockman.LookupBlockIndex(stats.hashBlock);
24422453
CHECK_NONFATAL(tip);
24432454
}
@@ -2462,16 +2473,13 @@ static RPCHelpMan dumptxoutset()
24622473
}
24632474

24642475
afile.fclose();
2465-
fs::rename(temppath, path);
24662476

24672477
UniValue result(UniValue::VOBJ);
24682478
result.pushKV("coins_written", stats.coins_count);
24692479
result.pushKV("base_hash", tip->GetBlockHash().ToString());
24702480
result.pushKV("base_height", tip->nHeight);
2471-
result.pushKV("path", path.string());
2481+
24722482
return result;
2473-
},
2474-
};
24752483
}
24762484

24772485
void RegisterBlockchainRPCCommands(CRPCTable &t)

src/rpc/blockchain.h

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
#define BITCOIN_RPC_BLOCKCHAIN_H
77

88
#include <amount.h>
9+
#include <streams.h>
910
#include <sync.h>
1011

1112
#include <stdint.h>
@@ -16,6 +17,7 @@ extern RecursiveMutex cs_main;
1617
class CBlock;
1718
class CBlockIndex;
1819
class CBlockPolicyEstimator;
20+
class CChainState;
1921
class CTxMemPool;
2022
class ChainstateManager;
2123
class UniValue;
@@ -57,4 +59,10 @@ CTxMemPool& EnsureMemPool(const util::Ref& context);
5759
ChainstateManager& EnsureChainman(const util::Ref& context);
5860
CBlockPolicyEstimator& EnsureFeeEstimator(const util::Ref& context);
5961

62+
/**
63+
* Helper to create UTXO snapshots given a chainstate and a file handle.
64+
* @return a UniValue map containing metadata about the snapshot.
65+
*/
66+
UniValue CreateUTXOSnapshot(NodeContext& node, CChainState& chainstate, CAutoFile& afile);
67+
6068
#endif

src/test/util/setup_common.cpp

Lines changed: 35 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -199,14 +199,43 @@ TestingSetup::TestingSetup(const std::string& chainName, const std::vector<const
199199
}
200200
}
201201

202-
TestChain100Setup::TestChain100Setup()
202+
TestChain100Setup::TestChain100Setup(bool deterministic)
203203
{
204+
m_deterministic = deterministic;
205+
206+
if (m_deterministic) {
207+
SetMockTime(1598887952);
208+
constexpr std::array<unsigned char, 32> vchKey = {
209+
{
210+
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1
211+
}
212+
};
213+
coinbaseKey.Set(vchKey.begin(), vchKey.end(), false);
214+
} else {
215+
coinbaseKey.MakeNewKey(true);
216+
}
217+
204218
// Generate a 100-block chain:
205-
coinbaseKey.MakeNewKey(true);
219+
this->mineBlocks(COINBASE_MATURITY);
220+
221+
if (m_deterministic) {
222+
LOCK(::cs_main);
223+
assert(
224+
m_node.chainman->ActiveChain().Tip()->GetBlockHash().ToString() ==
225+
"49c95db1e470fed04496d801c9d8fbb78155d2c7f855232c918823d2c17d0cf6");
226+
}
227+
}
228+
229+
void TestChain100Setup::mineBlocks(int num_blocks)
230+
{
206231
CScript scriptPubKey = CScript() << ToByteVector(coinbaseKey.GetPubKey()) << OP_CHECKSIG;
207-
for (int i = 0; i < COINBASE_MATURITY; i++) {
232+
for (int i = 0; i < num_blocks; i++)
233+
{
208234
std::vector<CMutableTransaction> noTxns;
209235
CBlock b = CreateAndProcessBlock(noTxns, scriptPubKey);
236+
if (m_deterministic) {
237+
SetMockTime(GetTime() + 1);
238+
}
210239
m_coinbase_txns.push_back(b.vtx[0]);
211240
}
212241
}
@@ -234,6 +263,9 @@ CBlock TestChain100Setup::CreateAndProcessBlock(const std::vector<CMutableTransa
234263
TestChain100Setup::~TestChain100Setup()
235264
{
236265
gArgs.ForceSetArg("-segwitheight", "0");
266+
if (m_deterministic) {
267+
SetMockTime(0);
268+
}
237269
}
238270

239271
CTxMemPoolEntry TestMemPoolEntryHelper::FromTx(const CMutableTransaction& tx) const

src/test/util/setup_common.h

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,6 @@ struct BasicTestingSetup {
7878
explicit BasicTestingSetup(const std::string& chainName = CBaseChainParams::MAIN, const std::vector<const char*>& extra_args = {});
7979
~BasicTestingSetup();
8080

81-
private:
8281
const fs::path m_path_root;
8382
};
8483

@@ -112,7 +111,7 @@ class CScript;
112111
* Testing fixture that pre-creates a 100-block REGTEST-mode block chain
113112
*/
114113
struct TestChain100Setup : public RegTestingSetup {
115-
TestChain100Setup();
114+
TestChain100Setup(bool deterministic = false);
116115

117116
/**
118117
* Create a new block with just given transactions, coinbase paying to
@@ -121,12 +120,21 @@ struct TestChain100Setup : public RegTestingSetup {
121120
CBlock CreateAndProcessBlock(const std::vector<CMutableTransaction>& txns,
122121
const CScript& scriptPubKey);
123122

123+
//! Mine a series of new blocks on the active chain.
124+
void mineBlocks(int num_blocks);
125+
124126
~TestChain100Setup();
125127

128+
bool m_deterministic;
126129
std::vector<CTransactionRef> m_coinbase_txns; // For convenience, coinbase transactions
127130
CKey coinbaseKey; // private/public key needed to spend coinbase transactions
128131
};
129132

133+
134+
struct TestChain100DeterministicSetup : public TestChain100Setup {
135+
TestChain100DeterministicSetup() : TestChain100Setup(true) { }
136+
};
137+
130138
class CTxMemPoolEntry;
131139

132140
struct TestMemPoolEntryHelper

0 commit comments

Comments
 (0)