mirror of
https://github.com/dashpay/dash.git
synced 2024-12-25 12:02:48 +01:00
Remove leftover RBF code from BTC (#2297)
* Remove leftover RBF code from BTC
* remove rbf #include
* remove rbf in rpc-tests
* removes replace-by-fee.py
* remove help text related to rbf
* remove comment text relating to rbf
* remove "-mempoolreplacement" cli option
* Remove (effectively dead) RBF code which would never have been called anyway and some assosiated variables
* since `setConflicts` is always empty, this is dead code
* Since we don't have RBF, don't have to do this check. Also, since `setConflicts` is always empty this is dead code
* removes unneccesary if as it will always be true
* remove unused `set<uint256> setConflicts`
* Removes replacement of conflicting txs, as conflicting txs are never accepted
* removes RBF from `validForFeeEstimation`
* removes (probably) unnecessary lock
* remove replacing part of the AcceptToMemoryPool and AcceptToMemoryPoolWIthTime
* fixes err in ps.cpp, didn't remove arg
* RBF in net_processing.cpp
* remove arg in ps-server.cpp
* removes another arg in PS code
* removes rawtx.c AcceptToMemoryPool arg
* removes arg in txvalidationcache_tests.cpp
* remove extra args
* forgot an arg
* fix typo in 82898b0
* remove unused fEnableReplacement in validation.h
* remove the removal reason REPLACED in txmempool.h
* removed unused variable
* comment typo
This commit is contained in:
parent
3cc4ac1376
commit
8ea40102c0
@ -199,7 +199,6 @@ testScriptsExt = [
|
||||
'invalidateblock.py',
|
||||
'maxblocksinflight.py',
|
||||
'p2p-acceptblock.py', # NOTE: needs dash_hash to pass
|
||||
# 'replace-by-fee.py', # RBF is disabled in Dash Core
|
||||
]
|
||||
|
||||
|
||||
|
@ -93,108 +93,6 @@ class ListTransactionsTest(BitcoinTestFramework):
|
||||
{"category":"receive","amount":Decimal("0.1")},
|
||||
{"txid":txid, "account" : "watchonly"} )
|
||||
|
||||
# rbf is disabled in Dash Core
|
||||
# self.run_rbf_opt_in_test()
|
||||
|
||||
# Check that the opt-in-rbf flag works properly, for sent and received
|
||||
# transactions.
|
||||
def run_rbf_opt_in_test(self):
|
||||
# Check whether a transaction signals opt-in RBF itself
|
||||
def is_opt_in(node, txid):
|
||||
rawtx = node.getrawtransaction(txid, 1)
|
||||
for x in rawtx["vin"]:
|
||||
if x["sequence"] < 0xfffffffe:
|
||||
return True
|
||||
return False
|
||||
|
||||
# Find an unconfirmed output matching a certain txid
|
||||
def get_unconfirmed_utxo_entry(node, txid_to_match):
|
||||
utxo = node.listunspent(0, 0)
|
||||
for i in utxo:
|
||||
if i["txid"] == txid_to_match:
|
||||
return i
|
||||
return None
|
||||
|
||||
# 1. Chain a few transactions that don't opt-in.
|
||||
txid_1 = self.nodes[0].sendtoaddress(self.nodes[1].getnewaddress(), 1)
|
||||
assert(not is_opt_in(self.nodes[0], txid_1))
|
||||
assert_array_result(self.nodes[0].listtransactions(), {"txid": txid_1}, {"bip125-replaceable":"no"})
|
||||
sync_mempools(self.nodes)
|
||||
assert_array_result(self.nodes[1].listtransactions(), {"txid": txid_1}, {"bip125-replaceable":"no"})
|
||||
|
||||
# Tx2 will build off txid_1, still not opting in to RBF.
|
||||
utxo_to_use = get_unconfirmed_utxo_entry(self.nodes[1], txid_1)
|
||||
|
||||
# Create tx2 using createrawtransaction
|
||||
inputs = [{"txid":utxo_to_use["txid"], "vout":utxo_to_use["vout"]}]
|
||||
outputs = {self.nodes[0].getnewaddress(): 0.999}
|
||||
tx2 = self.nodes[1].createrawtransaction(inputs, outputs)
|
||||
tx2_signed = self.nodes[1].signrawtransaction(tx2)["hex"]
|
||||
txid_2 = self.nodes[1].sendrawtransaction(tx2_signed)
|
||||
|
||||
# ...and check the result
|
||||
assert(not is_opt_in(self.nodes[1], txid_2))
|
||||
assert_array_result(self.nodes[1].listtransactions(), {"txid": txid_2}, {"bip125-replaceable":"no"})
|
||||
sync_mempools(self.nodes)
|
||||
assert_array_result(self.nodes[0].listtransactions(), {"txid": txid_2}, {"bip125-replaceable":"no"})
|
||||
|
||||
# Tx3 will opt-in to RBF
|
||||
utxo_to_use = get_unconfirmed_utxo_entry(self.nodes[0], txid_2)
|
||||
inputs = [{"txid": txid_2, "vout":utxo_to_use["vout"]}]
|
||||
outputs = {self.nodes[1].getnewaddress(): 0.998}
|
||||
tx3 = self.nodes[0].createrawtransaction(inputs, outputs)
|
||||
tx3_modified = txFromHex(tx3)
|
||||
tx3_modified.vin[0].nSequence = 0
|
||||
tx3 = bytes_to_hex_str(tx3_modified.serialize())
|
||||
tx3_signed = self.nodes[0].signrawtransaction(tx3)['hex']
|
||||
txid_3 = self.nodes[0].sendrawtransaction(tx3_signed)
|
||||
|
||||
assert(is_opt_in(self.nodes[0], txid_3))
|
||||
assert_array_result(self.nodes[0].listtransactions(), {"txid": txid_3}, {"bip125-replaceable":"yes"})
|
||||
sync_mempools(self.nodes)
|
||||
assert_array_result(self.nodes[1].listtransactions(), {"txid": txid_3}, {"bip125-replaceable":"yes"})
|
||||
|
||||
# Tx4 will chain off tx3. Doesn't signal itself, but depends on one
|
||||
# that does.
|
||||
utxo_to_use = get_unconfirmed_utxo_entry(self.nodes[1], txid_3)
|
||||
inputs = [{"txid": txid_3, "vout":utxo_to_use["vout"]}]
|
||||
outputs = {self.nodes[0].getnewaddress(): 0.997}
|
||||
tx4 = self.nodes[1].createrawtransaction(inputs, outputs)
|
||||
tx4_signed = self.nodes[1].signrawtransaction(tx4)["hex"]
|
||||
txid_4 = self.nodes[1].sendrawtransaction(tx4_signed)
|
||||
|
||||
assert(not is_opt_in(self.nodes[1], txid_4))
|
||||
assert_array_result(self.nodes[1].listtransactions(), {"txid": txid_4}, {"bip125-replaceable":"yes"})
|
||||
sync_mempools(self.nodes)
|
||||
assert_array_result(self.nodes[0].listtransactions(), {"txid": txid_4}, {"bip125-replaceable":"yes"})
|
||||
|
||||
# Replace tx3, and check that tx4 becomes unknown
|
||||
tx3_b = tx3_modified
|
||||
tx3_b.vout[0].nValue -= int(Decimal("0.004") * COIN) # bump the fee
|
||||
tx3_b = bytes_to_hex_str(tx3_b.serialize())
|
||||
tx3_b_signed = self.nodes[0].signrawtransaction(tx3_b)['hex']
|
||||
txid_3b = self.nodes[0].sendrawtransaction(tx3_b_signed, True)
|
||||
assert(is_opt_in(self.nodes[0], txid_3b))
|
||||
|
||||
assert_array_result(self.nodes[0].listtransactions(), {"txid": txid_4}, {"bip125-replaceable":"unknown"})
|
||||
sync_mempools(self.nodes)
|
||||
assert_array_result(self.nodes[1].listtransactions(), {"txid": txid_4}, {"bip125-replaceable":"unknown"})
|
||||
|
||||
# Check gettransaction as well:
|
||||
for n in self.nodes[0:2]:
|
||||
assert_equal(n.gettransaction(txid_1)["bip125-replaceable"], "no")
|
||||
assert_equal(n.gettransaction(txid_2)["bip125-replaceable"], "no")
|
||||
assert_equal(n.gettransaction(txid_3)["bip125-replaceable"], "yes")
|
||||
assert_equal(n.gettransaction(txid_3b)["bip125-replaceable"], "yes")
|
||||
assert_equal(n.gettransaction(txid_4)["bip125-replaceable"], "unknown")
|
||||
|
||||
# After mining a transaction, it's no longer BIP125-replaceable
|
||||
self.nodes[0].generate(1)
|
||||
assert(txid_3b not in self.nodes[0].getrawmempool())
|
||||
assert_equal(self.nodes[0].gettransaction(txid_3b)["bip125-replaceable"], "no")
|
||||
assert_equal(self.nodes[0].gettransaction(txid_4)["bip125-replaceable"], "unknown")
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
ListTransactionsTest().main()
|
||||
|
||||
|
@ -1,589 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
# Copyright (c) 2014-2016 The Bitcoin Core developers
|
||||
# Distributed under the MIT software license, see the accompanying
|
||||
# file COPYING or http://www.opensource.org/licenses/mit-license.php.
|
||||
|
||||
#
|
||||
# Test replace by fee code
|
||||
#
|
||||
|
||||
from test_framework.test_framework import BitcoinTestFramework
|
||||
from test_framework.util import *
|
||||
from test_framework.script import *
|
||||
from test_framework.mininode import *
|
||||
|
||||
MAX_REPLACEMENT_LIMIT = 100
|
||||
|
||||
def txToHex(tx):
|
||||
return bytes_to_hex_str(tx.serialize())
|
||||
|
||||
def make_utxo(node, amount, confirmed=True, scriptPubKey=CScript([1])):
|
||||
"""Create a txout with a given amount and scriptPubKey
|
||||
|
||||
Mines coins as needed.
|
||||
|
||||
confirmed - txouts created will be confirmed in the blockchain;
|
||||
unconfirmed otherwise.
|
||||
"""
|
||||
fee = 1*COIN
|
||||
while node.getbalance() < satoshi_round((amount + fee)/COIN):
|
||||
node.generate(100)
|
||||
#print (node.getbalance(), amount, fee)
|
||||
|
||||
new_addr = node.getnewaddress()
|
||||
#print new_addr
|
||||
txid = node.sendtoaddress(new_addr, satoshi_round((amount+fee)/COIN))
|
||||
tx1 = node.getrawtransaction(txid, 1)
|
||||
txid = int(txid, 16)
|
||||
i = None
|
||||
|
||||
for i, txout in enumerate(tx1['vout']):
|
||||
#print i, txout['scriptPubKey']['addresses']
|
||||
if txout['scriptPubKey']['addresses'] == [new_addr]:
|
||||
#print i
|
||||
break
|
||||
assert i is not None
|
||||
|
||||
tx2 = CTransaction()
|
||||
tx2.vin = [CTxIn(COutPoint(txid, i))]
|
||||
tx2.vout = [CTxOut(amount, scriptPubKey)]
|
||||
tx2.rehash()
|
||||
|
||||
signed_tx = node.signrawtransaction(txToHex(tx2))
|
||||
|
||||
txid = node.sendrawtransaction(signed_tx['hex'], True)
|
||||
|
||||
# If requested, ensure txouts are confirmed.
|
||||
if confirmed:
|
||||
mempool_size = len(node.getrawmempool())
|
||||
while mempool_size > 0:
|
||||
node.generate(1)
|
||||
new_size = len(node.getrawmempool())
|
||||
# Error out if we have something stuck in the mempool, as this
|
||||
# would likely be a bug.
|
||||
assert(new_size < mempool_size)
|
||||
mempool_size = new_size
|
||||
|
||||
return COutPoint(int(txid, 16), 0)
|
||||
|
||||
class ReplaceByFeeTest(BitcoinTestFramework):
|
||||
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self.num_nodes = 1
|
||||
self.setup_clean_chain = False
|
||||
|
||||
def setup_network(self):
|
||||
self.nodes = []
|
||||
self.nodes.append(start_node(0, self.options.tmpdir, ["-maxorphantx=1000", "-debug",
|
||||
"-whitelist=127.0.0.1",
|
||||
"-limitancestorcount=50",
|
||||
"-limitancestorsize=101",
|
||||
"-limitdescendantcount=200",
|
||||
"-limitdescendantsize=101"
|
||||
]))
|
||||
self.is_network_split = False
|
||||
|
||||
def run_test(self):
|
||||
make_utxo(self.nodes[0], 1*COIN)
|
||||
|
||||
print("Running test simple doublespend...")
|
||||
self.test_simple_doublespend()
|
||||
|
||||
print("Running test doublespend chain...")
|
||||
self.test_doublespend_chain()
|
||||
|
||||
print("Running test doublespend tree...")
|
||||
self.test_doublespend_tree()
|
||||
|
||||
print("Running test replacement feeperkb...")
|
||||
self.test_replacement_feeperkb()
|
||||
|
||||
print("Running test spends of conflicting outputs...")
|
||||
self.test_spends_of_conflicting_outputs()
|
||||
|
||||
print("Running test new unconfirmed inputs...")
|
||||
self.test_new_unconfirmed_inputs()
|
||||
|
||||
print("Running test too many replacements...")
|
||||
self.test_too_many_replacements()
|
||||
|
||||
print("Running test opt-in...")
|
||||
self.test_opt_in()
|
||||
|
||||
print("Running test prioritised transactions...")
|
||||
self.test_prioritised_transactions()
|
||||
|
||||
print("Passed\n")
|
||||
|
||||
def test_simple_doublespend(self):
|
||||
"""Simple doublespend"""
|
||||
tx0_outpoint = make_utxo(self.nodes[0], int(1.1*COIN))
|
||||
|
||||
tx1a = CTransaction()
|
||||
tx1a.vin = [CTxIn(tx0_outpoint, nSequence=0)]
|
||||
tx1a.vout = [CTxOut(1*COIN, CScript([b'a']))]
|
||||
tx1a_hex = txToHex(tx1a)
|
||||
tx1a_txid = self.nodes[0].sendrawtransaction(tx1a_hex, True)
|
||||
|
||||
# Should fail because we haven't changed the fee
|
||||
tx1b = CTransaction()
|
||||
tx1b.vin = [CTxIn(tx0_outpoint, nSequence=0)]
|
||||
tx1b.vout = [CTxOut(1*COIN, CScript([b'b']))]
|
||||
tx1b_hex = txToHex(tx1b)
|
||||
|
||||
try:
|
||||
tx1b_txid = self.nodes[0].sendrawtransaction(tx1b_hex, True)
|
||||
except JSONRPCException as exp:
|
||||
assert_equal(exp.error['code'], -26) # insufficient fee
|
||||
else:
|
||||
assert(False)
|
||||
|
||||
# Extra 0.1 BTC fee
|
||||
tx1b = CTransaction()
|
||||
tx1b.vin = [CTxIn(tx0_outpoint, nSequence=0)]
|
||||
tx1b.vout = [CTxOut(int(0.9*COIN), CScript([b'b']))]
|
||||
tx1b_hex = txToHex(tx1b)
|
||||
tx1b_txid = self.nodes[0].sendrawtransaction(tx1b_hex, True)
|
||||
|
||||
mempool = self.nodes[0].getrawmempool()
|
||||
|
||||
assert (tx1a_txid not in mempool)
|
||||
assert (tx1b_txid in mempool)
|
||||
|
||||
assert_equal(tx1b_hex, self.nodes[0].getrawtransaction(tx1b_txid))
|
||||
|
||||
def test_doublespend_chain(self):
|
||||
"""Doublespend of a long chain"""
|
||||
|
||||
initial_nValue = 50*COIN
|
||||
tx0_outpoint = make_utxo(self.nodes[0], initial_nValue)
|
||||
|
||||
prevout = tx0_outpoint
|
||||
remaining_value = initial_nValue
|
||||
chain_txids = []
|
||||
while remaining_value > 10*COIN:
|
||||
remaining_value -= 1*COIN
|
||||
tx = CTransaction()
|
||||
tx.vin = [CTxIn(prevout, nSequence=0)]
|
||||
tx.vout = [CTxOut(remaining_value, CScript([1]))]
|
||||
tx_hex = txToHex(tx)
|
||||
txid = self.nodes[0].sendrawtransaction(tx_hex, True)
|
||||
chain_txids.append(txid)
|
||||
prevout = COutPoint(int(txid, 16), 0)
|
||||
|
||||
# Whether the double-spend is allowed is evaluated by including all
|
||||
# child fees - 40 BTC - so this attempt is rejected.
|
||||
dbl_tx = CTransaction()
|
||||
dbl_tx.vin = [CTxIn(tx0_outpoint, nSequence=0)]
|
||||
dbl_tx.vout = [CTxOut(initial_nValue - 30*COIN, CScript([1]))]
|
||||
dbl_tx_hex = txToHex(dbl_tx)
|
||||
|
||||
try:
|
||||
self.nodes[0].sendrawtransaction(dbl_tx_hex, True)
|
||||
except JSONRPCException as exp:
|
||||
assert_equal(exp.error['code'], -26) # insufficient fee
|
||||
else:
|
||||
assert(False) # transaction mistakenly accepted!
|
||||
|
||||
# Accepted with sufficient fee
|
||||
dbl_tx = CTransaction()
|
||||
dbl_tx.vin = [CTxIn(tx0_outpoint, nSequence=0)]
|
||||
dbl_tx.vout = [CTxOut(1*COIN, CScript([1]))]
|
||||
dbl_tx_hex = txToHex(dbl_tx)
|
||||
self.nodes[0].sendrawtransaction(dbl_tx_hex, True)
|
||||
|
||||
mempool = self.nodes[0].getrawmempool()
|
||||
for doublespent_txid in chain_txids:
|
||||
assert(doublespent_txid not in mempool)
|
||||
|
||||
def test_doublespend_tree(self):
|
||||
"""Doublespend of a big tree of transactions"""
|
||||
|
||||
initial_nValue = 50*COIN
|
||||
tx0_outpoint = make_utxo(self.nodes[0], initial_nValue)
|
||||
|
||||
def branch(prevout, initial_value, max_txs, tree_width=5, fee=0.0001*COIN, _total_txs=None):
|
||||
if _total_txs is None:
|
||||
_total_txs = [0]
|
||||
if _total_txs[0] >= max_txs:
|
||||
return
|
||||
|
||||
txout_value = (initial_value - fee) // tree_width
|
||||
if txout_value < fee:
|
||||
return
|
||||
|
||||
vout = [CTxOut(txout_value, CScript([i+1]))
|
||||
for i in range(tree_width)]
|
||||
tx = CTransaction()
|
||||
tx.vin = [CTxIn(prevout, nSequence=0)]
|
||||
tx.vout = vout
|
||||
tx_hex = txToHex(tx)
|
||||
|
||||
assert(len(tx.serialize()) < 100000)
|
||||
txid = self.nodes[0].sendrawtransaction(tx_hex, True)
|
||||
yield tx
|
||||
_total_txs[0] += 1
|
||||
|
||||
txid = int(txid, 16)
|
||||
|
||||
for i, txout in enumerate(tx.vout):
|
||||
for x in branch(COutPoint(txid, i), txout_value,
|
||||
max_txs,
|
||||
tree_width=tree_width, fee=fee,
|
||||
_total_txs=_total_txs):
|
||||
yield x
|
||||
|
||||
fee = int(0.0001*COIN)
|
||||
n = MAX_REPLACEMENT_LIMIT
|
||||
tree_txs = list(branch(tx0_outpoint, initial_nValue, n, fee=fee))
|
||||
assert_equal(len(tree_txs), n)
|
||||
|
||||
# Attempt double-spend, will fail because too little fee paid
|
||||
dbl_tx = CTransaction()
|
||||
dbl_tx.vin = [CTxIn(tx0_outpoint, nSequence=0)]
|
||||
dbl_tx.vout = [CTxOut(initial_nValue - fee*n, CScript([1]))]
|
||||
dbl_tx_hex = txToHex(dbl_tx)
|
||||
try:
|
||||
self.nodes[0].sendrawtransaction(dbl_tx_hex, True)
|
||||
except JSONRPCException as exp:
|
||||
assert_equal(exp.error['code'], -26) # insufficient fee
|
||||
else:
|
||||
assert(False)
|
||||
|
||||
# 1 BTC fee is enough
|
||||
dbl_tx = CTransaction()
|
||||
dbl_tx.vin = [CTxIn(tx0_outpoint, nSequence=0)]
|
||||
dbl_tx.vout = [CTxOut(initial_nValue - fee*n - 1*COIN, CScript([1]))]
|
||||
dbl_tx_hex = txToHex(dbl_tx)
|
||||
self.nodes[0].sendrawtransaction(dbl_tx_hex, True)
|
||||
|
||||
mempool = self.nodes[0].getrawmempool()
|
||||
|
||||
for tx in tree_txs:
|
||||
tx.rehash()
|
||||
assert (tx.hash not in mempool)
|
||||
|
||||
# Try again, but with more total transactions than the "max txs
|
||||
# double-spent at once" anti-DoS limit.
|
||||
for n in (MAX_REPLACEMENT_LIMIT+1, MAX_REPLACEMENT_LIMIT*2):
|
||||
fee = int(0.0001*COIN)
|
||||
tx0_outpoint = make_utxo(self.nodes[0], initial_nValue)
|
||||
tree_txs = list(branch(tx0_outpoint, initial_nValue, n, fee=fee))
|
||||
assert_equal(len(tree_txs), n)
|
||||
|
||||
dbl_tx = CTransaction()
|
||||
dbl_tx.vin = [CTxIn(tx0_outpoint, nSequence=0)]
|
||||
dbl_tx.vout = [CTxOut(initial_nValue - 2*fee*n, CScript([1]))]
|
||||
dbl_tx_hex = txToHex(dbl_tx)
|
||||
try:
|
||||
self.nodes[0].sendrawtransaction(dbl_tx_hex, True)
|
||||
except JSONRPCException as exp:
|
||||
assert_equal(exp.error['code'], -26)
|
||||
assert_equal("too many potential replacements" in exp.error['message'], True)
|
||||
else:
|
||||
assert(False)
|
||||
|
||||
for tx in tree_txs:
|
||||
tx.rehash()
|
||||
self.nodes[0].getrawtransaction(tx.hash)
|
||||
|
||||
def test_replacement_feeperkb(self):
|
||||
"""Replacement requires fee-per-KB to be higher"""
|
||||
tx0_outpoint = make_utxo(self.nodes[0], int(1.1*COIN))
|
||||
|
||||
tx1a = CTransaction()
|
||||
tx1a.vin = [CTxIn(tx0_outpoint, nSequence=0)]
|
||||
tx1a.vout = [CTxOut(1*COIN, CScript([b'a']))]
|
||||
tx1a_hex = txToHex(tx1a)
|
||||
tx1a_txid = self.nodes[0].sendrawtransaction(tx1a_hex, True)
|
||||
|
||||
# Higher fee, but the fee per KB is much lower, so the replacement is
|
||||
# rejected.
|
||||
tx1b = CTransaction()
|
||||
tx1b.vin = [CTxIn(tx0_outpoint, nSequence=0)]
|
||||
tx1b.vout = [CTxOut(int(0.001*COIN), CScript([b'a'*999000]))]
|
||||
tx1b_hex = txToHex(tx1b)
|
||||
|
||||
try:
|
||||
tx1b_txid = self.nodes[0].sendrawtransaction(tx1b_hex, True)
|
||||
except JSONRPCException as exp:
|
||||
assert_equal(exp.error['code'], -26) # insufficient fee
|
||||
else:
|
||||
assert(False)
|
||||
|
||||
def test_spends_of_conflicting_outputs(self):
|
||||
"""Replacements that spend conflicting tx outputs are rejected"""
|
||||
utxo1 = make_utxo(self.nodes[0], int(1.2*COIN))
|
||||
utxo2 = make_utxo(self.nodes[0], 3*COIN)
|
||||
|
||||
tx1a = CTransaction()
|
||||
tx1a.vin = [CTxIn(utxo1, nSequence=0)]
|
||||
tx1a.vout = [CTxOut(int(1.1*COIN), CScript([b'a']))]
|
||||
tx1a_hex = txToHex(tx1a)
|
||||
tx1a_txid = self.nodes[0].sendrawtransaction(tx1a_hex, True)
|
||||
|
||||
tx1a_txid = int(tx1a_txid, 16)
|
||||
|
||||
# Direct spend an output of the transaction we're replacing.
|
||||
tx2 = CTransaction()
|
||||
tx2.vin = [CTxIn(utxo1, nSequence=0), CTxIn(utxo2, nSequence=0)]
|
||||
tx2.vin.append(CTxIn(COutPoint(tx1a_txid, 0), nSequence=0))
|
||||
tx2.vout = tx1a.vout
|
||||
tx2_hex = txToHex(tx2)
|
||||
|
||||
try:
|
||||
tx2_txid = self.nodes[0].sendrawtransaction(tx2_hex, True)
|
||||
except JSONRPCException as exp:
|
||||
assert_equal(exp.error['code'], -26)
|
||||
else:
|
||||
assert(False)
|
||||
|
||||
# Spend tx1a's output to test the indirect case.
|
||||
tx1b = CTransaction()
|
||||
tx1b.vin = [CTxIn(COutPoint(tx1a_txid, 0), nSequence=0)]
|
||||
tx1b.vout = [CTxOut(1*COIN, CScript([b'a']))]
|
||||
tx1b_hex = txToHex(tx1b)
|
||||
tx1b_txid = self.nodes[0].sendrawtransaction(tx1b_hex, True)
|
||||
tx1b_txid = int(tx1b_txid, 16)
|
||||
|
||||
tx2 = CTransaction()
|
||||
tx2.vin = [CTxIn(utxo1, nSequence=0), CTxIn(utxo2, nSequence=0),
|
||||
CTxIn(COutPoint(tx1b_txid, 0))]
|
||||
tx2.vout = tx1a.vout
|
||||
tx2_hex = txToHex(tx2)
|
||||
|
||||
try:
|
||||
tx2_txid = self.nodes[0].sendrawtransaction(tx2_hex, True)
|
||||
except JSONRPCException as exp:
|
||||
assert_equal(exp.error['code'], -26)
|
||||
else:
|
||||
assert(False)
|
||||
|
||||
def test_new_unconfirmed_inputs(self):
|
||||
"""Replacements that add new unconfirmed inputs are rejected"""
|
||||
confirmed_utxo = make_utxo(self.nodes[0], int(1.1*COIN))
|
||||
unconfirmed_utxo = make_utxo(self.nodes[0], int(0.1*COIN), False)
|
||||
|
||||
tx1 = CTransaction()
|
||||
tx1.vin = [CTxIn(confirmed_utxo)]
|
||||
tx1.vout = [CTxOut(1*COIN, CScript([b'a']))]
|
||||
tx1_hex = txToHex(tx1)
|
||||
tx1_txid = self.nodes[0].sendrawtransaction(tx1_hex, True)
|
||||
|
||||
tx2 = CTransaction()
|
||||
tx2.vin = [CTxIn(confirmed_utxo), CTxIn(unconfirmed_utxo)]
|
||||
tx2.vout = tx1.vout
|
||||
tx2_hex = txToHex(tx2)
|
||||
|
||||
try:
|
||||
tx2_txid = self.nodes[0].sendrawtransaction(tx2_hex, True)
|
||||
except JSONRPCException as exp:
|
||||
assert_equal(exp.error['code'], -26)
|
||||
else:
|
||||
assert(False)
|
||||
|
||||
def test_too_many_replacements(self):
|
||||
"""Replacements that evict too many transactions are rejected"""
|
||||
# Try directly replacing more than MAX_REPLACEMENT_LIMIT
|
||||
# transactions
|
||||
|
||||
# Start by creating a single transaction with many outputs
|
||||
initial_nValue = 10*COIN
|
||||
utxo = make_utxo(self.nodes[0], initial_nValue)
|
||||
fee = int(0.0001*COIN)
|
||||
split_value = int((initial_nValue-fee)/(MAX_REPLACEMENT_LIMIT+1))
|
||||
|
||||
outputs = []
|
||||
for i in range(MAX_REPLACEMENT_LIMIT+1):
|
||||
outputs.append(CTxOut(split_value, CScript([1])))
|
||||
|
||||
splitting_tx = CTransaction()
|
||||
splitting_tx.vin = [CTxIn(utxo, nSequence=0)]
|
||||
splitting_tx.vout = outputs
|
||||
splitting_tx_hex = txToHex(splitting_tx)
|
||||
|
||||
txid = self.nodes[0].sendrawtransaction(splitting_tx_hex, True)
|
||||
txid = int(txid, 16)
|
||||
|
||||
# Now spend each of those outputs individually
|
||||
for i in range(MAX_REPLACEMENT_LIMIT+1):
|
||||
tx_i = CTransaction()
|
||||
tx_i.vin = [CTxIn(COutPoint(txid, i), nSequence=0)]
|
||||
tx_i.vout = [CTxOut(split_value-fee, CScript([b'a']))]
|
||||
tx_i_hex = txToHex(tx_i)
|
||||
self.nodes[0].sendrawtransaction(tx_i_hex, True)
|
||||
|
||||
# Now create doublespend of the whole lot; should fail.
|
||||
# Need a big enough fee to cover all spending transactions and have
|
||||
# a higher fee rate
|
||||
double_spend_value = (split_value-100*fee)*(MAX_REPLACEMENT_LIMIT+1)
|
||||
inputs = []
|
||||
for i in range(MAX_REPLACEMENT_LIMIT+1):
|
||||
inputs.append(CTxIn(COutPoint(txid, i), nSequence=0))
|
||||
double_tx = CTransaction()
|
||||
double_tx.vin = inputs
|
||||
double_tx.vout = [CTxOut(double_spend_value, CScript([b'a']))]
|
||||
double_tx_hex = txToHex(double_tx)
|
||||
|
||||
try:
|
||||
self.nodes[0].sendrawtransaction(double_tx_hex, True)
|
||||
except JSONRPCException as exp:
|
||||
assert_equal(exp.error['code'], -26)
|
||||
assert_equal("too many potential replacements" in exp.error['message'], True)
|
||||
else:
|
||||
assert(False)
|
||||
|
||||
# If we remove an input, it should pass
|
||||
double_tx = CTransaction()
|
||||
double_tx.vin = inputs[0:-1]
|
||||
double_tx.vout = [CTxOut(double_spend_value, CScript([b'a']))]
|
||||
double_tx_hex = txToHex(double_tx)
|
||||
self.nodes[0].sendrawtransaction(double_tx_hex, True)
|
||||
|
||||
def test_opt_in(self):
|
||||
""" Replacing should only work if orig tx opted in """
|
||||
tx0_outpoint = make_utxo(self.nodes[0], int(1.1*COIN))
|
||||
|
||||
# Create a non-opting in transaction
|
||||
tx1a = CTransaction()
|
||||
tx1a.vin = [CTxIn(tx0_outpoint, nSequence=0xffffffff)]
|
||||
tx1a.vout = [CTxOut(1*COIN, CScript([b'a']))]
|
||||
tx1a_hex = txToHex(tx1a)
|
||||
tx1a_txid = self.nodes[0].sendrawtransaction(tx1a_hex, True)
|
||||
|
||||
# Shouldn't be able to double-spend
|
||||
tx1b = CTransaction()
|
||||
tx1b.vin = [CTxIn(tx0_outpoint, nSequence=0)]
|
||||
tx1b.vout = [CTxOut(int(0.9*COIN), CScript([b'b']))]
|
||||
tx1b_hex = txToHex(tx1b)
|
||||
|
||||
try:
|
||||
tx1b_txid = self.nodes[0].sendrawtransaction(tx1b_hex, True)
|
||||
except JSONRPCException as exp:
|
||||
assert_equal(exp.error['code'], -26)
|
||||
else:
|
||||
print(tx1b_txid)
|
||||
assert(False)
|
||||
|
||||
tx1_outpoint = make_utxo(self.nodes[0], int(1.1*COIN))
|
||||
|
||||
# Create a different non-opting in transaction
|
||||
tx2a = CTransaction()
|
||||
tx2a.vin = [CTxIn(tx1_outpoint, nSequence=0xfffffffe)]
|
||||
tx2a.vout = [CTxOut(1*COIN, CScript([b'a']))]
|
||||
tx2a_hex = txToHex(tx2a)
|
||||
tx2a_txid = self.nodes[0].sendrawtransaction(tx2a_hex, True)
|
||||
|
||||
# Still shouldn't be able to double-spend
|
||||
tx2b = CTransaction()
|
||||
tx2b.vin = [CTxIn(tx1_outpoint, nSequence=0)]
|
||||
tx2b.vout = [CTxOut(int(0.9*COIN), CScript([b'b']))]
|
||||
tx2b_hex = txToHex(tx2b)
|
||||
|
||||
try:
|
||||
tx2b_txid = self.nodes[0].sendrawtransaction(tx2b_hex, True)
|
||||
except JSONRPCException as exp:
|
||||
assert_equal(exp.error['code'], -26)
|
||||
else:
|
||||
assert(False)
|
||||
|
||||
# Now create a new transaction that spends from tx1a and tx2a
|
||||
# opt-in on one of the inputs
|
||||
# Transaction should be replaceable on either input
|
||||
|
||||
tx1a_txid = int(tx1a_txid, 16)
|
||||
tx2a_txid = int(tx2a_txid, 16)
|
||||
|
||||
tx3a = CTransaction()
|
||||
tx3a.vin = [CTxIn(COutPoint(tx1a_txid, 0), nSequence=0xffffffff),
|
||||
CTxIn(COutPoint(tx2a_txid, 0), nSequence=0xfffffffd)]
|
||||
tx3a.vout = [CTxOut(int(0.9*COIN), CScript([b'c'])), CTxOut(int(0.9*COIN), CScript([b'd']))]
|
||||
tx3a_hex = txToHex(tx3a)
|
||||
|
||||
self.nodes[0].sendrawtransaction(tx3a_hex, True)
|
||||
|
||||
tx3b = CTransaction()
|
||||
tx3b.vin = [CTxIn(COutPoint(tx1a_txid, 0), nSequence=0)]
|
||||
tx3b.vout = [CTxOut(int(0.5*COIN), CScript([b'e']))]
|
||||
tx3b_hex = txToHex(tx3b)
|
||||
|
||||
tx3c = CTransaction()
|
||||
tx3c.vin = [CTxIn(COutPoint(tx2a_txid, 0), nSequence=0)]
|
||||
tx3c.vout = [CTxOut(int(0.5*COIN), CScript([b'f']))]
|
||||
tx3c_hex = txToHex(tx3c)
|
||||
|
||||
self.nodes[0].sendrawtransaction(tx3b_hex, True)
|
||||
# If tx3b was accepted, tx3c won't look like a replacement,
|
||||
# but make sure it is accepted anyway
|
||||
self.nodes[0].sendrawtransaction(tx3c_hex, True)
|
||||
|
||||
def test_prioritised_transactions(self):
|
||||
# Ensure that fee deltas used via prioritisetransaction are
|
||||
# correctly used by replacement logic
|
||||
|
||||
# 1. Check that feeperkb uses modified fees
|
||||
tx0_outpoint = make_utxo(self.nodes[0], int(1.1*COIN))
|
||||
|
||||
tx1a = CTransaction()
|
||||
tx1a.vin = [CTxIn(tx0_outpoint, nSequence=0)]
|
||||
tx1a.vout = [CTxOut(1*COIN, CScript([b'a']))]
|
||||
tx1a_hex = txToHex(tx1a)
|
||||
tx1a_txid = self.nodes[0].sendrawtransaction(tx1a_hex, True)
|
||||
|
||||
# Higher fee, but the actual fee per KB is much lower.
|
||||
tx1b = CTransaction()
|
||||
tx1b.vin = [CTxIn(tx0_outpoint, nSequence=0)]
|
||||
tx1b.vout = [CTxOut(int(0.001*COIN), CScript([b'a'*740000]))]
|
||||
tx1b_hex = txToHex(tx1b)
|
||||
|
||||
# Verify tx1b cannot replace tx1a.
|
||||
try:
|
||||
tx1b_txid = self.nodes[0].sendrawtransaction(tx1b_hex, True)
|
||||
except JSONRPCException as exp:
|
||||
assert_equal(exp.error['code'], -26)
|
||||
else:
|
||||
assert(False)
|
||||
|
||||
# Use prioritisetransaction to set tx1a's fee to 0.
|
||||
self.nodes[0].prioritisetransaction(tx1a_txid, 0, int(-0.1*COIN))
|
||||
|
||||
# Now tx1b should be able to replace tx1a
|
||||
tx1b_txid = self.nodes[0].sendrawtransaction(tx1b_hex, True)
|
||||
|
||||
assert(tx1b_txid in self.nodes[0].getrawmempool())
|
||||
|
||||
# 2. Check that absolute fee checks use modified fee.
|
||||
tx1_outpoint = make_utxo(self.nodes[0], int(1.1*COIN))
|
||||
|
||||
tx2a = CTransaction()
|
||||
tx2a.vin = [CTxIn(tx1_outpoint, nSequence=0)]
|
||||
tx2a.vout = [CTxOut(1*COIN, CScript([b'a']))]
|
||||
tx2a_hex = txToHex(tx2a)
|
||||
tx2a_txid = self.nodes[0].sendrawtransaction(tx2a_hex, True)
|
||||
|
||||
# Lower fee, but we'll prioritise it
|
||||
tx2b = CTransaction()
|
||||
tx2b.vin = [CTxIn(tx1_outpoint, nSequence=0)]
|
||||
tx2b.vout = [CTxOut(int(1.01*COIN), CScript([b'a']))]
|
||||
tx2b.rehash()
|
||||
tx2b_hex = txToHex(tx2b)
|
||||
|
||||
# Verify tx2b cannot replace tx2a.
|
||||
try:
|
||||
tx2b_txid = self.nodes[0].sendrawtransaction(tx2b_hex, True)
|
||||
except JSONRPCException as exp:
|
||||
assert_equal(exp.error['code'], -26)
|
||||
else:
|
||||
assert(False)
|
||||
|
||||
# Now prioritise tx2b to have a higher modified fee
|
||||
self.nodes[0].prioritisetransaction(tx2b.hash, 0, int(0.1*COIN))
|
||||
|
||||
# tx2b should now be accepted
|
||||
tx2b_txid = self.nodes[0].sendrawtransaction(tx2b_hex, True)
|
||||
|
||||
assert(tx2b_txid in self.nodes[0].getrawmempool())
|
||||
|
||||
if __name__ == '__main__':
|
||||
ReplaceByFeeTest().main()
|
@ -154,7 +154,6 @@ BITCOIN_CORE_H = \
|
||||
noui.h \
|
||||
policy/fees.h \
|
||||
policy/policy.h \
|
||||
policy/rbf.h \
|
||||
pow.h \
|
||||
protocol.h \
|
||||
random.h \
|
||||
@ -304,7 +303,6 @@ libdash_wallet_a_SOURCES = \
|
||||
wallet/rpcwallet.cpp \
|
||||
wallet/wallet.cpp \
|
||||
wallet/walletdb.cpp \
|
||||
policy/rbf.cpp \
|
||||
$(BITCOIN_CORE_H)
|
||||
|
||||
# crypto primitives library
|
||||
|
10
src/init.cpp
10
src/init.cpp
@ -602,7 +602,6 @@ std::string HelpMessage(HelpMessageMode mode)
|
||||
strUsage += HelpMessageOpt("-bytespersigop", strprintf(_("Minimum bytes per sigop in transactions we relay and mine (default: %u)"), DEFAULT_BYTES_PER_SIGOP));
|
||||
strUsage += HelpMessageOpt("-datacarrier", strprintf(_("Relay and mine data carrier transactions (default: %u)"), DEFAULT_ACCEPT_DATACARRIER));
|
||||
strUsage += HelpMessageOpt("-datacarriersize", strprintf(_("Maximum size of data in data carrier transactions we relay and mine (default: %u)"), MAX_OP_RETURN_RELAY));
|
||||
strUsage += HelpMessageOpt("-mempoolreplacement", strprintf(_("Enable transaction replacement in the memory pool (default: %u)"), DEFAULT_ENABLE_REPLACEMENT));
|
||||
|
||||
strUsage += HelpMessageGroup(_("Block creation options:"));
|
||||
strUsage += HelpMessageOpt("-blockmaxsize=<n>", strprintf(_("Set maximum block size in bytes (default: %d)"), DEFAULT_BLOCK_MAX_SIZE));
|
||||
@ -1258,15 +1257,6 @@ bool AppInitParameterInteraction()
|
||||
|
||||
nMaxTipAge = GetArg("-maxtipage", DEFAULT_MAX_TIP_AGE);
|
||||
|
||||
fEnableReplacement = GetBoolArg("-mempoolreplacement", DEFAULT_ENABLE_REPLACEMENT);
|
||||
if ((!fEnableReplacement) && IsArgSet("-mempoolreplacement")) {
|
||||
// Minimal effort at forwards compatibility
|
||||
std::string strReplacementModeList = GetArg("-mempoolreplacement", ""); // default is impossible
|
||||
std::vector<std::string> vstrReplacementModes;
|
||||
boost::split(vstrReplacementModes, strReplacementModeList, boost::is_any_of(","));
|
||||
fEnableReplacement = (std::find(vstrReplacementModes.begin(), vstrReplacementModes.end(), "fee") != vstrReplacementModes.end());
|
||||
}
|
||||
|
||||
if (mapMultiArgs.count("-bip9params")) {
|
||||
// Allow overriding BIP9 parameters for testing
|
||||
if (!chainparams.MineBlocksOnDemand()) {
|
||||
|
@ -2005,9 +2005,7 @@ bool static ProcessMessage(CNode* pfrom, const std::string& strCommand, CDataStr
|
||||
|
||||
mapAlreadyAskedFor.erase(inv.hash);
|
||||
|
||||
std::list<CTransactionRef> lRemovedTxn;
|
||||
|
||||
if (!AlreadyHave(inv) && AcceptToMemoryPool(mempool, state, ptx, true, &fMissingInputs, &lRemovedTxn)) {
|
||||
if (!AlreadyHave(inv) && AcceptToMemoryPool(mempool, state, ptx, true, &fMissingInputs)) {
|
||||
// Process custom txes, this changes AlreadyHave to "true"
|
||||
if (strCommand == NetMsgType::DSTX) {
|
||||
LogPrintf("DSTX -- Masternode transaction accepted, txid=%s, peer=%d\n",
|
||||
@ -2057,7 +2055,7 @@ bool static ProcessMessage(CNode* pfrom, const std::string& strCommand, CDataStr
|
||||
|
||||
if (setMisbehaving.count(fromPeer))
|
||||
continue;
|
||||
if (AcceptToMemoryPool(mempool, stateDummy, porphanTx, true, &fMissingInputs2, &lRemovedTxn)) {
|
||||
if (AcceptToMemoryPool(mempool, stateDummy, porphanTx, true, &fMissingInputs2)) {
|
||||
LogPrint("mempool", " accepted orphan tx %s\n", orphanHash.ToString());
|
||||
connman.RelayTransaction(orphanTx);
|
||||
for (unsigned int i = 0; i < orphanTx.vout.size(); i++) {
|
||||
@ -2160,9 +2158,6 @@ bool static ProcessMessage(CNode* pfrom, const std::string& strCommand, CDataStr
|
||||
}
|
||||
}
|
||||
|
||||
for (const CTransactionRef& removedTx : lRemovedTxn)
|
||||
AddToCompactExtraTransactions(removedTx);
|
||||
|
||||
int nDoS = 0;
|
||||
if (state.IsInvalid(nDoS))
|
||||
{
|
||||
|
@ -1,47 +0,0 @@
|
||||
// Copyright (c) 2016 The Bitcoin Core developers
|
||||
// Distributed under the MIT software license, see the accompanying
|
||||
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
|
||||
|
||||
#include "policy/rbf.h"
|
||||
|
||||
bool SignalsOptInRBF(const CTransaction &tx)
|
||||
{
|
||||
BOOST_FOREACH(const CTxIn &txin, tx.vin) {
|
||||
if (txin.nSequence < std::numeric_limits<unsigned int>::max()-1) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
RBFTransactionState IsRBFOptIn(const CTransaction &tx, CTxMemPool &pool)
|
||||
{
|
||||
AssertLockHeld(pool.cs);
|
||||
|
||||
CTxMemPool::setEntries setAncestors;
|
||||
|
||||
// First check the transaction itself.
|
||||
if (SignalsOptInRBF(tx)) {
|
||||
return RBF_TRANSACTIONSTATE_REPLACEABLE_BIP125;
|
||||
}
|
||||
|
||||
// If this transaction is not in our mempool, then we can't be sure
|
||||
// we will know about all its inputs.
|
||||
if (!pool.exists(tx.GetHash())) {
|
||||
return RBF_TRANSACTIONSTATE_UNKNOWN;
|
||||
}
|
||||
|
||||
// If all the inputs have nSequence >= maxint-1, it still might be
|
||||
// signaled for RBF if any unconfirmed parents have signaled.
|
||||
uint64_t noLimit = std::numeric_limits<uint64_t>::max();
|
||||
std::string dummy;
|
||||
CTxMemPoolEntry entry = *pool.mapTx.find(tx.GetHash());
|
||||
pool.CalculateMemPoolAncestors(entry, setAncestors, noLimit, noLimit, noLimit, noLimit, dummy, false);
|
||||
|
||||
BOOST_FOREACH(CTxMemPool::txiter it, setAncestors) {
|
||||
if (SignalsOptInRBF(it->GetTx())) {
|
||||
return RBF_TRANSACTIONSTATE_REPLACEABLE_BIP125;
|
||||
}
|
||||
}
|
||||
return RBF_TRANSACTIONSTATE_FINAL;
|
||||
}
|
@ -1,26 +0,0 @@
|
||||
// Copyright (c) 2016 The Bitcoin Core developers
|
||||
// Distributed under the MIT software license, see the accompanying
|
||||
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
|
||||
|
||||
#ifndef BITCOIN_POLICY_RBF_H
|
||||
#define BITCOIN_POLICY_RBF_H
|
||||
|
||||
#include "txmempool.h"
|
||||
|
||||
enum RBFTransactionState {
|
||||
RBF_TRANSACTIONSTATE_UNKNOWN,
|
||||
RBF_TRANSACTIONSTATE_REPLACEABLE_BIP125,
|
||||
RBF_TRANSACTIONSTATE_FINAL
|
||||
};
|
||||
|
||||
// Check whether the sequence numbers on this transaction are signaling
|
||||
// opt-in to replace-by-fee, according to BIP 125
|
||||
bool SignalsOptInRBF(const CTransaction &tx);
|
||||
|
||||
// Determine whether an in-mempool transaction is signaling opt-in to RBF
|
||||
// according to BIP 125
|
||||
// This involves checking sequence numbers of the transaction, as well
|
||||
// as the sequence numbers of all in-mempool ancestors.
|
||||
RBFTransactionState IsRBFOptIn(const CTransaction &tx, CTxMemPool &pool);
|
||||
|
||||
#endif // BITCOIN_POLICY_RBF_H
|
@ -335,7 +335,7 @@ void CPrivateSendServer::CommitFinalTransaction(CConnman& connman)
|
||||
TRY_LOCK(cs_main, lockMain);
|
||||
CValidationState validationState;
|
||||
mempool.PrioritiseTransaction(hashTx, hashTx.ToString(), 1000, 0.1*COIN);
|
||||
if(!lockMain || !AcceptToMemoryPool(mempool, validationState, finalTransaction, false, NULL, NULL, false, maxTxFee, true))
|
||||
if(!lockMain || !AcceptToMemoryPool(mempool, validationState, finalTransaction, false, NULL, false, maxTxFee, true))
|
||||
{
|
||||
LogPrintf("CPrivateSendServer::CommitFinalTransaction -- AcceptToMemoryPool() error: Transaction not valid\n");
|
||||
SetNull();
|
||||
@ -437,7 +437,7 @@ void CPrivateSendServer::ChargeFees(CConnman& connman)
|
||||
LOCK(cs_main);
|
||||
|
||||
CValidationState state;
|
||||
if(!AcceptToMemoryPool(mempool, state, vecOffendersCollaterals[0], false, NULL, NULL, false, maxTxFee)) {
|
||||
if(!AcceptToMemoryPool(mempool, state, vecOffendersCollaterals[0], false, NULL, false, maxTxFee)) {
|
||||
// should never really happen
|
||||
LogPrintf("CPrivateSendServer::ChargeFees -- ERROR: AcceptToMemoryPool failed!\n");
|
||||
} else {
|
||||
@ -469,7 +469,7 @@ void CPrivateSendServer::ChargeRandomFees(CConnman& connman)
|
||||
LogPrintf("CPrivateSendServer::ChargeRandomFees -- charging random fees, txCollateral=%s", txCollateral->ToString());
|
||||
|
||||
CValidationState state;
|
||||
if(!AcceptToMemoryPool(mempool, state, txCollateral, false, NULL, NULL, false, maxTxFee)) {
|
||||
if(!AcceptToMemoryPool(mempool, state, txCollateral, false, NULL, false, maxTxFee)) {
|
||||
// should never really happen
|
||||
LogPrintf("CPrivateSendServer::ChargeRandomFees -- ERROR: AcceptToMemoryPool failed!\n");
|
||||
} else {
|
||||
|
@ -314,7 +314,7 @@ bool CPrivateSend::IsCollateralValid(const CTransaction& txCollateral)
|
||||
{
|
||||
LOCK(cs_main);
|
||||
CValidationState validationState;
|
||||
if(!AcceptToMemoryPool(mempool, validationState, MakeTransactionRef(txCollateral), false, NULL, NULL, false, maxTxFee, true)) {
|
||||
if(!AcceptToMemoryPool(mempool, validationState, MakeTransactionRef(txCollateral), false, NULL, false, maxTxFee, true)) {
|
||||
LogPrint("privatesend", "CPrivateSend::IsCollateralValid -- didn't pass AcceptToMemoryPool()\n");
|
||||
return false;
|
||||
}
|
||||
|
@ -984,7 +984,7 @@ UniValue sendrawtransaction(const JSONRPCRequest& request)
|
||||
}
|
||||
CValidationState state;
|
||||
bool fMissingInputs;
|
||||
if (!AcceptToMemoryPool(mempool, state, std::move(tx), !fBypassLimits, &fMissingInputs, NULL, false, nMaxRawTxFee)) {
|
||||
if (!AcceptToMemoryPool(mempool, state, std::move(tx), !fBypassLimits, &fMissingInputs, false, nMaxRawTxFee)) {
|
||||
if (state.IsInvalid()) {
|
||||
throw JSONRPCError(RPC_TRANSACTION_REJECTED, strprintf("%i: %s", state.GetRejectCode(), state.GetRejectReason()));
|
||||
} else {
|
||||
|
@ -23,7 +23,7 @@ ToMemPool(CMutableTransaction& tx)
|
||||
LOCK(cs_main);
|
||||
|
||||
CValidationState state;
|
||||
return AcceptToMemoryPool(mempool, state, MakeTransactionRef(tx), false, NULL, NULL, true, 0);
|
||||
return AcceptToMemoryPool(mempool, state, MakeTransactionRef(tx), false, NULL, true, 0);
|
||||
}
|
||||
|
||||
BOOST_FIXTURE_TEST_CASE(tx_mempool_block_doublespend, TestChain100Setup)
|
||||
|
@ -348,7 +348,6 @@ enum class MemPoolRemovalReason {
|
||||
REORG, //! Removed for reorganization
|
||||
BLOCK, //! Removed for block
|
||||
CONFLICT, //! Removed for conflict with in-block transaction
|
||||
REPLACED //! Removed for replacement
|
||||
};
|
||||
|
||||
class SaltedTxidHasher
|
||||
@ -374,8 +373,7 @@ public:
|
||||
* example, the following new transactions will not be added to the mempool:
|
||||
* - a transaction which doesn't meet the minimum fee requirements.
|
||||
* - a new transaction that double-spends an input of a transaction already in
|
||||
* the pool where the new transaction does not meet the Replace-By-Fee
|
||||
* requirements as defined in BIP 125.
|
||||
* the pool.
|
||||
* - a non-standard transaction.
|
||||
*
|
||||
* CTxMemPool::mapTx, and CTxMemPoolEntry bookkeeping:
|
||||
|
@ -91,7 +91,6 @@ size_t nCoinCacheUsage = 5000 * 300;
|
||||
uint64_t nPruneTarget = 0;
|
||||
bool fAlerts = DEFAULT_ALERTS;
|
||||
int64_t nMaxTipAge = DEFAULT_MAX_TIP_AGE;
|
||||
bool fEnableReplacement = DEFAULT_ENABLE_REPLACEMENT;
|
||||
|
||||
std::atomic<bool> fDIP0001ActiveAtTip{false};
|
||||
std::atomic<bool> fDIP0003ActiveAtTip{false};
|
||||
@ -631,9 +630,8 @@ static bool IsCurrentForFeeEstimation()
|
||||
}
|
||||
|
||||
bool AcceptToMemoryPoolWorker(CTxMemPool& pool, CValidationState& state, const CTransactionRef& ptx, bool fLimitFree,
|
||||
bool* pfMissingInputs, int64_t nAcceptTime, std::list<CTransactionRef>* plTxnReplaced,
|
||||
bool fOverrideMempoolLimit, const CAmount& nAbsurdFee,
|
||||
std::vector<COutPoint>& coins_to_uncache, bool fDryRun)
|
||||
bool* pfMissingInputs, int64_t nAcceptTime, bool fOverrideMempoolLimit,
|
||||
const CAmount& nAbsurdFee, std::vector<COutPoint>& coins_to_uncache, bool fDryRun)
|
||||
{
|
||||
const CTransaction& tx = *ptx;
|
||||
const uint256 hash = tx.GetHash();
|
||||
@ -689,7 +687,6 @@ bool AcceptToMemoryPoolWorker(CTxMemPool& pool, CValidationState& state, const C
|
||||
}
|
||||
|
||||
// Check for conflicts with in-memory transactions
|
||||
std::set<uint256> setConflicts;
|
||||
{
|
||||
LOCK(pool.cs); // protect pool.mapNextTx
|
||||
BOOST_FOREACH(const CTxIn &txin, tx.vin)
|
||||
@ -698,49 +695,21 @@ bool AcceptToMemoryPoolWorker(CTxMemPool& pool, CValidationState& state, const C
|
||||
if (itConflicting != pool.mapNextTx.end())
|
||||
{
|
||||
const CTransaction *ptxConflicting = itConflicting->second;
|
||||
if (!setConflicts.count(ptxConflicting->GetHash()))
|
||||
{
|
||||
// InstantSend txes are not replacable
|
||||
if(instantsend.HasTxLockRequest(ptxConflicting->GetHash())) {
|
||||
// this tx conflicts with a Transaction Lock Request candidate
|
||||
return state.DoS(0, error("AcceptToMemoryPool : Transaction %s conflicts with Transaction Lock Request %s",
|
||||
hash.ToString(), ptxConflicting->GetHash().ToString()),
|
||||
REJECT_INVALID, "tx-txlockreq-mempool-conflict");
|
||||
} else if (instantsend.HasTxLockRequest(hash)) {
|
||||
// this tx is a tx lock request and it conflicts with a normal tx
|
||||
return state.DoS(0, error("AcceptToMemoryPool : Transaction Lock Request %s conflicts with transaction %s",
|
||||
hash.ToString(), ptxConflicting->GetHash().ToString()),
|
||||
REJECT_INVALID, "txlockreq-tx-mempool-conflict");
|
||||
}
|
||||
// Allow opt-out of transaction replacement by setting
|
||||
// nSequence >= maxint-1 on all inputs.
|
||||
//
|
||||
// maxint-1 is picked to still allow use of nLockTime by
|
||||
// non-replaceable transactions. All inputs rather than just one
|
||||
// is for the sake of multi-party protocols, where we don't
|
||||
// want a single party to be able to disable replacement.
|
||||
//
|
||||
// The opt-out ignores descendants as anyone relying on
|
||||
// first-seen mempool behavior should be checking all
|
||||
// unconfirmed ancestors anyway; doing otherwise is hopelessly
|
||||
// insecure.
|
||||
bool fReplacementOptOut = true;
|
||||
if (fEnableReplacement)
|
||||
{
|
||||
BOOST_FOREACH(const CTxIn &_txin, ptxConflicting->vin)
|
||||
{
|
||||
if (_txin.nSequence < std::numeric_limits<unsigned int>::max()-1)
|
||||
{
|
||||
fReplacementOptOut = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (fReplacementOptOut)
|
||||
return state.Invalid(false, REJECT_CONFLICT, "txn-mempool-conflict");
|
||||
|
||||
setConflicts.insert(ptxConflicting->GetHash());
|
||||
|
||||
// InstantSend txes are not replacable
|
||||
if(instantsend.HasTxLockRequest(ptxConflicting->GetHash())) {
|
||||
// this tx conflicts with a Transaction Lock Request candidate
|
||||
return state.DoS(0, error("AcceptToMemoryPool : Transaction %s conflicts with Transaction Lock Request %s",
|
||||
hash.ToString(), ptxConflicting->GetHash().ToString()),
|
||||
REJECT_INVALID, "tx-txlockreq-mempool-conflict");
|
||||
} else if (instantsend.HasTxLockRequest(hash)) {
|
||||
// this tx is a tx lock request and it conflicts with a normal tx
|
||||
return state.DoS(0, error("AcceptToMemoryPool : Transaction Lock Request %s conflicts with transaction %s",
|
||||
hash.ToString(), ptxConflicting->GetHash().ToString()),
|
||||
REJECT_INVALID, "txlockreq-tx-mempool-conflict");
|
||||
}
|
||||
// Transaction conflicts with mempool and RBF doesn't exist in Dash
|
||||
return state.Invalid(false, REJECT_CONFLICT, "txn-mempool-conflict");
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -886,150 +855,6 @@ bool AcceptToMemoryPoolWorker(CTxMemPool& pool, CValidationState& state, const C
|
||||
return state.DoS(0, false, REJECT_NONSTANDARD, "too-long-mempool-chain", false, errString);
|
||||
}
|
||||
|
||||
// A transaction that spends outputs that would be replaced by it is invalid. Now
|
||||
// that we have the set of all ancestors we can detect this
|
||||
// pathological case by making sure setConflicts and setAncestors don't
|
||||
// intersect.
|
||||
BOOST_FOREACH(CTxMemPool::txiter ancestorIt, setAncestors)
|
||||
{
|
||||
const uint256 &hashAncestor = ancestorIt->GetTx().GetHash();
|
||||
if (setConflicts.count(hashAncestor))
|
||||
{
|
||||
return state.DoS(10, false,
|
||||
REJECT_INVALID, "bad-txns-spends-conflicting-tx", false,
|
||||
strprintf("%s spends conflicting transaction %s",
|
||||
hash.ToString(),
|
||||
hashAncestor.ToString()));
|
||||
}
|
||||
}
|
||||
|
||||
// Check if it's economically rational to mine this transaction rather
|
||||
// than the ones it replaces.
|
||||
CAmount nConflictingFees = 0;
|
||||
size_t nConflictingSize = 0;
|
||||
uint64_t nConflictingCount = 0;
|
||||
CTxMemPool::setEntries allConflicting;
|
||||
|
||||
// If we don't hold the lock allConflicting might be incomplete; the
|
||||
// subsequent RemoveStaged() and addUnchecked() calls don't guarantee
|
||||
// mempool consistency for us.
|
||||
LOCK(pool.cs);
|
||||
const bool fReplacementTransaction = setConflicts.size();
|
||||
if (fReplacementTransaction)
|
||||
{
|
||||
CFeeRate newFeeRate(nModifiedFees, nSize);
|
||||
std::set<uint256> setConflictsParents;
|
||||
const int maxDescendantsToVisit = 100;
|
||||
CTxMemPool::setEntries setIterConflicting;
|
||||
BOOST_FOREACH(const uint256 &hashConflicting, setConflicts)
|
||||
{
|
||||
CTxMemPool::txiter mi = pool.mapTx.find(hashConflicting);
|
||||
if (mi == pool.mapTx.end())
|
||||
continue;
|
||||
|
||||
// Save these to avoid repeated lookups
|
||||
setIterConflicting.insert(mi);
|
||||
|
||||
// Don't allow the replacement to reduce the feerate of the
|
||||
// mempool.
|
||||
//
|
||||
// We usually don't want to accept replacements with lower
|
||||
// feerates than what they replaced as that would lower the
|
||||
// feerate of the next block. Requiring that the feerate always
|
||||
// be increased is also an easy-to-reason about way to prevent
|
||||
// DoS attacks via replacements.
|
||||
//
|
||||
// The mining code doesn't (currently) take children into
|
||||
// account (CPFP) so we only consider the feerates of
|
||||
// transactions being directly replaced, not their indirect
|
||||
// descendants. While that does mean high feerate children are
|
||||
// ignored when deciding whether or not to replace, we do
|
||||
// require the replacement to pay more overall fees too,
|
||||
// mitigating most cases.
|
||||
CFeeRate oldFeeRate(mi->GetModifiedFee(), mi->GetTxSize());
|
||||
if (newFeeRate <= oldFeeRate)
|
||||
{
|
||||
return state.DoS(0, false,
|
||||
REJECT_INSUFFICIENTFEE, "insufficient fee", false,
|
||||
strprintf("rejecting replacement %s; new feerate %s <= old feerate %s",
|
||||
hash.ToString(),
|
||||
newFeeRate.ToString(),
|
||||
oldFeeRate.ToString()));
|
||||
}
|
||||
|
||||
BOOST_FOREACH(const CTxIn &txin, mi->GetTx().vin)
|
||||
{
|
||||
setConflictsParents.insert(txin.prevout.hash);
|
||||
}
|
||||
|
||||
nConflictingCount += mi->GetCountWithDescendants();
|
||||
}
|
||||
// This potentially overestimates the number of actual descendants
|
||||
// but we just want to be conservative to avoid doing too much
|
||||
// work.
|
||||
if (nConflictingCount <= maxDescendantsToVisit) {
|
||||
// If not too many to replace, then calculate the set of
|
||||
// transactions that would have to be evicted
|
||||
BOOST_FOREACH(CTxMemPool::txiter it, setIterConflicting) {
|
||||
pool.CalculateDescendants(it, allConflicting);
|
||||
}
|
||||
BOOST_FOREACH(CTxMemPool::txiter it, allConflicting) {
|
||||
nConflictingFees += it->GetModifiedFee();
|
||||
nConflictingSize += it->GetTxSize();
|
||||
}
|
||||
} else {
|
||||
return state.DoS(0, false,
|
||||
REJECT_NONSTANDARD, "too many potential replacements", false,
|
||||
strprintf("rejecting replacement %s; too many potential replacements (%d > %d)\n",
|
||||
hash.ToString(),
|
||||
nConflictingCount,
|
||||
maxDescendantsToVisit));
|
||||
}
|
||||
|
||||
for (unsigned int j = 0; j < tx.vin.size(); j++)
|
||||
{
|
||||
// We don't want to accept replacements that require low
|
||||
// feerate junk to be mined first. Ideally we'd keep track of
|
||||
// the ancestor feerates and make the decision based on that,
|
||||
// but for now requiring all new inputs to be confirmed works.
|
||||
if (!setConflictsParents.count(tx.vin[j].prevout.hash))
|
||||
{
|
||||
// Rather than check the UTXO set - potentially expensive -
|
||||
// it's cheaper to just check if the new input refers to a
|
||||
// tx that's in the mempool.
|
||||
if (pool.mapTx.find(tx.vin[j].prevout.hash) != pool.mapTx.end())
|
||||
return state.DoS(0, false,
|
||||
REJECT_NONSTANDARD, "replacement-adds-unconfirmed", false,
|
||||
strprintf("replacement %s adds unconfirmed input, idx %d",
|
||||
hash.ToString(), j));
|
||||
}
|
||||
}
|
||||
|
||||
// The replacement must pay greater fees than the transactions it
|
||||
// replaces - if we did the bandwidth used by those conflicting
|
||||
// transactions would not be paid for.
|
||||
if (nModifiedFees < nConflictingFees)
|
||||
{
|
||||
return state.DoS(0, false,
|
||||
REJECT_INSUFFICIENTFEE, "insufficient fee", false,
|
||||
strprintf("rejecting replacement %s, less fees than conflicting txs; %s < %s",
|
||||
hash.ToString(), FormatMoney(nModifiedFees), FormatMoney(nConflictingFees)));
|
||||
}
|
||||
|
||||
// Finally in addition to paying more fees than the conflicts the
|
||||
// new transaction must pay for its own bandwidth.
|
||||
CAmount nDeltaFees = nModifiedFees - nConflictingFees;
|
||||
if (nDeltaFees < ::incrementalRelayFee.GetFee(nSize))
|
||||
{
|
||||
return state.DoS(0, false,
|
||||
REJECT_INSUFFICIENTFEE, "insufficient fee", false,
|
||||
strprintf("rejecting replacement %s, not enough additional fees to relay; %s < %s",
|
||||
hash.ToString(),
|
||||
FormatMoney(nDeltaFees),
|
||||
FormatMoney(::incrementalRelayFee.GetFee(nSize))));
|
||||
}
|
||||
}
|
||||
|
||||
// If we aren't going to actually accept it but just were verifying it, we are fine already
|
||||
if(fDryRun) return true;
|
||||
|
||||
@ -1053,25 +878,10 @@ bool AcceptToMemoryPoolWorker(CTxMemPool& pool, CValidationState& state, const C
|
||||
__func__, hash.ToString(), FormatStateMessage(state));
|
||||
}
|
||||
|
||||
// Remove conflicting transactions from the mempool
|
||||
BOOST_FOREACH(const CTxMemPool::txiter it, allConflicting)
|
||||
{
|
||||
LogPrint("mempool", "replacing tx %s with %s for %s %s additional fees, %d delta bytes\n",
|
||||
it->GetTx().GetHash().ToString(),
|
||||
hash.ToString(),
|
||||
FormatMoney(nModifiedFees - nConflictingFees),
|
||||
CURRENCY_UNIT,
|
||||
(int)nSize - (int)nConflictingSize);
|
||||
if (plTxnReplaced)
|
||||
plTxnReplaced->push_back(it->GetSharedTx());
|
||||
}
|
||||
pool.RemoveStaged(allConflicting, false, MemPoolRemovalReason::REPLACED);
|
||||
|
||||
// This transaction should only count for fee estimation if it isn't a
|
||||
// BIP 125 replacement transaction (may not be widely supported), the
|
||||
// This transaction should only count for fee estimation if the
|
||||
// node is not behind, and the transaction is not dependent on any other
|
||||
// transactions in the mempool.
|
||||
bool validForFeeEstimation = !fReplacementTransaction && IsCurrentForFeeEstimation() && pool.HasNoInputsOf(tx);
|
||||
bool validForFeeEstimation = IsCurrentForFeeEstimation() && pool.HasNoInputsOf(tx);
|
||||
|
||||
// Store transaction in memory
|
||||
pool.addUnchecked(hash, entry, setAncestors, validForFeeEstimation);
|
||||
@ -1101,11 +911,11 @@ bool AcceptToMemoryPoolWorker(CTxMemPool& pool, CValidationState& state, const C
|
||||
}
|
||||
|
||||
bool AcceptToMemoryPoolWithTime(CTxMemPool& pool, CValidationState &state, const CTransactionRef &tx, bool fLimitFree,
|
||||
bool* pfMissingInputs, int64_t nAcceptTime, std::list<CTransactionRef>* plTxnReplaced,
|
||||
bool fOverrideMempoolLimit, const CAmount nAbsurdFee, bool fDryRun)
|
||||
bool* pfMissingInputs, int64_t nAcceptTime, bool fOverrideMempoolLimit,
|
||||
const CAmount nAbsurdFee, bool fDryRun)
|
||||
{
|
||||
std::vector<COutPoint> coins_to_uncache;
|
||||
bool res = AcceptToMemoryPoolWorker(pool, state, tx, fLimitFree, pfMissingInputs, nAcceptTime, plTxnReplaced, fOverrideMempoolLimit, nAbsurdFee, coins_to_uncache, fDryRun);
|
||||
bool res = AcceptToMemoryPoolWorker(pool, state, tx, fLimitFree, pfMissingInputs, nAcceptTime, fOverrideMempoolLimit, nAbsurdFee, coins_to_uncache, fDryRun);
|
||||
if (!res || fDryRun) {
|
||||
if(!res) LogPrint("mempool", "%s: %s %s (%s)\n", __func__, tx->GetHash().ToString(), state.GetRejectReason(), state.GetDebugMessage());
|
||||
BOOST_FOREACH(const COutPoint& hashTx, coins_to_uncache)
|
||||
@ -1118,10 +928,9 @@ bool AcceptToMemoryPoolWithTime(CTxMemPool& pool, CValidationState &state, const
|
||||
}
|
||||
|
||||
bool AcceptToMemoryPool(CTxMemPool& pool, CValidationState &state, const CTransactionRef &tx, bool fLimitFree,
|
||||
bool* pfMissingInputs, std::list<CTransactionRef>* plTxnReplaced,
|
||||
bool fOverrideMempoolLimit, const CAmount nAbsurdFee, bool fDryRun)
|
||||
bool* pfMissingInputs, bool fOverrideMempoolLimit, const CAmount nAbsurdFee, bool fDryRun)
|
||||
{
|
||||
return AcceptToMemoryPoolWithTime(pool, state, tx, fLimitFree, pfMissingInputs, GetTime(), plTxnReplaced, fOverrideMempoolLimit, nAbsurdFee, fDryRun);
|
||||
return AcceptToMemoryPoolWithTime(pool, state, tx, fLimitFree, pfMissingInputs, GetTime(), fOverrideMempoolLimit, nAbsurdFee, fDryRun);
|
||||
}
|
||||
|
||||
bool GetTimestampIndex(const unsigned int &high, const unsigned int &low, std::vector<uint256> &hashes)
|
||||
@ -2666,7 +2475,7 @@ bool static DisconnectTip(CValidationState& state, const CChainParams& chainpara
|
||||
const CTransaction& tx = *it;
|
||||
// ignore validation errors in resurrected transactions
|
||||
CValidationState stateDummy;
|
||||
if (tx.IsCoinBase() || !AcceptToMemoryPool(mempool, stateDummy, it, false, NULL, NULL, true)) {
|
||||
if (tx.IsCoinBase() || !AcceptToMemoryPool(mempool, stateDummy, it, false, NULL, true)) {
|
||||
mempool.removeRecursive(tx, MemPoolRemovalReason::REORG);
|
||||
} else if (mempool.exists(tx.GetHash())) {
|
||||
vHashUpdate.push_back(tx.GetHash());
|
||||
|
@ -139,9 +139,6 @@ static const bool DEFAULT_TIMESTAMPINDEX = false;
|
||||
static const bool DEFAULT_SPENTINDEX = false;
|
||||
static const unsigned int DEFAULT_BANSCORE_THRESHOLD = 100;
|
||||
|
||||
/** Default for -mempoolreplacement */
|
||||
static const bool DEFAULT_ENABLE_REPLACEMENT = false;
|
||||
|
||||
/** Maximum number of headers to announce when relaying blocks with headers message.*/
|
||||
static const unsigned int MAX_BLOCKS_TO_ANNOUNCE = 8;
|
||||
|
||||
@ -182,7 +179,6 @@ extern CAmount maxTxFee;
|
||||
extern bool fAlerts;
|
||||
/** If the tip is older than this (in seconds), the node is considered to be in initial block download. */
|
||||
extern int64_t nMaxTipAge;
|
||||
extern bool fEnableReplacement;
|
||||
|
||||
extern bool fLargeWorkForkFound;
|
||||
extern bool fLargeWorkInvalidChainFound;
|
||||
@ -334,16 +330,15 @@ void PruneAndFlush();
|
||||
/** Prune block files up to a given height */
|
||||
void PruneBlockFilesManual(int nPruneUpToHeight);
|
||||
|
||||
/** (try to) add transaction to memory pool
|
||||
* plTxnReplaced will be appended to with all transactions replaced from mempool **/
|
||||
/** (try to) add transaction to memory pool */
|
||||
bool AcceptToMemoryPool(CTxMemPool& pool, CValidationState &state, const CTransactionRef &tx, bool fLimitFree,
|
||||
bool* pfMissingInputs, std::list<CTransactionRef>* plTxnReplaced = NULL, bool fOverrideMempoolLimit=false,
|
||||
bool* pfMissingInputs, bool fOverrideMempoolLimit=false,
|
||||
const CAmount nAbsurdFee=0, bool fDryRun=false);
|
||||
|
||||
/** (try to) add transaction to memory pool with a specified acceptance time **/
|
||||
bool AcceptToMemoryPoolWithTime(CTxMemPool& pool, CValidationState &state, const CTransactionRef &tx, bool fLimitFree,
|
||||
bool* pfMissingInputs, int64_t nAcceptTime, std::list<CTransactionRef>* plTxnReplaced = NULL,
|
||||
bool fOverrideMempoolLimit=false, const CAmount nAbsurdFee=0, bool fDryRun=false);
|
||||
bool* pfMissingInputs, int64_t nAcceptTime, bool fOverrideMempoolLimit=false,
|
||||
const CAmount nAbsurdFee=0, bool fDryRun=false);
|
||||
|
||||
bool GetUTXOCoin(const COutPoint& outpoint, Coin& coin);
|
||||
int GetUTXOHeight(const COutPoint& outpoint);
|
||||
|
@ -12,7 +12,6 @@
|
||||
#include "init.h"
|
||||
#include "instantx.h"
|
||||
#include "net.h"
|
||||
#include "policy/rbf.h"
|
||||
#include "rpc/server.h"
|
||||
#include "timedata.h"
|
||||
#include "util.h"
|
||||
@ -82,18 +81,6 @@ void WalletTxToJSON(const CWalletTx& wtx, UniValue& entry)
|
||||
entry.push_back(Pair("time", wtx.GetTxTime()));
|
||||
entry.push_back(Pair("timereceived", (int64_t)wtx.nTimeReceived));
|
||||
|
||||
// Add opt-in RBF status
|
||||
std::string rbfStatus = "no";
|
||||
if (confirms <= 0) {
|
||||
LOCK(mempool.cs);
|
||||
RBFTransactionState rbfState = IsRBFOptIn(wtx, mempool);
|
||||
if (rbfState == RBF_TRANSACTIONSTATE_UNKNOWN)
|
||||
rbfStatus = "unknown";
|
||||
else if (rbfState == RBF_TRANSACTIONSTATE_REPLACEABLE_BIP125)
|
||||
rbfStatus = "yes";
|
||||
}
|
||||
entry.push_back(Pair("bip125-replaceable", rbfStatus));
|
||||
|
||||
BOOST_FOREACH(const PAIRTYPE(std::string, std::string)& item, wtx.mapValue)
|
||||
entry.push_back(Pair(item.first, item.second));
|
||||
}
|
||||
@ -1530,8 +1517,6 @@ UniValue listtransactions(const JSONRPCRequest& request)
|
||||
" \"otheraccount\": \"accountname\", (string) DEPRECATED. For the 'move' category of transactions, the account the funds came \n"
|
||||
" from (for receiving funds, positive amounts), or went to (for sending funds,\n"
|
||||
" negative amounts).\n"
|
||||
" \"bip125-replaceable\": \"yes|no|unknown\", (string) Whether this transaction could be replaced due to BIP125 (replace-by-fee);\n"
|
||||
" may be unknown for unconfirmed transactions not in the mempool\n"
|
||||
" \"abandoned\": xxx (bool) 'true' if the transaction has been abandoned (inputs are respendable). Only available for the \n"
|
||||
" 'send' category of transactions.\n"
|
||||
" }\n"
|
||||
@ -1722,8 +1707,6 @@ UniValue listsinceblock(const JSONRPCRequest& request)
|
||||
" \"txid\": \"transactionid\", (string) The transaction id. Available for 'send' and 'receive' category of transactions.\n"
|
||||
" \"time\": xxx, (numeric) The transaction time in seconds since epoch (Jan 1 1970 GMT).\n"
|
||||
" \"timereceived\": xxx, (numeric) The time received in seconds since epoch (Jan 1 1970 GMT). Available for 'send' and 'receive' category of transactions.\n"
|
||||
" \"bip125-replaceable\": \"yes|no|unknown\", (string) Whether this transaction could be replaced due to BIP125 (replace-by-fee);\n"
|
||||
" may be unknown for unconfirmed transactions not in the mempool\n"
|
||||
" \"abandoned\": xxx, (bool) 'true' if the transaction has been abandoned (inputs are respendable). Only available for the 'send' category of transactions.\n"
|
||||
" \"comment\": \"...\", (string) If a comment is associated with the transaction.\n"
|
||||
" \"label\" : \"label\" (string) A comment for the address/transaction, if any\n"
|
||||
@ -1824,8 +1807,6 @@ UniValue gettransaction(const JSONRPCRequest& request)
|
||||
" \"txid\" : \"transactionid\", (string) The transaction id.\n"
|
||||
" \"time\" : ttt, (numeric) The transaction time in seconds since epoch (1 Jan 1970 GMT)\n"
|
||||
" \"timereceived\" : ttt, (numeric) The time received in seconds since epoch (1 Jan 1970 GMT)\n"
|
||||
" \"bip125-replaceable\": \"yes|no|unknown\", (string) Whether this transaction could be replaced due to BIP125 (replace-by-fee);\n"
|
||||
" may be unknown for unconfirmed transactions not in the mempool\n"
|
||||
" \"details\" : [\n"
|
||||
" {\n"
|
||||
" \"account\" : \"accountname\", (string) DEPRECATED. The account name involved in the transaction, can be \"\" for the default account.\n"
|
||||
|
@ -5425,5 +5425,5 @@ int CMerkleTx::GetBlocksToMaturity() const
|
||||
|
||||
bool CMerkleTx::AcceptToMemoryPool(const CAmount& nAbsurdFee, CValidationState& state)
|
||||
{
|
||||
return ::AcceptToMemoryPool(mempool, state, tx, true, NULL, NULL, false, nAbsurdFee);
|
||||
return ::AcceptToMemoryPool(mempool, state, tx, true, NULL, false, nAbsurdFee);
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user