|
| 1 | +#!/usr/bin/env python3 |
| 2 | +from test_framework.address import address_to_scriptpubkey |
| 3 | +from test_framework.blocktools import COINBASE_MATURITY |
| 4 | +from test_framework.descriptors import descsum_create |
| 5 | +from test_framework.messages import COIN |
| 6 | +from test_framework.test_framework import BitcoinTestFramework |
| 7 | +from test_framework.util import assert_raises_rpc_error |
| 8 | +from test_framework.wallet import MiniWallet |
| 9 | + |
| 10 | +SILENT_PAYMENT_ADDRESS = "sprt1qqtzwfsu4f34wejks0nxwzed3zq6vh53cg2rnxj9w6ncyrmy95dxx7qnvd47fskn470t9tl4z8a8nul5k3fztquqp4fjrarl7d5lphu7rk52s4hsp" |
| 11 | + |
| 12 | + |
| 13 | +class SilentPaymentsSendingTest(BitcoinTestFramework): |
| 14 | + def set_test_params(self): |
| 15 | + self.num_nodes = 3 |
| 16 | + self.extra_args = [[], ["-txindex=1"], []] |
| 17 | + |
| 18 | + def skip_test_if_missing_module(self): |
| 19 | + self.skip_if_no_wallet() |
| 20 | + |
| 21 | + def init_wallet(self, *, node): |
| 22 | + pass |
| 23 | + |
| 24 | + def watch_only_wallet_send(self): |
| 25 | + self.log.info( |
| 26 | + "Testing if a watch only wallet is not able to send silent transaction" |
| 27 | + ) |
| 28 | + self.nodes[0].createwallet( |
| 29 | + wallet_name="watch_only_wallet", |
| 30 | + descriptors=True, |
| 31 | + disable_private_keys=True, |
| 32 | + blank=True, |
| 33 | + ) |
| 34 | + watch_only_wallet = self.nodes[0].get_wallet_rpc("watch_only_wallet") |
| 35 | + desc_import = [ |
| 36 | + { |
| 37 | + "desc": descsum_create( |
| 38 | + "wpkh(tpubD6NzVbkrYhZ4YNXVQbNhMK1WqguFsUXceaVJKbmno2aZ3B6QfbMeraaYvnBSGpV3vxLyTTK9DYT1yoEck4XUScMzXoQ2U2oSmE2JyMedq3H/0/*)" |
| 39 | + ), |
| 40 | + "timestamp": "now", |
| 41 | + "internal": False, |
| 42 | + "active": True, |
| 43 | + "keypool": True, |
| 44 | + "range": [0, 100], |
| 45 | + "watchonly": True, |
| 46 | + } |
| 47 | + ] |
| 48 | + watch_only_wallet.importdescriptors(desc_import) |
| 49 | + self.generatetoaddress( |
| 50 | + self.nodes[0], COINBASE_MATURITY + 10, watch_only_wallet.getnewaddress() |
| 51 | + ) |
| 52 | + self.log.info( |
| 53 | + "Watch-only wallets cannot send coins using silent_payment option" |
| 54 | + ) |
| 55 | + assert_raises_rpc_error( |
| 56 | + -4, |
| 57 | + "Silent payments require access to private keys to build transactions.", |
| 58 | + watch_only_wallet.sendtoaddress, |
| 59 | + SILENT_PAYMENT_ADDRESS, |
| 60 | + 21, |
| 61 | + ) |
| 62 | + |
| 63 | + def encrypted_wallet_send(self): |
| 64 | + self.log.info("Testing if encrypted SP wallet") |
| 65 | + self.nodes[0].createwallet( |
| 66 | + wallet_name="encrypted_wallet", descriptors=True, passphrase="passphrase" |
| 67 | + ) |
| 68 | + encrypted_wallet = self.nodes[0].get_wallet_rpc("encrypted_wallet") |
| 69 | + self.generatetoaddress( |
| 70 | + self.nodes[0], COINBASE_MATURITY + 10, encrypted_wallet.getnewaddress() |
| 71 | + ) |
| 72 | + self.log.info("encrypted wallets must be able to send coins after decryption") |
| 73 | + outputs = [ |
| 74 | + {"bcrt1pk0yzk76w2p55ykyjyfeq99td069c257se9nwugl7cl5geadq944spyc330": 15} |
| 75 | + ] |
| 76 | + |
| 77 | + # send RPC can be run without decrypting the wallet and it must generate a incomplete PSBT |
| 78 | + tx = encrypted_wallet.send(outputs=outputs, options={"add_to_wallet": False}) |
| 79 | + assert not tx["complete"] |
| 80 | + |
| 81 | + # but when silent_payment option is enabled, wallet must be decrypted |
| 82 | + assert_raises_rpc_error( |
| 83 | + -13, |
| 84 | + "Please enter the wallet passphrase with walletpassphrase first.", |
| 85 | + encrypted_wallet.sendtoaddress, |
| 86 | + SILENT_PAYMENT_ADDRESS, |
| 87 | + 21, |
| 88 | + ) |
| 89 | + |
| 90 | + encrypted_wallet.walletpassphrase("passphrase", 20) |
| 91 | + txid = encrypted_wallet.sendtoaddress( |
| 92 | + address=SILENT_PAYMENT_ADDRESS, |
| 93 | + amount=21, |
| 94 | + ) |
| 95 | + assert txid |
| 96 | + |
| 97 | + def test_simple_send(self): |
| 98 | + self.log.info("Testing a simple send") |
| 99 | + self.nodes[0].createwallet(wallet_name="simple_send") |
| 100 | + simple_send = self.nodes[0].get_wallet_rpc("simple_send") |
| 101 | + self.generatetoaddress( |
| 102 | + self.nodes[0], COINBASE_MATURITY + 10, simple_send.getnewaddress() |
| 103 | + ) |
| 104 | + txid = simple_send.sendtoaddress( |
| 105 | + address=SILENT_PAYMENT_ADDRESS, |
| 106 | + amount=21, |
| 107 | + ) |
| 108 | + assert txid |
| 109 | + |
| 110 | + def test_deterministic_send(self): |
| 111 | + # set up and fund the funder wallet. we will use this wallet to create UTXOs |
| 112 | + # at specific addresses in the sending wallet |
| 113 | + # using miniwallet for determinism |
| 114 | + funder = MiniWallet(self.nodes[0]) |
| 115 | + |
| 116 | + # set up the sender wallet |
| 117 | + xprv1 = "tprv8ZgxMBicQKsPevADjDCWsa6DfhkVXicu8NQUzfibwX2MexVwW4tCec5mXdCW8kJwkzBRRmAay1KZya4WsehVvjTGVW6JLqiqd8DdZ4xSg52" |
| 118 | + descriptor = { |
| 119 | + "desc": descsum_create("wpkh(" + xprv1 + "/0h/0h/*h)"), |
| 120 | + "timestamp": "now", |
| 121 | + "active": True, |
| 122 | + } |
| 123 | + self.nodes[1].createwallet("sending_wallet", descriptors=True) |
| 124 | + sender = self.nodes[1].get_wallet_rpc("sending_wallet") |
| 125 | + sender.importdescriptors([descriptor]) |
| 126 | + |
| 127 | + # fund the sender wallet with two utxos and mine a block |
| 128 | + address_one = sender.getnewaddress() |
| 129 | + address_two = sender.getnewaddress() |
| 130 | + funder.send_to( |
| 131 | + from_node=self.nodes[0], |
| 132 | + scriptPubKey=address_to_scriptpubkey(address_one), |
| 133 | + amount=10 * COIN, |
| 134 | + ) |
| 135 | + funder.send_to( |
| 136 | + from_node=self.nodes[0], |
| 137 | + scriptPubKey=address_to_scriptpubkey(address_two), |
| 138 | + amount=10 * COIN, |
| 139 | + ) |
| 140 | + self.generate(self.nodes[0], 1) |
| 141 | + |
| 142 | + # recipient address: generated with wallet seed hex `f00dbabe` |
| 143 | + silent_payment_address = "sprt1qqgste7k9hx0qftg6qmwlkqtwuy6cycyavzmzj85c6qdfhjdpdjtdgqjuexzk6murw56suy3e0rd2cgqvycxttddwsvgxe2usfpxumr70xcrdz399" |
| 144 | + amount = 15.0 |
| 145 | + txid = sender.sendtoaddress(address=silent_payment_address, amount=amount) |
| 146 | + tx = sender.getrawtransaction(txid, True) |
| 147 | + for output in tx["vout"]: |
| 148 | + if output["value"] == amount: |
| 149 | + assert ( |
| 150 | + output["scriptPubKey"]["address"] |
| 151 | + # == "bcrt1pfjcwk0este8fn8c77p78djswa2hram3m2v4nwg2jjzdkmv8299ys5sv99h" -- this is what it used to be |
| 152 | + # TODO: figure out why this changed. it shouldnt have but need to verify that the old value was in fact |
| 153 | + # the correct one |
| 154 | + == "bcrt1p3ayfzayc4zqxdkeyxfzae5qhd9aqgxrw8dz4s2pd33xtvvtfvc4q5r42c4" |
| 155 | + ) |
| 156 | + break |
| 157 | + else: |
| 158 | + assert False |
| 159 | + |
| 160 | + def test_address_reuse(self): |
| 161 | + self.nodes[0].createwallet(wallet_name="miner_wallet", descriptors=True) |
| 162 | + miner_wallet = self.nodes[0].get_wallet_rpc("miner_wallet") |
| 163 | + self.generatetoaddress( |
| 164 | + self.nodes[0], COINBASE_MATURITY + 10, miner_wallet.getnewaddress() |
| 165 | + ) |
| 166 | + |
| 167 | + self.nodes[0].createwallet(wallet_name="sender_wallet", descriptors=True) |
| 168 | + sender_wallet = self.nodes[0].get_wallet_rpc("sender_wallet") |
| 169 | + sender_address = sender_wallet.getnewaddress() |
| 170 | + |
| 171 | + miner_wallet.send(outputs=[{sender_address: 5}]) |
| 172 | + miner_wallet.send(outputs=[{sender_address: 7}]) |
| 173 | + |
| 174 | + self.generate(self.nodes[0], 8, sync_fun=self.no_op) |
| 175 | + |
| 176 | + silent_payment_address = "sprt1qqgste7k9hx0qftg6qmwlkqtwuy6cycyavzmzj85c6qdfhjdpdjtdgqjuexzk6murw56suy3e0rd2cgqvycxttddwsvgxe2usfpxumr70xcrdz399" |
| 177 | + txid1 = sender_wallet.sendtoaddress(address=silent_payment_address, amount=4) |
| 178 | + txid2 = sender_wallet.sendtoaddress(address=silent_payment_address, amount=6) |
| 179 | + |
| 180 | + output1 = "" |
| 181 | + output2 = "" |
| 182 | + for output in sender_wallet.getrawtransaction(txid1, True)["vout"]: |
| 183 | + if output["value"] == 4.0: |
| 184 | + output1 = output["scriptPubKey"]["address"] |
| 185 | + |
| 186 | + for output in sender_wallet.getrawtransaction(txid2, True)["vout"]: |
| 187 | + if output["value"] == 6.0: |
| 188 | + output2 = output["scriptPubKey"]["address"] |
| 189 | + |
| 190 | + assert output1 != output2 |
| 191 | + |
| 192 | + def run_test(self): |
| 193 | + self.watch_only_wallet_send() |
| 194 | + self.encrypted_wallet_send() |
| 195 | + self.test_simple_send() |
| 196 | + self.test_deterministic_send() |
| 197 | + self.test_address_reuse() |
| 198 | + |
| 199 | + |
| 200 | +if __name__ == "__main__": |
| 201 | + SilentPaymentsSendingTest(__file__).main() |
0 commit comments