Skip to content

Commit 7f5fa35

Browse files
laanwjknst
authored andcommitted
Merge bitcoin#22539: Re-include RBF replacement txs in fee estimation
3b61372 Add release notes for fee est with replacement txs (Antoine Poinsot) 4556406 qa: test fee estimation with replacement transactions (Antoine Poinsot) 053415b qa: split run_test into smaller parts (Antoine Poinsot) 06c5ce9 Re-include RBF replacement txs in fee estimation (Antoine Poinsot) Pull request description: This effectively reverts bitcoin#9519. RBF is now largely in use on the network (signaled for by around 20% of all transactions on average) and replacement logic is implemented in most end-user wallets. The rate of replaced transactions is also expected to rise as fee-bumping techniques are being developed for pre-signed transaction ("L2") protocols. ACKs for top commit: prayank23: reACK bitcoin@3b61372 Zero-1729: re-ACK 3b61372 benthecarman: reACK 3b61372 glozow: ACK 3b61372 theStack: re-ACK 3b61372 🍪 Tree-SHA512: a6146d15c80ff4ba9249314b0ef953a66a15673e61b8f98979642814f1b169b5695e330e3ee069fa9a7e4d1f8aa10e1dcb7f9aa79181cea5a4c4dbcaf5483023
1 parent 34fcbf1 commit 7f5fa35

File tree

2 files changed

+75
-27
lines changed

2 files changed

+75
-27
lines changed

doc/release-notes-22539.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
Notable changes
2+
===============
3+
4+
P2P and network changes
5+
-----------------------
6+
7+
- Fee estimation now takes the feerate of replacement (RBF) transactions into
8+
account.

test/functional/feature_fee_estimation.py

Lines changed: 67 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
# file COPYING or http://www.opensource.org/licenses/mit-license.php.
55
"""Test fee estimation code."""
66
from decimal import Decimal
7+
import os
78
import random
89

910
from test_framework.messages import (
@@ -158,6 +159,21 @@ def check_estimates(node, fees_seen):
158159
check_raw_estimates(node, fees_seen)
159160
check_smart_estimates(node, fees_seen)
160161

162+
163+
def send_tx(node, utxo, feerate):
164+
"""Broadcast a 1in-1out transaction with a specific input and feerate (sat/vb)."""
165+
overhead, op, scriptsig, nseq, value, spk = 10, 36, 5, 4, 8, 24
166+
tx_size = overhead + op + scriptsig + nseq + value + spk
167+
fee = tx_size * feerate
168+
169+
tx = CTransaction()
170+
tx.vin = [CTxIn(COutPoint(int(utxo["txid"], 16), utxo["vout"]), SCRIPT_SIG[utxo["vout"]])]
171+
tx.vout = [CTxOut(int(utxo["amount"] * COIN) - fee, P2SH_1)]
172+
txid = node.sendrawtransaction(tx.serialize().hex())
173+
174+
return txid
175+
176+
161177
class EstimateFeeTest(BitcoinTestFramework):
162178
def set_test_params(self):
163179
self.num_nodes = 3
@@ -215,48 +231,36 @@ def transact_and_mine(self, numblocks, mining_node):
215231
newmem.append(utx)
216232
self.memutxo = newmem
217233

218-
def run_test(self):
219-
self.log.info("This test is time consuming, please be patient")
220-
self.log.info("Splitting inputs so we can generate tx's")
221-
222-
# Start node0
223-
self.start_node(0)
234+
def initial_split(self, node):
235+
"""Split two coinbase UTxOs into many small coins"""
224236
self.txouts = []
225237
self.txouts2 = []
226238
# Split a coinbase into two transaction puzzle outputs
227-
split_inputs(self.nodes[0], self.nodes[0].listunspent(0), self.txouts, True)
239+
split_inputs(node, node.listunspent(0), self.txouts, True)
228240

229241
# Mine
230242
while len(self.nodes[0].getrawmempool()) > 0:
231-
self.generate(self.nodes[0], 1, sync_fun=self.no_op)
243+
self.generate(node, 1, sync_fun=self.no_op)
232244

233245
# Repeatedly split those 2 outputs, doubling twice for each rep
234246
# Use txouts to monitor the available utxo, since these won't be tracked in wallet
235247
reps = 0
236248
while reps < 5:
237249
# Double txouts to txouts2
238250
while len(self.txouts) > 0:
239-
split_inputs(self.nodes[0], self.txouts, self.txouts2)
240-
while len(self.nodes[0].getrawmempool()) > 0:
241-
self.generate(self.nodes[0], 1, sync_fun=self.no_op)
251+
split_inputs(node, self.txouts, self.txouts2)
252+
while len(node.getrawmempool()) > 0:
253+
self.generate(node, 1, sync_fun=self.no_op)
242254
# Double txouts2 to txouts
243255
while len(self.txouts2) > 0:
244-
split_inputs(self.nodes[0], self.txouts2, self.txouts)
245-
while len(self.nodes[0].getrawmempool()) > 0:
246-
self.generate(self.nodes[0], 1, sync_fun=self.no_op)
256+
split_inputs(node, self.txouts2, self.txouts)
257+
while len(node.getrawmempool()) > 0:
258+
self.generate(node, 1, sync_fun=self.no_op)
247259
reps += 1
248-
self.log.info("Finished splitting")
249-
250-
# Now we can connect the other nodes, didn't want to connect them earlier
251-
# so the estimates would not be affected by the splitting transactions
252-
self.start_node(1)
253-
self.start_node(2)
254-
self.connect_nodes(1, 0)
255-
self.connect_nodes(0, 2)
256-
self.connect_nodes(2, 1)
257-
258-
self.sync_all()
259260

261+
def sanity_check_estimates_range(self):
262+
"""Populate estimation buckets, assert estimates are in a sane range and
263+
are strictly increasing as the target decreases."""
260264
self.fees_per_kb = []
261265
self.memutxo = []
262266
self.confutxo = self.txouts # Start with the set of confirmed txouts after splitting
@@ -282,12 +286,48 @@ def run_test(self):
282286
self.log.info("Final estimates after emptying mempools")
283287
check_estimates(self.nodes[1], self.fees_per_kb)
284288

285-
# check that the effective feerate is greater than or equal to the mempoolminfee even for high mempoolminfee
286-
self.log.info("Test fee rate estimation after restarting node with high MempoolMinFee")
289+
def test_feerate_mempoolminfee(self):
287290
high_val = 3*self.nodes[1].estimatesmartfee(1)['feerate']
288291
self.restart_node(1, extra_args=[f'-minrelaytxfee={high_val}'])
289292
check_estimates(self.nodes[1], self.fees_per_kb)
290293
self.stop_node(1, expected_stderr="Warning: -minrelaytxfee is set very high! The wallet will avoid paying less than the minimum relay fee.")
294+
# TODO CHECK CHECK MAY BE THIS
295+
# self.restart_node(1)
296+
297+
298+
def run_test(self):
299+
self.log.info("This test is time consuming, please be patient")
300+
self.log.info("Splitting inputs so we can generate tx's")
301+
302+
# Split two coinbases into many small utxos
303+
self.start_node(0)
304+
self.initial_split(self.nodes[0])
305+
self.log.info("Finished splitting")
306+
307+
# Now we can connect the other nodes, didn't want to connect them earlier
308+
# so the estimates would not be affected by the splitting transactions
309+
self.start_node(1)
310+
self.start_node(2)
311+
self.connect_nodes(1, 0)
312+
self.connect_nodes(0, 2)
313+
self.connect_nodes(2, 1)
314+
self.sync_all()
315+
316+
self.log.info("Testing estimates with single transactions.")
317+
self.sanity_check_estimates_range()
318+
319+
# check that the effective feerate is greater than or equal to the mempoolminfee even for high mempoolminfee
320+
self.log.info("Test fee rate estimation after restarting node with high MempoolMinFee")
321+
self.test_feerate_mempoolminfee()
322+
323+
self.log.info("Restarting node with fresh estimation")
324+
self.stop_node(0)
325+
fee_dat = os.path.join(self.nodes[0].datadir, self.chain, "fee_estimates.dat")
326+
os.remove(fee_dat)
327+
self.start_node(0)
328+
self.connect_nodes(0, 1)
329+
self.connect_nodes(0, 2)
330+
291331

292332
self.log.info("Testing that fee estimation is disabled in blocksonly.")
293333
self.restart_node(0, ["-blocksonly"])

0 commit comments

Comments
 (0)