|
7 | 7 |
|
8 | 8 | from test_framework.test_framework import BitcoinTestFramework
|
9 | 9 | from test_framework.util import *
|
| 10 | +from test_framework.mininode import CTransaction |
| 11 | +import cStringIO |
| 12 | +import binascii |
10 | 13 |
|
| 14 | +def txFromHex(hexstring): |
| 15 | + tx = CTransaction() |
| 16 | + f = cStringIO.StringIO(binascii.unhexlify(hexstring)) |
| 17 | + tx.deserialize(f) |
| 18 | + return tx |
11 | 19 |
|
12 | 20 | def check_array_result(object_array, to_match, expected):
|
13 | 21 | """
|
@@ -103,6 +111,107 @@ def run_test(self):
|
103 | 111 | {"category":"receive","amount":Decimal("0.1")},
|
104 | 112 | {"txid":txid, "account" : "watchonly"} )
|
105 | 113 |
|
| 114 | + self.run_rbf_opt_in_test() |
| 115 | + |
| 116 | + # Check that the opt-in-rbf flag works properly, for sent and received |
| 117 | + # transactions. |
| 118 | + def run_rbf_opt_in_test(self): |
| 119 | + # Check whether a transaction signals opt-in RBF itself |
| 120 | + def is_opt_in(node, txid): |
| 121 | + rawtx = node.getrawtransaction(txid, 1) |
| 122 | + for x in rawtx["vin"]: |
| 123 | + if x["sequence"] < 0xfffffffe: |
| 124 | + return True |
| 125 | + return False |
| 126 | + |
| 127 | + # Find an unconfirmed output matching a certain txid |
| 128 | + def get_unconfirmed_utxo_entry(node, txid_to_match): |
| 129 | + utxo = node.listunspent(0, 0) |
| 130 | + for i in utxo: |
| 131 | + if i["txid"] == txid_to_match: |
| 132 | + return i |
| 133 | + return None |
| 134 | + |
| 135 | + # 1. Chain a few transactions that don't opt-in. |
| 136 | + txid_1 = self.nodes[0].sendtoaddress(self.nodes[1].getnewaddress(), 1) |
| 137 | + assert(not is_opt_in(self.nodes[0], txid_1)) |
| 138 | + check_array_result(self.nodes[0].listtransactions(), {"txid": txid_1}, {"bip125-replaceable":"no"}) |
| 139 | + sync_mempools(self.nodes) |
| 140 | + check_array_result(self.nodes[1].listtransactions(), {"txid": txid_1}, {"bip125-replaceable":"no"}) |
| 141 | + |
| 142 | + # Tx2 will build off txid_1, still not opting in to RBF. |
| 143 | + utxo_to_use = get_unconfirmed_utxo_entry(self.nodes[1], txid_1) |
| 144 | + |
| 145 | + # Create tx2 using createrawtransaction |
| 146 | + inputs = [{"txid":utxo_to_use["txid"], "vout":utxo_to_use["vout"]}] |
| 147 | + outputs = {self.nodes[0].getnewaddress(): 0.999} |
| 148 | + tx2 = self.nodes[1].createrawtransaction(inputs, outputs) |
| 149 | + tx2_signed = self.nodes[1].signrawtransaction(tx2)["hex"] |
| 150 | + txid_2 = self.nodes[1].sendrawtransaction(tx2_signed) |
| 151 | + |
| 152 | + # ...and check the result |
| 153 | + assert(not is_opt_in(self.nodes[1], txid_2)) |
| 154 | + check_array_result(self.nodes[1].listtransactions(), {"txid": txid_2}, {"bip125-replaceable":"no"}) |
| 155 | + sync_mempools(self.nodes) |
| 156 | + check_array_result(self.nodes[0].listtransactions(), {"txid": txid_2}, {"bip125-replaceable":"no"}) |
| 157 | + |
| 158 | + # Tx3 will opt-in to RBF |
| 159 | + utxo_to_use = get_unconfirmed_utxo_entry(self.nodes[0], txid_2) |
| 160 | + inputs = [{"txid": txid_2, "vout":utxo_to_use["vout"]}] |
| 161 | + outputs = {self.nodes[1].getnewaddress(): 0.998} |
| 162 | + tx3 = self.nodes[0].createrawtransaction(inputs, outputs) |
| 163 | + tx3_modified = txFromHex(tx3) |
| 164 | + tx3_modified.vin[0].nSequence = 0 |
| 165 | + tx3 = binascii.hexlify(tx3_modified.serialize()).decode('utf-8') |
| 166 | + tx3_signed = self.nodes[0].signrawtransaction(tx3)['hex'] |
| 167 | + txid_3 = self.nodes[0].sendrawtransaction(tx3_signed) |
| 168 | + |
| 169 | + assert(is_opt_in(self.nodes[0], txid_3)) |
| 170 | + check_array_result(self.nodes[0].listtransactions(), {"txid": txid_3}, {"bip125-replaceable":"yes"}) |
| 171 | + sync_mempools(self.nodes) |
| 172 | + check_array_result(self.nodes[1].listtransactions(), {"txid": txid_3}, {"bip125-replaceable":"yes"}) |
| 173 | + |
| 174 | + # Tx4 will chain off tx3. Doesn't signal itself, but depends on one |
| 175 | + # that does. |
| 176 | + utxo_to_use = get_unconfirmed_utxo_entry(self.nodes[1], txid_3) |
| 177 | + inputs = [{"txid": txid_3, "vout":utxo_to_use["vout"]}] |
| 178 | + outputs = {self.nodes[0].getnewaddress(): 0.997} |
| 179 | + tx4 = self.nodes[1].createrawtransaction(inputs, outputs) |
| 180 | + tx4_signed = self.nodes[1].signrawtransaction(tx4)["hex"] |
| 181 | + txid_4 = self.nodes[1].sendrawtransaction(tx4_signed) |
| 182 | + |
| 183 | + assert(not is_opt_in(self.nodes[1], txid_4)) |
| 184 | + check_array_result(self.nodes[1].listtransactions(), {"txid": txid_4}, {"bip125-replaceable":"yes"}) |
| 185 | + sync_mempools(self.nodes) |
| 186 | + check_array_result(self.nodes[0].listtransactions(), {"txid": txid_4}, {"bip125-replaceable":"yes"}) |
| 187 | + |
| 188 | + # Replace tx3, and check that tx4 becomes unknown |
| 189 | + tx3_b = tx3_modified |
| 190 | + tx3_b.vout[0].nValue -= 0.004*100000000 # bump the fee |
| 191 | + tx3_b = binascii.hexlify(tx3_b.serialize()).decode('utf-8') |
| 192 | + tx3_b_signed = self.nodes[0].signrawtransaction(tx3_b)['hex'] |
| 193 | + txid_3b = self.nodes[0].sendrawtransaction(tx3_b_signed, True) |
| 194 | + assert(is_opt_in(self.nodes[0], txid_3b)) |
| 195 | + |
| 196 | + check_array_result(self.nodes[0].listtransactions(), {"txid": txid_4}, {"bip125-replaceable":"unknown"}) |
| 197 | + sync_mempools(self.nodes) |
| 198 | + check_array_result(self.nodes[1].listtransactions(), {"txid": txid_4}, {"bip125-replaceable":"unknown"}) |
| 199 | + |
| 200 | + # Check gettransaction as well: |
| 201 | + for n in self.nodes[0:2]: |
| 202 | + assert_equal(n.gettransaction(txid_1)["bip125-replaceable"], "no") |
| 203 | + assert_equal(n.gettransaction(txid_2)["bip125-replaceable"], "no") |
| 204 | + assert_equal(n.gettransaction(txid_3)["bip125-replaceable"], "yes") |
| 205 | + assert_equal(n.gettransaction(txid_3b)["bip125-replaceable"], "yes") |
| 206 | + assert_equal(n.gettransaction(txid_4)["bip125-replaceable"], "unknown") |
| 207 | + |
| 208 | + # After mining a transaction, it's no longer BIP125-replaceable |
| 209 | + self.nodes[0].generate(1) |
| 210 | + assert(txid_3b not in self.nodes[0].getrawmempool()) |
| 211 | + assert_equal(self.nodes[0].gettransaction(txid_3b)["bip125-replaceable"], "no") |
| 212 | + assert_equal(self.nodes[0].gettransaction(txid_4)["bip125-replaceable"], "unknown") |
| 213 | + |
| 214 | + |
106 | 215 | if __name__ == '__main__':
|
107 | 216 | ListTransactionsTest().main()
|
108 | 217 |
|
0 commit comments