diff options
Diffstat (limited to 'test')
-rw-r--r-- | test/functional/test_framework/util.py | 4 | ||||
-rwxr-xr-x | test/functional/test_runner.py | 2 | ||||
-rwxr-xr-x | test/functional/wallet_simulaterawtx.py | 129 |
3 files changed, 135 insertions, 0 deletions
diff --git a/test/functional/test_framework/util.py b/test/functional/test_framework/util.py index fe61ff95f8..bfc835f272 100644 --- a/test/functional/test_framework/util.py +++ b/test/functional/test_framework/util.py @@ -29,6 +29,10 @@ logger = logging.getLogger("TestFramework.utils") def assert_approx(v, vexp, vspan=0.00001): """Assert that `v` is within `vspan` of `vexp`""" + if isinstance(v, Decimal) or isinstance(vexp, Decimal): + v=Decimal(v) + vexp=Decimal(vexp) + vspan=Decimal(vspan) if v < vexp - vspan: raise AssertionError("%s < [%s..%s]" % (str(v), str(vexp - vspan), str(vexp + vspan))) if v > vexp + vspan: diff --git a/test/functional/test_runner.py b/test/functional/test_runner.py index e5784eb614..2b365d8d10 100755 --- a/test/functional/test_runner.py +++ b/test/functional/test_runner.py @@ -265,6 +265,8 @@ BASE_SCRIPTS = [ 'wallet_implicitsegwit.py --legacy-wallet', 'rpc_named_arguments.py', 'feature_startupnotify.py', + 'wallet_simulaterawtx.py --legacy-wallet', + 'wallet_simulaterawtx.py --descriptors', 'wallet_listsinceblock.py --legacy-wallet', 'wallet_listsinceblock.py --descriptors', 'wallet_listdescriptors.py --descriptors', diff --git a/test/functional/wallet_simulaterawtx.py b/test/functional/wallet_simulaterawtx.py new file mode 100755 index 0000000000..a408b99515 --- /dev/null +++ b/test/functional/wallet_simulaterawtx.py @@ -0,0 +1,129 @@ +#!/usr/bin/env python3 +# Copyright (c) 2021 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 simulaterawtransaction. +""" + +from decimal import Decimal +from test_framework.blocktools import COINBASE_MATURITY +from test_framework.test_framework import BitcoinTestFramework +from test_framework.util import ( + assert_approx, + assert_equal, + assert_raises_rpc_error, +) + +class SimulateTxTest(BitcoinTestFramework): + def set_test_params(self): + self.setup_clean_chain = True + self.num_nodes = 1 + + def skip_test_if_missing_module(self): + self.skip_if_no_wallet() + + def setup_network(self, split=False): + self.setup_nodes() + + def run_test(self): + node = self.nodes[0] + + self.generate(node, 1, sync_fun=self.no_op) # Leave IBD + + node.createwallet(wallet_name='w0') + node.createwallet(wallet_name='w1') + node.createwallet(wallet_name='w2', disable_private_keys=True) + w0 = node.get_wallet_rpc('w0') + w1 = node.get_wallet_rpc('w1') + w2 = node.get_wallet_rpc('w2') + + self.generatetoaddress(node, COINBASE_MATURITY + 1, w0.getnewaddress()) + assert_equal(w0.getbalance(), 50.0) + assert_equal(w1.getbalance(), 0.0) + + address1 = w1.getnewaddress() + address2 = w1.getnewaddress() + + # Add address1 as watch-only to w2 + w2.importpubkey(pubkey=w1.getaddressinfo(address1)["pubkey"]) + + tx1 = node.createrawtransaction([], [{address1: 5.0}]) + tx2 = node.createrawtransaction([], [{address2: 10.0}]) + + # w0 should be unaffected, w2 should see +5 for tx1 + assert_equal(w0.simulaterawtransaction([tx1])["balance_change"], 0.0) + assert_equal(w2.simulaterawtransaction([tx1])["balance_change"], 5.0) + + # w1 should see +5 balance for tx1 + assert_equal(w1.simulaterawtransaction([tx1])["balance_change"], 5.0) + + # w0 should be unaffected, w2 should see +5 for both transactions + assert_equal(w0.simulaterawtransaction([tx1, tx2])["balance_change"], 0.0) + assert_equal(w2.simulaterawtransaction([tx1, tx2])["balance_change"], 5.0) + + # w1 should see +15 balance for both transactions + assert_equal(w1.simulaterawtransaction([tx1, tx2])["balance_change"], 15.0) + + # w0 funds transaction; it should now see a decrease in (tx fee and payment), and w1 should see the same as above + funding = w0.fundrawtransaction(tx1) + tx1 = funding["hex"] + tx1changepos = funding["changepos"] + bitcoin_fee = Decimal(funding["fee"]) + + # w0 sees fee + 5 btc decrease, w2 sees + 5 btc + assert_approx(w0.simulaterawtransaction([tx1])["balance_change"], -(Decimal("5") + bitcoin_fee)) + assert_approx(w2.simulaterawtransaction([tx1])["balance_change"], Decimal("5")) + + # w1 sees same as before + assert_equal(w1.simulaterawtransaction([tx1])["balance_change"], 5.0) + + # same inputs (tx) more than once should error + assert_raises_rpc_error(-8, "Transaction(s) are spending the same output more than once", w0.simulaterawtransaction, [tx1,tx1]) + + tx1ob = node.decoderawtransaction(tx1) + tx1hex = tx1ob["txid"] + tx1vout = 1 - tx1changepos + # tx3 spends new w1 UTXO paying to w0 + tx3 = node.createrawtransaction([{"txid": tx1hex, "vout": tx1vout}], {w0.getnewaddress(): 4.9999}) + # tx4 spends new w1 UTXO paying to w1 + tx4 = node.createrawtransaction([{"txid": tx1hex, "vout": tx1vout}], {w1.getnewaddress(): 4.9999}) + + # on their own, both should fail due to missing input(s) + assert_raises_rpc_error(-8, "One or more transaction inputs are missing or have been spent already", w0.simulaterawtransaction, [tx3]) + assert_raises_rpc_error(-8, "One or more transaction inputs are missing or have been spent already", w1.simulaterawtransaction, [tx3]) + assert_raises_rpc_error(-8, "One or more transaction inputs are missing or have been spent already", w0.simulaterawtransaction, [tx4]) + assert_raises_rpc_error(-8, "One or more transaction inputs are missing or have been spent already", w1.simulaterawtransaction, [tx4]) + + # they should succeed when including tx1: + # wallet tx3 tx4 + # w0 -5 - bitcoin_fee + 4.9999 -5 - bitcoin_fee + # w1 0 +4.9999 + assert_approx(w0.simulaterawtransaction([tx1, tx3])["balance_change"], -Decimal("5") - bitcoin_fee + Decimal("4.9999")) + assert_approx(w1.simulaterawtransaction([tx1, tx3])["balance_change"], 0) + assert_approx(w0.simulaterawtransaction([tx1, tx4])["balance_change"], -Decimal("5") - bitcoin_fee) + assert_approx(w1.simulaterawtransaction([tx1, tx4])["balance_change"], Decimal("4.9999")) + + # they should fail if attempting to include both tx3 and tx4 + assert_raises_rpc_error(-8, "Transaction(s) are spending the same output more than once", w0.simulaterawtransaction, [tx1, tx3, tx4]) + assert_raises_rpc_error(-8, "Transaction(s) are spending the same output more than once", w1.simulaterawtransaction, [tx1, tx3, tx4]) + + # send tx1 to avoid reusing same UTXO below + node.sendrawtransaction(w0.signrawtransactionwithwallet(tx1)["hex"]) + self.generate(node, 1, sync_fun=self.no_op) # Confirm tx to trigger error below + self.sync_all() + + # w0 funds transaction 2; it should now see a decrease in (tx fee and payment), and w1 should see the same as above + funding = w0.fundrawtransaction(tx2) + tx2 = funding["hex"] + bitcoin_fee2 = Decimal(funding["fee"]) + assert_approx(w0.simulaterawtransaction([tx2])["balance_change"], -(Decimal("10") + bitcoin_fee2)) + assert_approx(w1.simulaterawtransaction([tx2])["balance_change"], +(Decimal("10"))) + assert_approx(w2.simulaterawtransaction([tx2])["balance_change"], 0) + + # w0-w2 error due to tx1 already being mined + assert_raises_rpc_error(-8, "One or more transaction inputs are missing or have been spent already", w0.simulaterawtransaction, [tx1, tx2]) + assert_raises_rpc_error(-8, "One or more transaction inputs are missing or have been spent already", w1.simulaterawtransaction, [tx1, tx2]) + assert_raises_rpc_error(-8, "One or more transaction inputs are missing or have been spent already", w2.simulaterawtransaction, [tx1, tx2]) + +if __name__ == '__main__': + SimulateTxTest().main() |