Compare commits

...

6 commits

Author SHA1 Message Date
Ava Chow
5c4567888f
Merge 8da5ab60b5 into 66aa6a47bd 2025-01-08 20:46:03 +01:00
glozow
66aa6a47bd
Merge bitcoin/bitcoin#30391: BlockAssembler: return selected packages virtual size and fee
Some checks failed
CI / test each commit (push) Has been cancelled
CI / macOS 14 native, arm64, no depends, sqlite only, gui (push) Has been cancelled
CI / macOS 14 native, arm64, fuzz (push) Has been cancelled
CI / Win64 native, VS 2022 (push) Has been cancelled
CI / Win64 native fuzz, VS 2022 (push) Has been cancelled
CI / ASan + LSan + UBSan + integer, no depends, USDT (push) Has been cancelled
7c123c08dd  miner: add package feerate vector to CBlockTemplate (ismaelsadeeq)

Pull request description:

  This PR enables `BlockAssembler` to add all selected packages' fee and virtual size to a vector, and then return the vector as a member of `CBlockTemplate` struct.

  This PR is the first step in the https://github.com/bitcoin/bitcoin/issues/30392 project.

  The packages' vsize and fee are used in #30157 to select a percentile fee rate of the top block in the mempool.

ACKs for top commit:
  rkrux:
    tACK 7c123c08dd
  ryanofsky:
    Code review ACK 7c123c08dd. Changes since last review are rebasing due to a test conflict, giving the new field a better name and description, resolving the test conflict, and renaming a lot of test variables. The actual code change is still one-line change.
  glozow:
    reACK 7c123c08dd

Tree-SHA512: 767b0b3d4273cf1589fd2068d729a66c7414c0f9574b15989fbe293f8c85cd6c641dd783cde55bfabab32cd047d7d8a071d6897b06ed4295c0d071e588de0861
2025-01-08 13:01:23 -05:00
ismaelsadeeq
7c123c08dd
miner: add package feerate vector to CBlockTemplate
- The package feerates are ordered by the sequence in which
  packages are selected for inclusion in the block template.

- The commit also tests this new behaviour.

Co-authored-by: willcl-ark <will@256k1.dev>
2025-01-07 15:29:17 -05:00
Ava Chow
8da5ab60b5 psbt: Use specified sighash or SIGHASH_DEFAULT when dummy signing
When dummy signing for finalizing, use either the specificed sighash, or
SIGHASH_DEFAULT, rather than always SIGHASH_ALL.

For outputs, just use SIGHASH_DEFAULT.
2024-11-07 13:16:36 -05:00
Ava Chow
abb8228944 rpc: Include MuSig2 fields in decodepsbt 2024-11-07 13:15:14 -05:00
Ava Chow
925fdfd67f psbt: Implement un/ser of musig2 fields 2024-11-07 13:15:14 -05:00
6 changed files with 291 additions and 5 deletions

View file

@ -421,6 +421,7 @@ void BlockAssembler::addPackageTxs(int& nPackagesSelected, int& nDescendantsUpda
}
++nPackagesSelected;
pblocktemplate->m_package_feerates.emplace_back(packageFees, static_cast<int32_t>(packageSize));
// Update transactions that depend on each of these
nDescendantsUpdated += UpdatePackagesForAdded(mempool, ancestors, mapModifiedTx);

View file

@ -10,6 +10,7 @@
#include <policy/policy.h>
#include <primitives/block.h>
#include <txmempool.h>
#include <util/feefrac.h>
#include <memory>
#include <optional>
@ -39,6 +40,9 @@ struct CBlockTemplate
std::vector<CAmount> vTxFees;
std::vector<int64_t> vTxSigOpsCost;
std::vector<unsigned char> vchCoinbaseCommitment;
/* A vector of package fee rates, ordered by the sequence in which
* packages are selected for inclusion in the block template.*/
std::vector<FeeFrac> m_package_feerates;
};
// Container for tracking updates to ancestor feerate as we include (parent)

View file

@ -348,7 +348,7 @@ void UpdatePSBTOutput(const SigningProvider& provider, PartiallySignedTransactio
// Construct a would-be spend of this output, to update sigdata with.
// Note that ProduceSignature is used to fill in metadata (not actual signatures),
// so provider does not need to provide any private keys (it can be a HidingSigningProvider).
MutableTransactionSignatureCreator creator(tx, /*input_idx=*/0, out.nValue, SIGHASH_ALL);
MutableTransactionSignatureCreator creator(tx, /*input_idx=*/0, out.nValue, SIGHASH_DEFAULT);
ProduceSignature(provider, creator, out.scriptPubKey, sigdata);
// Put redeem_script, witness_script, key paths, into PSBTOutput.
@ -486,7 +486,8 @@ bool FinalizePSBT(PartiallySignedTransaction& psbtx)
bool complete = true;
const PrecomputedTransactionData txdata = PrecomputePSBTData(psbtx);
for (unsigned int i = 0; i < psbtx.tx->vin.size(); ++i) {
complete &= SignPSBTInput(DUMMY_SIGNING_PROVIDER, psbtx, i, &txdata, SIGHASH_ALL, nullptr, true);
int sighash_type = psbtx.inputs.at(i).sighash_type.value_or(SIGHASH_DEFAULT);
complete &= SignPSBTInput(DUMMY_SIGNING_PROVIDER, psbtx, i, &txdata, sighash_type, nullptr, true);
}
return complete;

View file

@ -50,6 +50,9 @@ static constexpr uint8_t PSBT_IN_TAP_LEAF_SCRIPT = 0x15;
static constexpr uint8_t PSBT_IN_TAP_BIP32_DERIVATION = 0x16;
static constexpr uint8_t PSBT_IN_TAP_INTERNAL_KEY = 0x17;
static constexpr uint8_t PSBT_IN_TAP_MERKLE_ROOT = 0x18;
static constexpr uint8_t PSBT_IN_MUSIG2_PARTICIPANT_PUBKEYS = 0x1a;
static constexpr uint8_t PSBT_IN_MUSIG2_PUB_NONCE = 0x1b;
static constexpr uint8_t PSBT_IN_MUSIG2_PARTIAL_SIG = 0x1c;
static constexpr uint8_t PSBT_IN_PROPRIETARY = 0xFC;
// Output types
@ -59,6 +62,7 @@ static constexpr uint8_t PSBT_OUT_BIP32_DERIVATION = 0x02;
static constexpr uint8_t PSBT_OUT_TAP_INTERNAL_KEY = 0x05;
static constexpr uint8_t PSBT_OUT_TAP_TREE = 0x06;
static constexpr uint8_t PSBT_OUT_TAP_BIP32_DERIVATION = 0x07;
static constexpr uint8_t PSBT_OUT_MUSIG2_PARTICIPANT_PUBKEYS = 0x08;
static constexpr uint8_t PSBT_OUT_PROPRIETARY = 0xFC;
// The separator is 0x00. Reading this in means that the unserializer can interpret it
@ -217,6 +221,11 @@ struct PSBTInput
XOnlyPubKey m_tap_internal_key;
uint256 m_tap_merkle_root;
// MuSig2 fields
std::map<CPubKey, std::vector<CPubKey>> m_musig2_participants;
std::map<std::pair<CPubKey, uint256>, std::map<CPubKey, std::vector<uint8_t>>> m_musig2_pubnonces;
std::map<std::pair<CPubKey, uint256>, std::map<CPubKey, uint256>> m_musig2_partial_sigs;
std::map<std::vector<unsigned char>, std::vector<unsigned char>> unknown;
std::set<PSBTProprietary> m_proprietary;
std::optional<int> sighash_type;
@ -337,6 +346,43 @@ struct PSBTInput
SerializeToVector(s, PSBT_IN_TAP_MERKLE_ROOT);
SerializeToVector(s, m_tap_merkle_root);
}
// Write MuSig2 Participants
for (const auto& [agg_key, part_pubs] : m_musig2_participants) {
SerializeToVector(s, CompactSizeWriter(PSBT_IN_MUSIG2_PARTICIPANT_PUBKEYS), Span{agg_key});
std::vector<unsigned char> value;
VectorWriter s_value{value, 0};
for (auto& pk : part_pubs) {
s_value << Span{pk};
}
s << value;
}
// Write MuSig2 pubnonces
for (const auto& [agg_key_leaf_hash, pubnonces] : m_musig2_pubnonces) {
const auto& [agg_key, leaf_hash] = agg_key_leaf_hash;
for (const auto& [pubkey, pubnonce] : pubnonces) {
if (leaf_hash.IsNull()) {
SerializeToVector(s, CompactSizeWriter(PSBT_IN_MUSIG2_PUB_NONCE), Span{pubkey}, Span{agg_key});
} else {
SerializeToVector(s, CompactSizeWriter(PSBT_IN_MUSIG2_PUB_NONCE), Span{pubkey}, Span{agg_key}, leaf_hash);
}
s << pubnonce;
}
}
// Write MuSig2 partial signatures
for (const auto& [agg_key_leaf_hash, psigs] : m_musig2_partial_sigs) {
const auto& [agg_key, leaf_hash] = agg_key_leaf_hash;
for (const auto& [pubkey, psig] : psigs) {
if (leaf_hash.IsNull()) {
SerializeToVector(s, CompactSizeWriter(PSBT_IN_MUSIG2_PARTIAL_SIG), Span{pubkey}, Span{agg_key});
} else {
SerializeToVector(s, CompactSizeWriter(PSBT_IN_MUSIG2_PARTIAL_SIG), Span{pubkey}, Span{agg_key}, leaf_hash);
}
SerializeToVector(s, psig);
}
}
}
// Write script sig
@ -672,6 +718,80 @@ struct PSBTInput
UnserializeFromVector(s, m_tap_merkle_root);
break;
}
case PSBT_IN_MUSIG2_PARTICIPANT_PUBKEYS:
{
if (!key_lookup.emplace(key).second) {
throw std::ios_base::failure("Duplicate Key, input participant pubkeys for an aggregate key already provided");
} else if (key.size() != 34) {
throw std::ios_base::failure("Input musig2 participants pubkeys aggregate key is more than 33 bytes");
}
std::array<unsigned char, 33> agg_key_bytes;
skey >> AsWritableBytes(Span{agg_key_bytes});
CPubKey agg_key(agg_key_bytes);
std::vector<CPubKey> participants;
std::vector<unsigned char> val;
s >> val;
SpanReader s_val{val};
while (!s_val.empty()) {
std::array<unsigned char, 33> part_key_bytes;
s_val >> AsWritableBytes(Span{part_key_bytes});
participants.emplace_back(Span{part_key_bytes});
}
m_musig2_participants.emplace(agg_key, participants);
break;
}
case PSBT_IN_MUSIG2_PUB_NONCE:
{
if (!key_lookup.emplace(key).second) {
throw std::ios_base::failure("Duplicate Key, input musig2 pubnonce already provided");
} else if (key.size() != 67 && key.size() != 99) {
throw std::ios_base::failure("Input musig2 pubnonce key is not expected size");
}
std::array<unsigned char, 33> part_pubkey_bytes;
std::array<unsigned char, 33> agg_pubkey_bytes;
uint256 leaf_hash;
skey >> AsWritableBytes(Span{part_pubkey_bytes}) >> AsWritableBytes(Span{agg_pubkey_bytes});
CPubKey agg_pub{Span{agg_pubkey_bytes}};
CPubKey part_pub{Span{part_pubkey_bytes}};
if (!skey.empty()) {
skey >> leaf_hash;
}
std::vector<uint8_t> pubnonce;
s >> pubnonce;
m_musig2_pubnonces[std::make_pair(agg_pub, leaf_hash)].emplace(part_pub, pubnonce);
break;
}
case PSBT_IN_MUSIG2_PARTIAL_SIG:
{
if (!key_lookup.emplace(key).second) {
throw std::ios_base::failure("Duplicate Key, input musig2 partial sig already provided");
} else if (key.size() != 67 && key.size() != 99) {
throw std::ios_base::failure("Input musig2 partial sig key is not expected size");
}
std::array<unsigned char, 33> part_pubkey_bytes;
std::array<unsigned char, 33> agg_pubkey_bytes;
uint256 leaf_hash;
skey >> AsWritableBytes(Span{part_pubkey_bytes}) >> AsWritableBytes(Span{agg_pubkey_bytes});
CPubKey agg_pub{Span{agg_pubkey_bytes}};
CPubKey part_pub{Span{part_pubkey_bytes}};
if (!skey.empty()) {
skey >> leaf_hash;
}
uint256 partial_sig;
UnserializeFromVector(s, partial_sig);
m_musig2_partial_sigs[std::make_pair(agg_pub, leaf_hash)].emplace(part_pub, partial_sig);
break;
}
case PSBT_IN_PROPRIETARY:
{
PSBTProprietary this_prop;
@ -719,6 +839,7 @@ struct PSBTOutput
XOnlyPubKey m_tap_internal_key;
std::vector<std::tuple<uint8_t, uint8_t, std::vector<unsigned char>>> m_tap_tree;
std::map<XOnlyPubKey, std::pair<std::set<uint256>, KeyOriginInfo>> m_tap_bip32_paths;
std::map<CPubKey, std::vector<CPubKey>> m_musig2_participants;
std::map<std::vector<unsigned char>, std::vector<unsigned char>> unknown;
std::set<PSBTProprietary> m_proprietary;
@ -781,6 +902,17 @@ struct PSBTOutput
s << value;
}
// Write MuSig2 Participants
for (const auto& [agg_key, part_pubs] : m_musig2_participants) {
SerializeToVector(s, CompactSizeWriter(PSBT_OUT_MUSIG2_PARTICIPANT_PUBKEYS), Span{agg_key});
std::vector<unsigned char> value;
VectorWriter s_value{value, 0};
for (auto& pk : part_pubs) {
s_value << Span{pk};
}
s << value;
}
// Write unknown things
for (auto& entry : unknown) {
s << entry.first;
@ -907,6 +1039,30 @@ struct PSBTOutput
m_tap_bip32_paths.emplace(xonly, std::make_pair(leaf_hashes, DeserializeKeyOrigin(s, origin_len)));
break;
}
case PSBT_OUT_MUSIG2_PARTICIPANT_PUBKEYS:
{
if (!key_lookup.emplace(key).second) {
throw std::ios_base::failure("Duplicate Key, output participant pubkeys for an aggregate key already provided");
} else if (key.size() != 34) {
throw std::ios_base::failure("Output musig2 participants pubkeys aggregate key is more than 33 bytes");
}
std::array<unsigned char, 33> agg_key_bytes;
skey.read(AsWritableBytes(Span{agg_key_bytes}));
CPubKey agg_key(agg_key_bytes);
std::vector<CPubKey> participants;
std::vector<unsigned char> val;
s >> val;
SpanReader s_val{val};
while (!s_val.empty()) {
std::array<unsigned char, 33> part_key_bytes;
s_val.read(AsWritableBytes(Span{part_key_bytes}));
participants.emplace_back(Span{part_key_bytes});
}
m_musig2_participants.emplace(agg_key, participants);
break;
}
case PSBT_OUT_PROPRIETARY:
{
PSBTProprietary this_prop;

View file

@ -916,6 +916,37 @@ const RPCResult decodepsbt_inputs{
}},
{RPCResult::Type::STR_HEX, "taproot_internal_key", /*optional=*/ true, "The hex-encoded Taproot x-only internal key"},
{RPCResult::Type::STR_HEX, "taproot_merkle_root", /*optional=*/ true, "The hex-encoded Taproot merkle root"},
{RPCResult::Type::ARR, "musig2_participant_pubkeys", /*optional*/true, "",
{
{RPCResult::Type::OBJ, "", "",
{
{RPCResult::Type::STR_HEX, "aggregate_pubkey", "The plain aggregate public key for which the participants create."},
{RPCResult::Type::ARR, "participant_pubkeys", "",
{
{RPCResult::Type::STR_HEX, "pubkey", "The plain public keys that are aggregated for aggregate_pubkey."},
}},
}},
}},
{RPCResult::Type::ARR, "musig2_pubnonces", /*optional*/true, "",
{
{RPCResult::Type::OBJ, "", "",
{
{RPCResult::Type::STR_HEX, "participant_pubkey", "The plain public key of the participant that created this pubnonce."},
{RPCResult::Type::STR_HEX, "aggregate_pubkey", "The plain aggregate public key for which this pubnonce is for."},
{RPCResult::Type::STR_HEX, "leaf_hash", /*optional=*/true, "The hash of the leaf script that contains the aggregate pubkey being signed for. Omitted when signing for the internal key."},
{RPCResult::Type::STR_HEX, "pubnonce", "The public nonce itself."},
}},
}},
{RPCResult::Type::ARR, "musig2_partial_sigs", /*optional*/true, "",
{
{RPCResult::Type::OBJ, "", "",
{
{RPCResult::Type::STR_HEX, "participant_pubkey", "The plain public key of the participant that created this partial signature."},
{RPCResult::Type::STR_HEX, "aggregate_pubkey", "The plain aggregate public key for which this partial signature is for."},
{RPCResult::Type::STR_HEX, "leaf_hash", /*optional=*/true, "The hash of the leaf script that contains the aggregate pubkey being signed for. Omitted when signing for the internal key."},
{RPCResult::Type::STR_HEX, "partial_sig", "The partial signature itself."},
}},
}},
{RPCResult::Type::OBJ_DYN, "unknown", /*optional=*/ true, "The unknown input fields",
{
{RPCResult::Type::STR_HEX, "key", "(key-value pair) An unknown key-value pair"},
@ -983,6 +1014,17 @@ const RPCResult decodepsbt_outputs{
}},
}},
}},
{RPCResult::Type::ARR, "musig2_participant_pubkeys", /*optional*/true, "",
{
{RPCResult::Type::OBJ, "", "",
{
{RPCResult::Type::STR_HEX, "aggregate_pubkey", "The plain aggregate public key for which the participants create."},
{RPCResult::Type::ARR, "participant_pubkeys", "",
{
{RPCResult::Type::STR_HEX, "pubkey", "The plain public keys that are aggregated for aggregate_pubkey."},
}},
}},
}},
{RPCResult::Type::OBJ_DYN, "unknown", /*optional=*/true, "The unknown output fields",
{
{RPCResult::Type::STR_HEX, "key", "(key-value pair) An unknown key-value pair"},
@ -1304,6 +1346,52 @@ static RPCHelpMan decodepsbt()
in.pushKV("taproot_merkle_root", HexStr(input.m_tap_merkle_root));
}
// Write MuSig2 fields
if (!input.m_musig2_participants.empty()) {
UniValue musig_keys(UniValue::VARR);
for (const auto& [agg, parts] : input.m_musig2_participants) {
UniValue musig_part(UniValue::VOBJ);
musig_part.pushKV("aggregate_pubkey", HexStr(agg));
UniValue part_keys(UniValue::VARR);
for (const auto& pub : parts) {
part_keys.push_back(HexStr(pub));
}
musig_part.pushKV("participant_pubkeys", part_keys);
musig_keys.push_back(musig_part);
}
in.pushKV("musig2_participant_pubkeys", musig_keys);
}
if (!input.m_musig2_pubnonces.empty()) {
UniValue musig_pubnonces(UniValue::UniValue::VARR);
for (const auto& [agg_lh, part_pubnonce] : input.m_musig2_pubnonces) {
const auto& [agg, lh] = agg_lh;
for (const auto& [part, pubnonce] : part_pubnonce) {
UniValue info(UniValue::VOBJ);
info.pushKV("participant_pubkey", HexStr(part));
info.pushKV("aggregate_pubkey", HexStr(agg));
if (!lh.IsNull()) info.pushKV("leaf_hash", HexStr(lh));
info.pushKV("pubnonce", HexStr(pubnonce));
musig_pubnonces.push_back(info);
}
}
in.pushKV("musig2_pubnonces", musig_pubnonces);
}
if (!input.m_musig2_partial_sigs.empty()) {
UniValue musig_partial_sigs(UniValue::UniValue::VARR);
for (const auto& [agg_lh, part_psig] : input.m_musig2_partial_sigs) {
const auto& [agg, lh] = agg_lh;
for (const auto& [part, psig] : part_psig) {
UniValue info(UniValue::VOBJ);
info.pushKV("participant_pubkey", HexStr(part));
info.pushKV("aggregate_pubkey", HexStr(agg));
if (!lh.IsNull()) info.pushKV("leaf_hash", HexStr(lh));
info.pushKV("partial_sig", HexStr(psig));
musig_partial_sigs.push_back(info);
}
}
in.pushKV("musig2_partial_sigs", musig_partial_sigs);
}
// Proprietary
if (!input.m_proprietary.empty()) {
UniValue proprietary(UniValue::VARR);
@ -1399,6 +1487,22 @@ static RPCHelpMan decodepsbt()
out.pushKV("taproot_bip32_derivs", std::move(keypaths));
}
// Write MuSig2 fields
if (!output.m_musig2_participants.empty()) {
UniValue musig_keys(UniValue::VARR);
for (const auto& [agg, parts] : output.m_musig2_participants) {
UniValue musig_part(UniValue::VOBJ);
musig_part.pushKV("aggregate_pubkey", HexStr(agg));
UniValue part_keys(UniValue::VARR);
for (const auto& pub : parts) {
part_keys.push_back(HexStr(pub));
}
musig_part.pushKV("participant_pubkeys", part_keys);
musig_keys.push_back(musig_part);
}
out.pushKV("musig2_participant_pubkeys", musig_keys);
}
// Proprietary
if (!output.m_proprietary.empty()) {
UniValue proprietary(UniValue::VARR);

View file

@ -16,6 +16,7 @@
#include <txmempool.h>
#include <uint256.h>
#include <util/check.h>
#include <util/feefrac.h>
#include <util/strencodings.h>
#include <util/time.h>
#include <util/translation.h>
@ -25,6 +26,7 @@
#include <test/util/setup_common.h>
#include <memory>
#include <vector>
#include <boost/test/unit_test.hpp>
@ -123,19 +125,22 @@ void MinerTestingSetup::TestPackageSelection(const CScript& scriptPubKey, const
tx.vout[0].nValue = 5000000000LL - 1000;
// This tx has a low fee: 1000 satoshis
Txid hashParentTx = tx.GetHash(); // save this txid for later use
AddToMempool(tx_mempool, entry.Fee(1000).Time(Now<NodeSeconds>()).SpendsCoinbase(true).FromTx(tx));
const auto parent_tx{entry.Fee(1000).Time(Now<NodeSeconds>()).SpendsCoinbase(true).FromTx(tx)};
AddToMempool(tx_mempool, parent_tx);
// This tx has a medium fee: 10000 satoshis
tx.vin[0].prevout.hash = txFirst[1]->GetHash();
tx.vout[0].nValue = 5000000000LL - 10000;
Txid hashMediumFeeTx = tx.GetHash();
AddToMempool(tx_mempool, entry.Fee(10000).Time(Now<NodeSeconds>()).SpendsCoinbase(true).FromTx(tx));
const auto medium_fee_tx{entry.Fee(10000).Time(Now<NodeSeconds>()).SpendsCoinbase(true).FromTx(tx)};
AddToMempool(tx_mempool, medium_fee_tx);
// This tx has a high fee, but depends on the first transaction
tx.vin[0].prevout.hash = hashParentTx;
tx.vout[0].nValue = 5000000000LL - 1000 - 50000; // 50k satoshi fee
Txid hashHighFeeTx = tx.GetHash();
AddToMempool(tx_mempool, entry.Fee(50000).Time(Now<NodeSeconds>()).SpendsCoinbase(false).FromTx(tx));
const auto high_fee_tx{entry.Fee(50000).Time(Now<NodeSeconds>()).SpendsCoinbase(false).FromTx(tx)};
AddToMempool(tx_mempool, high_fee_tx);
std::unique_ptr<BlockTemplate> block_template = mining->createNewBlock(options);
BOOST_REQUIRE(block_template);
@ -145,6 +150,21 @@ void MinerTestingSetup::TestPackageSelection(const CScript& scriptPubKey, const
BOOST_CHECK(block.vtx[2]->GetHash() == hashHighFeeTx);
BOOST_CHECK(block.vtx[3]->GetHash() == hashMediumFeeTx);
// Test the inclusion of package feerates in the block template and ensure they are sequential.
const auto block_package_feerates = BlockAssembler{m_node.chainman->ActiveChainstate(), &tx_mempool, options}.CreateNewBlock()->m_package_feerates;
BOOST_CHECK(block_package_feerates.size() == 2);
// parent_tx and high_fee_tx are added to the block as a package.
const auto combined_txs_fee = parent_tx.GetFee() + high_fee_tx.GetFee();
const auto combined_txs_size = parent_tx.GetTxSize() + high_fee_tx.GetTxSize();
FeeFrac package_feefrac{combined_txs_fee, combined_txs_size};
// The package should be added first.
BOOST_CHECK(block_package_feerates[0] == package_feefrac);
// The medium_fee_tx should be added next.
FeeFrac medium_tx_feefrac{medium_fee_tx.GetFee(), medium_fee_tx.GetTxSize()};
BOOST_CHECK(block_package_feerates[1] == medium_tx_feefrac);
// Test that a package below the block min tx fee doesn't get included
tx.vin[0].prevout.hash = hashHighFeeTx;
tx.vout[0].nValue = 5000000000LL - 1000 - 50000; // 0 fee