aboutsummaryrefslogtreecommitdiff
path: root/test
diff options
context:
space:
mode:
authorAndrew Chow <achow101-github@achow101.com>2022-08-05 15:08:42 -0400
committerAndrew Chow <achow101-github@achow101.com>2022-08-05 15:19:03 -0400
commit35305c759a4afe983272c3509675095743987aa5 (patch)
tree2b2171bab21a9e9a6a89af4e74ea0db25f2408d6 /test
parent7d3817b29a2ad6a1ca5bae6c342472bc74e2a7fe (diff)
parentdb10cf8ae36693cb4d3ed1b47b84709cf9c0d849 (diff)
downloadbitcoin-35305c759a4afe983272c3509675095743987aa5.tar.xz
Merge bitcoin/bitcoin#22751: rpc/wallet: add simulaterawtransaction RPC
db10cf8ae36693cb4d3ed1b47b84709cf9c0d849 rpc/wallet: add simulaterawtransaction RPC (Karl-Johan Alm) 701a64f548662e01821765b2934b6e4b321fda6d test: add support for Decimal to assert_approx (Karl-Johan Alm) Pull request description: (note: this was originally titled "add analyzerawtransaction RPC") This command iterates over the inputs and outputs of the given transactions, and tallies up the balance change for the given wallet. This can be useful e.g. when verifying that a coin join like transaction doesn't contain unexpected inputs that the wallet will then sign for unintentionally. I originally proposed this to Elements (https://github.com/ElementsProject/elements/pull/1016) and it was suggested that I propose this upstream. There is an alternative #22776 to instead add this info to `getbalances` when providing an optional transaction as argument. ACKs for top commit: jonatack: ACK db10cf8ae36693cb4d3ed1b47b84709cf9c0d849 achow101: re-ACK db10cf8ae36693cb4d3ed1b47b84709cf9c0d849 Tree-SHA512: adf222ec7dcdc068d007ae6f465dbc35b692dc7bb2db337be25340ad0c2f9c64cfab4124df23400995c700f41c83c29a2c34812121782c26063b100c7969b89d
Diffstat (limited to 'test')
-rw-r--r--test/functional/test_framework/util.py4
-rwxr-xr-xtest/functional/test_runner.py2
-rwxr-xr-xtest/functional/wallet_simulaterawtx.py129
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()