How about generating semi-realistic script sigs / unlock scripts?
<details><summary>WIP diff</summary>
diff --git a/src/bench/block_generator.cpp b/src/bench/block_generator.cpp
index 1891400d20..4ee174f8f5 100644
--- a/src/bench/block_generator.cpp
+++ b/src/bench/block_generator.cpp
@@ -40,39 +40,164 @@ CPubKey RandPub(FastRandomContext& rng)
return CPubKey(pubkey.begin(), pubkey.end());
}
-auto createScriptFactory(FastRandomContext& rng, const benchmark::ScriptRecipe& rec)
+struct FactoryEntry {
+ const double prob;
+ const std::function<CScript()> lock_script;
+ const std::function<CScript()> unlock_script;
+ const std::function<CScript()> witness_script; // TODO write + use field
+};
+
+auto createScriptFactory(FastRandomContext& rng, const CExtKey& xprv, const benchmark::ScriptRecipe& rec)
{
- std::array<std::pair<double, std::function<CScript()>>, 11> table{
- std::pair{rec.anchor_prob, [&] { return GetScriptForDestination(PayToAnchor{}); }},
- std::pair{rec.multisig_prob, [&] {
- const size_t keys_count{1 + rng.randrange<size_t>(15)};
- const size_t required{1 + rng.randrange<size_t>(keys_count)};
- std::vector<CPubKey> keys;
- keys.reserve(keys_count);
- for (size_t i{}; i < keys_count; ++i) keys.emplace_back(RandPub(rng));
- return GetScriptForMultisig(required, keys);
- }},
- {rec.null_data_prob, [&] {
- const auto len{1 + rng.randrange<size_t>(90)}; // sometimes exceed policy rules
- return CScript() << OP_RETURN << rng.randbytes<unsigned char>(len);
- }},
- std::pair{rec.pubkey_prob, [&] { return GetScriptForRawPubKey(RandPub(rng)); }},
- std::pair{rec.pubkeyhash_prob, [&] { return GetScriptForDestination(PKHash(RandPub(rng))); }},
- std::pair{rec.scripthash_prob, [&] { return GetScriptForDestination(ScriptHash(CScript() << OP_TRUE)); }},
- std::pair{rec.witness_v1_taproot_prob, [&] { return GetScriptForDestination(WitnessV1Taproot(XOnlyPubKey(RandPub(rng)))); }},
- std::pair{rec.witness_v0_keyhash_prob, [&] { return GetScriptForDestination(WitnessV0KeyHash(RandPub(rng))); }},
- std::pair{rec.witness_v0_scripthash_prob, [&] { return GetScriptForDestination(WitnessV0ScriptHash(CScript() << OP_TRUE)); }},
- std::pair{rec.witness_unknown_prob, [&] { return CScript() << OP_2 << rng.randbytes<uint8_t>(32); }},
- std::pair{rec.nonstandard_prob, [&] { return CScript() << OP_TRUE; }},
+ FactoryEntry table[] = {
+ {
+ .prob = rec.anchor_prob,
+ .lock_script = [&] { return GetScriptForDestination(PayToAnchor{}); },
+ .unlock_script = {}, // TODO: confirm correctness
+ .witness_script = {}, // TODO: confirm correctness
+ },
+ {
+ .prob = rec.multisig_prob,
+ .lock_script = [&] {
+ const size_t keys_count{1 + rng.randrange<size_t>(MAX_PUBKEYS_PER_MULTISIG)};
+ const size_t required{1 + rng.randrange<size_t>(keys_count)};
+ std::vector<CPubKey> keys;
+ keys.reserve(keys_count);
+ for (size_t i{}; i < keys_count; ++i) keys.emplace_back(RandPub(rng));
+ return GetScriptForMultisig(required, keys);
+ },
+ .unlock_script = [&] {
+ const size_t keys_count{1 + rng.randrange<size_t>(MAX_PUBKEYS_PER_MULTISIG)};
+ const size_t required{1 + rng.randrange<size_t>(keys_count)};
+ std::vector<CKey> priv_keys;
+ priv_keys.reserve(keys_count);
+ std::vector<CPubKey> pub_keys;
+ pub_keys.reserve(keys_count);
+
+ for (size_t i{}; i < keys_count; ++i) {
+ CExtKey child_xprv;
+ Assert(xprv.Derive(child_xprv, rng.rand<unsigned int>()));
+ const CKey priv{child_xprv.key};
+ priv_keys.push_back(priv);
+ pub_keys.push_back(priv.GetPubKey());
+ }
+
+ const CScript prevout_lock_script{GetScriptForMultisig(required, pub_keys)};
+
+ CScript s{OP_0}; // Extra required dummy value for CHECKMULTISIG.
+ const uint8_t sighash_type{SIGHASH_ALL};
+ CMutableTransaction tx;
+ tx.vin.emplace_back(COutPoint{}, CScript{}, rng.rand32());
+ const uint256 sighash{SignatureHash(prevout_lock_script, tx, 0, sighash_type, CAmount{0}, SigVersion::BASE)};
+ for (size_t i{}; i < required; ++i) {
+ std::vector<uint8_t> sig;
+ Assert(priv_keys[i].Sign(sighash, sig));
+ sig.push_back(sighash_type);
+ s << sig;
+ }
+
+ return s;
+ },
+ .witness_script = {}, // TODO: confirm correctness
+ },
+ {
+ .prob = rec.null_data_prob,
+ .lock_script = [&] {
+ const auto len{1 + rng.randrange<size_t>(90)}; // sometimes exceed policy rules
+ return CScript{} << OP_RETURN << rng.randbytes(len);
+ },
+ .unlock_script = {}, // Unspendable
+ .witness_script = {}, // Unspendable
+ },
+ {
+ .prob = rec.pubkey_prob,
+ .lock_script = [&] { return GetScriptForRawPubKey(RandPub(rng)); },
+ .unlock_script = [&] {
+ CExtKey child_xprv;
+ Assert(xprv.Derive(child_xprv, rng.rand<unsigned int>()));
+ const CKey priv{child_xprv.key};
+ const CPubKey pub{priv.GetPubKey()};
+ const CScript prevout_lock_script{GetScriptForRawPubKey(pub)};
+
+ const uint8_t sighash_type{SIGHASH_ALL};
+ CMutableTransaction tx;
+ tx.vin.emplace_back(COutPoint{}, CScript{}, rng.rand32());
+ const uint256 sighash{SignatureHash(prevout_lock_script, tx, 0, sighash_type, CAmount{0}, SigVersion::BASE)};
+ std::vector<uint8_t> sig;
+ Assert(priv.Sign(sighash, sig));
+ sig.push_back(sighash_type);
+
+ return CScript{} << sig;
+ },
+ .witness_script = {},
+ },
+ {
+ .prob = rec.pubkeyhash_prob,
+ .lock_script = [&] { return GetScriptForDestination(PKHash(RandPub(rng))); },
+ .unlock_script = [&] {
+ CExtKey child_xprv;
+ Assert(xprv.Derive(child_xprv, rng.rand<unsigned int>()));
+ const CKey priv{child_xprv.key};
+ const CPubKey pub{priv.GetPubKey()};
+ const CScript prevout_lock_script{GetScriptForRawPubKey(pub)};
+
+ const uint8_t sighash_type{SIGHASH_ALL};
+ CMutableTransaction tx;
+ tx.vin.emplace_back(COutPoint{}, CScript{}, rng.rand32());
+ const uint256 sighash{SignatureHash(prevout_lock_script, tx, 0, sighash_type, CAmount{0}, SigVersion::BASE)};
+ std::vector<uint8_t> sig;
+ Assert(priv.Sign(sighash, sig));
+ sig.push_back(sighash_type);
+
+ return CScript{} << sig << pub;
+ },
+ .witness_script = {},
+ },
+ {
+ .prob = rec.scripthash_prob,
+ .lock_script = [&] { return GetScriptForDestination(ScriptHash(CScript() << OP_TRUE)); },
+ .unlock_script = {}, // TODO
+ .witness_script = {}, // TODO
+ },
+ {
+ .prob = rec.witness_v1_taproot_prob,
+ .lock_script = [&] { return GetScriptForDestination(WitnessV1Taproot(XOnlyPubKey(RandPub(rng)))); },
+ .unlock_script = {}, // TODO
+ .witness_script = {}, // TODO
+ },
+ {
+ .prob = rec.witness_v0_keyhash_prob,
+ .lock_script = [&] { return GetScriptForDestination(WitnessV0KeyHash(RandPub(rng))); },
+ .unlock_script = {}, // TODO
+ .witness_script = {}, // TODO
+ },
+ {
+ .prob = rec.witness_v0_scripthash_prob,
+ .lock_script = [&] { return GetScriptForDestination(WitnessV0ScriptHash(CScript() << OP_TRUE)); },
+ .unlock_script = {}, // TODO
+ .witness_script = {}, // TODO
+ },
+ {
+ .prob = rec.witness_unknown_prob,
+ .lock_script = [&] { return CScript() << OP_2 << rng.randbytes<uint8_t>(32); },
+ .unlock_script = {}, // TODO
+ .witness_script = {}, // TODO
+ },
+ {
+ .prob = rec.nonstandard_prob,
+ .lock_script = [&] { return CScript() << OP_TRUE; },
+ .unlock_script = {}, // TODO
+ .witness_script = {}, // TODO
+ },
};
double sum{};
- for (const auto& p : table | std::views::keys) sum += p;
+ for (const auto& e : table) sum += e.prob;
// Verify that probabilities add up to ~1.0.
assert(sum <= 1);
assert(sum + 0.01 > 1.0);
- return table;
+ return std::to_array(table);
}
CBlock BuildBlock(const CChainParams& params, const benchmark::ScriptRecipe& rec, const uint256& seed)
@@ -80,6 +205,10 @@ CBlock BuildBlock(const CChainParams& params, const benchmark::ScriptRecipe& rec
assert(params.IsTestChain());
FastRandomContext rng{seed};
+ CExtKey xprv;
+ constexpr auto xprv_seed{std::to_array({std::byte{'2'}, std::byte{'1'}})};
+ xprv.SetSeed(xprv_seed);
+
assert(rec.geometric_base_prob >= 0 && rec.geometric_base_prob <= 1);
const auto tx_occupancy_limit{rec.tx_occupancy_limit != 0.0 ? rec.tx_occupancy_limit : MakeUnitDouble(rng.rand64())};
@@ -95,14 +224,33 @@ CBlock BuildBlock(const CChainParams& params, const benchmark::ScriptRecipe& rec
block.vtx.push_back(MakeTransactionRef(std::move(cb)));
}
- auto scriptFactory{createScriptFactory(rng, rec)};
- auto rand_script{[&] {
+ auto scriptFactory{createScriptFactory(rng, xprv, rec)};
+ auto rand_lock_script{[&] {
double probability{MakeUnitDouble(rng.rand64())};
- for (const auto& [p, factory] : scriptFactory) {
- if (probability < p) return factory();
- probability -= p;
+ for (const auto& entry : scriptFactory) {
+ if (probability < entry.prob) return entry.lock_script();
+ probability -= entry.prob;
+ }
+ return scriptFactory.back().lock_script();
+ }};
+ const double unlock_script_prob{[&] {
+ double sum{0.0};
+ for (const auto& entry : scriptFactory) {
+ if (entry.unlock_script) sum += entry.prob;
+ }
+ return sum;
+ }()};
+ auto rand_unlock_script{[&] {
+ const FactoryEntry* last_unlock_entry{nullptr};
+ double probability{MakeUnitDouble(rng.rand64()) * unlock_script_prob};
+ for (const auto& entry : scriptFactory) {
+ if (!entry.unlock_script) continue;
+ last_unlock_entry = &entry;
+ if (probability < entry.prob) return entry.unlock_script();
+ probability -= entry.prob;
}
- return scriptFactory.back().second();
+ assert(last_unlock_entry);
+ return last_unlock_entry->unlock_script();
}};
// Add 2 bytes to account for compact size representation of vtx vector increasing from 1 to 3 bytes.
@@ -120,7 +268,7 @@ CBlock BuildBlock(const CChainParams& params, const benchmark::ScriptRecipe& rec
for (size_t in{0}; in < in_count; ++in) {
auto& tx_in{tx.vin[in]};
tx_in.prevout = {Txid::FromUint256(rng.rand256()), uint32_t(GeomCount(rng, rec.geometric_base_prob))};
- tx_in.scriptSig = rand_script();
+ tx_in.scriptSig = rand_unlock_script();
const size_t witness_count{GeomCount(rng, rec.geometric_base_prob)};
tx_in.scriptWitness.stack.reserve(witness_count);
@@ -136,7 +284,7 @@ CBlock BuildBlock(const CChainParams& params, const benchmark::ScriptRecipe& rec
for (size_t out{0}; out < out_count; ++out) {
auto& tx_out{tx.vout[out]};
tx_out.nValue = rng.randrange(GeomCount(rng, rec.geometric_base_prob) * COIN);
- tx_out.scriptPubKey = rand_script();
+ tx_out.scriptPubKey = rand_lock_script();
}
block_size_no_witness += ::GetSerializeSize(TX_NO_WITNESS(tx));
diff --git a/src/bench/checkblock.cpp b/src/bench/checkblock.cpp
index 2bc03818ac..23354ba39c 100644
--- a/src/bench/checkblock.cpp
+++ b/src/bench/checkblock.cpp
@@ -7,6 +7,7 @@
#include <chainparams.h>
#include <common/args.h>
#include <consensus/validation.h>
+#include <key.h>
#include <primitives/block.h>
#include <primitives/transaction.h>
#include <streams.h>
@@ -16,7 +17,6 @@
#include <cassert>
#include <cstddef>
#include <memory>
-#include <vector>
// These are the two major time-sinks which happen after we have fully received
// a block off the wire, but before we can relay the block on to peers using
@@ -24,6 +24,7 @@
static void DeserializeBlockTest(benchmark::Bench& bench)
{
+ ECC_Context ec_context;
auto stream{benchmark::GetBlockData()};
std::byte a{0};
stream.write({&a, 1}); // Prevent compaction
@@ -37,6 +38,7 @@ static void DeserializeBlockTest(benchmark::Bench& bench)
static void DeserializeAndCheckBlockTest(benchmark::Bench& bench)
{
+ ECC_Context ec_context;
const auto& chain_params{CChainParams::RegTest(CChainParams::RegTestOptions{})};
auto stream{benchmark::GetBlockData(*chain_params)};
std::byte a{0};
</details>