Skip to content

Commit 2a3c0c6

Browse files
authored
dgb: pin embedded tx-select feerate-order + weight-cap skip (Phase B FEED conformance) (#277)
Two KATs over make_mempool_tx_source / get_sorted_txs_with_fees: - EmitsFeerateDescendingOrder: transactions[] emitted highest-feerate-first (m_feerate_index is std::greater-ordered), matching the order Core GBT / p2pool-dgb-scrypt consume. - WeightCapSkipsHeavyButPacksLighter: over-cap high-feerate tx is skipped (continue, not break) while a lighter lower-feerate tx below it still packs; total_fees reflects only the packed tx. Test-only, src/impl/dgb/ only, non-consensus, no p2pool-merged-v36 surface. Co-authored-by: frstrtr <frstrtr@users.noreply.github.com>
1 parent 8fe86da commit 2a3c0c6

1 file changed

Lines changed: 94 additions & 0 deletions

File tree

src/impl/dgb/test/embedded_tx_select_test.cpp

Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -125,3 +125,97 @@ TEST(DgbEmbeddedTxSelect, UnknownFeeTxExcluded)
125125
EXPECT_EQ(sel.total_fees, 0u);
126126
EXPECT_TRUE(sel.transactions.empty());
127127
}
128+
129+
// ---------------------------------------------------------------------------
130+
// CONFORMANCE: selection ORDER + weight-cap skip semantics.
131+
//
132+
// p2pool consumes the daemon GBT transactions[] in the order Core emits it --
133+
// ancestor-feerate descending (CreateNewBlock). The embedded shaper must emit
134+
// the same ordering so the share's tx set and any p2pool-side fee accounting
135+
// line up. get_sorted_txs_with_fees walks m_feerate_index, a
136+
// std::multimap<double,uint256,std::greater<double>> -> highest feerate first.
137+
// These pin that ordering and the weight-cap skip (a `continue`, NOT a `break`:
138+
// a high-feerate tx that overflows the cap is skipped while a lighter,
139+
// lower-feerate tx further down can still be packed).
140+
// ---------------------------------------------------------------------------
141+
142+
namespace {
143+
144+
// A tx with `n` outputs (each tagged distinct via index), to vary weight.
145+
MutableTransaction wide_tx(uint32_t index, int n_outputs)
146+
{
147+
MutableTransaction tx;
148+
tx.version = 1;
149+
tx.locktime = 0;
150+
TxIn in;
151+
in.prevout.hash.SetNull();
152+
in.prevout.index = index;
153+
in.sequence = 0xffffffff;
154+
tx.vin.push_back(in);
155+
for (int i = 0; i < n_outputs; ++i) {
156+
TxOut out;
157+
out.value = 1000 + i;
158+
tx.vout.push_back(out);
159+
}
160+
return tx;
161+
}
162+
163+
uint32_t tx_weight(const MutableTransaction& tx)
164+
{
165+
uint32_t base = 0, wit = 0, weight = 0;
166+
dgb::coin::compute_tx_weight(tx, base, wit, weight);
167+
return weight;
168+
}
169+
170+
} // namespace
171+
172+
// transactions[] are emitted highest-feerate-first (equal weight -> by fee).
173+
TEST(DgbEmbeddedTxSelect, EmitsFeerateDescendingOrder)
174+
{
175+
Mempool pool;
176+
MutableTransaction lo = tagged_tx(10, 0); // fee 300 -> lowest feerate
177+
MutableTransaction mid = tagged_tx(20, 1); // fee 600
178+
MutableTransaction hi = tagged_tx(30, 2); // fee 900 -> highest feerate
179+
// Insert out of feerate order to prove the index, not arrival, drives order.
180+
ASSERT_TRUE(pool.add_tx(mid));
181+
ASSERT_TRUE(pool.add_tx(lo));
182+
ASSERT_TRUE(pool.add_tx(hi));
183+
pool.set_tx_fee(compute_txid(lo), 300);
184+
pool.set_tx_fee(compute_txid(mid), 600);
185+
pool.set_tx_fee(compute_txid(hi), 900);
186+
187+
auto source = make_mempool_tx_source(pool, /*max_weight=*/4'000'000);
188+
const auto sel = source();
189+
190+
ASSERT_EQ(sel.transactions.size(), 3u);
191+
EXPECT_EQ(sel.transactions[0]["txid"], compute_txid(hi).GetHex());
192+
EXPECT_EQ(sel.transactions[1]["txid"], compute_txid(mid).GetHex());
193+
EXPECT_EQ(sel.transactions[2]["txid"], compute_txid(lo).GetHex());
194+
EXPECT_EQ(sel.total_fees, 1800u);
195+
}
196+
197+
// Weight cap skips an over-cap high-feerate tx but still packs a lighter one
198+
// below it (skip-not-break). total_fees reflects only the packed tx.
199+
TEST(DgbEmbeddedTxSelect, WeightCapSkipsHeavyButPacksLighter)
200+
{
201+
Mempool pool;
202+
MutableTransaction heavy = wide_tx(0, /*n_outputs=*/40); // large weight
203+
MutableTransaction light = tagged_tx(50, 1); // single output
204+
ASSERT_TRUE(pool.add_tx(heavy));
205+
ASSERT_TRUE(pool.add_tx(light));
206+
pool.set_tx_fee(compute_txid(heavy), 10'000'000); // huge fee -> top feerate
207+
pool.set_tx_fee(compute_txid(light), 100); // low feerate, low weight
208+
209+
const uint32_t w_heavy = tx_weight(heavy);
210+
const uint32_t w_light = tx_weight(light);
211+
ASSERT_GT(w_heavy, w_light) << "test needs heavy strictly heavier than light";
212+
213+
// Cap exactly fits `light` but not `heavy`: heavy is first (top feerate),
214+
// overflows -> skipped; light follows and fits.
215+
auto source = make_mempool_tx_source(pool, /*max_weight=*/w_light);
216+
const auto sel = source();
217+
218+
ASSERT_EQ(sel.transactions.size(), 1u);
219+
EXPECT_EQ(sel.transactions[0]["txid"], compute_txid(light).GetHex());
220+
EXPECT_EQ(sel.total_fees, 100u);
221+
}

0 commit comments

Comments
 (0)