0diff --git a/src/bench/block_generator.cpp b/src/bench/block_generator.cpp
1index 1891400d20..4ee174f8f5 100644
2--- a/src/bench/block_generator.cpp
3+++ b/src/bench/block_generator.cpp
4@@ -40,39 +40,164 @@ CPubKey RandPub(FastRandomContext& rng)
5 return CPubKey(pubkey.begin(), pubkey.end());
6 }
7
8-auto createScriptFactory(FastRandomContext& rng, const benchmark::ScriptRecipe& rec)
9+struct FactoryEntry {
10+ const double prob;
11+ const std::function<CScript()> lock_script;
12+ const std::function<CScript()> unlock_script;
13+ const std::function<CScript()> witness_script; // TODO write + use field
14+};
15+
16+auto createScriptFactory(FastRandomContext& rng, const CExtKey& xprv, const benchmark::ScriptRecipe& rec)
17 {
18- std::array<std::pair<double, std::function<CScript()>>, 11> table{
19- std::pair{rec.anchor_prob, [&] { return GetScriptForDestination(PayToAnchor{}); }},
20- std::pair{rec.multisig_prob, [&] {
21- const size_t keys_count{1 + rng.randrange<size_t>(15)};
22- const size_t required{1 + rng.randrange<size_t>(keys_count)};
23- std::vector<CPubKey> keys;
24- keys.reserve(keys_count);
25- for (size_t i{}; i < keys_count; ++i) keys.emplace_back(RandPub(rng));
26- return GetScriptForMultisig(required, keys);
27- }},
28- {rec.null_data_prob, [&] {
29- const auto len{1 + rng.randrange<size_t>(90)}; // sometimes exceed policy rules
30- return CScript() << OP_RETURN << rng.randbytes<unsigned char>(len);
31- }},
32- std::pair{rec.pubkey_prob, [&] { return GetScriptForRawPubKey(RandPub(rng)); }},
33- std::pair{rec.pubkeyhash_prob, [&] { return GetScriptForDestination(PKHash(RandPub(rng))); }},
34- std::pair{rec.scripthash_prob, [&] { return GetScriptForDestination(ScriptHash(CScript() << OP_TRUE)); }},
35- std::pair{rec.witness_v1_taproot_prob, [&] { return GetScriptForDestination(WitnessV1Taproot(XOnlyPubKey(RandPub(rng)))); }},
36- std::pair{rec.witness_v0_keyhash_prob, [&] { return GetScriptForDestination(WitnessV0KeyHash(RandPub(rng))); }},
37- std::pair{rec.witness_v0_scripthash_prob, [&] { return GetScriptForDestination(WitnessV0ScriptHash(CScript() << OP_TRUE)); }},
38- std::pair{rec.witness_unknown_prob, [&] { return CScript() << OP_2 << rng.randbytes<uint8_t>(32); }},
39- std::pair{rec.nonstandard_prob, [&] { return CScript() << OP_TRUE; }},
40+ FactoryEntry table[] = {
41+ {
42+ .prob = rec.anchor_prob,
43+ .lock_script = [&] { return GetScriptForDestination(PayToAnchor{}); },
44+ .unlock_script = {}, // TODO: confirm correctness
45+ .witness_script = {}, // TODO: confirm correctness
46+ },
47+ {
48+ .prob = rec.multisig_prob,
49+ .lock_script = [&] {
50+ const size_t keys_count{1 + rng.randrange<size_t>(MAX_PUBKEYS_PER_MULTISIG)};
51+ const size_t required{1 + rng.randrange<size_t>(keys_count)};
52+ std::vector<CPubKey> keys;
53+ keys.reserve(keys_count);
54+ for (size_t i{}; i < keys_count; ++i) keys.emplace_back(RandPub(rng));
55+ return GetScriptForMultisig(required, keys);
56+ },
57+ .unlock_script = [&] {
58+ const size_t keys_count{1 + rng.randrange<size_t>(MAX_PUBKEYS_PER_MULTISIG)};
59+ const size_t required{1 + rng.randrange<size_t>(keys_count)};
60+ std::vector<CKey> priv_keys;
61+ priv_keys.reserve(keys_count);
62+ std::vector<CPubKey> pub_keys;
63+ pub_keys.reserve(keys_count);
64+
65+ for (size_t i{}; i < keys_count; ++i) {
66+ CExtKey child_xprv;
67+ Assert(xprv.Derive(child_xprv, rng.rand<unsigned int>()));
68+ const CKey priv{child_xprv.key};
69+ priv_keys.push_back(priv);
70+ pub_keys.push_back(priv.GetPubKey());
71+ }
72+
73+ const CScript prevout_lock_script{GetScriptForMultisig(required, pub_keys)};
74+
75+ CScript s{OP_0}; // Extra required dummy value for CHECKMULTISIG.
76+ const uint8_t sighash_type{SIGHASH_ALL};
77+ CMutableTransaction tx;
78+ tx.vin.emplace_back(COutPoint{}, CScript{}, rng.rand32());
79+ const uint256 sighash{SignatureHash(prevout_lock_script, tx, 0, sighash_type, CAmount{0}, SigVersion::BASE)};
80+ for (size_t i{}; i < required; ++i) {
81+ std::vector<uint8_t> sig;
82+ Assert(priv_keys[i].Sign(sighash, sig));
83+ sig.push_back(sighash_type);
84+ s << sig;
85+ }
86+
87+ return s;
88+ },
89+ .witness_script = {}, // TODO: confirm correctness
90+ },
91+ {
92+ .prob = rec.null_data_prob,
93+ .lock_script = [&] {
94+ const auto len{1 + rng.randrange<size_t>(90)}; // sometimes exceed policy rules
95+ return CScript{} << OP_RETURN << rng.randbytes(len);
96+ },
97+ .unlock_script = {}, // Unspendable
98+ .witness_script = {}, // Unspendable
99+ },
100+ {
101+ .prob = rec.pubkey_prob,
102+ .lock_script = [&] { return GetScriptForRawPubKey(RandPub(rng)); },
103+ .unlock_script = [&] {
104+ CExtKey child_xprv;
105+ Assert(xprv.Derive(child_xprv, rng.rand<unsigned int>()));
106+ const CKey priv{child_xprv.key};
107+ const CPubKey pub{priv.GetPubKey()};
108+ const CScript prevout_lock_script{GetScriptForRawPubKey(pub)};
109+
110+ const uint8_t sighash_type{SIGHASH_ALL};
111+ CMutableTransaction tx;
112+ tx.vin.emplace_back(COutPoint{}, CScript{}, rng.rand32());
113+ const uint256 sighash{SignatureHash(prevout_lock_script, tx, 0, sighash_type, CAmount{0}, SigVersion::BASE)};
114+ std::vector<uint8_t> sig;
115+ Assert(priv.Sign(sighash, sig));
116+ sig.push_back(sighash_type);
117+
118+ return CScript{} << sig;
119+ },
120+ .witness_script = {},
121+ },
122+ {
123+ .prob = rec.pubkeyhash_prob,
124+ .lock_script = [&] { return GetScriptForDestination(PKHash(RandPub(rng))); },
125+ .unlock_script = [&] {
126+ CExtKey child_xprv;
127+ Assert(xprv.Derive(child_xprv, rng.rand<unsigned int>()));
128+ const CKey priv{child_xprv.key};
129+ const CPubKey pub{priv.GetPubKey()};
130+ const CScript prevout_lock_script{GetScriptForRawPubKey(pub)};
131+
132+ const uint8_t sighash_type{SIGHASH_ALL};
133+ CMutableTransaction tx;
134+ tx.vin.emplace_back(COutPoint{}, CScript{}, rng.rand32());
135+ const uint256 sighash{SignatureHash(prevout_lock_script, tx, 0, sighash_type, CAmount{0}, SigVersion::BASE)};
136+ std::vector<uint8_t> sig;
137+ Assert(priv.Sign(sighash, sig));
138+ sig.push_back(sighash_type);
139+
140+ return CScript{} << sig << pub;
141+ },
142+ .witness_script = {},
143+ },
144+ {
145+ .prob = rec.scripthash_prob,
146+ .lock_script = [&] { return GetScriptForDestination(ScriptHash(CScript() << OP_TRUE)); },
147+ .unlock_script = {}, // TODO
148+ .witness_script = {}, // TODO
149+ },
150+ {
151+ .prob = rec.witness_v1_taproot_prob,
152+ .lock_script = [&] { return GetScriptForDestination(WitnessV1Taproot(XOnlyPubKey(RandPub(rng)))); },
153+ .unlock_script = {}, // TODO
154+ .witness_script = {}, // TODO
155+ },
156+ {
157+ .prob = rec.witness_v0_keyhash_prob,
158+ .lock_script = [&] { return GetScriptForDestination(WitnessV0KeyHash(RandPub(rng))); },
159+ .unlock_script = {}, // TODO
160+ .witness_script = {}, // TODO
161+ },
162+ {
163+ .prob = rec.witness_v0_scripthash_prob,
164+ .lock_script = [&] { return GetScriptForDestination(WitnessV0ScriptHash(CScript() << OP_TRUE)); },
165+ .unlock_script = {}, // TODO
166+ .witness_script = {}, // TODO
167+ },
168+ {
169+ .prob = rec.witness_unknown_prob,
170+ .lock_script = [&] { return CScript() << OP_2 << rng.randbytes<uint8_t>(32); },
171+ .unlock_script = {}, // TODO
172+ .witness_script = {}, // TODO
173+ },
174+ {
175+ .prob = rec.nonstandard_prob,
176+ .lock_script = [&] { return CScript() << OP_TRUE; },
177+ .unlock_script = {}, // TODO
178+ .witness_script = {}, // TODO
179+ },
180 };
181
182 double sum{};
183- for (const auto& p : table | std::views::keys) sum += p;
184+ for (const auto& e : table) sum += e.prob;
185 // Verify that probabilities add up to ~1.0.
186 assert(sum <= 1);
187 assert(sum + 0.01 > 1.0);
188
189- return table;
190+ return std::to_array(table);
191 }
192
193 CBlock BuildBlock(const CChainParams& params, const benchmark::ScriptRecipe& rec, const uint256& seed)
194@@ -80,6 +205,10 @@ CBlock BuildBlock(const CChainParams& params, const benchmark::ScriptRecipe& rec
195 assert(params.IsTestChain());
196 FastRandomContext rng{seed};
197
198+ CExtKey xprv;
199+ constexpr auto xprv_seed{std::to_array({std::byte{'2'}, std::byte{'1'}})};
200+ xprv.SetSeed(xprv_seed);
201+
202 assert(rec.geometric_base_prob >= 0 && rec.geometric_base_prob <= 1);
203 const auto tx_occupancy_limit{rec.tx_occupancy_limit != 0.0 ? rec.tx_occupancy_limit : MakeUnitDouble(rng.rand64())};
204
205@@ -95,14 +224,33 @@ CBlock BuildBlock(const CChainParams& params, const benchmark::ScriptRecipe& rec
206 block.vtx.push_back(MakeTransactionRef(std::move(cb)));
207 }
208
209- auto scriptFactory{createScriptFactory(rng, rec)};
210- auto rand_script{[&] {
211+ auto scriptFactory{createScriptFactory(rng, xprv, rec)};
212+ auto rand_lock_script{[&] {
213 double probability{MakeUnitDouble(rng.rand64())};
214- for (const auto& [p, factory] : scriptFactory) {
215- if (probability < p) return factory();
216- probability -= p;
217+ for (const auto& entry : scriptFactory) {
218+ if (probability < entry.prob) return entry.lock_script();
219+ probability -= entry.prob;
220+ }
221+ return scriptFactory.back().lock_script();
222+ }};
223+ const double unlock_script_prob{[&] {
224+ double sum{0.0};
225+ for (const auto& entry : scriptFactory) {
226+ if (entry.unlock_script) sum += entry.prob;
227+ }
228+ return sum;
229+ }()};
230+ auto rand_unlock_script{[&] {
231+ const FactoryEntry* last_unlock_entry{nullptr};
232+ double probability{MakeUnitDouble(rng.rand64()) * unlock_script_prob};
233+ for (const auto& entry : scriptFactory) {
234+ if (!entry.unlock_script) continue;
235+ last_unlock_entry = &entry;
236+ if (probability < entry.prob) return entry.unlock_script();
237+ probability -= entry.prob;
238 }
239- return scriptFactory.back().second();
240+ assert(last_unlock_entry);
241+ return last_unlock_entry->unlock_script();
242 }};
243
244 // Add 2 bytes to account for compact size representation of vtx vector increasing from 1 to 3 bytes.
245@@ -120,7 +268,7 @@ CBlock BuildBlock(const CChainParams& params, const benchmark::ScriptRecipe& rec
246 for (size_t in{0}; in < in_count; ++in) {
247 auto& tx_in{tx.vin[in]};
248 tx_in.prevout = {Txid::FromUint256(rng.rand256()), uint32_t(GeomCount(rng, rec.geometric_base_prob))};
249- tx_in.scriptSig = rand_script();
250+ tx_in.scriptSig = rand_unlock_script();
251
252 const size_t witness_count{GeomCount(rng, rec.geometric_base_prob)};
253 tx_in.scriptWitness.stack.reserve(witness_count);
254@@ -136,7 +284,7 @@ CBlock BuildBlock(const CChainParams& params, const benchmark::ScriptRecipe& rec
255 for (size_t out{0}; out < out_count; ++out) {
256 auto& tx_out{tx.vout[out]};
257 tx_out.nValue = rng.randrange(GeomCount(rng, rec.geometric_base_prob) * COIN);
258- tx_out.scriptPubKey = rand_script();
259+ tx_out.scriptPubKey = rand_lock_script();
260 }
261
262 block_size_no_witness += ::GetSerializeSize(TX_NO_WITNESS(tx));
263diff --git a/src/bench/checkblock.cpp b/src/bench/checkblock.cpp
264index 2bc03818ac..23354ba39c 100644
265--- a/src/bench/checkblock.cpp
266+++ b/src/bench/checkblock.cpp
267@@ -7,6 +7,7 @@
268 #include <chainparams.h>
269 #include <common/args.h>
270 #include <consensus/validation.h>
271+#include <key.h>
272 #include <primitives/block.h>
273 #include <primitives/transaction.h>
274 #include <streams.h>
275@@ -16,7 +17,6 @@
276 #include <cassert>
277 #include <cstddef>
278 #include <memory>
279-#include <vector>
280
281 // These are the two major time-sinks which happen after we have fully received
282 // a block off the wire, but before we can relay the block on to peers using
283@@ -24,6 +24,7 @@
284
285 static void DeserializeBlockTest(benchmark::Bench& bench)
286 {
287+ ECC_Context ec_context;
288 auto stream{benchmark::GetBlockData()};
289 std::byte a{0};
290 stream.write({&a, 1}); // Prevent compaction
291@@ -37,6 +38,7 @@ static void DeserializeBlockTest(benchmark::Bench& bench)
292
293 static void DeserializeAndCheckBlockTest(benchmark::Bench& bench)
294 {
295+ ECC_Context ec_context;
296 const auto& chain_params{CChainParams::RegTest(CChainParams::RegTestOptions{})};
297 auto stream{benchmark::GetBlockData(*chain_params)};
298 std::byte a{0};