|
| 1 | +#!/usr/bin/env python3 |
| 2 | +# Copyright (c) 2020 The DIVI developers |
| 3 | +# Distributed under the MIT/X11 software license, see the accompanying |
| 4 | +# file COPYING or http://www.opensource.org/licenses/mit-license.php. |
| 5 | + |
| 6 | +# Tests the policy changes in wallet and mempool around the |
| 7 | +# segwit-light activation time that prevent spending of |
| 8 | +# unconfirmed outputs. |
| 9 | + |
| 10 | +from test_framework import BitcoinTestFramework |
| 11 | +from authproxy import JSONRPCException |
| 12 | +from util import * |
| 13 | + |
| 14 | +ACTIVATION_TIME = 2_100_000_000 |
| 15 | + |
| 16 | +# Offset around the activation time where no restrictions are in place. |
| 17 | +NO_RESTRICTIONS = 24 * 3_600 |
| 18 | + |
| 19 | +# Offset around the activation time where the wallet disallows |
| 20 | +# spending unconfirmed change, but the mempool still accepts it. |
| 21 | +WALLET_RESTRICTED = 10 * 3_600 |
| 22 | + |
| 23 | +# Offset around the activation time where wallet and mempool |
| 24 | +# disallow spending unconfirmed outputs. |
| 25 | +MEMPOOL_RESTRICTED = 3_600 |
| 26 | + |
| 27 | + |
| 28 | +class AroundSegwitLightTest (BitcoinTestFramework): |
| 29 | + |
| 30 | + def setup_network (self, split=False): |
| 31 | + args = ["-debug", "-spendzeroconfchange"] |
| 32 | + self.nodes = start_nodes (1, self.options.tmpdir, extra_args=[args]) |
| 33 | + self.node = self.nodes[0] |
| 34 | + self.is_network_split = False |
| 35 | + |
| 36 | + def build_spending_chain (self, key, addr, initialValue): |
| 37 | + """ |
| 38 | + Spends the initialValue to addr, and then builds a follow-up |
| 39 | + transaction that spends that output again back to addr with |
| 40 | + a smaller value (for fees). The second transaction is built |
| 41 | + using the raw-transactions API and returned as hex, not |
| 42 | + submitted to the node already. |
| 43 | + """ |
| 44 | + |
| 45 | + txid = self.node.sendtoaddress (addr, initialValue) |
| 46 | + data = self.node.getrawtransaction (txid, 1) |
| 47 | + outputIndex = None |
| 48 | + for i in range (len (data["vout"])): |
| 49 | + if data["vout"][i]["value"] == initialValue: |
| 50 | + outputIndex = i |
| 51 | + break |
| 52 | + assert outputIndex is not None |
| 53 | + |
| 54 | + inputs = [{"txid": data[key], "vout": outputIndex}] |
| 55 | + outputs = {addr: initialValue - 10} |
| 56 | + tx = self.node.createrawtransaction (inputs, outputs) |
| 57 | + signed = self.node.signrawtransaction (tx) |
| 58 | + assert signed["complete"] |
| 59 | + |
| 60 | + return signed["hex"] |
| 61 | + |
| 62 | + def expect_unrestricted (self): |
| 63 | + """ |
| 64 | + Checks that spending of unconfirmed change is possible without |
| 65 | + restrictions of wallet or mempool. |
| 66 | + """ |
| 67 | + |
| 68 | + balance = self.node.getbalance () |
| 69 | + addr = self.node.getnewaddress () |
| 70 | + |
| 71 | + self.node.sendtoaddress (addr, balance - 10) |
| 72 | + self.node.sendtoaddress (addr, balance - 20) |
| 73 | + self.node.setgenerate (True, 1) |
| 74 | + |
| 75 | + def expect_wallet_restricted (self, key): |
| 76 | + """ |
| 77 | + Checks that the wallet forbids spending unconfirmed change, |
| 78 | + while the mempool still allows it. |
| 79 | + """ |
| 80 | + |
| 81 | + balance = self.node.getbalance () |
| 82 | + addr = self.node.getnewaddress () |
| 83 | + |
| 84 | + self.node.sendtoaddress (addr, balance - 10) |
| 85 | + assert_raises (JSONRPCException, self.node.sendtoaddress, addr, balance - 20) |
| 86 | + self.node.setgenerate (True, 1) |
| 87 | + |
| 88 | + balance = self.node.getbalance () |
| 89 | + tx = self.build_spending_chain (key, addr, balance - 10) |
| 90 | + self.node.sendrawtransaction (tx) |
| 91 | + self.node.setgenerate (True, 1) |
| 92 | + |
| 93 | + def expect_mempool_restricted (self, key): |
| 94 | + """ |
| 95 | + Checks that the mempool does not allow spending unconfirmed |
| 96 | + outputs (even if the transaction is built and submitted directly), |
| 97 | + while blocks should still allow it. |
| 98 | + """ |
| 99 | + |
| 100 | + balance = self.node.getbalance () |
| 101 | + addr = self.node.getnewaddress () |
| 102 | + |
| 103 | + tx = self.build_spending_chain (key, addr, balance - 10) |
| 104 | + assert_raises (JSONRPCException, self.node.sendrawtransaction, tx) |
| 105 | + self.node.generateblock ({"extratx": [tx]}) |
| 106 | + |
| 107 | + def run_test (self): |
| 108 | + self.node.setgenerate (True, 30) |
| 109 | + |
| 110 | + # Before restrictions come into force, doing a normal |
| 111 | + # spend of unconfirmed change through the wallet is fine. |
| 112 | + set_node_times (self.nodes, ACTIVATION_TIME - NO_RESTRICTIONS) |
| 113 | + self.expect_unrestricted () |
| 114 | + |
| 115 | + # Next the wallet doesn't allow those spends, but the mempool |
| 116 | + # will (if done directly). |
| 117 | + set_node_times (self.nodes, ACTIVATION_TIME - WALLET_RESTRICTED) |
| 118 | + self.expect_wallet_restricted ("txid") |
| 119 | + |
| 120 | + # Very close to the fork (on both sides), even the mempool won't |
| 121 | + # allow spending unconfirmed change. If we include it directly in |
| 122 | + # a block, it works. |
| 123 | + set_node_times (self.nodes, ACTIVATION_TIME - MEMPOOL_RESTRICTED) |
| 124 | + self.expect_mempool_restricted ("txid") |
| 125 | + set_node_times (self.nodes, ACTIVATION_TIME + MEMPOOL_RESTRICTED) |
| 126 | + self.expect_mempool_restricted ("txid") |
| 127 | + |
| 128 | + # Finally, we should run into mempool-only or no restrictions |
| 129 | + # at all if we go further into the future, away from the fork. |
| 130 | + set_node_times (self.nodes, ACTIVATION_TIME + WALLET_RESTRICTED) |
| 131 | + self.expect_wallet_restricted ("txid") |
| 132 | + set_node_times (self.nodes, ACTIVATION_TIME + NO_RESTRICTIONS) |
| 133 | + self.expect_unrestricted () |
| 134 | + |
| 135 | +if __name__ == '__main__': |
| 136 | + AroundSegwitLightTest ().main () |
0 commit comments