aboutsummaryrefslogtreecommitdiff
path: root/test/functional
diff options
context:
space:
mode:
Diffstat (limited to 'test/functional')
-rw-r--r--test/functional/README.md4
-rw-r--r--test/functional/data/rpc_decodescript.json2
-rw-r--r--test/functional/data/rpc_psbt.json10
-rwxr-xr-xtest/functional/example_test.py11
-rwxr-xr-xtest/functional/feature_addrman.py2
-rwxr-xr-xtest/functional/feature_bip68_sequence.py31
-rwxr-xr-xtest/functional/feature_block.py2
-rwxr-xr-xtest/functional/feature_config_args.py25
-rwxr-xr-xtest/functional/feature_dbcrash.py8
-rwxr-xr-xtest/functional/feature_discover.py75
-rwxr-xr-xtest/functional/feature_init.py1
-rwxr-xr-xtest/functional/feature_maxuploadtarget.py2
-rwxr-xr-xtest/functional/feature_minchainwork.py2
-rwxr-xr-xtest/functional/feature_nulldummy.py65
-rwxr-xr-xtest/functional/feature_proxy.py64
-rwxr-xr-xtest/functional/feature_rbf.py431
-rwxr-xr-xtest/functional/feature_segwit.py5
-rwxr-xr-xtest/functional/feature_taproot.py20
-rwxr-xr-xtest/functional/interface_rest.py15
-rwxr-xr-xtest/functional/interface_usdt_net.py2
-rwxr-xr-xtest/functional/interface_usdt_utxocache.py40
-rwxr-xr-xtest/functional/interface_usdt_validation.py13
-rwxr-xr-xtest/functional/mempool_accept.py8
-rwxr-xr-xtest/functional/mempool_datacarrier.py71
-rwxr-xr-xtest/functional/mempool_expiry.py8
-rwxr-xr-xtest/functional/mempool_limit.py2
-rwxr-xr-xtest/functional/mempool_package_limits.py316
-rwxr-xr-xtest/functional/mempool_package_onemore.py17
-rwxr-xr-xtest/functional/mempool_packages.py45
-rwxr-xr-xtest/functional/mempool_persist.py26
-rwxr-xr-xtest/functional/mempool_updatefromblock.py19
-rwxr-xr-xtest/functional/mining_basic.py2
-rwxr-xr-xtest/functional/mining_prioritisetransaction.py6
-rwxr-xr-xtest/functional/mocks/invalid_signer.py2
-rwxr-xr-xtest/functional/mocks/multi_signers.py30
-rwxr-xr-xtest/functional/mocks/signer.py2
-rwxr-xr-xtest/functional/p2p_blocksonly.py1
-rwxr-xr-xtest/functional/p2p_compactblocks.py24
-rwxr-xr-xtest/functional/p2p_dos_header_tree.py3
-rwxr-xr-xtest/functional/p2p_headers_sync_with_minchainwork.py164
-rwxr-xr-xtest/functional/p2p_i2p_sessions.py36
-rwxr-xr-xtest/functional/p2p_initial_headers_sync.py105
-rwxr-xr-xtest/functional/p2p_invalid_tx.py68
-rwxr-xr-xtest/functional/p2p_leak.py5
-rwxr-xr-xtest/functional/p2p_permissions.py3
-rwxr-xr-xtest/functional/p2p_segwit.py14
-rwxr-xr-xtest/functional/p2p_sendtxrcncl.py191
-rwxr-xr-xtest/functional/p2p_timeouts.py5
-rwxr-xr-xtest/functional/p2p_unrequested_blocks.py14
-rwxr-xr-xtest/functional/rpc_blockchain.py36
-rwxr-xr-xtest/functional/rpc_estimatefee.py8
-rwxr-xr-xtest/functional/rpc_fundrawtransaction.py147
-rwxr-xr-xtest/functional/rpc_getblockfrompeer.py9
-rwxr-xr-xtest/functional/rpc_getdescriptorinfo.py2
-rwxr-xr-xtest/functional/rpc_help.py2
-rwxr-xr-xtest/functional/rpc_invalidateblock.py5
-rwxr-xr-xtest/functional/rpc_packages.py137
-rwxr-xr-xtest/functional/rpc_psbt.py113
-rwxr-xr-xtest/functional/rpc_rawtransaction.py24
-rwxr-xr-xtest/functional/rpc_scanblocks.py103
-rwxr-xr-xtest/functional/rpc_scantxoutset.py3
-rwxr-xr-xtest/functional/rpc_signer.py5
-rwxr-xr-xtest/functional/rpc_signmessagewithprivkey.py22
-rwxr-xr-xtest/functional/rpc_signrawtransactionwithkey.py140
-rw-r--r--test/functional/test_framework/blocktools.py24
-rwxr-xr-xtest/functional/test_framework/messages.py51
-rwxr-xr-xtest/functional/test_framework/p2p.py4
-rw-r--r--test/functional/test_framework/psbt.py140
-rwxr-xr-xtest/functional/test_framework/test_framework.py17
-rwxr-xr-xtest/functional/test_framework/test_node.py9
-rw-r--r--test/functional/test_framework/util.py36
-rw-r--r--test/functional/test_framework/wallet.py80
-rwxr-xr-xtest/functional/test_runner.py33
-rwxr-xr-xtest/functional/tool_wallet.py10
-rwxr-xr-xtest/functional/wallet_abandonconflict.py3
-rwxr-xr-xtest/functional/wallet_address_types.py28
-rwxr-xr-xtest/functional/wallet_avoid_mixing_output_types.py177
-rwxr-xr-xtest/functional/wallet_balance.py24
-rwxr-xr-xtest/functional/wallet_basic.py48
-rwxr-xr-xtest/functional/wallet_bumpfee.py49
-rwxr-xr-xtest/functional/wallet_coinbase_category.py1
-rwxr-xr-xtest/functional/wallet_encryption.py18
-rwxr-xr-xtest/functional/wallet_groups.py7
-rwxr-xr-xtest/functional/wallet_hd.py8
-rwxr-xr-xtest/functional/wallet_import_rescan.py57
-rwxr-xr-xtest/functional/wallet_importdescriptors.py7
-rwxr-xr-xtest/functional/wallet_importmulti.py19
-rwxr-xr-xtest/functional/wallet_listdescriptors.py4
-rwxr-xr-xtest/functional/wallet_listreceivedby.py7
-rwxr-xr-xtest/functional/wallet_listsinceblock.py70
-rwxr-xr-xtest/functional/wallet_listtransactions.py6
-rwxr-xr-xtest/functional/wallet_migration.py407
-rwxr-xr-xtest/functional/wallet_miniscript.py93
-rwxr-xr-xtest/functional/wallet_multiwallet.py2
-rwxr-xr-xtest/functional/wallet_resendwallettransactions.py74
-rwxr-xr-xtest/functional/wallet_sendall.py67
-rwxr-xr-xtest/functional/wallet_signer.py15
-rwxr-xr-xtest/functional/wallet_signrawtransactionwithwallet.py (renamed from test/functional/rpc_signrawtransaction.py)100
-rwxr-xr-xtest/functional/wallet_simulaterawtx.py129
-rwxr-xr-xtest/functional/wallet_taproot.py17
-rwxr-xr-xtest/functional/wallet_transactiontime_rescan.py7
101 files changed, 3673 insertions, 948 deletions
diff --git a/test/functional/README.md b/test/functional/README.md
index 914dbfd977..1bd618a0c3 100644
--- a/test/functional/README.md
+++ b/test/functional/README.md
@@ -28,7 +28,9 @@ don't have test cases for.
could lead to bugs and issues in the test code.
- Use [type hints](https://docs.python.org/3/library/typing.html) in your code to improve code readability
and to detect possible bugs earlier.
-- Avoid wildcard imports
+- Avoid wildcard imports.
+- If more than one name from a module is needed, use lexicographically sorted multi-line imports
+ in order to reduce the possibility of potential merge conflicts.
- Use a module-level docstring to describe what the test is testing, and how it
is testing it.
- When subclassing the BitcoinTestFramework, place overrides for the
diff --git a/test/functional/data/rpc_decodescript.json b/test/functional/data/rpc_decodescript.json
index 8903f5efac..4a15ae8792 100644
--- a/test/functional/data/rpc_decodescript.json
+++ b/test/functional/data/rpc_decodescript.json
@@ -4,7 +4,7 @@
{
"asm": "1 eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee",
"address": "bcrt1pamhwamhwamhwamhwamhwamhwamhwamhwamhwamhwamhwamhwamhqz6nvlh",
- "desc": "addr(bcrt1pamhwamhwamhwamhwamhwamhwamhwamhwamhwamhwamhwamhwamhqz6nvlh)#v52jnujz",
+ "desc": "rawtr(eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee)#jk7c6kys",
"type": "witness_v1_taproot"
}
],
diff --git a/test/functional/data/rpc_psbt.json b/test/functional/data/rpc_psbt.json
index 430a1802a8..3127350872 100644
--- a/test/functional/data/rpc_psbt.json
+++ b/test/functional/data/rpc_psbt.json
@@ -40,6 +40,16 @@
"cHNidP8BAF4CAAAAAZvUh2UjC/mnLmYgAflyVW5U8Mb5f+tWvLVgDYF/aZUmAQAAAAD/////AUjmBSoBAAAAIlEgAw2k/OT32yjCyylRYx4ANxOFZZf+ljiCy1AOaBEsymMAAAAAAAEBKwDyBSoBAAAAIlEgwiR++/2SrEf29AuNQtFpF1oZ+p+hDkol1/NetN2FtpJjFcFQkpt0waBJVLeLS2A16XpeB4paDyjsltVHv+6azoA6wG99YgWelJehpKJnVp2YdtpgEBr/OONSm5uTnOf5GulwEV8uSQr3zEXE94UR82BXzlxaXFYyWin7RN/CA/NW4fgAIyAssTrGgkjegGqmo2Wc88A+toIdCcgRSk6Gj+vehlu20qzAAAA=",
"cHNidP8BAF4CAAAAAZvUh2UjC/mnLmYgAflyVW5U8Mb5f+tWvLVgDYF/aZUmAQAAAAD/////AUjmBSoBAAAAIlEgAw2k/OT32yjCyylRYx4ANxOFZZf+ljiCy1AOaBEsymMAAAAAAAEBKwDyBSoBAAAAIlEgwiR++/2SrEf29AuNQtFpF1oZ+p+hDkol1/NetN2FtpJhFcFQkpt0waBJVLeLS2A16XpeB4paDyjsltVHv+6azoA6wG99YgWelJehpKJnVp2YdtpgEBr/OONSm5uTnOf5GulwEV8uSQr3zEXE94UR82BXzlxaXFYyWin7RN/CA/NW4SMgLLE6xoJI3oBqpqNlnPPAPraCHQnIEUpOho/r3oZbttKswAAA"
],
+ "invalid_with_msg": [
+ [
+ "cHNidP8BAKOro2MDAwMDA5ggCAAA////CQAtAAD+///1AAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAJAAAAAAAAAAAAAAAAAAAAAAAAAD+///1Zm9ybmV3nWx1Y2vmelLmegAAAAAAAAAAAAAAAAAAAAMKAwMDAwMDAwMDAwMACvMBA3FkAAAAAAAAAAAABAAlAAAAAAAAACEWDQ0zDQ0NDQ0NDQ0NCwEAAH9/f39/fwMAAABNo6P///kAAA==",
+ "Input Taproot BIP32 keypath has an invalid length"
+ ],
+ [
+ "cHNidP8BAIkCAAAAAapfm08b0MipBvW9thL06f8rMbeazW7TIa0W9plHj4WoAAAAAAD9////AoCWmAAAAAAAIlEgC+blBlIP1iijRWxqjw1u9H02sqr7y8fno6/LdnvGqPl895x2AAAAACJRIM5wyjSexMbADl4K+AI1/68zyaDlE7guKvrEDUAjwqU1AAAAAAABASsAlDV3AAAAACJRIDfCpO/CIAqc0JKgBhsCfaPGdyroYtmH+4gQK/Mnn72UIRZGOixxmh9h2gqDIecYHcQHRa8w+Sokc//iDiqXz7uMGRkAHzYIzlYAAIABAACAAAAAgAAAAABhAAAAARcgRjoscZofYdoKgyHnGB3EB0WvMPkqJHP/4g4ql8+7jBkAAQUg1YCB33LpmkGemw3ncz7fcnjhL/bBG/PjH8vpgr2L3cUBBgAhB9WAgd9y6ZpBnpsN53M+33J44S/2wRvz4x/L6YK9i93FGQAfNgjOVgAAgAEAAIAAAACAAAAAAGIAAAAAAQUg9jMNus8cd+GAosBk9wn+pNP9wn7A+jy2Vq0cy+siJ8wBBgAhB/YzDbrPHHfhgKLAZPcJ/qTT/cJ+wPo8tlatHMvrIifMGQAfNgjOVgAAgAEAAIAAAACAAQAAAFEBAAAA",
+ "Output Taproot tree must not be empty"
+ ]
+ ],
"valid" : [
"cHNidP8BAHUCAAAAASaBcTce3/KF6Tet7qSze3gADAVmy7OtZGQXE8pCFxv2AAAAAAD+////AtPf9QUAAAAAGXapFNDFmQPFusKGh2DpD9UhpGZap2UgiKwA4fUFAAAAABepFDVF5uM7gyxHBQ8k0+65PJwDlIvHh7MuEwAAAQD9pQEBAAAAAAECiaPHHqtNIOA3G7ukzGmPopXJRjr6Ljl/hTPMti+VZ+UBAAAAFxYAFL4Y0VKpsBIDna89p95PUzSe7LmF/////4b4qkOnHf8USIk6UwpyN+9rRgi7st0tAXHmOuxqSJC0AQAAABcWABT+Pp7xp0XpdNkCxDVZQ6vLNL1TU/////8CAMLrCwAAAAAZdqkUhc/xCX/Z4Ai7NK9wnGIZeziXikiIrHL++E4sAAAAF6kUM5cluiHv1irHU6m80GfWx6ajnQWHAkcwRAIgJxK+IuAnDzlPVoMR3HyppolwuAJf3TskAinwf4pfOiQCIAGLONfc0xTnNMkna9b7QPZzMlvEuqFEyADS8vAtsnZcASED0uFWdJQbrUqZY3LLh+GFbTZSYG2YVi/jnF6efkE/IQUCSDBFAiEA0SuFLYXc2WHS9fSrZgZU327tzHlMDDPOXMMJ/7X85Y0CIGczio4OFyXBl/saiK9Z9R5E5CVbIBZ8hoQDHAXR8lkqASECI7cr7vCWXRC+B3jv7NYfysb3mk6haTkzgHNEZPhPKrMAAAAAAAAA",
"cHNidP8BAKACAAAAAqsJSaCMWvfEm4IS9Bfi8Vqz9cM9zxU4IagTn4d6W3vkAAAAAAD+////qwlJoIxa98SbghL0F+LxWrP1wz3PFTghqBOfh3pbe+QBAAAAAP7///8CYDvqCwAAAAAZdqkUdopAu9dAy+gdmI5x3ipNXHE5ax2IrI4kAAAAAAAAGXapFG9GILVT+glechue4O/p+gOcykWXiKwAAAAAAAEHakcwRAIgR1lmF5fAGwNrJZKJSGhiGDR9iYZLcZ4ff89X0eURZYcCIFMJ6r9Wqk2Ikf/REf3xM286KdqGbX+EhtdVRs7tr5MZASEDXNxh/HupccC1AaZGoqg7ECy0OIEhfKaC3Ibi1z+ogpIAAQEgAOH1BQAAAAAXqRQ1RebjO4MsRwUPJNPuuTycA5SLx4cBBBYAFIXRNTfy4mVAWjTbr6nj3aAfuCMIAAAA",
diff --git a/test/functional/example_test.py b/test/functional/example_test.py
index 2ad96da854..9cf756060e 100755
--- a/test/functional/example_test.py
+++ b/test/functional/example_test.py
@@ -14,8 +14,15 @@ is testing and *how* it's being tested
from collections import defaultdict
# Avoid wildcard * imports
-from test_framework.blocktools import (create_block, create_coinbase)
-from test_framework.messages import CInv, MSG_BLOCK
+# Use lexicographically sorted multi-line imports
+from test_framework.blocktools import (
+ create_block,
+ create_coinbase,
+)
+from test_framework.messages import (
+ CInv,
+ MSG_BLOCK,
+)
from test_framework.p2p import (
P2PInterface,
msg_block,
diff --git a/test/functional/feature_addrman.py b/test/functional/feature_addrman.py
index 5e49d0214a..63abf0d9f8 100755
--- a/test/functional/feature_addrman.py
+++ b/test/functional/feature_addrman.py
@@ -95,7 +95,7 @@ class AddrmanTest(BitcoinTestFramework):
with open(peers_dat, "wb") as f:
f.write(serialize_addrman()[:-1])
self.nodes[0].assert_start_raises_init_error(
- expected_msg=init_error("CAutoFile::read: end of file.*"),
+ expected_msg=init_error("AutoFile::read: end of file.*"),
match=ErrorMatch.FULL_REGEX,
)
diff --git a/test/functional/feature_bip68_sequence.py b/test/functional/feature_bip68_sequence.py
index 05d274a9fe..5b43fe4f8e 100755
--- a/test/functional/feature_bip68_sequence.py
+++ b/test/functional/feature_bip68_sequence.py
@@ -10,15 +10,21 @@ from test_framework.blocktools import (
NORMAL_GBT_REQUEST_PARAMS,
add_witness_commitment,
create_block,
+ script_to_p2wsh_script,
)
from test_framework.messages import (
COIN,
COutPoint,
CTransaction,
CTxIn,
+ CTxInWitness,
CTxOut,
tx_from_hex,
)
+from test_framework.script import (
+ CScript,
+ OP_TRUE,
+)
from test_framework.test_framework import BitcoinTestFramework
from test_framework.util import (
assert_equal,
@@ -26,7 +32,8 @@ from test_framework.util import (
assert_raises_rpc_error,
softfork_active,
)
-from test_framework.script_util import DUMMY_P2WPKH_SCRIPT
+
+SCRIPT_W0_SH_OP_TRUE = script_to_p2wsh_script(CScript([OP_TRUE]))
SEQUENCE_LOCKTIME_DISABLE_FLAG = (1<<31)
SEQUENCE_LOCKTIME_TYPE_FLAG = (1<<22) # this means use time (0 means height)
@@ -42,11 +49,9 @@ class BIP68Test(BitcoinTestFramework):
self.extra_args = [
[
'-testactivationheight=csv@432',
- "-acceptnonstdtxn=1",
],
[
'-testactivationheight=csv@432',
- "-acceptnonstdtxn=0",
],
]
@@ -100,7 +105,7 @@ class BIP68Test(BitcoinTestFramework):
# input to mature.
sequence_value = SEQUENCE_LOCKTIME_DISABLE_FLAG | 1
tx1.vin = [CTxIn(COutPoint(int(utxo["txid"], 16), utxo["vout"]), nSequence=sequence_value)]
- tx1.vout = [CTxOut(value, DUMMY_P2WPKH_SCRIPT)]
+ tx1.vout = [CTxOut(value, SCRIPT_W0_SH_OP_TRUE)]
tx1_signed = self.nodes[0].signrawtransactionwithwallet(tx1.serialize().hex())["hex"]
tx1_id = self.nodes[0].sendrawtransaction(tx1_signed)
@@ -112,7 +117,9 @@ class BIP68Test(BitcoinTestFramework):
tx2.nVersion = 2
sequence_value = sequence_value & 0x7fffffff
tx2.vin = [CTxIn(COutPoint(tx1_id, 0), nSequence=sequence_value)]
- tx2.vout = [CTxOut(int(value - self.relayfee * COIN), DUMMY_P2WPKH_SCRIPT)]
+ tx2.wit.vtxinwit = [CTxInWitness()]
+ tx2.wit.vtxinwit[0].scriptWitness.stack = [CScript([OP_TRUE])]
+ tx2.vout = [CTxOut(int(value - self.relayfee * COIN), SCRIPT_W0_SH_OP_TRUE)]
tx2.rehash()
assert_raises_rpc_error(-26, NOT_FINAL_ERROR, self.nodes[0].sendrawtransaction, tx2.serialize().hex())
@@ -207,7 +214,7 @@ class BIP68Test(BitcoinTestFramework):
value += utxos[j]["amount"]*COIN
# Overestimate the size of the tx - signatures should be less than 120 bytes, and leave 50 for the output
tx_size = len(tx.serialize().hex())//2 + 120*num_inputs + 50
- tx.vout.append(CTxOut(int(value-self.relayfee*tx_size*COIN/1000), DUMMY_P2WPKH_SCRIPT))
+ tx.vout.append(CTxOut(int(value - self.relayfee * tx_size * COIN / 1000), SCRIPT_W0_SH_OP_TRUE))
rawtx = self.nodes[0].signrawtransactionwithwallet(tx.serialize().hex())["hex"]
if (using_sequence_locks and not should_pass):
@@ -236,7 +243,7 @@ class BIP68Test(BitcoinTestFramework):
tx2 = CTransaction()
tx2.nVersion = 2
tx2.vin = [CTxIn(COutPoint(tx1.sha256, 0), nSequence=0)]
- tx2.vout = [CTxOut(int(tx1.vout[0].nValue - self.relayfee*COIN), DUMMY_P2WPKH_SCRIPT)]
+ tx2.vout = [CTxOut(int(tx1.vout[0].nValue - self.relayfee * COIN), SCRIPT_W0_SH_OP_TRUE)]
tx2_raw = self.nodes[0].signrawtransactionwithwallet(tx2.serialize().hex())["hex"]
tx2 = tx_from_hex(tx2_raw)
tx2.rehash()
@@ -254,7 +261,9 @@ class BIP68Test(BitcoinTestFramework):
tx = CTransaction()
tx.nVersion = 2
tx.vin = [CTxIn(COutPoint(orig_tx.sha256, 0), nSequence=sequence_value)]
- tx.vout = [CTxOut(int(orig_tx.vout[0].nValue - relayfee * COIN), DUMMY_P2WPKH_SCRIPT)]
+ tx.wit.vtxinwit = [CTxInWitness()]
+ tx.wit.vtxinwit[0].scriptWitness.stack = [CScript([OP_TRUE])]
+ tx.vout = [CTxOut(int(orig_tx.vout[0].nValue - relayfee * COIN), SCRIPT_W0_SH_OP_TRUE)]
tx.rehash()
if (orig_tx.hash in node.getrawmempool()):
@@ -367,7 +376,7 @@ class BIP68Test(BitcoinTestFramework):
tx2 = CTransaction()
tx2.nVersion = 1
tx2.vin = [CTxIn(COutPoint(tx1.sha256, 0), nSequence=0)]
- tx2.vout = [CTxOut(int(tx1.vout[0].nValue - self.relayfee*COIN), DUMMY_P2WPKH_SCRIPT)]
+ tx2.vout = [CTxOut(int(tx1.vout[0].nValue - self.relayfee * COIN), SCRIPT_W0_SH_OP_TRUE)]
# sign tx2
tx2_raw = self.nodes[0].signrawtransactionwithwallet(tx2.serialize().hex())["hex"]
@@ -382,7 +391,9 @@ class BIP68Test(BitcoinTestFramework):
tx3 = CTransaction()
tx3.nVersion = 2
tx3.vin = [CTxIn(COutPoint(tx2.sha256, 0), nSequence=sequence_value)]
- tx3.vout = [CTxOut(int(tx2.vout[0].nValue - self.relayfee * COIN), DUMMY_P2WPKH_SCRIPT)]
+ tx3.wit.vtxinwit = [CTxInWitness()]
+ tx3.wit.vtxinwit[0].scriptWitness.stack = [CScript([OP_TRUE])]
+ tx3.vout = [CTxOut(int(tx2.vout[0].nValue - self.relayfee * COIN), SCRIPT_W0_SH_OP_TRUE)]
tx3.rehash()
assert_raises_rpc_error(-26, NOT_FINAL_ERROR, self.nodes[0].sendrawtransaction, tx3.serialize().hex())
diff --git a/test/functional/feature_block.py b/test/functional/feature_block.py
index 462deeae32..850cb8334c 100755
--- a/test/functional/feature_block.py
+++ b/test/functional/feature_block.py
@@ -1297,7 +1297,7 @@ class FullBlockTest(BitcoinTestFramework):
blocks2 = []
for i in range(89, LARGE_REORG_SIZE + 89):
blocks2.append(self.next_block("alt" + str(i)))
- self.send_blocks(blocks2, False, force_send=True)
+ self.send_blocks(blocks2, False, force_send=False)
# extend alt chain to trigger re-org
block = self.next_block("alt" + str(chain1_tip + 1))
diff --git a/test/functional/feature_config_args.py b/test/functional/feature_config_args.py
index 6c51a5ac31..112dbb9e6a 100755
--- a/test/functional/feature_config_args.py
+++ b/test/functional/feature_config_args.py
@@ -20,11 +20,25 @@ class ConfArgsTest(BitcoinTestFramework):
self.disable_autoconnect = False
def test_config_file_parser(self):
+ self.log.info('Test config file parser')
self.stop_node(0)
+ # Check that startup fails if conf= is set in bitcoin.conf or in an included conf file
+ bad_conf_file_path = os.path.join(self.options.tmpdir, 'node0', 'bitcoin_bad.conf')
+ util.write_config(bad_conf_file_path, n=0, chain='', extra_config=f'conf=some.conf\n')
+ conf_in_config_file_err = 'Error: Error reading configuration file: conf cannot be set in the configuration file; use includeconf= if you want to include additional config files'
+ self.nodes[0].assert_start_raises_init_error(
+ extra_args=[f'-conf={bad_conf_file_path}'],
+ expected_msg=conf_in_config_file_err,
+ )
inc_conf_file_path = os.path.join(self.nodes[0].datadir, 'include.conf')
with open(os.path.join(self.nodes[0].datadir, 'bitcoin.conf'), 'a', encoding='utf-8') as conf:
conf.write(f'includeconf={inc_conf_file_path}\n')
+ with open(inc_conf_file_path, 'w', encoding='utf-8') as conf:
+ conf.write('conf=some.conf\n')
+ self.nodes[0].assert_start_raises_init_error(
+ expected_msg=conf_in_config_file_err,
+ )
self.nodes[0].assert_start_raises_init_error(
expected_msg='Error: Error parsing command line arguments: Invalid parameter -dash_cli=1',
@@ -32,11 +46,19 @@ class ConfArgsTest(BitcoinTestFramework):
)
with open(inc_conf_file_path, 'w', encoding='utf-8') as conf:
conf.write('dash_conf=1\n')
+
with self.nodes[0].assert_debug_log(expected_msgs=['Ignoring unknown configuration value dash_conf']):
self.start_node(0)
self.stop_node(0)
with open(inc_conf_file_path, 'w', encoding='utf-8') as conf:
+ conf.write('reindex=1\n')
+
+ with self.nodes[0].assert_debug_log(expected_msgs=['Warning: reindex=1 is set in the configuration file, which will significantly slow down startup. Consider removing or commenting out this option for better performance, unless there is currently a condition which makes rebuilding the indexes necessary']):
+ self.start_node(0)
+ self.stop_node(0)
+
+ with open(inc_conf_file_path, 'w', encoding='utf-8') as conf:
conf.write('-dash=1\n')
self.nodes[0].assert_start_raises_init_error(expected_msg='Error: Error reading configuration file: parse error on line 1: -dash=1, options in configuration file must be specified without leading -')
@@ -186,11 +208,12 @@ class ConfArgsTest(BitcoinTestFramework):
with self.nodes[0].assert_debug_log(expected_msgs=[
"Loaded 0 addresses from peers.dat",
"DNS seeding disabled",
- "Adding fixed seeds as -dnsseed=0, -addnode is not provided and all -seednode(s) attempted\n",
+ "Adding fixed seeds as -dnsseed=0 (or IPv4/IPv6 connections are disabled via -onlynet), -addnode is not provided and all -seednode(s) attempted\n",
]):
self.start_node(0, extra_args=['-dnsseed=0', '-fixedseeds=1'])
assert time.time() - start < 60
self.stop_node(0)
+ self.nodes[0].assert_start_raises_init_error(['-dnsseed=1', '-onlynet=i2p', '-i2psam=127.0.0.1:7656'], "Error: Incompatible options: -dnsseed=1 was explicitly specified, but -onlynet forbids connections to IPv4/IPv6")
# No peers.dat exists and dns seeds are disabled.
# We expect the node will not add fixed seeds when explicitly disabled.
diff --git a/test/functional/feature_dbcrash.py b/test/functional/feature_dbcrash.py
index 62e9bec663..f606f26e70 100755
--- a/test/functional/feature_dbcrash.py
+++ b/test/functional/feature_dbcrash.py
@@ -62,8 +62,8 @@ class ChainstateWriteCrashTest(BitcoinTestFramework):
self.node2_args = ["-dbcrashratio=24", "-dbcache=16"] + self.base_args
# Node3 is a normal node with default args, except will mine full blocks
- # and non-standard txs (e.g. txs with "dust" outputs)
- self.node3_args = ["-blockmaxweight=4000000", "-acceptnonstdtxn"]
+ # and txs with "dust" outputs
+ self.node3_args = ["-blockmaxweight=4000000", "-dustrelayfee=0"]
self.extra_args = [self.node0_args, self.node1_args, self.node2_args, self.node3_args]
def setup_network(self):
@@ -211,7 +211,9 @@ class ChainstateWriteCrashTest(BitcoinTestFramework):
self.crashed_on_restart = 0 # Track count of crashes during recovery
# Start by creating a lot of utxos on node3
- utxo_list = self.wallet.send_self_transfer_multi(from_node=self.nodes[3], num_outputs=5000)['new_utxos']
+ utxo_list = []
+ for _ in range(5):
+ utxo_list.extend(self.wallet.send_self_transfer_multi(from_node=self.nodes[3], num_outputs=1000)['new_utxos'])
self.generate(self.nodes[3], 1, sync_fun=self.no_op)
assert_equal(len(self.nodes[3].getrawmempool()), 0)
self.log.info(f"Prepped {len(utxo_list)} utxo entries")
diff --git a/test/functional/feature_discover.py b/test/functional/feature_discover.py
new file mode 100755
index 0000000000..7f4b81114e
--- /dev/null
+++ b/test/functional/feature_discover.py
@@ -0,0 +1,75 @@
+#!/usr/bin/env python3
+# Copyright (c) 2022 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 -discover command."""
+
+import socket
+
+from test_framework.test_framework import BitcoinTestFramework
+from test_framework.util import assert_equal
+
+
+def is_valid_ipv4_address(address):
+ try:
+ socket.inet_aton(address)
+ except socket.error:
+ return False
+ return True
+
+
+def is_valid_ipv6_address(address):
+ try:
+ socket.inet_pton(socket.AF_INET6, address)
+ except socket.error:
+ return False
+ return True
+
+
+class DiscoverTest(BitcoinTestFramework):
+ def set_test_params(self):
+ self.setup_clean_chain = True
+ self.bind_to_localhost_only = False
+ self.num_nodes = 1
+
+ def validate_addresses(self, addresses_obj):
+ for address_obj in addresses_obj:
+ address = address_obj['address']
+ self.log.info(f"Validating {address}")
+ valid = (is_valid_ipv4_address(address)
+ or is_valid_ipv6_address(address))
+ assert_equal(valid, True)
+
+ def test_local_addresses(self, test_case, *, expect_empty=False):
+ self.log.info(f"Restart node with {test_case}")
+ self.restart_node(0, test_case)
+ network_info = self.nodes[0].getnetworkinfo()
+ network_enabled = [n for n in network_info['networks']
+ if n['reachable'] and n['name'] in ['ipv4', 'ipv6']]
+ local_addrs = list(network_info["localaddresses"])
+ if expect_empty or not network_enabled:
+ assert_equal(local_addrs, [])
+ elif len(local_addrs) > 0:
+ self.validate_addresses(local_addrs)
+
+ def run_test(self):
+ test_cases = [
+ ["-listen", "-discover"],
+ ["-discover"],
+ ]
+
+ test_cases_empty = [
+ ["-discover=0"],
+ ["-listen", "-discover=0"],
+ [],
+ ]
+
+ for test_case in test_cases:
+ self.test_local_addresses(test_case, expect_empty=False)
+
+ for test_case in test_cases_empty:
+ self.test_local_addresses(test_case, expect_empty=True)
+
+
+if __name__ == '__main__':
+ DiscoverTest().main()
diff --git a/test/functional/feature_init.py b/test/functional/feature_init.py
index 13c7326519..56d093c396 100755
--- a/test/functional/feature_init.py
+++ b/test/functional/feature_init.py
@@ -55,7 +55,6 @@ class InitStressTest(BitcoinTestFramework):
b'Loading P2P addresses',
b'Loading banlist',
b'Loading block index',
- b'Switching active chainstate',
b'Checking all blk files are present',
b'Loaded best chain:',
b'init message: Verifying blocks',
diff --git a/test/functional/feature_maxuploadtarget.py b/test/functional/feature_maxuploadtarget.py
index 0b9d651226..3ea412002a 100755
--- a/test/functional/feature_maxuploadtarget.py
+++ b/test/functional/feature_maxuploadtarget.py
@@ -46,7 +46,7 @@ class MaxUploadTest(BitcoinTestFramework):
self.num_nodes = 1
self.extra_args = [[
"-maxuploadtarget=800M",
- "-acceptnonstdtxn=1",
+ "-datacarriersize=100000",
]]
self.supports_cli = False
diff --git a/test/functional/feature_minchainwork.py b/test/functional/feature_minchainwork.py
index fa10855a98..fb4024b1b0 100755
--- a/test/functional/feature_minchainwork.py
+++ b/test/functional/feature_minchainwork.py
@@ -82,7 +82,7 @@ class MinimumChainWorkTest(BitcoinTestFramework):
msg.hashstop = 0
peer.send_and_ping(msg)
time.sleep(5)
- assert "headers" not in peer.last_message
+ assert "headers" not in peer.last_message or len(peer.last_message["headers"].headers) == 0
self.log.info("Generating one more block")
self.generate(self.nodes[0], 1)
diff --git a/test/functional/feature_nulldummy.py b/test/functional/feature_nulldummy.py
index 7a84098a83..9bfb79057e 100755
--- a/test/functional/feature_nulldummy.py
+++ b/test/functional/feature_nulldummy.py
@@ -19,9 +19,11 @@ from test_framework.blocktools import (
NORMAL_GBT_REQUEST_PARAMS,
add_witness_commitment,
create_block,
- create_transaction,
)
-from test_framework.messages import CTransaction
+from test_framework.messages import (
+ CTransaction,
+ tx_from_hex,
+)
from test_framework.script import (
OP_0,
OP_TRUE,
@@ -31,6 +33,9 @@ from test_framework.util import (
assert_equal,
assert_raises_rpc_error,
)
+from test_framework.wallet import getnewdestination
+from test_framework.key import ECKey
+from test_framework.wallet_util import bytes_to_wif
NULLDUMMY_ERROR = "non-mandatory-script-verify-flag (Dummy CHECKMULTISIG argument must be zero)"
@@ -55,22 +60,26 @@ class NULLDUMMYTest(BitcoinTestFramework):
'-par=1', # Use only one script thread to get the exact reject reason for testing
]]
- def skip_test_if_missing_module(self):
- self.skip_if_no_wallet()
+ def create_transaction(self, *, txid, input_details=None, addr, amount, privkey):
+ input = {"txid": txid, "vout": 0}
+ output = {addr: amount}
+ rawtx = self.nodes[0].createrawtransaction([input], output)
+ # Details only needed for scripthash or witness spends
+ input = None if not input_details else [{**input, **input_details}]
+ signedtx = self.nodes[0].signrawtransactionwithkey(rawtx, [privkey], input)
+ return tx_from_hex(signedtx["hex"])
def run_test(self):
- self.nodes[0].createwallet(wallet_name='wmulti', disable_private_keys=True)
- wmulti = self.nodes[0].get_wallet_rpc('wmulti')
- w0 = self.nodes[0].get_wallet_rpc(self.default_wallet_name)
- self.address = w0.getnewaddress()
- self.pubkey = w0.getaddressinfo(self.address)['pubkey']
- self.ms_address = wmulti.addmultisigaddress(1, [self.pubkey])['address']
- self.wit_address = w0.getnewaddress(address_type='p2sh-segwit')
- self.wit_ms_address = wmulti.addmultisigaddress(1, [self.pubkey], '', 'p2sh-segwit')['address']
- if not self.options.descriptors:
- # Legacy wallets need to import these so that they are watched by the wallet. This is unnecessary (and does not need to be tested) for descriptor wallets
- wmulti.importaddress(self.ms_address)
- wmulti.importaddress(self.wit_ms_address)
+ eckey = ECKey()
+ eckey.generate()
+ self.privkey = bytes_to_wif(eckey.get_bytes())
+ self.pubkey = eckey.get_pubkey().get_bytes().hex()
+ cms = self.nodes[0].createmultisig(1, [self.pubkey])
+ wms = self.nodes[0].createmultisig(1, [self.pubkey], 'p2sh-segwit')
+ self.ms_address = cms["address"]
+ ms_unlock_details = {"scriptPubKey": self.nodes[0].validateaddress(self.ms_address)["scriptPubKey"],
+ "redeemScript": cms["redeemScript"]}
+ self.wit_ms_address = wms['address']
self.coinbase_blocks = self.generate(self.nodes[0], 2) # block height = 2
coinbase_txid = []
@@ -82,16 +91,23 @@ class NULLDUMMYTest(BitcoinTestFramework):
self.lastblocktime = int(time.time()) + self.lastblockheight
self.log.info(f"Test 1: NULLDUMMY compliant base transactions should be accepted to mempool and mined before activation [{COINBASE_MATURITY + 3}]")
- test1txs = [create_transaction(self.nodes[0], coinbase_txid[0], self.ms_address, amount=49)]
+ test1txs = [self.create_transaction(txid=coinbase_txid[0], addr=self.ms_address, amount=49,
+ privkey=self.nodes[0].get_deterministic_priv_key().key)]
txid1 = self.nodes[0].sendrawtransaction(test1txs[0].serialize_with_witness().hex(), 0)
- test1txs.append(create_transaction(self.nodes[0], txid1, self.ms_address, amount=48))
+ test1txs.append(self.create_transaction(txid=txid1, input_details=ms_unlock_details,
+ addr=self.ms_address, amount=48,
+ privkey=self.privkey))
txid2 = self.nodes[0].sendrawtransaction(test1txs[1].serialize_with_witness().hex(), 0)
- test1txs.append(create_transaction(self.nodes[0], coinbase_txid[1], self.wit_ms_address, amount=49))
+ test1txs.append(self.create_transaction(txid=coinbase_txid[1],
+ addr=self.wit_ms_address, amount=49,
+ privkey=self.nodes[0].get_deterministic_priv_key().key))
txid3 = self.nodes[0].sendrawtransaction(test1txs[2].serialize_with_witness().hex(), 0)
self.block_submit(self.nodes[0], test1txs, accept=True)
self.log.info("Test 2: Non-NULLDUMMY base multisig transaction should not be accepted to mempool before activation")
- test2tx = create_transaction(self.nodes[0], txid2, self.ms_address, amount=47)
+ test2tx = self.create_transaction(txid=txid2, input_details=ms_unlock_details,
+ addr=self.ms_address, amount=47,
+ privkey=self.privkey)
invalidate_nulldummy_tx(test2tx)
assert_raises_rpc_error(-26, NULLDUMMY_ERROR, self.nodes[0].sendrawtransaction, test2tx.serialize_with_witness().hex(), 0)
@@ -99,14 +115,19 @@ class NULLDUMMYTest(BitcoinTestFramework):
self.block_submit(self.nodes[0], [test2tx], accept=True)
self.log.info("Test 4: Non-NULLDUMMY base multisig transaction is invalid after activation")
- test4tx = create_transaction(self.nodes[0], test2tx.hash, self.address, amount=46)
+ test4tx = self.create_transaction(txid=test2tx.hash, input_details=ms_unlock_details,
+ addr=getnewdestination()[2], amount=46,
+ privkey=self.privkey)
test6txs = [CTransaction(test4tx)]
invalidate_nulldummy_tx(test4tx)
assert_raises_rpc_error(-26, NULLDUMMY_ERROR, self.nodes[0].sendrawtransaction, test4tx.serialize_with_witness().hex(), 0)
self.block_submit(self.nodes[0], [test4tx], accept=False)
self.log.info("Test 5: Non-NULLDUMMY P2WSH multisig transaction invalid after activation")
- test5tx = create_transaction(self.nodes[0], txid3, self.wit_address, amount=48)
+ test5tx = self.create_transaction(txid=txid3, input_details={"scriptPubKey": test1txs[2].vout[0].scriptPubKey.hex(),
+ "amount": 49, "witnessScript": wms["redeemScript"]},
+ addr=getnewdestination(address_type='p2sh-segwit')[2], amount=48,
+ privkey=self.privkey)
test6txs.append(CTransaction(test5tx))
test5tx.wit.vtxinwit[0].scriptWitness.stack[0] = b'\x01'
assert_raises_rpc_error(-26, NULLDUMMY_ERROR, self.nodes[0].sendrawtransaction, test5tx.serialize_with_witness().hex(), 0)
diff --git a/test/functional/feature_proxy.py b/test/functional/feature_proxy.py
index dd3cdc96ca..18b079cd71 100755
--- a/test/functional/feature_proxy.py
+++ b/test/functional/feature_proxy.py
@@ -317,35 +317,67 @@ class ProxyTest(BitcoinTestFramework):
self.stop_node(1)
- self.log.info("Test passing invalid -proxy raises expected init error")
- self.nodes[1].extra_args = ["-proxy=abc:def"]
- msg = "Error: Invalid -proxy address or hostname: 'abc:def'"
+ self.log.info("Test passing invalid -proxy hostname raises expected init error")
+ self.nodes[1].extra_args = ["-proxy=abc..abc:23456"]
+ msg = "Error: Invalid -proxy address or hostname: 'abc..abc:23456'"
self.nodes[1].assert_start_raises_init_error(expected_msg=msg)
- self.log.info("Test passing invalid -onion raises expected init error")
- self.nodes[1].extra_args = ["-onion=xyz:abc"]
- msg = "Error: Invalid -onion address or hostname: 'xyz:abc'"
+ self.log.info("Test passing invalid -proxy port raises expected init error")
+ self.nodes[1].extra_args = ["-proxy=192.0.0.1:def"]
+ msg = "Error: Invalid port specified in -proxy: '192.0.0.1:def'"
self.nodes[1].assert_start_raises_init_error(expected_msg=msg)
- self.log.info("Test passing invalid -i2psam raises expected init error")
- self.nodes[1].extra_args = ["-i2psam=def:xyz"]
- msg = "Error: Invalid -i2psam address or hostname: 'def:xyz'"
+ self.log.info("Test passing invalid -onion hostname raises expected init error")
+ self.nodes[1].extra_args = ["-onion=xyz..xyz:23456"]
+ msg = "Error: Invalid -onion address or hostname: 'xyz..xyz:23456'"
self.nodes[1].assert_start_raises_init_error(expected_msg=msg)
- msg = (
- "Error: Outbound connections restricted to Tor (-onlynet=onion) but "
- "the proxy for reaching the Tor network is not provided (no -proxy= "
- "and no -onion= given) or it is explicitly forbidden (-onion=0)"
- )
- self.log.info("Test passing -onlynet=onion without -proxy or -onion raises expected init error")
- self.nodes[1].extra_args = ["-onlynet=onion"]
+ self.log.info("Test passing invalid -onion port raises expected init error")
+ self.nodes[1].extra_args = ["-onion=192.0.0.1:def"]
+ msg = "Error: Invalid port specified in -onion: '192.0.0.1:def'"
+ self.nodes[1].assert_start_raises_init_error(expected_msg=msg)
+
+ self.log.info("Test passing invalid -i2psam hostname raises expected init error")
+ self.nodes[1].extra_args = ["-i2psam=def..def:23456"]
+ msg = "Error: Invalid -i2psam address or hostname: 'def..def:23456'"
+ self.nodes[1].assert_start_raises_init_error(expected_msg=msg)
+
+ self.log.info("Test passing invalid -i2psam port raises expected init error")
+ self.nodes[1].extra_args = ["-i2psam=192.0.0.1:def"]
+ msg = "Error: Invalid port specified in -i2psam: '192.0.0.1:def'"
+ self.nodes[1].assert_start_raises_init_error(expected_msg=msg)
+
+ self.log.info("Test passing invalid -onlynet=i2p without -i2psam raises expected init error")
+ self.nodes[1].extra_args = ["-onlynet=i2p"]
+ msg = "Error: Outbound connections restricted to i2p (-onlynet=i2p) but -i2psam is not provided"
+ self.nodes[1].assert_start_raises_init_error(expected_msg=msg)
+
+ self.log.info("Test passing invalid -onlynet=cjdns without -cjdnsreachable raises expected init error")
+ self.nodes[1].extra_args = ["-onlynet=cjdns"]
+ msg = "Error: Outbound connections restricted to CJDNS (-onlynet=cjdns) but -cjdnsreachable is not provided"
self.nodes[1].assert_start_raises_init_error(expected_msg=msg)
self.log.info("Test passing -onlynet=onion with -onion=0/-noonion raises expected init error")
+ msg = (
+ "Error: Outbound connections restricted to Tor (-onlynet=onion) but "
+ "the proxy for reaching the Tor network is explicitly forbidden: -onion=0"
+ )
for arg in ["-onion=0", "-noonion"]:
self.nodes[1].extra_args = ["-onlynet=onion", arg]
self.nodes[1].assert_start_raises_init_error(expected_msg=msg)
+ self.log.info("Test passing -onlynet=onion without -proxy, -onion or -listenonion raises expected init error")
+ self.nodes[1].extra_args = ["-onlynet=onion", "-listenonion=0"]
+ msg = (
+ "Error: Outbound connections restricted to Tor (-onlynet=onion) but the proxy for "
+ "reaching the Tor network is not provided: none of -proxy, -onion or -listenonion is given"
+ )
+ self.nodes[1].assert_start_raises_init_error(expected_msg=msg)
+
+ self.log.info("Test passing -onlynet=onion without -proxy or -onion but with -listenonion=1 is ok")
+ self.start_node(1, extra_args=["-onlynet=onion", "-listenonion=1"])
+ self.stop_node(1)
+
self.log.info("Test passing unknown network to -onlynet raises expected init error")
self.nodes[1].extra_args = ["-onlynet=abc"]
msg = "Error: Unknown network specified in -onlynet: 'abc'"
diff --git a/test/functional/feature_rbf.py b/test/functional/feature_rbf.py
index 91dc222bab..7603248ae5 100755
--- a/test/functional/feature_rbf.py
+++ b/test/functional/feature_rbf.py
@@ -4,28 +4,18 @@
# file COPYING or http://www.opensource.org/licenses/mit-license.php.
"""Test the RBF code."""
-from copy import deepcopy
from decimal import Decimal
from test_framework.messages import (
- BIP125_SEQUENCE_NUMBER,
+ MAX_BIP125_RBF_SEQUENCE,
COIN,
- COutPoint,
- CTransaction,
- CTxIn,
- CTxOut,
SEQUENCE_FINAL,
)
-from test_framework.script import CScript, OP_DROP
from test_framework.test_framework import BitcoinTestFramework
from test_framework.util import (
assert_equal,
assert_raises_rpc_error,
)
-from test_framework.script_util import (
- DUMMY_P2WPKH_SCRIPT,
- DUMMY_2_P2WPKH_SCRIPT,
-)
from test_framework.wallet import MiniWallet
from test_framework.address import ADDRESS_BCRT1_UNSPENDABLE
@@ -35,7 +25,6 @@ class ReplaceByFeeTest(BitcoinTestFramework):
self.num_nodes = 2
self.extra_args = [
[
- "-acceptnonstdtxn=1",
"-maxorphantx=1000",
"-limitancestorcount=50",
"-limitancestorsize=101",
@@ -94,17 +83,19 @@ class ReplaceByFeeTest(BitcoinTestFramework):
self.log.info("Running test replacement relay fee...")
self.test_replacement_relay_fee()
+ self.log.info("Running test full replace by fee...")
+ self.test_fullrbf()
+
self.log.info("Passed")
- def make_utxo(self, node, amount, confirmed=True, scriptPubKey=DUMMY_P2WPKH_SCRIPT):
+ def make_utxo(self, node, amount, *, confirmed=True, scriptPubKey=None):
"""Create a txout with a given amount and scriptPubKey
- confirmed - txouts created will be confirmed in the blockchain;
+ confirmed - txout created will be confirmed in the blockchain;
unconfirmed otherwise.
"""
- txid, n = self.wallet.send_to(from_node=node, scriptPubKey=scriptPubKey, amount=amount)
+ txid, n = self.wallet.send_to(from_node=node, scriptPubKey=scriptPubKey or self.wallet.get_scriptPubKey(), amount=amount)
- # If requested, ensure txouts are confirmed.
if confirmed:
mempool_size = len(node.getrawmempool())
while mempool_size > 0:
@@ -115,30 +106,24 @@ class ReplaceByFeeTest(BitcoinTestFramework):
assert new_size < mempool_size
mempool_size = new_size
- return COutPoint(int(txid, 16), n)
+ return self.wallet.get_utxo(txid=txid, vout=n)
def test_simple_doublespend(self):
"""Simple doublespend"""
# we use MiniWallet to create a transaction template with inputs correctly set,
# and modify the output (amount, scriptPubKey) according to our needs
- tx_template = self.wallet.create_self_transfer()['tx']
-
- tx1a = deepcopy(tx_template)
- tx1a.vout = [CTxOut(1 * COIN, DUMMY_P2WPKH_SCRIPT)]
- tx1a_hex = tx1a.serialize().hex()
- tx1a_txid = self.nodes[0].sendrawtransaction(tx1a_hex, 0)
+ tx = self.wallet.create_self_transfer()["tx"]
+ tx1a_txid = self.nodes[0].sendrawtransaction(tx.serialize().hex())
# Should fail because we haven't changed the fee
- tx1b = deepcopy(tx_template)
- tx1b.vout = [CTxOut(1 * COIN, DUMMY_2_P2WPKH_SCRIPT)]
- tx1b_hex = tx1b.serialize().hex()
+ tx.vout[0].scriptPubKey[-1] ^= 1
# This will raise an exception due to insufficient fee
- assert_raises_rpc_error(-26, "insufficient fee", self.nodes[0].sendrawtransaction, tx1b_hex, 0)
+ assert_raises_rpc_error(-26, "insufficient fee", self.nodes[0].sendrawtransaction, tx.serialize().hex(), 0)
# Extra 0.1 BTC fee
- tx1b.vout[0].nValue -= int(0.1 * COIN)
- tx1b_hex = tx1b.serialize().hex()
+ tx.vout[0].nValue -= int(0.1 * COIN)
+ tx1b_hex = tx.serialize().hex()
# Works when enabled
tx1b_txid = self.nodes[0].sendrawtransaction(tx1b_hex, 0)
@@ -160,28 +145,28 @@ class ReplaceByFeeTest(BitcoinTestFramework):
chain_txids = []
while remaining_value > 1 * COIN:
remaining_value -= int(0.1 * COIN)
- tx = CTransaction()
- tx.vin = [CTxIn(prevout, nSequence=0)]
- tx.vout = [CTxOut(remaining_value, CScript([1, OP_DROP] * 15 + [1]))]
- tx_hex = tx.serialize().hex()
- txid = self.nodes[0].sendrawtransaction(tx_hex, 0)
- chain_txids.append(txid)
- prevout = COutPoint(int(txid, 16), 0)
+ prevout = self.wallet.send_self_transfer(
+ from_node=self.nodes[0],
+ utxo_to_spend=prevout,
+ sequence=0,
+ fee=Decimal("0.1"),
+ )["new_utxo"]
+ chain_txids.append(prevout["txid"])
# Whether the double-spend is allowed is evaluated by including all
# child fees - 4 BTC - so this attempt is rejected.
- dbl_tx = CTransaction()
- dbl_tx.vin = [CTxIn(tx0_outpoint, nSequence=0)]
- dbl_tx.vout = [CTxOut(initial_nValue - 3 * COIN, DUMMY_P2WPKH_SCRIPT)]
+ dbl_tx = self.wallet.create_self_transfer(
+ utxo_to_spend=tx0_outpoint,
+ sequence=0,
+ fee=Decimal("3"),
+ )["tx"]
dbl_tx_hex = dbl_tx.serialize().hex()
# This will raise an exception due to insufficient fee
assert_raises_rpc_error(-26, "insufficient fee", self.nodes[0].sendrawtransaction, dbl_tx_hex, 0)
# Accepted with sufficient fee
- dbl_tx = CTransaction()
- dbl_tx.vin = [CTxIn(tx0_outpoint, nSequence=0)]
- dbl_tx.vout = [CTxOut(int(0.1 * COIN), DUMMY_P2WPKH_SCRIPT)]
+ dbl_tx.vout[0].nValue = int(0.1 * COIN)
dbl_tx_hex = dbl_tx.serialize().hex()
self.nodes[0].sendrawtransaction(dbl_tx_hex, 0)
@@ -205,22 +190,19 @@ class ReplaceByFeeTest(BitcoinTestFramework):
if txout_value < fee:
return
- vout = [CTxOut(txout_value, CScript([i+1]))
- for i in range(tree_width)]
- tx = CTransaction()
- tx.vin = [CTxIn(prevout, nSequence=0)]
- tx.vout = vout
- tx_hex = tx.serialize().hex()
+ tx = self.wallet.send_self_transfer_multi(
+ utxos_to_spend=[prevout],
+ from_node=self.nodes[0],
+ sequence=0,
+ num_outputs=tree_width,
+ amount_per_output=txout_value,
+ )
- assert len(tx.serialize()) < 100000
- txid = self.nodes[0].sendrawtransaction(tx_hex, 0)
- yield tx
+ yield tx["txid"]
_total_txs[0] += 1
- txid = int(txid, 16)
-
- for i, txout in enumerate(tx.vout):
- for x in branch(COutPoint(txid, i), txout_value,
+ for utxo in tx["new_utxos"]:
+ for x in branch(utxo, txout_value,
max_txs,
tree_width=tree_width, fee=fee,
_total_txs=_total_txs):
@@ -232,25 +214,26 @@ class ReplaceByFeeTest(BitcoinTestFramework):
assert_equal(len(tree_txs), n)
# Attempt double-spend, will fail because too little fee paid
- dbl_tx = CTransaction()
- dbl_tx.vin = [CTxIn(tx0_outpoint, nSequence=0)]
- dbl_tx.vout = [CTxOut(initial_nValue - fee * n, DUMMY_P2WPKH_SCRIPT)]
- dbl_tx_hex = dbl_tx.serialize().hex()
+ dbl_tx_hex = self.wallet.create_self_transfer(
+ utxo_to_spend=tx0_outpoint,
+ sequence=0,
+ fee=(Decimal(fee) / COIN) * n,
+ )["hex"]
# This will raise an exception due to insufficient fee
assert_raises_rpc_error(-26, "insufficient fee", self.nodes[0].sendrawtransaction, dbl_tx_hex, 0)
# 0.1 BTC fee is enough
- dbl_tx = CTransaction()
- dbl_tx.vin = [CTxIn(tx0_outpoint, nSequence=0)]
- dbl_tx.vout = [CTxOut(initial_nValue - fee * n - int(0.1 * COIN), DUMMY_P2WPKH_SCRIPT)]
- dbl_tx_hex = dbl_tx.serialize().hex()
+ dbl_tx_hex = self.wallet.create_self_transfer(
+ utxo_to_spend=tx0_outpoint,
+ sequence=0,
+ fee=(Decimal(fee) / COIN) * n + Decimal("0.1"),
+ )["hex"]
self.nodes[0].sendrawtransaction(dbl_tx_hex, 0)
mempool = self.nodes[0].getrawmempool()
- for tx in tree_txs:
- tx.rehash()
- assert tx.hash not in mempool
+ for txid in tree_txs:
+ assert txid not in mempool
# Try again, but with more total transactions than the "max txs
# double-spent at once" anti-DoS limit.
@@ -260,33 +243,36 @@ class ReplaceByFeeTest(BitcoinTestFramework):
tree_txs = list(branch(tx0_outpoint, initial_nValue, n, fee=fee))
assert_equal(len(tree_txs), n)
- dbl_tx = CTransaction()
- dbl_tx.vin = [CTxIn(tx0_outpoint, nSequence=0)]
- dbl_tx.vout = [CTxOut(initial_nValue - 2 * fee * n, DUMMY_P2WPKH_SCRIPT)]
- dbl_tx_hex = dbl_tx.serialize().hex()
+ dbl_tx_hex = self.wallet.create_self_transfer(
+ utxo_to_spend=tx0_outpoint,
+ sequence=0,
+ fee=2 * (Decimal(fee) / COIN) * n,
+ )["hex"]
# This will raise an exception
assert_raises_rpc_error(-26, "too many potential replacements", self.nodes[0].sendrawtransaction, dbl_tx_hex, 0)
- for tx in tree_txs:
- tx.rehash()
- self.nodes[0].getrawtransaction(tx.hash)
+ for txid in tree_txs:
+ self.nodes[0].getrawtransaction(txid)
def test_replacement_feeperkb(self):
"""Replacement requires fee-per-KB to be higher"""
tx0_outpoint = self.make_utxo(self.nodes[0], int(1.1 * COIN))
- tx1a = CTransaction()
- tx1a.vin = [CTxIn(tx0_outpoint, nSequence=0)]
- tx1a.vout = [CTxOut(1 * COIN, DUMMY_P2WPKH_SCRIPT)]
- tx1a_hex = tx1a.serialize().hex()
- self.nodes[0].sendrawtransaction(tx1a_hex, 0)
+ self.wallet.send_self_transfer(
+ from_node=self.nodes[0],
+ utxo_to_spend=tx0_outpoint,
+ sequence=0,
+ fee=Decimal("0.1"),
+ )
# Higher fee, but the fee per KB is much lower, so the replacement is
# rejected.
- tx1b = CTransaction()
- tx1b.vin = [CTxIn(tx0_outpoint, nSequence=0)]
- tx1b.vout = [CTxOut(int(0.001 * COIN), CScript([b'a' * 999000]))]
- tx1b_hex = tx1b.serialize().hex()
+ tx1b_hex = self.wallet.create_self_transfer_multi(
+ utxos_to_spend=[tx0_outpoint],
+ sequence=0,
+ num_outputs=100,
+ amount_per_output=1000,
+ )["hex"]
# This will raise an exception due to insufficient fee
assert_raises_rpc_error(-26, "insufficient fee", self.nodes[0].sendrawtransaction, tx1b_hex, 0)
@@ -296,37 +282,36 @@ class ReplaceByFeeTest(BitcoinTestFramework):
utxo1 = self.make_utxo(self.nodes[0], int(1.2 * COIN))
utxo2 = self.make_utxo(self.nodes[0], 3 * COIN)
- tx1a = CTransaction()
- tx1a.vin = [CTxIn(utxo1, nSequence=0)]
- tx1a.vout = [CTxOut(int(1.1 * COIN), DUMMY_P2WPKH_SCRIPT)]
- tx1a_hex = tx1a.serialize().hex()
- tx1a_txid = self.nodes[0].sendrawtransaction(tx1a_hex, 0)
-
- tx1a_txid = int(tx1a_txid, 16)
+ tx1a_utxo = self.wallet.send_self_transfer(
+ from_node=self.nodes[0],
+ utxo_to_spend=utxo1,
+ sequence=0,
+ fee=Decimal("0.1"),
+ )["new_utxo"]
# Direct spend an output of the transaction we're replacing.
- tx2 = CTransaction()
- tx2.vin = [CTxIn(utxo1, nSequence=0), CTxIn(utxo2, nSequence=0)]
- tx2.vin.append(CTxIn(COutPoint(tx1a_txid, 0), nSequence=0))
- tx2.vout = tx1a.vout
- tx2_hex = tx2.serialize().hex()
+ tx2_hex = self.wallet.create_self_transfer_multi(
+ utxos_to_spend=[utxo1, utxo2, tx1a_utxo],
+ sequence=0,
+ amount_per_output=int(COIN * tx1a_utxo["value"]),
+ )["hex"]
# This will raise an exception
assert_raises_rpc_error(-26, "bad-txns-spends-conflicting-tx", self.nodes[0].sendrawtransaction, tx2_hex, 0)
# Spend tx1a's output to test the indirect case.
- tx1b = CTransaction()
- tx1b.vin = [CTxIn(COutPoint(tx1a_txid, 0), nSequence=0)]
- tx1b.vout = [CTxOut(1 * COIN, DUMMY_P2WPKH_SCRIPT)]
- tx1b_hex = tx1b.serialize().hex()
- tx1b_txid = self.nodes[0].sendrawtransaction(tx1b_hex, 0)
- tx1b_txid = int(tx1b_txid, 16)
+ tx1b_utxo = self.wallet.send_self_transfer(
+ from_node=self.nodes[0],
+ utxo_to_spend=tx1a_utxo,
+ sequence=0,
+ fee=Decimal("0.1"),
+ )["new_utxo"]
- tx2 = CTransaction()
- tx2.vin = [CTxIn(utxo1, nSequence=0), CTxIn(utxo2, nSequence=0),
- CTxIn(COutPoint(tx1b_txid, 0))]
- tx2.vout = tx1a.vout
- tx2_hex = tx2.serialize().hex()
+ tx2_hex = self.wallet.create_self_transfer_multi(
+ utxos_to_spend=[utxo1, utxo2, tx1b_utxo],
+ sequence=0,
+ amount_per_output=int(COIN * tx1a_utxo["value"]),
+ )["hex"]
# This will raise an exception
assert_raises_rpc_error(-26, "bad-txns-spends-conflicting-tx", self.nodes[0].sendrawtransaction, tx2_hex, 0)
@@ -334,18 +319,20 @@ class ReplaceByFeeTest(BitcoinTestFramework):
def test_new_unconfirmed_inputs(self):
"""Replacements that add new unconfirmed inputs are rejected"""
confirmed_utxo = self.make_utxo(self.nodes[0], int(1.1 * COIN))
- unconfirmed_utxo = self.make_utxo(self.nodes[0], int(0.1 * COIN), False)
+ unconfirmed_utxo = self.make_utxo(self.nodes[0], int(0.1 * COIN), confirmed=False)
- tx1 = CTransaction()
- tx1.vin = [CTxIn(confirmed_utxo)]
- tx1.vout = [CTxOut(1 * COIN, DUMMY_P2WPKH_SCRIPT)]
- tx1_hex = tx1.serialize().hex()
- self.nodes[0].sendrawtransaction(tx1_hex, 0)
+ self.wallet.send_self_transfer(
+ from_node=self.nodes[0],
+ utxo_to_spend=confirmed_utxo,
+ sequence=0,
+ fee=Decimal("0.1"),
+ )
- tx2 = CTransaction()
- tx2.vin = [CTxIn(confirmed_utxo), CTxIn(unconfirmed_utxo)]
- tx2.vout = tx1.vout
- tx2_hex = tx2.serialize().hex()
+ tx2_hex = self.wallet.create_self_transfer_multi(
+ utxos_to_spend=[confirmed_utxo, unconfirmed_utxo],
+ sequence=0,
+ amount_per_output=1 * COIN,
+ )["hex"]
# This will raise an exception
assert_raises_rpc_error(-26, "replacement-adds-unconfirmed", self.nodes[0].sendrawtransaction, tx2_hex, 0)
@@ -361,51 +348,45 @@ class ReplaceByFeeTest(BitcoinTestFramework):
fee = int(0.0001 * COIN)
split_value = int((initial_nValue - fee) / (MAX_REPLACEMENT_LIMIT + 1))
- outputs = []
- for _ in range(MAX_REPLACEMENT_LIMIT + 1):
- outputs.append(CTxOut(split_value, CScript([1])))
-
- splitting_tx = CTransaction()
- splitting_tx.vin = [CTxIn(utxo, nSequence=0)]
- splitting_tx.vout = outputs
- splitting_tx_hex = splitting_tx.serialize().hex()
-
- txid = self.nodes[0].sendrawtransaction(splitting_tx_hex, 0)
- txid = int(txid, 16)
+ splitting_tx_utxos = self.wallet.send_self_transfer_multi(
+ from_node=self.nodes[0],
+ utxos_to_spend=[utxo],
+ sequence=0,
+ num_outputs=MAX_REPLACEMENT_LIMIT + 1,
+ amount_per_output=split_value,
+ )["new_utxos"]
# Now spend each of those outputs individually
- for i in range(MAX_REPLACEMENT_LIMIT + 1):
- tx_i = CTransaction()
- tx_i.vin = [CTxIn(COutPoint(txid, i), nSequence=0)]
- tx_i.vout = [CTxOut(split_value - fee, DUMMY_P2WPKH_SCRIPT)]
- tx_i_hex = tx_i.serialize().hex()
- self.nodes[0].sendrawtransaction(tx_i_hex, 0)
+ for utxo in splitting_tx_utxos:
+ self.wallet.send_self_transfer(
+ from_node=self.nodes[0],
+ utxo_to_spend=utxo,
+ sequence=0,
+ fee=Decimal(fee) / COIN,
+ )
# Now create doublespend of the whole lot; should fail.
# Need a big enough fee to cover all spending transactions and have
# a higher fee rate
double_spend_value = (split_value - 100 * fee) * (MAX_REPLACEMENT_LIMIT + 1)
- inputs = []
- for i in range(MAX_REPLACEMENT_LIMIT + 1):
- inputs.append(CTxIn(COutPoint(txid, i), nSequence=0))
- double_tx = CTransaction()
- double_tx.vin = inputs
- double_tx.vout = [CTxOut(double_spend_value, CScript([b'a']))]
+ double_tx = self.wallet.create_self_transfer_multi(
+ utxos_to_spend=splitting_tx_utxos,
+ sequence=0,
+ amount_per_output=double_spend_value,
+ )["tx"]
double_tx_hex = double_tx.serialize().hex()
# This will raise an exception
assert_raises_rpc_error(-26, "too many potential replacements", self.nodes[0].sendrawtransaction, double_tx_hex, 0)
# If we remove an input, it should pass
- double_tx = CTransaction()
- double_tx.vin = inputs[0:-1]
- double_tx.vout = [CTxOut(double_spend_value, CScript([b'a']))]
+ double_tx.vin.pop()
double_tx_hex = double_tx.serialize().hex()
self.nodes[0].sendrawtransaction(double_tx_hex, 0)
def test_too_many_replacements_with_default_mempool_params(self):
"""
- Test rule 5 of BIP125 (do not allow replacements that cause more than 100
+ Test rule 5 (do not allow replacements that cause more than 100
evictions) without having to rely on non-default mempool parameters.
In order to do this, create a number of "root" UTXOs, and then hang
@@ -424,7 +405,7 @@ class ReplaceByFeeTest(BitcoinTestFramework):
# limit; 10 works.
num_tx_graphs = 10
- # (Number of transactions per graph, BIP125 rule 5 failure expected)
+ # (Number of transactions per graph, rule 5 failure expected)
cases = [
# Test the base case of evicting fewer than MAX_REPLACEMENT_LIMIT
# transactions.
@@ -447,7 +428,7 @@ class ReplaceByFeeTest(BitcoinTestFramework):
optin_parent_tx = wallet.send_self_transfer_multi(
from_node=normal_node,
- sequence=BIP125_SEQUENCE_NUMBER,
+ sequence=MAX_BIP125_RBF_SEQUENCE,
utxos_to_spend=[root_utxos[graph_num]],
num_outputs=txs_per_graph,
)
@@ -494,20 +475,22 @@ class ReplaceByFeeTest(BitcoinTestFramework):
tx0_outpoint = self.make_utxo(self.nodes[0], int(1.1 * COIN))
# Create a non-opting in transaction
- tx1a = CTransaction()
- tx1a.vin = [CTxIn(tx0_outpoint, nSequence=SEQUENCE_FINAL)]
- tx1a.vout = [CTxOut(1 * COIN, DUMMY_P2WPKH_SCRIPT)]
- tx1a_hex = tx1a.serialize().hex()
- tx1a_txid = self.nodes[0].sendrawtransaction(tx1a_hex, 0)
+ tx1a_utxo = self.wallet.send_self_transfer(
+ from_node=self.nodes[0],
+ utxo_to_spend=tx0_outpoint,
+ sequence=SEQUENCE_FINAL,
+ fee=Decimal("0.1"),
+ )["new_utxo"]
# This transaction isn't shown as replaceable
- assert_equal(self.nodes[0].getmempoolentry(tx1a_txid)['bip125-replaceable'], False)
+ assert_equal(self.nodes[0].getmempoolentry(tx1a_utxo["txid"])['bip125-replaceable'], False)
# Shouldn't be able to double-spend
- tx1b = CTransaction()
- tx1b.vin = [CTxIn(tx0_outpoint, nSequence=0)]
- tx1b.vout = [CTxOut(int(0.9 * COIN), DUMMY_P2WPKH_SCRIPT)]
- tx1b_hex = tx1b.serialize().hex()
+ tx1b_hex = self.wallet.create_self_transfer(
+ utxo_to_spend=tx0_outpoint,
+ sequence=0,
+ fee=Decimal("0.2"),
+ )["hex"]
# This will raise an exception
assert_raises_rpc_error(-26, "txn-mempool-conflict", self.nodes[0].sendrawtransaction, tx1b_hex, 0)
@@ -515,17 +498,19 @@ class ReplaceByFeeTest(BitcoinTestFramework):
tx1_outpoint = self.make_utxo(self.nodes[0], int(1.1 * COIN))
# Create a different non-opting in transaction
- tx2a = CTransaction()
- tx2a.vin = [CTxIn(tx1_outpoint, nSequence=0xfffffffe)]
- tx2a.vout = [CTxOut(1 * COIN, DUMMY_P2WPKH_SCRIPT)]
- tx2a_hex = tx2a.serialize().hex()
- tx2a_txid = self.nodes[0].sendrawtransaction(tx2a_hex, 0)
+ tx2a_utxo = self.wallet.send_self_transfer(
+ from_node=self.nodes[0],
+ utxo_to_spend=tx1_outpoint,
+ sequence=0xfffffffe,
+ fee=Decimal("0.1"),
+ )["new_utxo"]
# Still shouldn't be able to double-spend
- tx2b = CTransaction()
- tx2b.vin = [CTxIn(tx1_outpoint, nSequence=0)]
- tx2b.vout = [CTxOut(int(0.9 * COIN), DUMMY_P2WPKH_SCRIPT)]
- tx2b_hex = tx2b.serialize().hex()
+ tx2b_hex = self.wallet.create_self_transfer(
+ utxo_to_spend=tx1_outpoint,
+ sequence=0,
+ fee=Decimal("0.2"),
+ )["hex"]
# This will raise an exception
assert_raises_rpc_error(-26, "txn-mempool-conflict", self.nodes[0].sendrawtransaction, tx2b_hex, 0)
@@ -534,34 +519,31 @@ class ReplaceByFeeTest(BitcoinTestFramework):
# opt-in on one of the inputs
# Transaction should be replaceable on either input
- tx1a_txid = int(tx1a_txid, 16)
- tx2a_txid = int(tx2a_txid, 16)
-
- tx3a = CTransaction()
- tx3a.vin = [CTxIn(COutPoint(tx1a_txid, 0), nSequence=SEQUENCE_FINAL),
- CTxIn(COutPoint(tx2a_txid, 0), nSequence=0xfffffffd)]
- tx3a.vout = [CTxOut(int(0.9 * COIN), CScript([b'c'])), CTxOut(int(0.9 * COIN), CScript([b'd']))]
- tx3a_hex = tx3a.serialize().hex()
-
- tx3a_txid = self.nodes[0].sendrawtransaction(tx3a_hex, 0)
+ tx3a_txid = self.wallet.send_self_transfer_multi(
+ from_node=self.nodes[0],
+ utxos_to_spend=[tx1a_utxo, tx2a_utxo],
+ sequence=[SEQUENCE_FINAL, 0xfffffffd],
+ fee_per_output=int(0.1 * COIN),
+ )["txid"]
# This transaction is shown as replaceable
assert_equal(self.nodes[0].getmempoolentry(tx3a_txid)['bip125-replaceable'], True)
- tx3b = CTransaction()
- tx3b.vin = [CTxIn(COutPoint(tx1a_txid, 0), nSequence=0)]
- tx3b.vout = [CTxOut(int(0.5 * COIN), DUMMY_P2WPKH_SCRIPT)]
- tx3b_hex = tx3b.serialize().hex()
-
- tx3c = CTransaction()
- tx3c.vin = [CTxIn(COutPoint(tx2a_txid, 0), nSequence=0)]
- tx3c.vout = [CTxOut(int(0.5 * COIN), DUMMY_P2WPKH_SCRIPT)]
- tx3c_hex = tx3c.serialize().hex()
+ self.wallet.send_self_transfer(
+ from_node=self.nodes[0],
+ utxo_to_spend=tx1a_utxo,
+ sequence=0,
+ fee=Decimal("0.4"),
+ )
- self.nodes[0].sendrawtransaction(tx3b_hex, 0)
# If tx3b was accepted, tx3c won't look like a replacement,
# but make sure it is accepted anyway
- self.nodes[0].sendrawtransaction(tx3c_hex, 0)
+ self.wallet.send_self_transfer(
+ from_node=self.nodes[0],
+ utxo_to_spend=tx2a_utxo,
+ sequence=0,
+ fee=Decimal("0.4"),
+ )
def test_prioritised_transactions(self):
# Ensure that fee deltas used via prioritisetransaction are
@@ -570,17 +552,20 @@ class ReplaceByFeeTest(BitcoinTestFramework):
# 1. Check that feeperkb uses modified fees
tx0_outpoint = self.make_utxo(self.nodes[0], int(1.1 * COIN))
- tx1a = CTransaction()
- tx1a.vin = [CTxIn(tx0_outpoint, nSequence=0)]
- tx1a.vout = [CTxOut(1 * COIN, DUMMY_P2WPKH_SCRIPT)]
- tx1a_hex = tx1a.serialize().hex()
- tx1a_txid = self.nodes[0].sendrawtransaction(tx1a_hex, 0)
+ tx1a_txid = self.wallet.send_self_transfer(
+ from_node=self.nodes[0],
+ utxo_to_spend=tx0_outpoint,
+ sequence=0,
+ fee=Decimal("0.1"),
+ )["txid"]
# Higher fee, but the actual fee per KB is much lower.
- tx1b = CTransaction()
- tx1b.vin = [CTxIn(tx0_outpoint, nSequence=0)]
- tx1b.vout = [CTxOut(int(0.001 * COIN), CScript([b'a' * 740000]))]
- tx1b_hex = tx1b.serialize().hex()
+ tx1b_hex = self.wallet.create_self_transfer_multi(
+ utxos_to_spend=[tx0_outpoint],
+ sequence=0,
+ num_outputs=100,
+ amount_per_output=int(0.00001 * COIN),
+ )["hex"]
# Verify tx1b cannot replace tx1a.
assert_raises_rpc_error(-26, "insufficient fee", self.nodes[0].sendrawtransaction, tx1b_hex, 0)
@@ -596,27 +581,29 @@ class ReplaceByFeeTest(BitcoinTestFramework):
# 2. Check that absolute fee checks use modified fee.
tx1_outpoint = self.make_utxo(self.nodes[0], int(1.1 * COIN))
- tx2a = CTransaction()
- tx2a.vin = [CTxIn(tx1_outpoint, nSequence=0)]
- tx2a.vout = [CTxOut(1 * COIN, DUMMY_P2WPKH_SCRIPT)]
- tx2a_hex = tx2a.serialize().hex()
- self.nodes[0].sendrawtransaction(tx2a_hex, 0)
+ # tx2a
+ self.wallet.send_self_transfer(
+ from_node=self.nodes[0],
+ utxo_to_spend=tx1_outpoint,
+ sequence=0,
+ fee=Decimal("0.1"),
+ )
# Lower fee, but we'll prioritise it
- tx2b = CTransaction()
- tx2b.vin = [CTxIn(tx1_outpoint, nSequence=0)]
- tx2b.vout = [CTxOut(int(1.01 * COIN), DUMMY_P2WPKH_SCRIPT)]
- tx2b.rehash()
- tx2b_hex = tx2b.serialize().hex()
+ tx2b = self.wallet.create_self_transfer(
+ utxo_to_spend=tx1_outpoint,
+ sequence=0,
+ fee=Decimal("0.09"),
+ )
# Verify tx2b cannot replace tx2a.
- assert_raises_rpc_error(-26, "insufficient fee", self.nodes[0].sendrawtransaction, tx2b_hex, 0)
+ assert_raises_rpc_error(-26, "insufficient fee", self.nodes[0].sendrawtransaction, tx2b["hex"], 0)
# Now prioritise tx2b to have a higher modified fee
- self.nodes[0].prioritisetransaction(txid=tx2b.hash, fee_delta=int(0.1 * COIN))
+ self.nodes[0].prioritisetransaction(txid=tx2b["txid"], fee_delta=int(0.1 * COIN))
# tx2b should now be accepted
- tx2b_txid = self.nodes[0].sendrawtransaction(tx2b_hex, 0)
+ tx2b_txid = self.nodes[0].sendrawtransaction(tx2b["hex"], 0)
assert tx2b_txid in self.nodes[0].getrawmempool()
@@ -649,14 +636,14 @@ class ReplaceByFeeTest(BitcoinTestFramework):
optin_parent_tx = self.wallet.send_self_transfer(
from_node=self.nodes[0],
utxo_to_spend=confirmed_utxo,
- sequence=BIP125_SEQUENCE_NUMBER,
+ sequence=MAX_BIP125_RBF_SEQUENCE,
fee_rate=Decimal('0.01'),
)
assert_equal(True, self.nodes[0].getmempoolentry(optin_parent_tx['txid'])['bip125-replaceable'])
replacement_parent_tx = self.wallet.create_self_transfer(
utxo_to_spend=confirmed_utxo,
- sequence=BIP125_SEQUENCE_NUMBER,
+ sequence=MAX_BIP125_RBF_SEQUENCE,
fee_rate=Decimal('0.02'),
)
@@ -714,5 +701,33 @@ class ReplaceByFeeTest(BitcoinTestFramework):
tx.vout[0].nValue -= 1
assert_raises_rpc_error(-26, "insufficient fee", self.nodes[0].sendrawtransaction, tx.serialize().hex())
+ def test_fullrbf(self):
+
+ confirmed_utxo = self.make_utxo(self.nodes[0], int(2 * COIN))
+ self.restart_node(0, extra_args=["-mempoolfullrbf=1"])
+ assert self.nodes[0].getmempoolinfo()["fullrbf"]
+
+ # Create an explicitly opt-out transaction
+ optout_tx = self.wallet.send_self_transfer(
+ from_node=self.nodes[0],
+ utxo_to_spend=confirmed_utxo,
+ sequence=MAX_BIP125_RBF_SEQUENCE + 1,
+ fee_rate=Decimal('0.01'),
+ )
+ assert_equal(False, self.nodes[0].getmempoolentry(optout_tx['txid'])['bip125-replaceable'])
+
+ conflicting_tx = self.wallet.create_self_transfer(
+ utxo_to_spend=confirmed_utxo,
+ sequence=SEQUENCE_FINAL,
+ fee_rate=Decimal('0.02'),
+ )
+
+ # Send the replacement transaction, conflicting with the optout_tx.
+ self.nodes[0].sendrawtransaction(conflicting_tx['hex'], 0)
+
+ # Optout_tx is not anymore in the mempool.
+ assert optout_tx['txid'] not in self.nodes[0].getrawmempool()
+ assert conflicting_tx['txid'] in self.nodes[0].getrawmempool()
+
if __name__ == '__main__':
ReplaceByFeeTest().main()
diff --git a/test/functional/feature_segwit.py b/test/functional/feature_segwit.py
index f0faf1421b..7f2a615be1 100755
--- a/test/functional/feature_segwit.py
+++ b/test/functional/feature_segwit.py
@@ -609,6 +609,11 @@ class SegWitTest(BitcoinTestFramework):
assert_equal(self.nodes[1].gettransaction(txid, True)["txid"], txid)
assert_equal(self.nodes[1].listtransactions("*", 1, 0, True)[0]["txid"], txid)
+ self.log.info('Test negative and unknown rpcserialversion throw an init error')
+ self.stop_node(0)
+ self.nodes[0].assert_start_raises_init_error(["-rpcserialversion=-1"], "Error: rpcserialversion must be non-negative.")
+ self.nodes[0].assert_start_raises_init_error(["-rpcserialversion=100"], "Error: Unknown rpcserialversion requested.")
+
def mine_and_test_listunspent(self, script_list, ismine):
utxo = find_spendable_utxo(self.nodes[0], 50)
tx = CTransaction()
diff --git a/test/functional/feature_taproot.py b/test/functional/feature_taproot.py
index 0e44038196..cbb2e0338b 100755
--- a/test/functional/feature_taproot.py
+++ b/test/functional/feature_taproot.py
@@ -91,7 +91,11 @@ from test_framework.script_util import (
script_to_p2wsh_script,
)
from test_framework.test_framework import BitcoinTestFramework
-from test_framework.util import assert_raises_rpc_error, assert_equal
+from test_framework.util import (
+ assert_raises_rpc_error,
+ assert_equal,
+ random_bytes,
+)
from test_framework.key import generate_privkey, compute_xonly_pubkey, sign_schnorr, tweak_add_privkey, ECKey
from test_framework.address import (
hash160,
@@ -566,10 +570,6 @@ def random_checksig_style(pubkey):
ret = CScript([pubkey, opcode])
return bytes(ret)
-def random_bytes(n):
- """Return a random bytes object of length n."""
- return bytes(random.getrandbits(8) for i in range(n))
-
def bitflipper(expr):
"""Return a callable that evaluates expr and returns it with a random bitflip."""
def fn(ctx):
@@ -1007,13 +1007,13 @@ def spenders_taproot_active():
# input a valid signature with the passed pk followed by a dummy push of bytes that are to be dropped, and
# will execute sigops signature checks.
SIGOPS_RATIO_SCRIPTS = [
- # n OP_CHECKSIGVERFIYs and 1 OP_CHECKSIG.
+ # n OP_CHECKSIGVERIFYs and 1 OP_CHECKSIG.
lambda n, pk: (CScript([OP_DROP, pk] + [OP_2DUP, OP_CHECKSIGVERIFY] * n + [OP_CHECKSIG]), n + 1),
# n OP_CHECKSIGVERIFYs and 1 OP_CHECKSIGADD, but also one unexecuted OP_CHECKSIGVERIFY.
lambda n, pk: (CScript([OP_DROP, pk, OP_0, OP_IF, OP_2DUP, OP_CHECKSIGVERIFY, OP_ENDIF] + [OP_2DUP, OP_CHECKSIGVERIFY] * n + [OP_2, OP_SWAP, OP_CHECKSIGADD, OP_3, OP_EQUAL]), n + 1),
# n OP_CHECKSIGVERIFYs and 1 OP_CHECKSIGADD, but also one unexecuted OP_CHECKSIG.
lambda n, pk: (CScript([random_bytes(220), OP_2DROP, pk, OP_1, OP_NOTIF, OP_2DUP, OP_CHECKSIG, OP_VERIFY, OP_ENDIF] + [OP_2DUP, OP_CHECKSIGVERIFY] * n + [OP_4, OP_SWAP, OP_CHECKSIGADD, OP_5, OP_EQUAL]), n + 1),
- # n OP_CHECKSIGVERFIYs and 1 OP_CHECKSIGADD, but also one unexecuted OP_CHECKSIGADD.
+ # n OP_CHECKSIGVERIFYs and 1 OP_CHECKSIGADD, but also one unexecuted OP_CHECKSIGADD.
lambda n, pk: (CScript([OP_DROP, pk, OP_1, OP_IF, OP_ELSE, OP_2DUP, OP_6, OP_SWAP, OP_CHECKSIGADD, OP_7, OP_EQUALVERIFY, OP_ENDIF] + [OP_2DUP, OP_CHECKSIGVERIFY] * n + [OP_8, OP_SWAP, OP_CHECKSIGADD, OP_9, OP_EQUAL]), n + 1),
# n+1 OP_CHECKSIGs, but also one OP_CHECKSIG with an empty signature.
lambda n, pk: (CScript([OP_DROP, OP_0, pk, OP_CHECKSIG, OP_NOT, OP_VERIFY, pk] + [OP_2DUP, OP_CHECKSIG, OP_VERIFY] * n + [OP_CHECKSIG]), n + 1),
@@ -1131,6 +1131,12 @@ def spenders_taproot_active():
tap = taproot_construct(pubs[0], scripts)
add_spender(spenders, "alwaysvalid/notsuccessx", tap=tap, leaf="op_success", inputs=[], standard=False, failure={"leaf": "normal"}) # err_msg differs based on opcode
+ # == Test case for https://github.com/bitcoin/bitcoin/issues/24765 ==
+
+ zero_fn = lambda h: bytes([0 for _ in range(32)])
+ tap = taproot_construct(pubs[0], [("leaf", CScript([pubs[1], OP_CHECKSIG, pubs[1], OP_CHECKSIGADD, OP_2, OP_EQUAL])), zero_fn])
+ add_spender(spenders, "case24765", tap=tap, leaf="leaf", inputs=[getter("sign"), getter("sign")], key=secs[1], no_fail=True)
+
# == Legacy tests ==
# Also add a few legacy spends into the mix, so that transactions which combine taproot and pre-taproot spends get tested too.
diff --git a/test/functional/interface_rest.py b/test/functional/interface_rest.py
index f36bbda3af..24252610be 100755
--- a/test/functional/interface_rest.py
+++ b/test/functional/interface_rest.py
@@ -288,6 +288,10 @@ class RESTTest (BitcoinTestFramework):
# See if we can get 5 headers in one response
self.generate(self.nodes[1], 5)
+ expected_filter = {
+ 'basic block filter index': {'synced': True, 'best_block_height': 208},
+ }
+ self.wait_until(lambda: self.nodes[0].getindexinfo() == expected_filter)
json_obj = self.test_rest_request(f"/headers/{bb_hash}", query_params={"count": 5})
assert_equal(len(json_obj), 5) # now we should have 5 header objects
json_obj = self.test_rest_request(f"/blockfilterheaders/basic/{bb_hash}", query_params={"count": 5})
@@ -383,6 +387,17 @@ class RESTTest (BitcoinTestFramework):
assert_equal(self.test_rest_request(f"/headers/{bb_hash}", query_params={"count": 1}), self.test_rest_request(f"/headers/1/{bb_hash}"))
assert_equal(self.test_rest_request(f"/blockfilterheaders/basic/{bb_hash}", query_params={"count": 1}), self.test_rest_request(f"/blockfilterheaders/basic/5/{bb_hash}"))
+ self.log.info("Test the /deploymentinfo URI")
+
+ deployment_info = self.nodes[0].getdeploymentinfo()
+ assert_equal(deployment_info, self.test_rest_request('/deploymentinfo'))
+
+ non_existing_blockhash = '42759cde25462784395a337460bde75f58e73d3f08bd31fdc3507cbac856a2c4'
+ resp = self.test_rest_request(f'/deploymentinfo/{non_existing_blockhash}', ret_type=RetType.OBJ, status=400)
+ assert_equal(resp.read().decode('utf-8').rstrip(), "Block not found")
+
+ resp = self.test_rest_request(f"/deploymentinfo/{INVALID_PARAM}", ret_type=RetType.OBJ, status=400)
+ assert_equal(resp.read().decode('utf-8').rstrip(), f"Invalid hash: {INVALID_PARAM}")
if __name__ == '__main__':
RESTTest().main()
diff --git a/test/functional/interface_usdt_net.py b/test/functional/interface_usdt_net.py
index 9522cd8c59..2235da702b 100755
--- a/test/functional/interface_usdt_net.py
+++ b/test/functional/interface_usdt_net.py
@@ -109,7 +109,7 @@ class NetTracepointTest(BitcoinTestFramework):
self.log.info(
"hook into the net:inbound_message and net:outbound_message tracepoints")
- ctx = USDT(path=str(self.options.bitcoind))
+ ctx = USDT(pid=self.nodes[0].process.pid)
ctx.enable_probe(probe="net:inbound_message",
fn_name="trace_inbound_message")
ctx.enable_probe(probe="net:outbound_message",
diff --git a/test/functional/interface_usdt_utxocache.py b/test/functional/interface_usdt_utxocache.py
index f48ff9699d..2280de1479 100755
--- a/test/functional/interface_usdt_utxocache.py
+++ b/test/functional/interface_usdt_utxocache.py
@@ -173,7 +173,7 @@ class UTXOCacheTracepointTest(BitcoinTestFramework):
invalid_tx.vin[0].prevout.hash = int(block_1_coinbase_txid, 16)
self.log.info("hooking into the utxocache:uncache tracepoint")
- ctx = USDT(path=str(self.options.bitcoind))
+ ctx = USDT(pid=self.nodes[0].process.pid)
ctx.enable_probe(probe="utxocache:uncache",
fn_name="trace_utxocache_uncache")
bpf = BPF(text=utxocache_changes_program, usdt_contexts=[ctx], debug=0)
@@ -238,7 +238,7 @@ class UTXOCacheTracepointTest(BitcoinTestFramework):
self.log.info(
"hook into the utxocache:add and utxocache:spent tracepoints")
- ctx = USDT(path=str(self.options.bitcoind))
+ ctx = USDT(pid=self.nodes[0].process.pid)
ctx.enable_probe(probe="utxocache:add", fn_name="trace_utxocache_add")
ctx.enable_probe(probe="utxocache:spent",
fn_name="trace_utxocache_spent")
@@ -334,7 +334,7 @@ class UTXOCacheTracepointTest(BitcoinTestFramework):
self.log.info("test the utxocache:flush tracepoint API")
self.log.info("hook into the utxocache:flush tracepoint")
- ctx = USDT(path=str(self.options.bitcoind))
+ ctx = USDT(pid=self.nodes[0].process.pid)
ctx.enable_probe(probe="utxocache:flush",
fn_name="trace_utxocache_flush")
bpf = BPF(text=utxocache_flushes_program, usdt_contexts=[ctx], debug=0)
@@ -345,16 +345,17 @@ class UTXOCacheTracepointTest(BitcoinTestFramework):
# that the handle_* functions succeeded.
EXPECTED_HANDLE_FLUSH_SUCCESS = 3
handle_flush_succeeds = 0
- possible_cache_sizes = set()
- expected_flushes = []
+ expected_flushes = list()
def handle_utxocache_flush(_, data, __):
nonlocal handle_flush_succeeds
event = ctypes.cast(data, ctypes.POINTER(UTXOCacheFlush)).contents
self.log.info(f"handle_utxocache_flush(): {event}")
- expected = expected_flushes.pop(0)
- assert_equal(expected["mode"], FLUSHMODE_NAME[event.mode])
- possible_cache_sizes.remove(event.size) # fails if size not in set
+ expected_flushes.remove({
+ "mode": FLUSHMODE_NAME[event.mode],
+ "for_prune": event.for_prune,
+ "size": event.size
+ })
# sanity checks only
assert(event.memory > 0)
assert(event.duration > 0)
@@ -363,20 +364,19 @@ class UTXOCacheTracepointTest(BitcoinTestFramework):
bpf["utxocache_flush"].open_perf_buffer(handle_utxocache_flush)
self.log.info("stop the node to flush the UTXO cache")
- UTXOS_IN_CACHE = 104 # might need to be changed if the eariler tests are modified
+ UTXOS_IN_CACHE = 2 # might need to be changed if the eariler tests are modified
# A node shutdown causes two flushes. One that flushes UTXOS_IN_CACHE
# UTXOs and one that flushes 0 UTXOs. Normally the 0-UTXO-flush is the
# second flush, however it can happen that the order changes.
- possible_cache_sizes = {UTXOS_IN_CACHE, 0}
- flush_for_shutdown = {"mode": "ALWAYS", "for_prune": False}
- expected_flushes.extend([flush_for_shutdown, flush_for_shutdown])
+ expected_flushes.append({"mode": "ALWAYS", "for_prune": False, "size": UTXOS_IN_CACHE})
+ expected_flushes.append({"mode": "ALWAYS", "for_prune": False, "size": 0})
self.stop_node(0)
bpf.perf_buffer_poll(timeout=200)
+ bpf.cleanup()
self.log.info("check that we don't expect additional flushes")
assert_equal(0, len(expected_flushes))
- assert_equal(0, len(possible_cache_sizes))
self.log.info("restart the node with -prune")
self.start_node(0, ["-fastprune=1", "-prune=1"])
@@ -384,12 +384,17 @@ class UTXOCacheTracepointTest(BitcoinTestFramework):
BLOCKS_TO_MINE = 350
self.log.info(f"mine {BLOCKS_TO_MINE} blocks to be able to prune")
self.generate(self.wallet, BLOCKS_TO_MINE)
- # we added BLOCKS_TO_MINE coinbase UTXOs to the cache
- possible_cache_sizes = {BLOCKS_TO_MINE}
- expected_flushes.append(
- {"mode": "NONE", "for_prune": True, "size_fn": lambda x: x == BLOCKS_TO_MINE})
+
+ self.log.info("test the utxocache:flush tracepoint API with pruning")
+ self.log.info("hook into the utxocache:flush tracepoint")
+ ctx = USDT(pid=self.nodes[0].process.pid)
+ ctx.enable_probe(probe="utxocache:flush",
+ fn_name="trace_utxocache_flush")
+ bpf = BPF(text=utxocache_flushes_program, usdt_contexts=[ctx], debug=0)
+ bpf["utxocache_flush"].open_perf_buffer(handle_utxocache_flush)
self.log.info(f"prune blockchain to trigger a flush for pruning")
+ expected_flushes.append({"mode": "NONE", "for_prune": True, "size": 0})
self.nodes[0].pruneblockchain(315)
bpf.perf_buffer_poll(timeout=500)
@@ -398,7 +403,6 @@ class UTXOCacheTracepointTest(BitcoinTestFramework):
self.log.info(
f"check that we don't expect additional flushes and that the handle_* function succeeded")
assert_equal(0, len(expected_flushes))
- assert_equal(0, len(possible_cache_sizes))
assert_equal(EXPECTED_HANDLE_FLUSH_SUCCESS, handle_flush_succeeds)
diff --git a/test/functional/interface_usdt_validation.py b/test/functional/interface_usdt_validation.py
index d11809273b..8953dd023b 100755
--- a/test/functional/interface_usdt_validation.py
+++ b/test/functional/interface_usdt_validation.py
@@ -91,10 +91,10 @@ class ValidationTracepointTest(BitcoinTestFramework):
# that the handle_* functions succeeded.
BLOCKS_EXPECTED = 2
blocks_checked = 0
- expected_blocks = list()
+ expected_blocks = dict()
self.log.info("hook into the validation:block_connected tracepoint")
- ctx = USDT(path=str(self.options.bitcoind))
+ ctx = USDT(pid=self.nodes[0].process.pid)
ctx.enable_probe(probe="validation:block_connected",
fn_name="trace_block_connected")
bpf = BPF(text=validation_blockconnected_program,
@@ -104,15 +104,16 @@ class ValidationTracepointTest(BitcoinTestFramework):
nonlocal expected_blocks, blocks_checked
event = ctypes.cast(data, ctypes.POINTER(Block)).contents
self.log.info(f"handle_blockconnected(): {event}")
- block = expected_blocks.pop(0)
- assert_equal(block["hash"], bytes(event.hash[::-1]).hex())
+ block_hash = bytes(event.hash[::-1]).hex()
+ block = expected_blocks[block_hash]
+ assert_equal(block["hash"], block_hash)
assert_equal(block["height"], event.height)
assert_equal(len(block["tx"]), event.transactions)
assert_equal(len([tx["vin"] for tx in block["tx"]]), event.inputs)
assert_equal(0, event.sigops) # no sigops in coinbase tx
# only plausibility checks
assert(event.duration > 0)
-
+ del expected_blocks[block_hash]
blocks_checked += 1
bpf["block_connected"].open_perf_buffer(
@@ -122,7 +123,7 @@ class ValidationTracepointTest(BitcoinTestFramework):
block_hashes = self.generatetoaddress(
self.nodes[0], BLOCKS_EXPECTED, ADDRESS_BCRT1_UNSPENDABLE)
for block_hash in block_hashes:
- expected_blocks.append(self.nodes[0].getblock(block_hash, 2))
+ expected_blocks[block_hash] = self.nodes[0].getblock(block_hash, 2)
bpf.perf_buffer_poll(timeout=200)
bpf.cleanup()
diff --git a/test/functional/mempool_accept.py b/test/functional/mempool_accept.py
index 85464b8d0d..02ec18140c 100755
--- a/test/functional/mempool_accept.py
+++ b/test/functional/mempool_accept.py
@@ -11,7 +11,7 @@ import math
from test_framework.test_framework import BitcoinTestFramework
from test_framework.key import ECKey
from test_framework.messages import (
- BIP125_SEQUENCE_NUMBER,
+ MAX_BIP125_RBF_SEQUENCE,
COIN,
COutPoint,
CTxIn,
@@ -65,7 +65,7 @@ class MempoolAcceptanceTest(BitcoinTestFramework):
assert_equal(node.getmempoolinfo()['size'], self.mempool_size)
self.log.info('Should not accept garbage to testmempoolaccept')
- assert_raises_rpc_error(-3, 'Expected type array, got string', lambda: node.testmempoolaccept(rawtxs='ff00baar'))
+ assert_raises_rpc_error(-3, 'JSON value of type string is not of expected type array', lambda: node.testmempoolaccept(rawtxs='ff00baar'))
assert_raises_rpc_error(-8, 'Array must contain between 1 and 25 transactions.', lambda: node.testmempoolaccept(rawtxs=['ff22']*26))
assert_raises_rpc_error(-8, 'Array must contain between 1 and 25 transactions.', lambda: node.testmempoolaccept(rawtxs=[]))
assert_raises_rpc_error(-22, 'TX decode failed', lambda: node.testmempoolaccept(rawtxs=['ff00baar']))
@@ -87,7 +87,7 @@ class MempoolAcceptanceTest(BitcoinTestFramework):
self.log.info('A transaction not in the mempool')
fee = Decimal('0.000007')
utxo_to_spend = self.wallet.get_utxo(txid=txid_in_block) # use 0.3 BTC UTXO
- tx = self.wallet.create_self_transfer(utxo_to_spend=utxo_to_spend, sequence=BIP125_SEQUENCE_NUMBER)['tx']
+ tx = self.wallet.create_self_transfer(utxo_to_spend=utxo_to_spend, sequence=MAX_BIP125_RBF_SEQUENCE)['tx']
tx.vout[0].nValue = int((Decimal('0.3') - fee) * COIN)
raw_tx_0 = tx.serialize().hex()
txid_0 = tx.rehash()
@@ -125,7 +125,7 @@ class MempoolAcceptanceTest(BitcoinTestFramework):
self.log.info('A transaction that replaces a mempool transaction')
tx = tx_from_hex(raw_tx_0)
tx.vout[0].nValue -= int(fee * COIN) # Double the fee
- tx.vin[0].nSequence = BIP125_SEQUENCE_NUMBER + 1 # Now, opt out of RBF
+ tx.vin[0].nSequence = MAX_BIP125_RBF_SEQUENCE + 1 # Now, opt out of RBF
raw_tx_0 = tx.serialize().hex()
txid_0 = tx.rehash()
self.check_mempool_result(
diff --git a/test/functional/mempool_datacarrier.py b/test/functional/mempool_datacarrier.py
new file mode 100755
index 0000000000..13df564a37
--- /dev/null
+++ b/test/functional/mempool_datacarrier.py
@@ -0,0 +1,71 @@
+#!/usr/bin/env python3
+# Copyright (c) 2020-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 datacarrier functionality"""
+from test_framework.messages import (
+ CTxOut,
+ MAX_OP_RETURN_RELAY,
+)
+from test_framework.script import (
+ CScript,
+ OP_RETURN,
+)
+from test_framework.test_framework import BitcoinTestFramework
+from test_framework.test_node import TestNode
+from test_framework.util import (
+ assert_raises_rpc_error,
+ random_bytes,
+)
+from test_framework.wallet import MiniWallet
+
+
+class DataCarrierTest(BitcoinTestFramework):
+ def set_test_params(self):
+ self.num_nodes = 3
+ self.extra_args = [
+ [],
+ ["-datacarrier=0"],
+ ["-datacarrier=1", f"-datacarriersize={MAX_OP_RETURN_RELAY - 1}"]
+ ]
+
+ def test_null_data_transaction(self, node: TestNode, data: bytes, success: bool) -> None:
+ tx = self.wallet.create_self_transfer(fee_rate=0)["tx"]
+ tx.vout.append(CTxOut(nValue=0, scriptPubKey=CScript([OP_RETURN, data])))
+ tx.vout[0].nValue -= tx.get_vsize() # simply pay 1sat/vbyte fee
+
+ tx_hex = tx.serialize().hex()
+
+ if success:
+ self.wallet.sendrawtransaction(from_node=node, tx_hex=tx_hex)
+ assert tx.rehash() in node.getrawmempool(True), f'{tx_hex} not in mempool'
+ else:
+ assert_raises_rpc_error(-26, "scriptpubkey", self.wallet.sendrawtransaction, from_node=node, tx_hex=tx_hex)
+
+ def run_test(self):
+ self.wallet = MiniWallet(self.nodes[0])
+ self.wallet.rescan_utxos()
+
+ # By default, only 80 bytes are used for data (+1 for OP_RETURN, +2 for the pushdata opcodes).
+ default_size_data = random_bytes(MAX_OP_RETURN_RELAY - 3)
+ too_long_data = random_bytes(MAX_OP_RETURN_RELAY - 2)
+ small_data = random_bytes(MAX_OP_RETURN_RELAY - 4)
+
+ self.log.info("Testing null data transaction with default -datacarrier and -datacarriersize values.")
+ self.test_null_data_transaction(node=self.nodes[0], data=default_size_data, success=True)
+
+ self.log.info("Testing a null data transaction larger than allowed by the default -datacarriersize value.")
+ self.test_null_data_transaction(node=self.nodes[0], data=too_long_data, success=False)
+
+ self.log.info("Testing a null data transaction with -datacarrier=false.")
+ self.test_null_data_transaction(node=self.nodes[1], data=default_size_data, success=False)
+
+ self.log.info("Testing a null data transaction with a size larger than accepted by -datacarriersize.")
+ self.test_null_data_transaction(node=self.nodes[2], data=default_size_data, success=False)
+
+ self.log.info("Testing a null data transaction with a size smaller than accepted by -datacarriersize.")
+ self.test_null_data_transaction(node=self.nodes[2], data=small_data, success=True)
+
+
+if __name__ == '__main__':
+ DataCarrierTest().main()
diff --git a/test/functional/mempool_expiry.py b/test/functional/mempool_expiry.py
index f301b29c25..21721177e6 100755
--- a/test/functional/mempool_expiry.py
+++ b/test/functional/mempool_expiry.py
@@ -5,7 +5,7 @@
"""Tests that a mempool transaction expires after a given timeout and that its
children are removed as well.
-Both the default expiry timeout defined by DEFAULT_MEMPOOL_EXPIRY and a user
+Both the default expiry timeout defined by DEFAULT_MEMPOOL_EXPIRY_HOURS and a user
definable expiry timeout via the '-mempoolexpiry=<n>' command line argument
(<n> is the timeout in hours) are tested.
"""
@@ -13,6 +13,7 @@ definable expiry timeout via the '-mempoolexpiry=<n>' command line argument
from datetime import timedelta
from test_framework.blocktools import COINBASE_MATURITY
+from test_framework.messages import DEFAULT_MEMPOOL_EXPIRY_HOURS
from test_framework.test_framework import BitcoinTestFramework
from test_framework.util import (
assert_equal,
@@ -20,7 +21,6 @@ from test_framework.util import (
)
from test_framework.wallet import MiniWallet
-DEFAULT_MEMPOOL_EXPIRY = 336 # hours
CUSTOM_MEMPOOL_EXPIRY = 10 # hours
@@ -98,8 +98,8 @@ class MempoolExpiryTest(BitcoinTestFramework):
def run_test(self):
self.log.info('Test default mempool expiry timeout of %d hours.' %
- DEFAULT_MEMPOOL_EXPIRY)
- self.test_transaction_expiry(DEFAULT_MEMPOOL_EXPIRY)
+ DEFAULT_MEMPOOL_EXPIRY_HOURS)
+ self.test_transaction_expiry(DEFAULT_MEMPOOL_EXPIRY_HOURS)
self.log.info('Test custom mempool expiry timeout of %d hours.' %
CUSTOM_MEMPOOL_EXPIRY)
diff --git a/test/functional/mempool_limit.py b/test/functional/mempool_limit.py
index e92f73304b..7080662b49 100755
--- a/test/functional/mempool_limit.py
+++ b/test/functional/mempool_limit.py
@@ -23,7 +23,7 @@ class MempoolLimitTest(BitcoinTestFramework):
self.setup_clean_chain = True
self.num_nodes = 1
self.extra_args = [[
- "-acceptnonstdtxn=1",
+ "-datacarriersize=100000",
"-maxmempool=5",
"-spendzeroconfchange=0",
]]
diff --git a/test/functional/mempool_package_limits.py b/test/functional/mempool_package_limits.py
index 89a5c83826..1f12e93982 100755
--- a/test/functional/mempool_package_limits.py
+++ b/test/functional/mempool_package_limits.py
@@ -6,28 +6,16 @@
from decimal import Decimal
-from test_framework.address import ADDRESS_BCRT1_P2WSH_OP_TRUE
+from test_framework.blocktools import COINBASE_MATURITY
from test_framework.test_framework import BitcoinTestFramework
from test_framework.messages import (
COIN,
- CTransaction,
- CTxInWitness,
- tx_from_hex,
WITNESS_SCALE_FACTOR,
)
-from test_framework.script import (
- CScript,
- OP_TRUE,
-)
from test_framework.util import (
assert_equal,
)
-from test_framework.wallet import (
- bulk_transaction,
- create_child_with_parents,
- make_chain,
- DEFAULT_FEE,
-)
+from test_framework.wallet import MiniWallet
class MempoolPackageLimitsTest(BitcoinTestFramework):
def set_test_params(self):
@@ -35,19 +23,10 @@ class MempoolPackageLimitsTest(BitcoinTestFramework):
self.setup_clean_chain = True
def run_test(self):
- self.log.info("Generate blocks to create UTXOs")
- node = self.nodes[0]
- self.privkeys = [node.get_deterministic_priv_key().key]
- self.address = node.get_deterministic_priv_key().address
- self.coins = []
- # The last 100 coinbase transactions are premature
- for b in self.generatetoaddress(node, 200, self.address)[:100]:
- coinbase = node.getblock(blockhash=b, verbosity=2)["tx"][0]
- self.coins.append({
- "txid": coinbase["txid"],
- "amount": coinbase["vout"][0]["value"],
- "scriptPubKey": coinbase["vout"][0]["scriptPubKey"],
- })
+ self.wallet = MiniWallet(self.nodes[0])
+ # Add enough mature utxos to the wallet so that all txs spend confirmed coins.
+ self.generate(self.wallet, 35)
+ self.generate(self.nodes[0], COINBASE_MATURITY)
self.test_chain_limits()
self.test_desc_count_limits()
@@ -56,30 +35,22 @@ class MempoolPackageLimitsTest(BitcoinTestFramework):
self.test_anc_count_limits_2()
self.test_anc_count_limits_bushy()
- # The node will accept our (nonstandard) extra large OP_RETURN outputs
- self.restart_node(0, extra_args=["-acceptnonstdtxn=1"])
+ # The node will accept (nonstandard) extra large OP_RETURN outputs
+ self.restart_node(0, extra_args=["-datacarriersize=100000"])
self.test_anc_size_limits()
self.test_desc_size_limits()
def test_chain_limits_helper(self, mempool_count, package_count):
node = self.nodes[0]
assert_equal(0, node.getmempoolinfo()["size"])
- first_coin = self.coins.pop()
- spk = None
- txid = first_coin["txid"]
chain_hex = []
- chain_txns = []
- value = first_coin["amount"]
-
- for i in range(mempool_count + package_count):
- (tx, txhex, value, spk) = make_chain(node, self.address, self.privkeys, txid, value, 0, spk)
- txid = tx.rehash()
- if i < mempool_count:
- node.sendrawtransaction(txhex)
- assert_equal(node.getmempoolentry(txid)["ancestorcount"], i + 1)
- else:
- chain_hex.append(txhex)
- chain_txns.append(tx)
+
+ chaintip_utxo = self.wallet.send_self_transfer_chain(from_node=node, chain_length=mempool_count)
+ # in-package transactions
+ for _ in range(package_count):
+ tx = self.wallet.create_self_transfer(utxo_to_spend=chaintip_utxo)
+ chaintip_utxo = tx["new_utxo"]
+ chain_hex.append(tx["hex"])
testres_too_long = node.testmempoolaccept(rawtxs=chain_hex)
for txres in testres_too_long:
assert_equal(txres["package-error"], "package-mempool-limits")
@@ -125,49 +96,20 @@ class MempoolPackageLimitsTest(BitcoinTestFramework):
assert_equal(0, node.getmempoolinfo()["size"])
self.log.info("Check that in-mempool and in-package descendants are calculated properly in packages")
# Top parent in mempool, M1
- first_coin = self.coins.pop()
- parent_value = (first_coin["amount"] - Decimal("0.0002")) / 2 # Deduct reasonable fee and make 2 outputs
- inputs = [{"txid": first_coin["txid"], "vout": 0}]
- outputs = [{self.address : parent_value}, {ADDRESS_BCRT1_P2WSH_OP_TRUE : parent_value}]
- rawtx = node.createrawtransaction(inputs, outputs)
-
- parent_signed = node.signrawtransactionwithkey(hexstring=rawtx, privkeys=self.privkeys)
- assert parent_signed["complete"]
- parent_tx = tx_from_hex(parent_signed["hex"])
- parent_txid = parent_tx.rehash()
- node.sendrawtransaction(parent_signed["hex"])
+ m1_utxos = self.wallet.send_self_transfer_multi(from_node=node, num_outputs=2)['new_utxos']
package_hex = []
-
- # Chain A
- spk = parent_tx.vout[0].scriptPubKey.hex()
- value = parent_value
- txid = parent_txid
- for i in range(12):
- (tx, txhex, value, spk) = make_chain(node, self.address, self.privkeys, txid, value, 0, spk)
- txid = tx.rehash()
- if i < 11: # M2a... M12a
- node.sendrawtransaction(txhex)
- else: # Pa
- package_hex.append(txhex)
-
- # Chain B
- value = parent_value - Decimal("0.0001")
- rawtx_b = node.createrawtransaction([{"txid": parent_txid, "vout": 1}], {self.address : value})
- tx_child_b = tx_from_hex(rawtx_b) # M2b
- tx_child_b.wit.vtxinwit = [CTxInWitness()]
- tx_child_b.wit.vtxinwit[0].scriptWitness.stack = [CScript([OP_TRUE])]
- tx_child_b_hex = tx_child_b.serialize().hex()
- node.sendrawtransaction(tx_child_b_hex)
- spk = tx_child_b.vout[0].scriptPubKey.hex()
- txid = tx_child_b.rehash()
- for i in range(12):
- (tx, txhex, value, spk) = make_chain(node, self.address, self.privkeys, txid, value, 0, spk)
- txid = tx.rehash()
- if i < 11: # M3b... M13b
- node.sendrawtransaction(txhex)
- else: # Pb
- package_hex.append(txhex)
+ # Chain A (M2a... M12a)
+ chain_a_tip_utxo = self.wallet.send_self_transfer_chain(from_node=node, chain_length=11, utxo_to_spend=m1_utxos[0])
+ # Pa
+ pa_hex = self.wallet.create_self_transfer(utxo_to_spend=chain_a_tip_utxo)["hex"]
+ package_hex.append(pa_hex)
+
+ # Chain B (M2b... M13b)
+ chain_b_tip_utxo = self.wallet.send_self_transfer_chain(from_node=node, chain_length=12, utxo_to_spend=m1_utxos[1])
+ # Pb
+ pb_hex = self.wallet.create_self_transfer(utxo_to_spend=chain_b_tip_utxo)["hex"]
+ package_hex.append(pb_hex)
assert_equal(24, node.getmempoolinfo()["size"])
assert_equal(2, len(package_hex))
@@ -200,41 +142,18 @@ class MempoolPackageLimitsTest(BitcoinTestFramework):
node = self.nodes[0]
package_hex = []
# M1
- first_coin_a = self.coins.pop()
- parent_value = (first_coin_a["amount"] - DEFAULT_FEE) / 2 # Deduct reasonable fee and make 2 outputs
- inputs = [{"txid": first_coin_a["txid"], "vout": 0}]
- outputs = [{self.address : parent_value}, {ADDRESS_BCRT1_P2WSH_OP_TRUE : parent_value}]
- rawtx = node.createrawtransaction(inputs, outputs)
-
- parent_signed = node.signrawtransactionwithkey(hexstring=rawtx, privkeys=self.privkeys)
- assert parent_signed["complete"]
- parent_tx = tx_from_hex(parent_signed["hex"])
- parent_txid = parent_tx.rehash()
- node.sendrawtransaction(parent_signed["hex"])
+ m1_utxos = self.wallet.send_self_transfer_multi(from_node=node, num_outputs=2)['new_utxos']
# Chain M2...M24
- spk = parent_tx.vout[0].scriptPubKey.hex()
- value = parent_value
- txid = parent_txid
- for i in range(23): # M2...M24
- (tx, txhex, value, spk) = make_chain(node, self.address, self.privkeys, txid, value, 0, spk)
- txid = tx.rehash()
- node.sendrawtransaction(txhex)
+ self.wallet.send_self_transfer_chain(from_node=node, chain_length=23, utxo_to_spend=m1_utxos[0])
# P1
- value_p1 = (parent_value - DEFAULT_FEE)
- rawtx_p1 = node.createrawtransaction([{"txid": parent_txid, "vout": 1}], [{self.address : value_p1}])
- tx_child_p1 = tx_from_hex(rawtx_p1)
- tx_child_p1.wit.vtxinwit = [CTxInWitness()]
- tx_child_p1.wit.vtxinwit[0].scriptWitness.stack = [CScript([OP_TRUE])]
- tx_child_p1_hex = tx_child_p1.serialize().hex()
- txid_child_p1 = tx_child_p1.rehash()
- package_hex.append(tx_child_p1_hex)
- tx_child_p1_spk = tx_child_p1.vout[0].scriptPubKey.hex()
+ p1_tx = self.wallet.create_self_transfer(utxo_to_spend=m1_utxos[1])
+ package_hex.append(p1_tx["hex"])
# P2
- (_, tx_child_p2_hex, _, _) = make_chain(node, self.address, self.privkeys, txid_child_p1, value_p1, 0, tx_child_p1_spk)
- package_hex.append(tx_child_p2_hex)
+ p2_tx = self.wallet.create_self_transfer(utxo_to_spend=p1_tx["new_utxo"])
+ package_hex.append(p2_tx["hex"])
assert_equal(24, node.getmempoolinfo()["size"])
assert_equal(2, len(package_hex))
@@ -266,32 +185,21 @@ class MempoolPackageLimitsTest(BitcoinTestFramework):
node = self.nodes[0]
assert_equal(0, node.getmempoolinfo()["size"])
package_hex = []
- parents_tx = []
- values = []
- scripts = []
+ pc_parent_utxos = []
self.log.info("Check that in-mempool and in-package ancestors are calculated properly in packages")
# Two chains of 13 transactions each
for _ in range(2):
- spk = None
- top_coin = self.coins.pop()
- txid = top_coin["txid"]
- value = top_coin["amount"]
- for i in range(13):
- (tx, txhex, value, spk) = make_chain(node, self.address, self.privkeys, txid, value, 0, spk)
- txid = tx.rehash()
- if i < 12:
- node.sendrawtransaction(txhex)
- else: # Save the 13th transaction for the package
- package_hex.append(txhex)
- parents_tx.append(tx)
- scripts.append(spk)
- values.append(value)
+ chain_tip_utxo = self.wallet.send_self_transfer_chain(from_node=node, chain_length=12)
+ # Save the 13th transaction for the package
+ tx = self.wallet.create_self_transfer(utxo_to_spend=chain_tip_utxo)
+ package_hex.append(tx["hex"])
+ pc_parent_utxos.append(tx["new_utxo"])
# Child Pc
- child_hex = create_child_with_parents(node, self.address, self.privkeys, parents_tx, values, scripts)
- package_hex.append(child_hex)
+ pc_hex = self.wallet.create_self_transfer_multi(utxos_to_spend=pc_parent_utxos)["hex"]
+ package_hex.append(pc_hex)
assert_equal(24, node.getmempoolinfo()["size"])
assert_equal(3, len(package_hex))
@@ -321,45 +229,29 @@ class MempoolPackageLimitsTest(BitcoinTestFramework):
"""
node = self.nodes[0]
assert_equal(0, node.getmempoolinfo()["size"])
- parents_tx = []
- values = []
- scripts = []
+ pc_parent_utxos = []
self.log.info("Check that in-mempool and in-package ancestors are calculated properly in packages")
# Two chains of 12 transactions each
for _ in range(2):
- spk = None
- top_coin = self.coins.pop()
- txid = top_coin["txid"]
- value = top_coin["amount"]
- for i in range(12):
- (tx, txhex, value, spk) = make_chain(node, self.address, self.privkeys, txid, value, 0, spk)
- txid = tx.rehash()
- value -= Decimal("0.0001")
- node.sendrawtransaction(txhex)
- if i == 11:
- # last 2 transactions will be the parents of Pc
- parents_tx.append(tx)
- values.append(value)
- scripts.append(spk)
+ chaintip_utxo = self.wallet.send_self_transfer_chain(from_node=node, chain_length=12)
+ # last 2 transactions will be the parents of Pc
+ pc_parent_utxos.append(chaintip_utxo)
# Child Pc
- pc_hex = create_child_with_parents(node, self.address, self.privkeys, parents_tx, values, scripts)
- pc_tx = tx_from_hex(pc_hex)
- pc_value = sum(values) - Decimal("0.0002")
- pc_spk = pc_tx.vout[0].scriptPubKey.hex()
+ pc_tx = self.wallet.create_self_transfer_multi(utxos_to_spend=pc_parent_utxos)
# Child Pd
- (_, pd_hex, _, _) = make_chain(node, self.address, self.privkeys, pc_tx.rehash(), pc_value, 0, pc_spk)
+ pd_tx = self.wallet.create_self_transfer(utxo_to_spend=pc_tx["new_utxos"][0])
assert_equal(24, node.getmempoolinfo()["size"])
- testres_too_long = node.testmempoolaccept(rawtxs=[pc_hex, pd_hex])
+ testres_too_long = node.testmempoolaccept(rawtxs=[pc_tx["hex"], pd_tx["hex"]])
for txres in testres_too_long:
assert_equal(txres["package-error"], "package-mempool-limits")
# Clear mempool and check that the package passes now
self.generate(node, 1)
- assert all([res["allowed"] for res in node.testmempoolaccept(rawtxs=[pc_hex, pd_hex])])
+ assert all([res["allowed"] for res in node.testmempoolaccept(rawtxs=[pc_tx["hex"], pd_tx["hex"]])])
def test_anc_count_limits_bushy(self):
"""Create a tree with 20 transactions in the mempool and 6 in the package:
@@ -375,31 +267,18 @@ class MempoolPackageLimitsTest(BitcoinTestFramework):
node = self.nodes[0]
assert_equal(0, node.getmempoolinfo()["size"])
package_hex = []
- parent_txns = []
- parent_values = []
- scripts = []
+ pc_parent_utxos = []
for _ in range(5): # Make package transactions P0 ... P4
- gp_tx = []
- gp_values = []
- gp_scripts = []
+ pc_grandparent_utxos = []
for _ in range(4): # Make mempool transactions M(4i+1)...M(4i+4)
- parent_coin = self.coins.pop()
- value = parent_coin["amount"]
- txid = parent_coin["txid"]
- (tx, txhex, value, spk) = make_chain(node, self.address, self.privkeys, txid, value)
- gp_tx.append(tx)
- gp_values.append(value)
- gp_scripts.append(spk)
- node.sendrawtransaction(txhex)
+ pc_grandparent_utxos.append(self.wallet.send_self_transfer(from_node=node)["new_utxo"])
# Package transaction Pi
- pi_hex = create_child_with_parents(node, self.address, self.privkeys, gp_tx, gp_values, gp_scripts)
- package_hex.append(pi_hex)
- pi_tx = tx_from_hex(pi_hex)
- parent_txns.append(pi_tx)
- parent_values.append(Decimal(pi_tx.vout[0].nValue) / COIN)
- scripts.append(pi_tx.vout[0].scriptPubKey.hex())
+ pi_tx = self.wallet.create_self_transfer_multi(utxos_to_spend=pc_grandparent_utxos)
+ package_hex.append(pi_tx["hex"])
+ pc_parent_utxos.append(pi_tx["new_utxos"][0])
# Package transaction PC
- package_hex.append(create_child_with_parents(node, self.address, self.privkeys, parent_txns, parent_values, scripts))
+ pc_hex = self.wallet.create_self_transfer_multi(utxos_to_spend=pc_parent_utxos)["hex"]
+ package_hex.append(pc_hex)
assert_equal(20, node.getmempoolinfo()["size"])
assert_equal(6, len(package_hex))
@@ -424,51 +303,30 @@ class MempoolPackageLimitsTest(BitcoinTestFramework):
"""
node = self.nodes[0]
assert_equal(0, node.getmempoolinfo()["size"])
- parents_tx = []
- values = []
- scripts = []
+ parent_utxos = []
target_weight = WITNESS_SCALE_FACTOR * 1000 * 30 # 30KvB
high_fee = Decimal("0.003") # 10 sats/vB
self.log.info("Check that in-mempool and in-package ancestor size limits are calculated properly in packages")
# Mempool transactions A and B
for _ in range(2):
- spk = None
- top_coin = self.coins.pop()
- txid = top_coin["txid"]
- value = top_coin["amount"]
- (tx, _, _, _) = make_chain(node, self.address, self.privkeys, txid, value, 0, spk, high_fee)
- bulked_tx = bulk_transaction(tx, node, target_weight, self.privkeys)
- node.sendrawtransaction(bulked_tx.serialize().hex())
- parents_tx.append(bulked_tx)
- values.append(Decimal(bulked_tx.vout[0].nValue) / COIN)
- scripts.append(bulked_tx.vout[0].scriptPubKey.hex())
+ bulked_tx = self.wallet.create_self_transfer(target_weight=target_weight)
+ self.wallet.sendrawtransaction(from_node=node, tx_hex=bulked_tx["hex"])
+ parent_utxos.append(bulked_tx["new_utxo"])
# Package transaction C
- small_pc_hex = create_child_with_parents(node, self.address, self.privkeys, parents_tx, values, scripts, high_fee)
- pc_tx = bulk_transaction(tx_from_hex(small_pc_hex), node, target_weight, self.privkeys)
- pc_value = Decimal(pc_tx.vout[0].nValue) / COIN
- pc_spk = pc_tx.vout[0].scriptPubKey.hex()
- pc_hex = pc_tx.serialize().hex()
+ pc_tx = self.wallet.create_self_transfer_multi(utxos_to_spend=parent_utxos, fee_per_output=int(high_fee * COIN), target_weight=target_weight)
# Package transaction D
- (small_pd, _, val, spk) = make_chain(node, self.address, self.privkeys, pc_tx.rehash(), pc_value, 0, pc_spk, high_fee)
- prevtxs = [{
- "txid": pc_tx.rehash(),
- "vout": 0,
- "scriptPubKey": spk,
- "amount": val,
- }]
- pd_tx = bulk_transaction(small_pd, node, target_weight, self.privkeys, prevtxs)
- pd_hex = pd_tx.serialize().hex()
+ pd_tx = self.wallet.create_self_transfer(utxo_to_spend=pc_tx["new_utxos"][0], target_weight=target_weight)
assert_equal(2, node.getmempoolinfo()["size"])
- testres_too_heavy = node.testmempoolaccept(rawtxs=[pc_hex, pd_hex])
+ testres_too_heavy = node.testmempoolaccept(rawtxs=[pc_tx["hex"], pd_tx["hex"]])
for txres in testres_too_heavy:
assert_equal(txres["package-error"], "package-mempool-limits")
# Clear mempool and check that the package passes now
self.generate(node, 1)
- assert all([res["allowed"] for res in node.testmempoolaccept(rawtxs=[pc_hex, pd_hex])])
+ assert all([res["allowed"] for res in node.testmempoolaccept(rawtxs=[pc_tx["hex"], pd_tx["hex"]])])
def test_desc_size_limits(self):
"""Create 3 mempool transactions and 2 package transactions (25KvB each):
@@ -486,50 +344,18 @@ class MempoolPackageLimitsTest(BitcoinTestFramework):
high_fee = Decimal("0.0021") # 10 sats/vB
self.log.info("Check that in-mempool and in-package descendant sizes are calculated properly in packages")
# Top parent in mempool, Ma
- first_coin = self.coins.pop()
- parent_value = (first_coin["amount"] - high_fee) / 2 # Deduct fee and make 2 outputs
- inputs = [{"txid": first_coin["txid"], "vout": 0}]
- outputs = [{self.address : parent_value}, {ADDRESS_BCRT1_P2WSH_OP_TRUE: parent_value}]
- rawtx = node.createrawtransaction(inputs, outputs)
- parent_tx = bulk_transaction(tx_from_hex(rawtx), node, target_weight, self.privkeys)
- node.sendrawtransaction(parent_tx.serialize().hex())
+ ma_tx = self.wallet.create_self_transfer_multi(num_outputs=2, fee_per_output=int(high_fee / 2 * COIN), target_weight=target_weight)
+ self.wallet.sendrawtransaction(from_node=node, tx_hex=ma_tx["hex"])
package_hex = []
for j in range(2): # Two legs (left and right)
# Mempool transaction (Mb and Mc)
- mempool_tx = CTransaction()
- spk = parent_tx.vout[j].scriptPubKey.hex()
- value = Decimal(parent_tx.vout[j].nValue) / COIN
- txid = parent_tx.rehash()
- prevtxs = [{
- "txid": txid,
- "vout": j,
- "scriptPubKey": spk,
- "amount": value,
- }]
- if j == 0: # normal key
- (tx_small, _, _, _) = make_chain(node, self.address, self.privkeys, txid, value, j, spk, high_fee)
- mempool_tx = bulk_transaction(tx_small, node, target_weight, self.privkeys, prevtxs)
- else: # OP_TRUE
- inputs = [{"txid": txid, "vout": 1}]
- outputs = {self.address: value - high_fee}
- small_tx = tx_from_hex(node.createrawtransaction(inputs, outputs))
- mempool_tx = bulk_transaction(small_tx, node, target_weight, None, prevtxs)
- node.sendrawtransaction(mempool_tx.serialize().hex())
+ mempool_tx = self.wallet.create_self_transfer(utxo_to_spend=ma_tx["new_utxos"][j], target_weight=target_weight)
+ self.wallet.sendrawtransaction(from_node=node, tx_hex=mempool_tx["hex"])
# Package transaction (Pd and Pe)
- spk = mempool_tx.vout[0].scriptPubKey.hex()
- value = Decimal(mempool_tx.vout[0].nValue) / COIN
- txid = mempool_tx.rehash()
- (tx_small, _, _, _) = make_chain(node, self.address, self.privkeys, txid, value, 0, spk, high_fee)
- prevtxs = [{
- "txid": txid,
- "vout": 0,
- "scriptPubKey": spk,
- "amount": value,
- }]
- package_tx = bulk_transaction(tx_small, node, target_weight, self.privkeys, prevtxs)
- package_hex.append(package_tx.serialize().hex())
+ package_tx = self.wallet.create_self_transfer(utxo_to_spend=mempool_tx["new_utxo"], target_weight=target_weight)
+ package_hex.append(package_tx["hex"])
assert_equal(3, node.getmempoolinfo()["size"])
assert_equal(2, len(package_hex))
diff --git a/test/functional/mempool_package_onemore.py b/test/functional/mempool_package_onemore.py
index 423a5bf2ee..9a981bd5a5 100755
--- a/test/functional/mempool_package_onemore.py
+++ b/test/functional/mempool_package_onemore.py
@@ -7,6 +7,9 @@
size.
"""
+from test_framework.messages import (
+ DEFAULT_ANCESTOR_LIMIT,
+)
from test_framework.test_framework import BitcoinTestFramework
from test_framework.util import (
assert_equal,
@@ -15,10 +18,6 @@ from test_framework.util import (
from test_framework.wallet import MiniWallet
-MAX_ANCESTORS = 25
-MAX_DESCENDANTS = 25
-
-
class MempoolPackagesTest(BitcoinTestFramework):
def set_test_params(self):
self.num_nodes = 1
@@ -34,19 +33,19 @@ class MempoolPackagesTest(BitcoinTestFramework):
self.wallet = MiniWallet(self.nodes[0])
self.wallet.rescan_utxos()
- # MAX_ANCESTORS transactions off a confirmed tx should be fine
+ # DEFAULT_ANCESTOR_LIMIT transactions off a confirmed tx should be fine
chain = []
utxo = self.wallet.get_utxo()
for _ in range(4):
utxo, utxo2 = self.chain_tx([utxo], num_outputs=2)
chain.append(utxo2)
- for _ in range(MAX_ANCESTORS - 4):
+ for _ in range(DEFAULT_ANCESTOR_LIMIT - 4):
utxo, = self.chain_tx([utxo])
chain.append(utxo)
second_chain, = self.chain_tx([self.wallet.get_utxo()])
- # Check mempool has MAX_ANCESTORS + 1 transactions in it
- assert_equal(len(self.nodes[0].getrawmempool()), MAX_ANCESTORS + 1)
+ # Check mempool has DEFAULT_ANCESTOR_LIMIT + 1 transactions in it
+ assert_equal(len(self.nodes[0].getrawmempool()), DEFAULT_ANCESTOR_LIMIT + 1)
# Adding one more transaction on to the chain should fail.
assert_raises_rpc_error(-26, "too-long-mempool-chain, too many unconfirmed ancestors [limit: 25]", self.chain_tx, [utxo])
@@ -67,7 +66,7 @@ class MempoolPackagesTest(BitcoinTestFramework):
self.nodes[0].sendrawtransaction(replacable_tx.serialize().hex())
# Finally, check that we added two transactions
- assert_equal(len(self.nodes[0].getrawmempool()), MAX_ANCESTORS + 3)
+ assert_equal(len(self.nodes[0].getrawmempool()), DEFAULT_ANCESTOR_LIMIT + 3)
if __name__ == '__main__':
diff --git a/test/functional/mempool_packages.py b/test/functional/mempool_packages.py
index a2a2caf324..def0b1fce4 100755
--- a/test/functional/mempool_packages.py
+++ b/test/functional/mempool_packages.py
@@ -7,7 +7,11 @@
from decimal import Decimal
from test_framework.blocktools import COINBASE_MATURITY
-from test_framework.messages import COIN
+from test_framework.messages import (
+ COIN,
+ DEFAULT_ANCESTOR_LIMIT,
+ DEFAULT_DESCENDANT_LIMIT,
+)
from test_framework.p2p import P2PTxInvStore
from test_framework.test_framework import BitcoinTestFramework
from test_framework.util import (
@@ -16,13 +20,12 @@ from test_framework.util import (
chain_transaction,
)
-# default limits
-MAX_ANCESTORS = 25
-MAX_DESCENDANTS = 25
+
# custom limits for node1
-MAX_ANCESTORS_CUSTOM = 5
-MAX_DESCENDANTS_CUSTOM = 10
-assert MAX_DESCENDANTS_CUSTOM >= MAX_ANCESTORS_CUSTOM
+CUSTOM_ANCESTOR_LIMIT = 5
+CUSTOM_DESCENDANT_LIMIT = 10
+assert CUSTOM_DESCENDANT_LIMIT >= CUSTOM_ANCESTOR_LIMIT
+
class MempoolPackagesTest(BitcoinTestFramework):
def set_test_params(self):
@@ -34,8 +37,8 @@ class MempoolPackagesTest(BitcoinTestFramework):
],
[
"-maxorphantx=1000",
- "-limitancestorcount={}".format(MAX_ANCESTORS_CUSTOM),
- "-limitdescendantcount={}".format(MAX_DESCENDANTS_CUSTOM),
+ "-limitancestorcount={}".format(CUSTOM_ANCESTOR_LIMIT),
+ "-limitdescendantcount={}".format(CUSTOM_DESCENDANT_LIMIT),
],
]
@@ -55,12 +58,12 @@ class MempoolPackagesTest(BitcoinTestFramework):
assert 'ancestorfees' not in utxo[0]
fee = Decimal("0.0001")
- # MAX_ANCESTORS transactions off a confirmed tx should be fine
+ # DEFAULT_ANCESTOR_LIMIT transactions off a confirmed tx should be fine
chain = []
witness_chain = []
ancestor_vsize = 0
ancestor_fees = Decimal(0)
- for i in range(MAX_ANCESTORS):
+ for i in range(DEFAULT_ANCESTOR_LIMIT):
(txid, sent_value) = chain_transaction(self.nodes[0], [txid], [0], value, fee, 1)
value = sent_value
chain.append(txid)
@@ -81,16 +84,16 @@ class MempoolPackagesTest(BitcoinTestFramework):
# Otherwise, getrawmempool may be inconsistent with getmempoolentry if unbroadcast changes in between
peer_inv_store.wait_for_broadcast(witness_chain)
- # Check mempool has MAX_ANCESTORS transactions in it, and descendant and ancestor
+ # Check mempool has DEFAULT_ANCESTOR_LIMIT transactions in it, and descendant and ancestor
# count and fees should look correct
mempool = self.nodes[0].getrawmempool(True)
- assert_equal(len(mempool), MAX_ANCESTORS)
+ assert_equal(len(mempool), DEFAULT_ANCESTOR_LIMIT)
descendant_count = 1
descendant_fees = 0
descendant_vsize = 0
assert_equal(ancestor_vsize, sum([mempool[tx]['vsize'] for tx in mempool]))
- ancestor_count = MAX_ANCESTORS
+ ancestor_count = DEFAULT_ANCESTOR_LIMIT
assert_equal(ancestor_fees, sum([mempool[tx]['fees']['base'] for tx in mempool]))
descendants = []
@@ -213,9 +216,9 @@ class MempoolPackagesTest(BitcoinTestFramework):
# Check that node1's mempool is as expected (-> custom ancestor limit)
mempool0 = self.nodes[0].getrawmempool(False)
mempool1 = self.nodes[1].getrawmempool(False)
- assert_equal(len(mempool1), MAX_ANCESTORS_CUSTOM)
+ assert_equal(len(mempool1), CUSTOM_ANCESTOR_LIMIT)
assert set(mempool1).issubset(set(mempool0))
- for tx in chain[:MAX_ANCESTORS_CUSTOM]:
+ for tx in chain[:CUSTOM_ANCESTOR_LIMIT]:
assert tx in mempool1
# TODO: more detailed check of node1's mempool (fees etc.)
# check transaction unbroadcast info (should be false if in both mempools)
@@ -240,7 +243,7 @@ class MempoolPackagesTest(BitcoinTestFramework):
# Sign and send up to MAX_DESCENDANT transactions chained off the parent tx
chain = [] # save sent txs for the purpose of checking node1's mempool later (see below)
- for _ in range(MAX_DESCENDANTS - 1):
+ for _ in range(DEFAULT_DESCENDANT_LIMIT - 1):
utxo = transaction_package.pop(0)
(txid, sent_value) = chain_transaction(self.nodes[0], [utxo['txid']], [utxo['vout']], utxo['amount'], fee, 10)
chain.append(txid)
@@ -250,7 +253,7 @@ class MempoolPackagesTest(BitcoinTestFramework):
transaction_package.append({'txid': txid, 'vout': j, 'amount': sent_value})
mempool = self.nodes[0].getrawmempool(True)
- assert_equal(mempool[parent_transaction]['descendantcount'], MAX_DESCENDANTS)
+ assert_equal(mempool[parent_transaction]['descendantcount'], DEFAULT_DESCENDANT_LIMIT)
assert_equal(sorted(mempool[parent_transaction]['spentby']), sorted(tx_children))
for child in tx_children:
@@ -265,14 +268,14 @@ class MempoolPackagesTest(BitcoinTestFramework):
# - parent tx for descendant test
# - txs chained off parent tx (-> custom descendant limit)
self.wait_until(lambda: len(self.nodes[1].getrawmempool()) ==
- MAX_ANCESTORS_CUSTOM + 1 + MAX_DESCENDANTS_CUSTOM, timeout=10)
+ CUSTOM_ANCESTOR_LIMIT + 1 + CUSTOM_DESCENDANT_LIMIT, timeout=10)
mempool0 = self.nodes[0].getrawmempool(False)
mempool1 = self.nodes[1].getrawmempool(False)
assert set(mempool1).issubset(set(mempool0))
assert parent_transaction in mempool1
- for tx in chain[:MAX_DESCENDANTS_CUSTOM]:
+ for tx in chain[:CUSTOM_DESCENDANT_LIMIT]:
assert tx in mempool1
- for tx in chain[MAX_DESCENDANTS_CUSTOM:]:
+ for tx in chain[CUSTOM_DESCENDANT_LIMIT:]:
assert tx not in mempool1
# TODO: more detailed check of node1's mempool (fees etc.)
diff --git a/test/functional/mempool_persist.py b/test/functional/mempool_persist.py
index 8c9379b90b..b6fa7fbd91 100755
--- a/test/functional/mempool_persist.py
+++ b/test/functional/mempool_persist.py
@@ -105,6 +105,11 @@ class MempoolPersistTest(BitcoinTestFramework):
assert_equal(len(self.nodes[0].p2ps), 0)
self.mini_wallet.send_self_transfer(from_node=self.nodes[0])
+ # Test persistence of prioritisation for transactions not in the mempool.
+ # Create a tx and prioritise but don't submit until after the restart.
+ tx_prioritised_not_submitted = self.mini_wallet.create_self_transfer()
+ self.nodes[0].prioritisetransaction(txid=tx_prioritised_not_submitted['txid'], fee_delta=9999)
+
self.log.debug("Stop-start the nodes. Verify that node0 has the transactions in its mempool and node1 does not. Verify that node2 calculates its balance correctly after loading wallet transactions.")
self.stop_nodes()
# Give this node a head-start, so we can be "extra-sure" that it didn't load anything later
@@ -125,6 +130,9 @@ class MempoolPersistTest(BitcoinTestFramework):
self.log.debug('Verify all fields are loaded correctly')
assert_equal(last_entry, self.nodes[0].getmempoolentry(txid=last_txid))
+ self.nodes[0].sendrawtransaction(tx_prioritised_not_submitted['hex'])
+ entry_prioritised_before_restart = self.nodes[0].getmempoolentry(txid=tx_prioritised_not_submitted['txid'])
+ assert_equal(entry_prioritised_before_restart['fees']['base'] + Decimal('0.00009999'), entry_prioritised_before_restart['fees']['modified'])
# Verify accounting of mempool transactions after restart is correct
if self.is_sqlite_compiled():
@@ -133,6 +141,16 @@ class MempoolPersistTest(BitcoinTestFramework):
self.nodes[2].syncwithvalidationinterfacequeue() # Flush mempool to wallet
assert_equal(node2_balance, wallet_watch.getbalance())
+ mempooldat0 = os.path.join(self.nodes[0].datadir, self.chain, 'mempool.dat')
+ mempooldat1 = os.path.join(self.nodes[1].datadir, self.chain, 'mempool.dat')
+
+ self.log.debug("Force -persistmempool=0 node1 to savemempool to disk via RPC")
+ assert not os.path.exists(mempooldat1)
+ result1 = self.nodes[1].savemempool()
+ assert os.path.isfile(mempooldat1)
+ assert_equal(result1['filename'], mempooldat1)
+ os.remove(mempooldat1)
+
self.log.debug("Stop-start node0 with -persistmempool=0. Verify that it doesn't load its mempool.dat file.")
self.stop_nodes()
self.start_node(0, extra_args=["-persistmempool=0"])
@@ -143,22 +161,20 @@ class MempoolPersistTest(BitcoinTestFramework):
self.stop_nodes()
self.start_node(0)
assert self.nodes[0].getmempoolinfo()["loaded"]
- assert_equal(len(self.nodes[0].getrawmempool()), 6)
+ assert_equal(len(self.nodes[0].getrawmempool()), 7)
- mempooldat0 = os.path.join(self.nodes[0].datadir, self.chain, 'mempool.dat')
- mempooldat1 = os.path.join(self.nodes[1].datadir, self.chain, 'mempool.dat')
self.log.debug("Remove the mempool.dat file. Verify that savemempool to disk via RPC re-creates it")
os.remove(mempooldat0)
result0 = self.nodes[0].savemempool()
assert os.path.isfile(mempooldat0)
assert_equal(result0['filename'], mempooldat0)
- self.log.debug("Stop nodes, make node1 use mempool.dat from node0. Verify it has 6 transactions")
+ self.log.debug("Stop nodes, make node1 use mempool.dat from node0. Verify it has 7 transactions")
os.rename(mempooldat0, mempooldat1)
self.stop_nodes()
self.start_node(1, extra_args=["-persistmempool"])
assert self.nodes[1].getmempoolinfo()["loaded"]
- assert_equal(len(self.nodes[1].getrawmempool()), 6)
+ assert_equal(len(self.nodes[1].getrawmempool()), 7)
self.log.debug("Prevent bitcoind from writing mempool.dat to disk. Verify that `savemempool` fails")
# to test the exception we are creating a tmp folder called mempool.dat.new
diff --git a/test/functional/mempool_updatefromblock.py b/test/functional/mempool_updatefromblock.py
index 51de582ce0..f97c2223a6 100755
--- a/test/functional/mempool_updatefromblock.py
+++ b/test/functional/mempool_updatefromblock.py
@@ -12,6 +12,9 @@ import time
from decimal import Decimal
from test_framework.test_framework import BitcoinTestFramework
from test_framework.util import assert_equal
+from test_framework.address import key_to_p2pkh
+from test_framework.wallet_util import bytes_to_wif
+from test_framework.key import ECKey
class MempoolUpdateFromBlockTest(BitcoinTestFramework):
@@ -19,8 +22,13 @@ class MempoolUpdateFromBlockTest(BitcoinTestFramework):
self.num_nodes = 1
self.extra_args = [['-limitdescendantsize=1000', '-limitancestorsize=1000', '-limitancestorcount=100']]
- def skip_test_if_missing_module(self):
- self.skip_if_no_wallet()
+ def get_new_address(self):
+ key = ECKey()
+ key.generate()
+ pubkey = key.get_pubkey().get_bytes()
+ address = key_to_p2pkh(pubkey)
+ self.priv_keys.append(bytes_to_wif(key.get_bytes()))
+ return address
def transaction_graph_test(self, size, n_tx_to_mine=None, start_input_txid='', end_address='', fee=Decimal(0.00100000)):
"""Create an acyclic tournament (a type of directed graph) of transactions and use it for testing.
@@ -38,11 +46,12 @@ class MempoolUpdateFromBlockTest(BitcoinTestFramework):
More details: https://en.wikipedia.org/wiki/Tournament_(graph_theory)
"""
+ self.priv_keys = [self.nodes[0].get_deterministic_priv_key().key]
if not start_input_txid:
start_input_txid = self.nodes[0].getblock(self.nodes[0].getblockhash(1))['tx'][0]
if not end_address:
- end_address = self.nodes[0].getnewaddress()
+ end_address = self.get_new_address()
first_block_hash = ''
tx_id = []
@@ -74,7 +83,7 @@ class MempoolUpdateFromBlockTest(BitcoinTestFramework):
output_value = ((inputs_value - fee) / Decimal(n_outputs)).quantize(Decimal('0.00000001'))
outputs = {}
for _ in range(n_outputs):
- outputs[self.nodes[0].getnewaddress()] = output_value
+ outputs[self.get_new_address()] = output_value
else:
output_value = (inputs_value - fee).quantize(Decimal('0.00000001'))
outputs = {end_address: output_value}
@@ -84,7 +93,7 @@ class MempoolUpdateFromBlockTest(BitcoinTestFramework):
# Create a new transaction.
unsigned_raw_tx = self.nodes[0].createrawtransaction(inputs, outputs)
- signed_raw_tx = self.nodes[0].signrawtransactionwithwallet(unsigned_raw_tx)
+ signed_raw_tx = self.nodes[0].signrawtransactionwithkey(unsigned_raw_tx, self.priv_keys)
tx_id.append(self.nodes[0].sendrawtransaction(signed_raw_tx['hex']))
tx_size.append(self.nodes[0].getmempoolentry(tx_id[-1])['vsize'])
diff --git a/test/functional/mining_basic.py b/test/functional/mining_basic.py
index 9c64bb1945..ac7eb96ac1 100755
--- a/test/functional/mining_basic.py
+++ b/test/functional/mining_basic.py
@@ -200,7 +200,7 @@ class MiningTest(BitcoinTestFramework):
self.log.info("getblocktemplate: Test bad timestamps")
bad_block = copy.deepcopy(block)
- bad_block.nTime = 2**31 - 1
+ bad_block.nTime = 2**32 - 1
assert_template(node, bad_block, 'time-too-new')
assert_submitblock(bad_block, 'time-too-new', 'time-too-new')
bad_block.nTime = 0
diff --git a/test/functional/mining_prioritisetransaction.py b/test/functional/mining_prioritisetransaction.py
index fb3974c1d5..581cf5896e 100755
--- a/test/functional/mining_prioritisetransaction.py
+++ b/test/functional/mining_prioritisetransaction.py
@@ -26,7 +26,7 @@ class PrioritiseTransactionTest(BitcoinTestFramework):
self.num_nodes = 1
self.extra_args = [[
"-printpriority=1",
- "-acceptnonstdtxn=1",
+ "-datacarriersize=100000",
]] * self.num_nodes
self.supports_cli = False
@@ -122,11 +122,11 @@ class PrioritiseTransactionTest(BitcoinTestFramework):
# Test `prioritisetransaction` invalid `dummy`
txid = '1d1d4e24ed99057e84c3f80fd8fbec79ed9e1acee37da269356ecea000000000'
- assert_raises_rpc_error(-1, "JSON value is not a number as expected", self.nodes[0].prioritisetransaction, txid, 'foo', 0)
+ assert_raises_rpc_error(-3, "JSON value of type string is not of expected type number", self.nodes[0].prioritisetransaction, txid, 'foo', 0)
assert_raises_rpc_error(-8, "Priority is no longer supported, dummy argument to prioritisetransaction must be 0.", self.nodes[0].prioritisetransaction, txid, 1, 0)
# Test `prioritisetransaction` invalid `fee_delta`
- assert_raises_rpc_error(-1, "JSON value is not an integer as expected", self.nodes[0].prioritisetransaction, txid=txid, fee_delta='foo')
+ assert_raises_rpc_error(-3, "JSON value of type string is not of expected type number", self.nodes[0].prioritisetransaction, txid=txid, fee_delta='foo')
self.test_diamond()
diff --git a/test/functional/mocks/invalid_signer.py b/test/functional/mocks/invalid_signer.py
index e30cc9e20b..14f9fed72e 100755
--- a/test/functional/mocks/invalid_signer.py
+++ b/test/functional/mocks/invalid_signer.py
@@ -18,7 +18,7 @@ def perform_pre_checks():
sys.exit(int(mock_result[0]))
def enumerate(args):
- sys.stdout.write(json.dumps([{"fingerprint": "b3c19bfc", "type": "trezor", "model": "trezor_t"}, {"fingerprint": "00000002"}]))
+ sys.stdout.write(json.dumps([{"fingerprint": "b3c19bfc", "type": "trezor", "model": "trezor_t"}]))
def getdescriptors(args):
xpub_pkh = "xpub6CRhJvXV8x2AKWvqi1ZSMFU6cbxzQiYrv3dxSUXCawjMJ1JzpqVsveH4way1yCmJm29KzH1zrVZmVwes4Qo6oXVE1HFn4fdiKrYJngqFFc6"
diff --git a/test/functional/mocks/multi_signers.py b/test/functional/mocks/multi_signers.py
new file mode 100755
index 0000000000..88f93e23de
--- /dev/null
+++ b/test/functional/mocks/multi_signers.py
@@ -0,0 +1,30 @@
+#!/usr/bin/env python3
+# Copyright (c) 2022 The Bitcoin Core developers
+# Distributed under the MIT software license, see the accompanying
+# file COPYING or http://www.opensource.org/licenses/mit-license.php.
+
+import argparse
+import json
+import sys
+
+def enumerate(args):
+ sys.stdout.write(json.dumps([{"fingerprint": "00000001", "type": "trezor", "model": "trezor_t"},
+ {"fingerprint": "00000002", "type": "trezor", "model": "trezor_one"}]))
+
+parser = argparse.ArgumentParser(prog='./multi_signers.py', description='External multi-signer mock')
+
+subparsers = parser.add_subparsers(description='Commands', dest='command')
+subparsers.required = True
+
+parser_enumerate = subparsers.add_parser('enumerate', help='list available signers')
+parser_enumerate.set_defaults(func=enumerate)
+
+
+if not sys.stdin.isatty():
+ buffer = sys.stdin.read()
+ if buffer and buffer.rstrip() != "":
+ sys.argv.extend(buffer.rstrip().split(" "))
+
+args = parser.parse_args()
+
+args.func(args)
diff --git a/test/functional/mocks/signer.py b/test/functional/mocks/signer.py
index 0b4f964c47..6699914249 100755
--- a/test/functional/mocks/signer.py
+++ b/test/functional/mocks/signer.py
@@ -18,7 +18,7 @@ def perform_pre_checks():
sys.exit(int(mock_result[0]))
def enumerate(args):
- sys.stdout.write(json.dumps([{"fingerprint": "00000001", "type": "trezor", "model": "trezor_t"}, {"fingerprint": "00000002"}]))
+ sys.stdout.write(json.dumps([{"fingerprint": "00000001", "type": "trezor", "model": "trezor_t"}]))
def getdescriptors(args):
xpub = "tpubD6NzVbkrYhZ4WaWSyoBvQwbpLkojyoTZPRsgXELWz3Popb3qkjcJyJUGLnL4qHHoQvao8ESaAstxYSnhyswJ76uZPStJRJCTKvosUCJZL5B"
diff --git a/test/functional/p2p_blocksonly.py b/test/functional/p2p_blocksonly.py
index 8ac38bff3a..231d2e12c9 100755
--- a/test/functional/p2p_blocksonly.py
+++ b/test/functional/p2p_blocksonly.py
@@ -57,6 +57,7 @@ class P2PBlocksOnly(BitcoinTestFramework):
second_peer = self.nodes[0].add_p2p_connection(P2PInterface())
peer_1_info = self.nodes[0].getpeerinfo()[0]
assert_equal(peer_1_info['permissions'], ['relay'])
+ assert_equal(first_peer.relay, 1)
peer_2_info = self.nodes[0].getpeerinfo()[1]
assert_equal(peer_2_info['permissions'], ['relay'])
assert_equal(self.nodes[0].testmempoolaccept([tx_hex])[0]['allowed'], True)
diff --git a/test/functional/p2p_compactblocks.py b/test/functional/p2p_compactblocks.py
index 5e50e1ebce..3cbb948e3c 100755
--- a/test/functional/p2p_compactblocks.py
+++ b/test/functional/p2p_compactblocks.py
@@ -615,6 +615,27 @@ class CompactBlocksTest(BitcoinTestFramework):
bad_peer.send_message(msg)
bad_peer.wait_for_disconnect()
+ def test_low_work_compactblocks(self, test_node):
+ # A compactblock with insufficient work won't get its header included
+ node = self.nodes[0]
+ hashPrevBlock = int(node.getblockhash(node.getblockcount() - 150), 16)
+ block = self.build_block_on_tip(node)
+ block.hashPrevBlock = hashPrevBlock
+ block.solve()
+
+ comp_block = HeaderAndShortIDs()
+ comp_block.initialize_from_block(block)
+ with self.nodes[0].assert_debug_log(['[net] Ignoring low-work compact block from peer 0']):
+ test_node.send_and_ping(msg_cmpctblock(comp_block.to_p2p()))
+
+ tips = node.getchaintips()
+ found = False
+ for x in tips:
+ if x["hash"] == block.hash:
+ found = True
+ break
+ assert not found
+
def test_compactblocks_not_at_tip(self, test_node):
node = self.nodes[0]
# Test that requesting old compactblocks doesn't work.
@@ -833,6 +854,9 @@ class CompactBlocksTest(BitcoinTestFramework):
self.log.info("Testing compactblock requests/announcements not at chain tip...")
self.test_compactblocks_not_at_tip(self.segwit_node)
+ self.log.info("Testing handling of low-work compact blocks...")
+ self.test_low_work_compactblocks(self.segwit_node)
+
self.log.info("Testing handling of incorrect blocktxn responses...")
self.test_incorrect_blocktxn_response(self.segwit_node)
diff --git a/test/functional/p2p_dos_header_tree.py b/test/functional/p2p_dos_header_tree.py
index fde1e4bfa2..7e26994511 100755
--- a/test/functional/p2p_dos_header_tree.py
+++ b/test/functional/p2p_dos_header_tree.py
@@ -22,6 +22,7 @@ class RejectLowDifficultyHeadersTest(BitcoinTestFramework):
self.setup_clean_chain = True
self.chain = 'testnet3' # Use testnet chain because it has an early checkpoint
self.num_nodes = 2
+ self.extra_args = [["-minimumchainwork=0x0"], ["-minimumchainwork=0x0"]]
def add_options(self, parser):
parser.add_argument(
@@ -62,7 +63,7 @@ class RejectLowDifficultyHeadersTest(BitcoinTestFramework):
self.log.info("Feed all fork headers (succeeds without checkpoint)")
# On node 0 it succeeds because checkpoints are disabled
- self.restart_node(0, extra_args=['-nocheckpoints'])
+ self.restart_node(0, extra_args=['-nocheckpoints', "-minimumchainwork=0x0"])
peer_no_checkpoint = self.nodes[0].add_p2p_connection(P2PInterface())
peer_no_checkpoint.send_and_ping(msg_headers(self.headers_fork))
assert {
diff --git a/test/functional/p2p_headers_sync_with_minchainwork.py b/test/functional/p2p_headers_sync_with_minchainwork.py
new file mode 100755
index 0000000000..991e3348ed
--- /dev/null
+++ b/test/functional/p2p_headers_sync_with_minchainwork.py
@@ -0,0 +1,164 @@
+#!/usr/bin/env python3
+# Copyright (c) 2019-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 that we reject low difficulty headers to prevent our block tree from filling up with useless bloat"""
+
+from test_framework.test_framework import BitcoinTestFramework
+
+from test_framework.p2p import (
+ P2PInterface,
+)
+
+from test_framework.messages import (
+ msg_headers,
+)
+
+from test_framework.blocktools import (
+ NORMAL_GBT_REQUEST_PARAMS,
+ create_block,
+)
+
+from test_framework.util import assert_equal
+
+NODE1_BLOCKS_REQUIRED = 15
+NODE2_BLOCKS_REQUIRED = 2047
+
+
+class RejectLowDifficultyHeadersTest(BitcoinTestFramework):
+ def set_test_params(self):
+ self.setup_clean_chain = True
+ self.num_nodes = 4
+ # Node0 has no required chainwork; node1 requires 15 blocks on top of the genesis block; node2 requires 2047
+ self.extra_args = [["-minimumchainwork=0x0", "-checkblockindex=0"], ["-minimumchainwork=0x1f", "-checkblockindex=0"], ["-minimumchainwork=0x1000", "-checkblockindex=0"], ["-minimumchainwork=0x1000", "-checkblockindex=0", "-whitelist=noban@127.0.0.1"]]
+
+ def setup_network(self):
+ self.setup_nodes()
+ self.reconnect_all()
+ self.sync_all()
+
+ def disconnect_all(self):
+ self.disconnect_nodes(0, 1)
+ self.disconnect_nodes(0, 2)
+ self.disconnect_nodes(0, 3)
+
+ def reconnect_all(self):
+ self.connect_nodes(0, 1)
+ self.connect_nodes(0, 2)
+ self.connect_nodes(0, 3)
+
+ def test_chains_sync_when_long_enough(self):
+ self.log.info("Generate blocks on the node with no required chainwork, and verify nodes 1 and 2 have no new headers in their headers tree")
+ with self.nodes[1].assert_debug_log(expected_msgs=["[net] Ignoring low-work chain (height=14)"]), self.nodes[2].assert_debug_log(expected_msgs=["[net] Ignoring low-work chain (height=14)"]), self.nodes[3].assert_debug_log(expected_msgs=["Synchronizing blockheaders, height: 14"]):
+ self.generate(self.nodes[0], NODE1_BLOCKS_REQUIRED-1, sync_fun=self.no_op)
+
+ # Node3 should always allow headers due to noban permissions
+ self.log.info("Check that node3 will sync headers (due to noban permissions)")
+
+ def check_node3_chaintips(num_tips, tip_hash, height):
+ node3_chaintips = self.nodes[3].getchaintips()
+ assert(len(node3_chaintips) == num_tips)
+ assert {
+ 'height': height,
+ 'hash': tip_hash,
+ 'branchlen': height,
+ 'status': 'headers-only',
+ } in node3_chaintips
+
+ check_node3_chaintips(2, self.nodes[0].getbestblockhash(), NODE1_BLOCKS_REQUIRED-1)
+
+ for node in self.nodes[1:3]:
+ chaintips = node.getchaintips()
+ assert(len(chaintips) == 1)
+ assert {
+ 'height': 0,
+ 'hash': '0f9188f13cb7b2c71f2a335e3a4fc328bf5beb436012afca590b1a11466e2206',
+ 'branchlen': 0,
+ 'status': 'active',
+ } in chaintips
+
+ self.log.info("Generate more blocks to satisfy node1's minchainwork requirement, and verify node2 still has no new headers in headers tree")
+ with self.nodes[2].assert_debug_log(expected_msgs=["[net] Ignoring low-work chain (height=15)"]), self.nodes[3].assert_debug_log(expected_msgs=["Synchronizing blockheaders, height: 15"]):
+ self.generate(self.nodes[0], NODE1_BLOCKS_REQUIRED - self.nodes[0].getblockcount(), sync_fun=self.no_op)
+ self.sync_blocks(self.nodes[0:2]) # node3 will sync headers (noban permissions) but not blocks (due to minchainwork)
+
+ assert {
+ 'height': 0,
+ 'hash': '0f9188f13cb7b2c71f2a335e3a4fc328bf5beb436012afca590b1a11466e2206',
+ 'branchlen': 0,
+ 'status': 'active',
+ } in self.nodes[2].getchaintips()
+
+ assert(len(self.nodes[2].getchaintips()) == 1)
+
+ self.log.info("Check that node3 accepted these headers as well")
+ check_node3_chaintips(2, self.nodes[0].getbestblockhash(), NODE1_BLOCKS_REQUIRED)
+
+ self.log.info("Generate long chain for node0/node1/node3")
+ self.generate(self.nodes[0], NODE2_BLOCKS_REQUIRED-self.nodes[0].getblockcount(), sync_fun=self.no_op)
+
+ self.log.info("Verify that node2 and node3 will sync the chain when it gets long enough")
+ self.sync_blocks()
+
+ def test_peerinfo_includes_headers_presync_height(self):
+ self.log.info("Test that getpeerinfo() includes headers presync height")
+
+ # Disconnect network, so that we can find our own peer connection more
+ # easily
+ self.disconnect_all()
+
+ p2p = self.nodes[0].add_p2p_connection(P2PInterface())
+ node = self.nodes[0]
+
+ # Ensure we have a long chain already
+ current_height = self.nodes[0].getblockcount()
+ if (current_height < 3000):
+ self.generate(node, 3000-current_height, sync_fun=self.no_op)
+
+ # Send a group of 2000 headers, forking from genesis.
+ new_blocks = []
+ hashPrevBlock = int(node.getblockhash(0), 16)
+ for i in range(2000):
+ block = create_block(hashprev = hashPrevBlock, tmpl=node.getblocktemplate(NORMAL_GBT_REQUEST_PARAMS))
+ block.solve()
+ new_blocks.append(block)
+ hashPrevBlock = block.sha256
+
+ headers_message = msg_headers(headers=new_blocks)
+ p2p.send_and_ping(headers_message)
+
+ # getpeerinfo should show a sync in progress
+ assert_equal(node.getpeerinfo()[0]['presynced_headers'], 2000)
+
+ def test_large_reorgs_can_succeed(self):
+ self.log.info("Test that a 2000+ block reorg, starting from a point that is more than 2000 blocks before a locator entry, can succeed")
+
+ self.sync_all() # Ensure all nodes are synced.
+ self.disconnect_all()
+
+ # locator(block at height T) will have heights:
+ # [T, T-1, ..., T-10, T-12, T-16, T-24, T-40, T-72, T-136, T-264,
+ # T-520, T-1032, T-2056, T-4104, ...]
+ # So mine a number of blocks > 4104 to ensure that the first window of
+ # received headers during a sync are fully between locator entries.
+ BLOCKS_TO_MINE = 4110
+
+ self.generate(self.nodes[0], BLOCKS_TO_MINE, sync_fun=self.no_op)
+ self.generate(self.nodes[1], BLOCKS_TO_MINE+2, sync_fun=self.no_op)
+
+ self.reconnect_all()
+
+ self.sync_blocks(timeout=300) # Ensure tips eventually agree
+
+
+ def run_test(self):
+ self.test_chains_sync_when_long_enough()
+
+ self.test_large_reorgs_can_succeed()
+
+ self.test_peerinfo_includes_headers_presync_height()
+
+
+
+if __name__ == '__main__':
+ RejectLowDifficultyHeadersTest().main()
diff --git a/test/functional/p2p_i2p_sessions.py b/test/functional/p2p_i2p_sessions.py
new file mode 100755
index 0000000000..4e52522b81
--- /dev/null
+++ b/test/functional/p2p_i2p_sessions.py
@@ -0,0 +1,36 @@
+#!/usr/bin/env python3
+# Copyright (c) 2022-2022 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 whether persistent or transient I2P sessions are being used, based on `-i2pacceptincoming`.
+"""
+
+from test_framework.test_framework import BitcoinTestFramework
+
+
+class I2PSessions(BitcoinTestFramework):
+ def set_test_params(self):
+ self.num_nodes = 2
+ # The test assumes that an I2P SAM proxy is not listening here.
+ self.extra_args = [
+ ["-i2psam=127.0.0.1:60000", "-i2pacceptincoming=1"],
+ ["-i2psam=127.0.0.1:60000", "-i2pacceptincoming=0"],
+ ]
+
+ def run_test(self):
+ addr = "zsxwyo6qcn3chqzwxnseusqgsnuw3maqnztkiypyfxtya4snkoka.b32.i2p"
+
+ self.log.info("Ensure we create a persistent session when -i2pacceptincoming=1")
+ node0 = self.nodes[0]
+ with node0.assert_debug_log(expected_msgs=[f"Creating persistent SAM session"]):
+ node0.addnode(node=addr, command="onetry")
+
+ self.log.info("Ensure we create a transient session when -i2pacceptincoming=0")
+ node1 = self.nodes[1]
+ with node1.assert_debug_log(expected_msgs=[f"Creating transient SAM session"]):
+ node1.addnode(node=addr, command="onetry")
+
+
+if __name__ == '__main__':
+ I2PSessions().main()
diff --git a/test/functional/p2p_initial_headers_sync.py b/test/functional/p2p_initial_headers_sync.py
new file mode 100755
index 0000000000..e67c384da7
--- /dev/null
+++ b/test/functional/p2p_initial_headers_sync.py
@@ -0,0 +1,105 @@
+#!/usr/bin/env python3
+# Copyright (c) 2022 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 initial headers download
+
+Test that we only try to initially sync headers from one peer (until our chain
+is close to caught up), and that each block announcement results in only one
+additional peer receiving a getheaders message.
+"""
+
+from test_framework.test_framework import BitcoinTestFramework
+from test_framework.messages import (
+ CInv,
+ MSG_BLOCK,
+ msg_headers,
+ msg_inv,
+)
+from test_framework.p2p import (
+ p2p_lock,
+ P2PInterface,
+)
+from test_framework.util import (
+ assert_equal,
+)
+import random
+
+class HeadersSyncTest(BitcoinTestFramework):
+ def set_test_params(self):
+ self.setup_clean_chain = True
+ self.num_nodes = 1
+
+ def announce_random_block(self, peers):
+ new_block_announcement = msg_inv(inv=[CInv(MSG_BLOCK, random.randrange(1<<256))])
+ for p in peers:
+ p.send_and_ping(new_block_announcement)
+
+ def run_test(self):
+ self.log.info("Adding a peer to node0")
+ peer1 = self.nodes[0].add_p2p_connection(P2PInterface())
+
+ # Wait for peer1 to receive a getheaders
+ peer1.wait_for_getheaders()
+ # An empty reply will clear the outstanding getheaders request,
+ # allowing additional getheaders requests to be sent to this peer in
+ # the future.
+ peer1.send_message(msg_headers())
+
+ self.log.info("Connecting two more peers to node0")
+ # Connect 2 more peers; they should not receive a getheaders yet
+ peer2 = self.nodes[0].add_p2p_connection(P2PInterface())
+ peer3 = self.nodes[0].add_p2p_connection(P2PInterface())
+
+ all_peers = [peer1, peer2, peer3]
+
+ self.log.info("Verify that peer2 and peer3 don't receive a getheaders after connecting")
+ for p in all_peers:
+ p.sync_with_ping()
+ with p2p_lock:
+ assert "getheaders" not in peer2.last_message
+ assert "getheaders" not in peer3.last_message
+
+ with p2p_lock:
+ peer1.last_message.pop("getheaders", None)
+
+ self.log.info("Have all peers announce a new block")
+ self.announce_random_block(all_peers)
+
+ self.log.info("Check that peer1 receives a getheaders in response")
+ peer1.wait_for_getheaders()
+ peer1.send_message(msg_headers()) # Send empty response, see above
+ with p2p_lock:
+ peer1.last_message.pop("getheaders", None)
+
+ self.log.info("Check that exactly 1 of {peer2, peer3} received a getheaders in response")
+ count = 0
+ peer_receiving_getheaders = None
+ for p in [peer2, peer3]:
+ with p2p_lock:
+ if "getheaders" in p.last_message:
+ count += 1
+ peer_receiving_getheaders = p
+ p.last_message.pop("getheaders", None)
+ p.send_message(msg_headers()) # Send empty response, see above
+
+ assert_equal(count, 1)
+
+ self.log.info("Announce another new block, from all peers")
+ self.announce_random_block(all_peers)
+
+ self.log.info("Check that peer1 receives a getheaders in response")
+ peer1.wait_for_getheaders()
+
+ self.log.info("Check that the remaining peer received a getheaders as well")
+ expected_peer = peer2
+ if peer2 == peer_receiving_getheaders:
+ expected_peer = peer3
+
+ expected_peer.wait_for_getheaders()
+
+ self.log.info("Success!")
+
+if __name__ == '__main__':
+ HeadersSyncTest().main()
+
diff --git a/test/functional/p2p_invalid_tx.py b/test/functional/p2p_invalid_tx.py
index 139f4d64e7..28efd5a81e 100755
--- a/test/functional/p2p_invalid_tx.py
+++ b/test/functional/p2p_invalid_tx.py
@@ -60,7 +60,6 @@ class InvalidTxRequestTest(BitcoinTestFramework):
block.solve()
# Save the coinbase for later
block1 = block
- tip = block.sha256
node.p2ps[0].send_blocks_and_test([block], node, success=True)
self.log.info("Mature the block.")
@@ -93,24 +92,24 @@ class InvalidTxRequestTest(BitcoinTestFramework):
SCRIPT_PUB_KEY_OP_TRUE = b'\x51\x75' * 15 + b'\x51'
tx_withhold = CTransaction()
tx_withhold.vin.append(CTxIn(outpoint=COutPoint(block1.vtx[0].sha256, 0)))
- tx_withhold.vout.append(CTxOut(nValue=50 * COIN - 12000, scriptPubKey=SCRIPT_PUB_KEY_OP_TRUE))
+ tx_withhold.vout = [CTxOut(nValue=25 * COIN - 12000, scriptPubKey=SCRIPT_PUB_KEY_OP_TRUE)] * 2
tx_withhold.calc_sha256()
# Our first orphan tx with some outputs to create further orphan txs
tx_orphan_1 = CTransaction()
tx_orphan_1.vin.append(CTxIn(outpoint=COutPoint(tx_withhold.sha256, 0)))
- tx_orphan_1.vout = [CTxOut(nValue=10 * COIN, scriptPubKey=SCRIPT_PUB_KEY_OP_TRUE)] * 3
+ tx_orphan_1.vout = [CTxOut(nValue=8 * COIN, scriptPubKey=SCRIPT_PUB_KEY_OP_TRUE)] * 3
tx_orphan_1.calc_sha256()
# A valid transaction with low fee
tx_orphan_2_no_fee = CTransaction()
tx_orphan_2_no_fee.vin.append(CTxIn(outpoint=COutPoint(tx_orphan_1.sha256, 0)))
- tx_orphan_2_no_fee.vout.append(CTxOut(nValue=10 * COIN, scriptPubKey=SCRIPT_PUB_KEY_OP_TRUE))
+ tx_orphan_2_no_fee.vout.append(CTxOut(nValue=8 * COIN, scriptPubKey=SCRIPT_PUB_KEY_OP_TRUE))
# A valid transaction with sufficient fee
tx_orphan_2_valid = CTransaction()
tx_orphan_2_valid.vin.append(CTxIn(outpoint=COutPoint(tx_orphan_1.sha256, 1)))
- tx_orphan_2_valid.vout.append(CTxOut(nValue=10 * COIN - 12000, scriptPubKey=SCRIPT_PUB_KEY_OP_TRUE))
+ tx_orphan_2_valid.vout.append(CTxOut(nValue=8 * COIN - 12000, scriptPubKey=SCRIPT_PUB_KEY_OP_TRUE))
tx_orphan_2_valid.calc_sha256()
# An invalid transaction with negative fee
@@ -157,6 +156,7 @@ class InvalidTxRequestTest(BitcoinTestFramework):
with node.assert_debug_log(['orphanage overflow, removed 1 tx']):
node.p2ps[0].send_txs_and_test(orphan_tx_pool, node, success=False)
+ self.log.info('Test orphan with rejected parents')
rejected_parent = CTransaction()
rejected_parent.vin.append(CTxIn(outpoint=COutPoint(tx_orphan_2_invalid.sha256, 0)))
rejected_parent.vout.append(CTxOut(nValue=11 * COIN, scriptPubKey=SCRIPT_PUB_KEY_OP_TRUE))
@@ -164,6 +164,64 @@ class InvalidTxRequestTest(BitcoinTestFramework):
with node.assert_debug_log(['not keeping orphan with rejected parents {}'.format(rejected_parent.hash)]):
node.p2ps[0].send_txs_and_test([rejected_parent], node, success=False)
+ self.log.info('Test that a peer disconnection causes erase its transactions from the orphan pool')
+ with node.assert_debug_log(['Erased 100 orphan tx from peer=25']):
+ self.reconnect_p2p(num_connections=1)
+
+ self.log.info('Test that a transaction in the orphan pool is included in a new tip block causes erase this transaction from the orphan pool')
+ tx_withhold_until_block_A = CTransaction()
+ tx_withhold_until_block_A.vin.append(CTxIn(outpoint=COutPoint(tx_withhold.sha256, 1)))
+ tx_withhold_until_block_A.vout = [CTxOut(nValue=12 * COIN, scriptPubKey=SCRIPT_PUB_KEY_OP_TRUE)] * 2
+ tx_withhold_until_block_A.calc_sha256()
+
+ tx_orphan_include_by_block_A = CTransaction()
+ tx_orphan_include_by_block_A.vin.append(CTxIn(outpoint=COutPoint(tx_withhold_until_block_A.sha256, 0)))
+ tx_orphan_include_by_block_A.vout.append(CTxOut(nValue=12 * COIN - 12000, scriptPubKey=SCRIPT_PUB_KEY_OP_TRUE))
+ tx_orphan_include_by_block_A.calc_sha256()
+
+ self.log.info('Send the orphan ... ')
+ node.p2ps[0].send_txs_and_test([tx_orphan_include_by_block_A], node, success=False)
+
+ tip = int(node.getbestblockhash(), 16)
+ height = node.getblockcount() + 1
+ block_A = create_block(tip, create_coinbase(height))
+ block_A.vtx.extend([tx_withhold, tx_withhold_until_block_A, tx_orphan_include_by_block_A])
+ block_A.hashMerkleRoot = block_A.calc_merkle_root()
+ block_A.solve()
+
+ self.log.info('Send the block that includes the previous orphan ... ')
+ with node.assert_debug_log(["Erased 1 orphan tx included or conflicted by block"]):
+ node.p2ps[0].send_blocks_and_test([block_A], node, success=True)
+
+ self.log.info('Test that a transaction in the orphan pool conflicts with a new tip block causes erase this transaction from the orphan pool')
+ tx_withhold_until_block_B = CTransaction()
+ tx_withhold_until_block_B.vin.append(CTxIn(outpoint=COutPoint(tx_withhold_until_block_A.sha256, 1)))
+ tx_withhold_until_block_B.vout.append(CTxOut(nValue=11 * COIN, scriptPubKey=SCRIPT_PUB_KEY_OP_TRUE))
+ tx_withhold_until_block_B.calc_sha256()
+
+ tx_orphan_include_by_block_B = CTransaction()
+ tx_orphan_include_by_block_B.vin.append(CTxIn(outpoint=COutPoint(tx_withhold_until_block_B.sha256, 0)))
+ tx_orphan_include_by_block_B.vout.append(CTxOut(nValue=10 * COIN, scriptPubKey=SCRIPT_PUB_KEY_OP_TRUE))
+ tx_orphan_include_by_block_B.calc_sha256()
+
+ tx_orphan_conflict_by_block_B = CTransaction()
+ tx_orphan_conflict_by_block_B.vin.append(CTxIn(outpoint=COutPoint(tx_withhold_until_block_B.sha256, 0)))
+ tx_orphan_conflict_by_block_B.vout.append(CTxOut(nValue=9 * COIN, scriptPubKey=SCRIPT_PUB_KEY_OP_TRUE))
+ tx_orphan_conflict_by_block_B.calc_sha256()
+ self.log.info('Send the orphan ... ')
+ node.p2ps[0].send_txs_and_test([tx_orphan_conflict_by_block_B], node, success=False)
+
+ tip = int(node.getbestblockhash(), 16)
+ height = node.getblockcount() + 1
+ block_B = create_block(tip, create_coinbase(height))
+ block_B.vtx.extend([tx_withhold_until_block_B, tx_orphan_include_by_block_B])
+ block_B.hashMerkleRoot = block_B.calc_merkle_root()
+ block_B.solve()
+
+ self.log.info('Send the block that includes a transaction which conflicts with the previous orphan ... ')
+ with node.assert_debug_log(["Erased 1 orphan tx included or conflicted by block"]):
+ node.p2ps[0].send_blocks_and_test([block_B], node, success=True)
+
if __name__ == '__main__':
InvalidTxRequestTest().main()
diff --git a/test/functional/p2p_leak.py b/test/functional/p2p_leak.py
index af8e45d578..936c22197c 100755
--- a/test/functional/p2p_leak.py
+++ b/test/functional/p2p_leak.py
@@ -138,6 +138,9 @@ class P2PLeakTest(BitcoinTestFramework):
# Give the node enough time to possibly leak out a message
time.sleep(PEER_TIMEOUT + 2)
+ self.log.info("Connect peer to ensure the net thread runs the disconnect logic at least once")
+ self.nodes[0].add_p2p_connection(P2PInterface())
+
# Make sure only expected messages came in
assert not no_version_idle_peer.unexpected_msg
assert not no_version_idle_peer.got_wtxidrelay
@@ -169,7 +172,7 @@ class P2PLeakTest(BitcoinTestFramework):
self.log.info('Check that old peers are disconnected')
p2p_old_peer = self.nodes[0].add_p2p_connection(P2PInterface(), send_version=False, wait_for_verack=False)
- with self.nodes[0].assert_debug_log(['peer=4 using obsolete version 31799; disconnecting']):
+ with self.nodes[0].assert_debug_log(["using obsolete version 31799; disconnecting"]):
p2p_old_peer.send_message(self.create_old_version(31799))
p2p_old_peer.wait_for_disconnect()
diff --git a/test/functional/p2p_permissions.py b/test/functional/p2p_permissions.py
index 185011c2df..453a0920cc 100755
--- a/test/functional/p2p_permissions.py
+++ b/test/functional/p2p_permissions.py
@@ -111,7 +111,8 @@ class P2PPermissionsTests(BitcoinTestFramework):
'vout': 0,
}], outputs=[{
ADDRESS_BCRT1_P2WSH_OP_TRUE: 5,
- }]),
+ }],
+ replaceable=False),
)
tx.wit.vtxinwit = [CTxInWitness()]
tx.wit.vtxinwit[0].scriptWitness.stack = [CScript([OP_TRUE])]
diff --git a/test/functional/p2p_segwit.py b/test/functional/p2p_segwit.py
index 952f1e5cc5..311b0b67db 100755
--- a/test/functional/p2p_segwit.py
+++ b/test/functional/p2p_segwit.py
@@ -16,7 +16,7 @@ from test_framework.blocktools import (
)
from test_framework.key import ECKey
from test_framework.messages import (
- BIP125_SEQUENCE_NUMBER,
+ MAX_BIP125_RBF_SEQUENCE,
CBlockHeader,
CInv,
COutPoint,
@@ -245,7 +245,7 @@ class SegWitTest(BitcoinTestFramework):
self.test_node = self.nodes[0].add_p2p_connection(TestP2PConn(), services=P2P_SERVICES)
# self.old_node sets only NODE_NETWORK
self.old_node = self.nodes[0].add_p2p_connection(TestP2PConn(), services=NODE_NETWORK)
- # self.std_node is for testing node1 (fRequireStandard=true)
+ # self.std_node is for testing node1 (requires standard txs)
self.std_node = self.nodes[1].add_p2p_connection(TestP2PConn(), services=P2P_SERVICES)
# self.std_wtx_node is for testing node1 with wtxid relay
self.std_wtx_node = self.nodes[1].add_p2p_connection(TestP2PConn(wtxidrelay=True), services=P2P_SERVICES)
@@ -371,6 +371,10 @@ class SegWitTest(BitcoinTestFramework):
block1 = self.build_next_block()
block1.solve()
+ # Send an empty headers message, to clear out any prior getheaders
+ # messages that our peer may be waiting for us on.
+ self.test_node.send_message(msg_headers())
+
self.test_node.announce_block_and_wait_for_getdata(block1, use_header=False)
assert self.test_node.last_message["getdata"].inv[0].type == blocktype
test_witness_block(self.nodes[0], self.test_node, block1, True)
@@ -585,7 +589,7 @@ class SegWitTest(BitcoinTestFramework):
tx.vin = [CTxIn(COutPoint(p2sh_tx.sha256, 0), CScript([witness_script]))]
tx.vout = [CTxOut(p2sh_tx.vout[0].nValue - 10000, script_pubkey)]
tx.vout.append(CTxOut(8000, script_pubkey)) # Might burn this later
- tx.vin[0].nSequence = BIP125_SEQUENCE_NUMBER # Just to have the option to bump this tx from the mempool
+ tx.vin[0].nSequence = MAX_BIP125_RBF_SEQUENCE # Just to have the option to bump this tx from the mempool
tx.rehash()
# This is always accepted, since the mempool policy is to consider segwit as always active
@@ -1378,7 +1382,7 @@ class SegWitTest(BitcoinTestFramework):
tx3.vout.append(CTxOut(total_value - 1000, script_pubkey))
tx3.rehash()
- # First we test this transaction against fRequireStandard=true node
+ # First we test this transaction against std_node
# making sure the txid is added to the reject filter
self.std_node.announce_tx_and_wait_for_getdata(tx3)
test_transaction_acceptance(self.nodes[1], self.std_node, tx3, with_witness=True, accepted=False, reason="bad-txns-nonstandard-inputs")
@@ -1386,7 +1390,7 @@ class SegWitTest(BitcoinTestFramework):
self.std_node.announce_tx_and_wait_for_getdata(tx3, success=False)
# Spending a higher version witness output is not allowed by policy,
- # even with fRequireStandard=false.
+ # even with the node that accepts non-standard txs.
test_transaction_acceptance(self.nodes[0], self.test_node, tx3, with_witness=True, accepted=False, reason="reserved for soft-fork upgrades")
# Building a block with the transaction must be valid, however.
diff --git a/test/functional/p2p_sendtxrcncl.py b/test/functional/p2p_sendtxrcncl.py
new file mode 100755
index 0000000000..f4c5dd4586
--- /dev/null
+++ b/test/functional/p2p_sendtxrcncl.py
@@ -0,0 +1,191 @@
+#!/usr/bin/env python3
+# Copyright (c) 2022 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 SENDTXRCNCL message
+"""
+
+from test_framework.messages import (
+ msg_sendtxrcncl,
+ msg_verack,
+ msg_version,
+ msg_wtxidrelay,
+)
+from test_framework.p2p import (
+ P2PInterface,
+ P2P_SERVICES,
+ P2P_SUBVERSION,
+ P2P_VERSION,
+)
+from test_framework.test_framework import BitcoinTestFramework
+from test_framework.util import assert_equal
+
+class PeerNoVerack(P2PInterface):
+ def __init__(self, wtxidrelay=True):
+ super().__init__(wtxidrelay=wtxidrelay)
+
+ def on_version(self, message):
+ # Avoid sending verack in response to version.
+ # When calling add_p2p_connection, wait_for_verack=False must be set (see
+ # comment in add_p2p_connection).
+ if message.nVersion >= 70016 and self.wtxidrelay:
+ self.send_message(msg_wtxidrelay())
+
+class SendTxrcnclReceiver(P2PInterface):
+ def __init__(self):
+ super().__init__()
+ self.sendtxrcncl_msg_received = None
+
+ def on_sendtxrcncl(self, message):
+ self.sendtxrcncl_msg_received = message
+
+class PeerTrackMsgOrder(P2PInterface):
+ def __init__(self):
+ super().__init__()
+ self.messages = []
+
+ def on_message(self, message):
+ super().on_message(message)
+ self.messages.append(message)
+
+def create_sendtxrcncl_msg(initiator=True):
+ sendtxrcncl_msg = msg_sendtxrcncl()
+ sendtxrcncl_msg.initiator = initiator
+ sendtxrcncl_msg.responder = not initiator
+ sendtxrcncl_msg.version = 1
+ sendtxrcncl_msg.salt = 2
+ return sendtxrcncl_msg
+
+class SendTxRcnclTest(BitcoinTestFramework):
+ def set_test_params(self):
+ self.num_nodes = 1
+ self.extra_args = [['-txreconciliation']]
+
+ def run_test(self):
+ self.log.info('SENDTXRCNCL sent to an inbound')
+ peer = self.nodes[0].add_p2p_connection(SendTxrcnclReceiver(), send_version=True, wait_for_verack=True)
+ assert peer.sendtxrcncl_msg_received
+ assert not peer.sendtxrcncl_msg_received.initiator
+ assert peer.sendtxrcncl_msg_received.responder
+ assert_equal(peer.sendtxrcncl_msg_received.version, 1)
+ peer.peer_disconnect()
+
+ self.log.info('SENDTXRCNCL should be sent before VERACK')
+ peer = self.nodes[0].add_p2p_connection(PeerTrackMsgOrder(), send_version=True, wait_for_verack=True)
+ peer.wait_for_verack()
+ verack_index = [i for i, msg in enumerate(peer.messages) if msg.msgtype == b'verack'][0]
+ sendtxrcncl_index = [i for i, msg in enumerate(peer.messages) if msg.msgtype == b'sendtxrcncl'][0]
+ assert(sendtxrcncl_index < verack_index)
+ peer.peer_disconnect()
+
+ self.log.info('SENDTXRCNCL on pre-WTXID version should not be sent')
+ peer = self.nodes[0].add_p2p_connection(SendTxrcnclReceiver(), send_version=False, wait_for_verack=False)
+ pre_wtxid_version_msg = msg_version()
+ pre_wtxid_version_msg.nVersion = 70015
+ pre_wtxid_version_msg.strSubVer = P2P_SUBVERSION
+ pre_wtxid_version_msg.nServices = P2P_SERVICES
+ pre_wtxid_version_msg.relay = 1
+ peer.send_message(pre_wtxid_version_msg)
+ peer.wait_for_verack()
+ assert not peer.sendtxrcncl_msg_received
+ peer.peer_disconnect()
+
+ self.log.info('SENDTXRCNCL for fRelay=false should not be sent')
+ peer = self.nodes[0].add_p2p_connection(SendTxrcnclReceiver(), send_version=False, wait_for_verack=False)
+ no_txrelay_version_msg = msg_version()
+ no_txrelay_version_msg.nVersion = P2P_VERSION
+ no_txrelay_version_msg.strSubVer = P2P_SUBVERSION
+ no_txrelay_version_msg.nServices = P2P_SERVICES
+ no_txrelay_version_msg.relay = 0
+ peer.send_message(no_txrelay_version_msg)
+ peer.wait_for_verack()
+ assert not peer.sendtxrcncl_msg_received
+ peer.peer_disconnect()
+
+ self.log.info('valid SENDTXRCNCL received')
+ peer = self.nodes[0].add_p2p_connection(PeerNoVerack(), send_version=True, wait_for_verack=False)
+ peer.send_message(create_sendtxrcncl_msg())
+ self.wait_until(lambda : "sendtxrcncl" in self.nodes[0].getpeerinfo()[-1]["bytesrecv_per_msg"])
+ self.log.info('second SENDTXRCNCL triggers a disconnect')
+ peer.send_message(create_sendtxrcncl_msg())
+ peer.wait_for_disconnect()
+
+ self.log.info('SENDTXRCNCL with initiator=responder=0 triggers a disconnect')
+ sendtxrcncl_no_role = create_sendtxrcncl_msg()
+ sendtxrcncl_no_role.initiator = False
+ sendtxrcncl_no_role.responder = False
+ peer = self.nodes[0].add_p2p_connection(PeerNoVerack(), send_version=True, wait_for_verack=False)
+ peer.send_message(sendtxrcncl_no_role)
+ peer.wait_for_disconnect()
+
+ self.log.info('SENDTXRCNCL with initiator=0 and responder=1 from inbound triggers a disconnect')
+ sendtxrcncl_wrong_role = create_sendtxrcncl_msg(initiator=False)
+ peer = self.nodes[0].add_p2p_connection(PeerNoVerack(), send_version=True, wait_for_verack=False)
+ peer.send_message(sendtxrcncl_wrong_role)
+ peer.wait_for_disconnect()
+
+ self.log.info('SENDTXRCNCL with version=0 triggers a disconnect')
+ sendtxrcncl_low_version = create_sendtxrcncl_msg()
+ sendtxrcncl_low_version.version = 0
+ peer = self.nodes[0].add_p2p_connection(PeerNoVerack(), send_version=True, wait_for_verack=False)
+ peer.send_message(sendtxrcncl_low_version)
+ peer.wait_for_disconnect()
+
+ self.log.info('sending SENDTXRCNCL after sending VERACK triggers a disconnect')
+ # We use PeerNoVerack even though verack is sent right after, to make sure it was actually
+ # sent before sendtxrcncl is sent.
+ peer = self.nodes[0].add_p2p_connection(PeerNoVerack(), send_version=True, wait_for_verack=False)
+ peer.send_and_ping(msg_verack())
+ peer.send_message(create_sendtxrcncl_msg())
+ peer.wait_for_disconnect()
+
+ self.log.info('SENDTXRCNCL without WTXIDRELAY is ignored (recon state is erased after VERACK)')
+ peer = self.nodes[0].add_p2p_connection(PeerNoVerack(wtxidrelay=False), send_version=True, wait_for_verack=False)
+ with self.nodes[0].assert_debug_log(['Forget txreconciliation state of peer']):
+ peer.send_message(create_sendtxrcncl_msg())
+ peer.send_message(msg_verack())
+ peer.peer_disconnect()
+
+ self.log.info('SENDTXRCNCL sent to an outbound')
+ peer = self.nodes[0].add_outbound_p2p_connection(
+ SendTxrcnclReceiver(), wait_for_verack=True, p2p_idx=1, connection_type="outbound-full-relay")
+ assert peer.sendtxrcncl_msg_received
+ assert peer.sendtxrcncl_msg_received.initiator
+ assert not peer.sendtxrcncl_msg_received.responder
+ assert_equal(peer.sendtxrcncl_msg_received.version, 1)
+ peer.peer_disconnect()
+
+ self.log.info('SENDTXRCNCL should not be sent if block-relay-only')
+ peer = self.nodes[0].add_outbound_p2p_connection(
+ SendTxrcnclReceiver(), wait_for_verack=True, p2p_idx=2, connection_type="block-relay-only")
+ assert not peer.sendtxrcncl_msg_received
+ peer.peer_disconnect()
+
+ self.log.info('SENDTXRCNCL if block-relay-only triggers a disconnect')
+ peer = self.nodes[0].add_outbound_p2p_connection(
+ PeerNoVerack(), wait_for_verack=False, p2p_idx=3, connection_type="block-relay-only")
+ peer.send_message(create_sendtxrcncl_msg(initiator=False))
+ peer.wait_for_disconnect()
+
+ self.log.info('SENDTXRCNCL with initiator=1 and responder=0 from outbound triggers a disconnect')
+ sendtxrcncl_wrong_role = create_sendtxrcncl_msg(initiator=True)
+ peer = self.nodes[0].add_outbound_p2p_connection(
+ P2PInterface(), wait_for_verack=False, p2p_idx=4, connection_type="outbound-full-relay")
+ peer.send_message(sendtxrcncl_wrong_role)
+ peer.wait_for_disconnect()
+
+ self.log.info('SENDTXRCNCL not sent if -txreconciliation flag is not set')
+ self.restart_node(0, [])
+ peer = self.nodes[0].add_p2p_connection(SendTxrcnclReceiver(), send_version=True, wait_for_verack=True)
+ assert not peer.sendtxrcncl_msg_received
+ peer.peer_disconnect()
+
+ self.log.info('SENDTXRCNCL not sent if blocksonly is set')
+ self.restart_node(0, ["-txreconciliation", "-blocksonly"])
+ peer = self.nodes[0].add_p2p_connection(SendTxrcnclReceiver(), send_version=True, wait_for_verack=True)
+ assert not peer.sendtxrcncl_msg_received
+ peer.peer_disconnect()
+
+
+if __name__ == '__main__':
+ SendTxRcnclTest().main()
diff --git a/test/functional/p2p_timeouts.py b/test/functional/p2p_timeouts.py
index f0abbc7d8b..15a879ae3c 100755
--- a/test/functional/p2p_timeouts.py
+++ b/test/functional/p2p_timeouts.py
@@ -94,6 +94,11 @@ class TimeoutsTest(BitcoinTestFramework):
no_version_node.wait_for_disconnect(timeout=1)
no_send_node.wait_for_disconnect(timeout=1)
+ self.stop_nodes(0)
+ self.nodes[0].assert_start_raises_init_error(
+ expected_msg='Error: peertimeout must be a positive integer.',
+ extra_args=['-peertimeout=0'],
+ )
if __name__ == '__main__':
TimeoutsTest().main()
diff --git a/test/functional/p2p_unrequested_blocks.py b/test/functional/p2p_unrequested_blocks.py
index 76d9b045ce..5030e7af26 100755
--- a/test/functional/p2p_unrequested_blocks.py
+++ b/test/functional/p2p_unrequested_blocks.py
@@ -72,6 +72,13 @@ class AcceptBlockTest(BitcoinTestFramework):
def setup_network(self):
self.setup_nodes()
+ def check_hash_in_chaintips(self, node, blockhash):
+ tips = node.getchaintips()
+ for x in tips:
+ if x["hash"] == blockhash:
+ return True
+ return False
+
def run_test(self):
test_node = self.nodes[0].add_p2p_connection(P2PInterface())
min_work_node = self.nodes[1].add_p2p_connection(P2PInterface())
@@ -89,10 +96,15 @@ class AcceptBlockTest(BitcoinTestFramework):
blocks_h2[i].solve()
block_time += 1
test_node.send_and_ping(msg_block(blocks_h2[0]))
- min_work_node.send_and_ping(msg_block(blocks_h2[1]))
+
+ with self.nodes[1].assert_debug_log(expected_msgs=[f"AcceptBlockHeader: not adding new block header {blocks_h2[1].hash}, missing anti-dos proof-of-work validation"]):
+ min_work_node.send_and_ping(msg_block(blocks_h2[1]))
assert_equal(self.nodes[0].getblockcount(), 2)
assert_equal(self.nodes[1].getblockcount(), 1)
+
+ # Ensure that the header of the second block was also not accepted by node1
+ assert_equal(self.check_hash_in_chaintips(self.nodes[1], blocks_h2[1].hash), False)
self.log.info("First height 2 block accepted by node0; correctly rejected by node1")
# 3. Send another block that builds on genesis.
diff --git a/test/functional/rpc_blockchain.py b/test/functional/rpc_blockchain.py
index 193bd3f1cd..80e8fe55a3 100755
--- a/test/functional/rpc_blockchain.py
+++ b/test/functional/rpc_blockchain.py
@@ -38,6 +38,7 @@ from test_framework.messages import (
msg_block,
)
from test_framework.p2p import P2PInterface
+from test_framework.script import hash256
from test_framework.test_framework import BitcoinTestFramework
from test_framework.util import (
assert_equal,
@@ -88,6 +89,7 @@ class BlockchainTest(BitcoinTestFramework):
self._test_waitforblockheight()
self._test_getblock()
self._test_getdeploymentinfo()
+ self._test_y2106()
assert self.nodes[0].verifychain(4, 0)
def mine_chain(self):
@@ -254,6 +256,14 @@ class BlockchainTest(BitcoinTestFramework):
# calling with an explicit hash works
self.check_signalling_deploymentinfo_result(self.nodes[0].getdeploymentinfo(gbci207["bestblockhash"]), gbci207["blocks"], gbci207["bestblockhash"], "started")
+ def _test_y2106(self):
+ self.log.info("Check that block timestamps work until year 2106")
+ self.generate(self.nodes[0], 8)[-1]
+ time_2106 = 2**32 - 1
+ self.nodes[0].setmocktime(time_2106)
+ last = self.generate(self.nodes[0], 6)[-1]
+ assert_equal(self.nodes[0].getblockheader(last)["mediantime"], time_2106)
+
def _test_getchaintxstats(self):
self.log.info("Test getchaintxstats")
@@ -261,12 +271,12 @@ class BlockchainTest(BitcoinTestFramework):
assert_raises_rpc_error(-1, 'getchaintxstats', self.nodes[0].getchaintxstats, 0, '', 0)
# Test `getchaintxstats` invalid `nblocks`
- assert_raises_rpc_error(-1, "JSON value is not an integer as expected", self.nodes[0].getchaintxstats, '')
+ assert_raises_rpc_error(-3, "JSON value of type string is not of expected type number", self.nodes[0].getchaintxstats, '')
assert_raises_rpc_error(-8, "Invalid block count: should be between 0 and the block's height - 1", self.nodes[0].getchaintxstats, -1)
assert_raises_rpc_error(-8, "Invalid block count: should be between 0 and the block's height - 1", self.nodes[0].getchaintxstats, self.nodes[0].getblockcount())
# Test `getchaintxstats` invalid `blockhash`
- assert_raises_rpc_error(-1, "JSON value is not a string as expected", self.nodes[0].getchaintxstats, blockhash=0)
+ assert_raises_rpc_error(-3, "JSON value of type number is not of expected type string", self.nodes[0].getchaintxstats, blockhash=0)
assert_raises_rpc_error(-8, "blockhash must be of length 64 (not 1, for '0')", self.nodes[0].getchaintxstats, blockhash='0')
assert_raises_rpc_error(-8, "blockhash must be hexadecimal string (not 'ZZZ0000000000000000000000000000000000000000000000000000000000000')", self.nodes[0].getchaintxstats, blockhash='ZZZ0000000000000000000000000000000000000000000000000000000000000')
assert_raises_rpc_error(-5, "Block not found", self.nodes[0].getchaintxstats, blockhash='0000000000000000000000000000000000000000000000000000000000000000')
@@ -452,8 +462,9 @@ class BlockchainTest(BitcoinTestFramework):
# (Previously this was broken based on setting
# `rpc/blockchain.cpp:latestblock` incorrectly.)
#
- b20hash = node.getblockhash(20)
- b20 = node.getblock(b20hash)
+ fork_height = current_height - 100 # choose something vaguely near our tip
+ fork_hash = node.getblockhash(fork_height)
+ fork_block = node.getblock(fork_hash)
def solve_and_send_block(prevhash, height, time):
b = create_block(prevhash, create_coinbase(height), time)
@@ -461,10 +472,10 @@ class BlockchainTest(BitcoinTestFramework):
peer.send_and_ping(msg_block(b))
return b
- b21f = solve_and_send_block(int(b20hash, 16), 21, b20['time'] + 1)
- b22f = solve_and_send_block(b21f.sha256, 22, b21f.nTime + 1)
+ b1 = solve_and_send_block(int(fork_hash, 16), fork_height+1, fork_block['time'] + 1)
+ b2 = solve_and_send_block(b1.sha256, fork_height+2, b1.nTime + 1)
- node.invalidateblock(b22f.hash)
+ node.invalidateblock(b2.hash)
def assert_waitforheight(height, timeout=2):
assert_equal(
@@ -484,6 +495,10 @@ class BlockchainTest(BitcoinTestFramework):
self.wallet.send_self_transfer(fee_rate=fee_per_kb, from_node=node)
blockhash = self.generate(node, 1)[0]
+ def assert_hexblock_hashes(verbosity):
+ block = node.getblock(blockhash, verbosity)
+ assert_equal(blockhash, hash256(bytes.fromhex(block[:160]))[::-1].hex())
+
def assert_fee_not_in_block(verbosity):
block = node.getblock(blockhash, verbosity)
assert 'fee' not in block['tx'][1]
@@ -518,8 +533,13 @@ class BlockchainTest(BitcoinTestFramework):
for vin in tx["vin"]:
assert "prevout" not in vin
+ self.log.info("Test that getblock with verbosity 0 hashes to expected value")
+ assert_hexblock_hashes(0)
+ assert_hexblock_hashes(False)
+
self.log.info("Test that getblock with verbosity 1 doesn't include fee")
assert_fee_not_in_block(1)
+ assert_fee_not_in_block(True)
self.log.info('Test that getblock with verbosity 2 and 3 includes expected fee')
assert_fee_in_block(2)
@@ -536,7 +556,7 @@ class BlockchainTest(BitcoinTestFramework):
datadir = get_datadir_path(self.options.tmpdir, 0)
self.log.info("Test getblock with invalid verbosity type returns proper error message")
- assert_raises_rpc_error(-1, "JSON value is not an integer as expected", node.getblock, blockhash, "2")
+ assert_raises_rpc_error(-3, "JSON value of type string is not of expected type number", node.getblock, blockhash, "2")
def move_block_file(old, new):
old_path = os.path.join(datadir, self.chain, 'blocks', old)
diff --git a/test/functional/rpc_estimatefee.py b/test/functional/rpc_estimatefee.py
index 51b7efb4c3..b057400887 100755
--- a/test/functional/rpc_estimatefee.py
+++ b/test/functional/rpc_estimatefee.py
@@ -22,15 +22,15 @@ class EstimateFeeTest(BitcoinTestFramework):
assert_raises_rpc_error(-1, "estimaterawfee", self.nodes[0].estimaterawfee)
# wrong type for conf_target
- assert_raises_rpc_error(-3, "Expected type number, got string", self.nodes[0].estimatesmartfee, 'foo')
- assert_raises_rpc_error(-3, "Expected type number, got string", self.nodes[0].estimaterawfee, 'foo')
+ assert_raises_rpc_error(-3, "JSON value of type string is not of expected type number", self.nodes[0].estimatesmartfee, 'foo')
+ assert_raises_rpc_error(-3, "JSON value of type string is not of expected type number", self.nodes[0].estimaterawfee, 'foo')
# wrong type for estimatesmartfee(estimate_mode)
- assert_raises_rpc_error(-3, "Expected type string, got number", self.nodes[0].estimatesmartfee, 1, 1)
+ assert_raises_rpc_error(-3, "JSON value of type number is not of expected type string", self.nodes[0].estimatesmartfee, 1, 1)
assert_raises_rpc_error(-8, 'Invalid estimate_mode parameter, must be one of: "unset", "economical", "conservative"', self.nodes[0].estimatesmartfee, 1, 'foo')
# wrong type for estimaterawfee(threshold)
- assert_raises_rpc_error(-3, "Expected type number, got string", self.nodes[0].estimaterawfee, 1, 'foo')
+ assert_raises_rpc_error(-3, "JSON value of type string is not of expected type number", self.nodes[0].estimaterawfee, 1, 'foo')
# extra params
assert_raises_rpc_error(-1, "estimatesmartfee", self.nodes[0].estimatesmartfee, 1, 'ECONOMICAL', 1)
diff --git a/test/functional/rpc_fundrawtransaction.py b/test/functional/rpc_fundrawtransaction.py
index 948deaaec4..17c6fce9c2 100755
--- a/test/functional/rpc_fundrawtransaction.py
+++ b/test/functional/rpc_fundrawtransaction.py
@@ -106,6 +106,7 @@ class RawTransactionsTest(BitcoinTestFramework):
self.generate(self.nodes[2], 1)
self.generate(self.nodes[0], 121)
+ self.test_add_inputs_default_value()
self.test_weight_calculation()
self.test_change_position()
self.test_simple()
@@ -301,7 +302,7 @@ class RawTransactionsTest(BitcoinTestFramework):
inputs = [ {'txid' : utx['txid'], 'vout' : utx['vout']} ]
outputs = { self.nodes[0].getnewaddress() : Decimal(4.0) }
rawtx = self.nodes[2].createrawtransaction(inputs, outputs)
- assert_raises_rpc_error(-1, "JSON value is not a string as expected", self.nodes[2].fundrawtransaction, rawtx, {'change_type': None})
+ assert_raises_rpc_error(-3, "JSON value of type null is not of expected type string", self.nodes[2].fundrawtransaction, rawtx, {'change_type': None})
assert_raises_rpc_error(-5, "Unknown change type ''", self.nodes[2].fundrawtransaction, rawtx, {'change_type': ''})
rawtx = self.nodes[2].fundrawtransaction(rawtx, {'change_type': 'bech32'})
dec_tx = self.nodes[2].decoderawtransaction(rawtx['hex'])
@@ -408,7 +409,7 @@ class RawTransactionsTest(BitcoinTestFramework):
inputs = [ {'txid' : "1c7f966dab21119bac53213a2bc7532bff1fa844c124fd750a7d0b1332440bd1", 'vout' : 0} ] #invalid vin!
outputs = { self.nodes[0].getnewaddress() : 1.0}
rawtx = self.nodes[2].createrawtransaction(inputs, outputs)
- assert_raises_rpc_error(-4, "Insufficient funds", self.nodes[2].fundrawtransaction, rawtx)
+ assert_raises_rpc_error(-4, "Unable to find UTXO for external input", self.nodes[2].fundrawtransaction, rawtx)
def test_fee_p2pkh(self):
"""Compare fee of a standard pubkeyhash transaction."""
@@ -635,7 +636,7 @@ class RawTransactionsTest(BitcoinTestFramework):
self.log.info("Test fundrawtxn fee with many inputs")
# Empty node1, send some small coins from node0 to node1.
- self.nodes[1].sendtoaddress(self.nodes[0].getnewaddress(), self.nodes[1].getbalance(), "", "", True)
+ self.nodes[1].sendall(recipients=[self.nodes[0].getnewaddress()])
self.generate(self.nodes[1], 1)
for _ in range(20):
@@ -661,7 +662,7 @@ class RawTransactionsTest(BitcoinTestFramework):
self.log.info("Test fundrawtxn sign+send with many inputs")
# Again, empty node1, send some small coins from node0 to node1.
- self.nodes[1].sendtoaddress(self.nodes[0].getnewaddress(), self.nodes[1].getbalance(), "", "", True)
+ self.nodes[1].sendall(recipients=[self.nodes[0].getnewaddress()])
self.generate(self.nodes[1], 1)
for _ in range(20):
@@ -1073,23 +1074,149 @@ class RawTransactionsTest(BitcoinTestFramework):
self.nodes[2].unloadwallet("extfund")
+ def test_add_inputs_default_value(self):
+ self.log.info("Test 'add_inputs' default value")
+
+ # Create and fund the wallet with 5 BTC
+ self.nodes[2].createwallet("test_preset_inputs")
+ wallet = self.nodes[2].get_wallet_rpc("test_preset_inputs")
+ addr1 = wallet.getnewaddress(address_type="bech32")
+ self.nodes[0].sendtoaddress(addr1, 5)
+ self.generate(self.nodes[0], 1)
+
+ # Covered cases:
+ # 1. Default add_inputs value with no preset inputs (add_inputs=true):
+ # Expect: automatically add coins from the wallet to the tx.
+ # 2. Default add_inputs value with preset inputs (add_inputs=false):
+ # Expect: disallow automatic coin selection.
+ # 3. Explicit add_inputs=true and preset inputs (with preset inputs not-covering the target amount).
+ # Expect: include inputs from the wallet.
+ # 4. Explicit add_inputs=true and preset inputs (with preset inputs covering the target amount).
+ # Expect: only preset inputs are used.
+ # 5. Explicit add_inputs=true, no preset inputs (same as (1) but with an explicit set):
+ # Expect: include inputs from the wallet.
+
+ # Case (1), 'send' command
+ # 'add_inputs' value is true unless "inputs" are specified, in such case, add_inputs=false.
+ # So, the wallet will automatically select coins and create the transaction if only the outputs are provided.
+ tx = wallet.send(outputs=[{addr1: 3}])
+ assert tx["complete"]
+
+ # Case (2), 'send' command
+ # Select an input manually, which doesn't cover the entire output amount and
+ # verify that the dynamically set 'add_inputs=false' value works.
+
+ # Fund wallet with 2 outputs, 5 BTC each.
+ addr2 = wallet.getnewaddress(address_type="bech32")
+ source_tx = self.nodes[0].send(outputs=[{addr1: 5}, {addr2: 5}], options={"change_position": 0})
+ self.generate(self.nodes[0], 1)
+
+ # Select only one input.
+ options = {
+ "inputs": [
+ {
+ "txid": source_tx["txid"],
+ "vout": 1 # change position was hardcoded to index 0
+ }
+ ]
+ }
+ assert_raises_rpc_error(-4, "Insufficient funds", wallet.send, outputs=[{addr1: 8}], options=options)
+
+ # Case (3), Explicit add_inputs=true and preset inputs (with preset inputs not-covering the target amount)
+ options["add_inputs"] = True
+ options["add_to_wallet"] = False
+ tx = wallet.send(outputs=[{addr1: 8}], options=options)
+ assert tx["complete"]
+
+ # Case (4), Explicit add_inputs=true and preset inputs (with preset inputs covering the target amount)
+ options["inputs"].append({
+ "txid": source_tx["txid"],
+ "vout": 2 # change position was hardcoded to index 0
+ })
+ tx = wallet.send(outputs=[{addr1: 8}], options=options)
+ assert tx["complete"]
+ # Check that only the preset inputs were added to the tx
+ decoded_psbt_inputs = self.nodes[0].decodepsbt(tx["psbt"])['tx']['vin']
+ assert_equal(len(decoded_psbt_inputs), 2)
+ for input in decoded_psbt_inputs:
+ assert_equal(input["txid"], source_tx["txid"])
+
+ # Case (5), assert that inputs are added to the tx by explicitly setting add_inputs=true
+ options = {"add_inputs": True, "add_to_wallet": True}
+ tx = wallet.send(outputs=[{addr1: 8}], options=options)
+ assert tx["complete"]
+
+ ################################################
+
+ # Case (1), 'walletcreatefundedpsbt' command
+ # Default add_inputs value with no preset inputs (add_inputs=true)
+ inputs = []
+ outputs = {self.nodes[1].getnewaddress(): 8}
+ assert "psbt" in wallet.walletcreatefundedpsbt(inputs=inputs, outputs=outputs)
+
+ # Case (2), 'walletcreatefundedpsbt' command
+ # Default add_inputs value with preset inputs (add_inputs=false).
+ inputs = [{
+ "txid": source_tx["txid"],
+ "vout": 1 # change position was hardcoded to index 0
+ }]
+ outputs = {self.nodes[1].getnewaddress(): 8}
+ assert_raises_rpc_error(-4, "Insufficient funds", wallet.walletcreatefundedpsbt, inputs=inputs, outputs=outputs)
+
+ # Case (3), Explicit add_inputs=true and preset inputs (with preset inputs not-covering the target amount)
+ options["add_inputs"] = True
+ options["add_to_wallet"] = False
+ assert "psbt" in wallet.walletcreatefundedpsbt(outputs=[{addr1: 8}], inputs=inputs, options=options)
+
+ # Case (4), Explicit add_inputs=true and preset inputs (with preset inputs covering the target amount)
+ inputs.append({
+ "txid": source_tx["txid"],
+ "vout": 2 # change position was hardcoded to index 0
+ })
+ psbt_tx = wallet.walletcreatefundedpsbt(outputs=[{addr1: 8}], inputs=inputs, options=options)
+ # Check that only the preset inputs were added to the tx
+ decoded_psbt_inputs = self.nodes[0].decodepsbt(psbt_tx["psbt"])['tx']['vin']
+ assert_equal(len(decoded_psbt_inputs), 2)
+ for input in decoded_psbt_inputs:
+ assert_equal(input["txid"], source_tx["txid"])
+
+ # Case (5), 'walletcreatefundedpsbt' command
+ # Explicit add_inputs=true, no preset inputs
+ options = {
+ "add_inputs": True
+ }
+ assert "psbt" in wallet.walletcreatefundedpsbt(inputs=[], outputs=outputs, options=options)
+
+ self.nodes[2].unloadwallet("test_preset_inputs")
+
def test_weight_calculation(self):
self.log.info("Test weight calculation with external inputs")
self.nodes[2].createwallet("test_weight_calculation")
wallet = self.nodes[2].get_wallet_rpc("test_weight_calculation")
- addr = wallet.getnewaddress()
- txid = self.nodes[0].sendtoaddress(addr, 5)
+ addr = wallet.getnewaddress(address_type="bech32")
+ ext_addr = self.nodes[0].getnewaddress(address_type="bech32")
+ txid = self.nodes[0].send([{addr: 5}, {ext_addr: 5}])["txid"]
vout = find_vout_for_address(self.nodes[0], txid, addr)
+ ext_vout = find_vout_for_address(self.nodes[0], txid, ext_addr)
- self.nodes[0].sendtoaddress(wallet.getnewaddress(), 5)
+ self.nodes[0].sendtoaddress(wallet.getnewaddress(address_type="bech32"), 5)
self.generate(self.nodes[0], 1)
- rawtx = wallet.createrawtransaction([{'txid': txid, 'vout': vout}], [{self.nodes[0].getnewaddress(): 9.999}])
- fundedtx = wallet.fundrawtransaction(rawtx, {'fee_rate': 10})
+ rawtx = wallet.createrawtransaction([{'txid': txid, 'vout': vout}], [{self.nodes[0].getnewaddress(address_type="bech32"): 8}])
+ fundedtx = wallet.fundrawtransaction(rawtx, {'fee_rate': 10, "change_type": "bech32"})
# with 71-byte signatures we should expect following tx size
- tx_size = 10 + 41*2 + 31*2 + (2 + 107*2)/4
+ # tx overhead (10) + 2 inputs (41 each) + 2 p2wpkh (31 each) + (segwit marker and flag (2) + 2 p2wpkh 71 byte sig witnesses (107 each)) / witness scaling factor (4)
+ tx_size = ceil(10 + 41*2 + 31*2 + (2 + 107*2)/4)
+ assert_equal(fundedtx['fee'] * COIN, tx_size * 10)
+
+ # Using the other output should have 72 byte sigs
+ rawtx = wallet.createrawtransaction([{'txid': txid, 'vout': ext_vout}], [{self.nodes[0].getnewaddress(): 13}])
+ ext_desc = self.nodes[0].getaddressinfo(ext_addr)["desc"]
+ fundedtx = wallet.fundrawtransaction(rawtx, {'fee_rate': 10, "change_type": "bech32", "solving_data": {"descriptors": [ext_desc]}})
+ # tx overhead (10) + 3 inputs (41 each) + 2 p2wpkh(31 each) + (segwit marker and flag (2) + 2 p2wpkh 71 bytes sig witnesses (107 each) + p2wpkh 72 byte sig witness (108)) / witness scaling factor (4)
+ tx_size = ceil(10 + 41*3 + 31*2 + (2 + 107*2 + 108)/4)
assert_equal(fundedtx['fee'] * COIN, tx_size * 10)
self.nodes[2].unloadwallet("test_weight_calculation")
diff --git a/test/functional/rpc_getblockfrompeer.py b/test/functional/rpc_getblockfrompeer.py
index a7628b5591..278a343b2b 100755
--- a/test/functional/rpc_getblockfrompeer.py
+++ b/test/functional/rpc_getblockfrompeer.py
@@ -54,14 +54,17 @@ class GetBlockFromPeerTest(BitcoinTestFramework):
assert_equal(len(peers), 1)
peer_0_peer_1_id = peers[0]["id"]
- self.log.info("Arguments must be sensible")
- assert_raises_rpc_error(-8, "hash must be of length 64 (not 4, for '1234')", self.nodes[0].getblockfrompeer, "1234", 0)
+ self.log.info("Arguments must be valid")
+ assert_raises_rpc_error(-8, "hash must be of length 64 (not 4, for '1234')", self.nodes[0].getblockfrompeer, "1234", peer_0_peer_1_id)
+ assert_raises_rpc_error(-3, "JSON value of type number is not of expected type string", self.nodes[0].getblockfrompeer, 1234, peer_0_peer_1_id)
+ assert_raises_rpc_error(-3, "JSON value of type string is not of expected type number", self.nodes[0].getblockfrompeer, short_tip, "0")
self.log.info("We must already have the header")
assert_raises_rpc_error(-1, "Block header missing", self.nodes[0].getblockfrompeer, "00" * 32, 0)
self.log.info("Non-existent peer generates error")
- assert_raises_rpc_error(-1, "Peer does not exist", self.nodes[0].getblockfrompeer, short_tip, peer_0_peer_1_id + 1)
+ for peer_id in [-1, peer_0_peer_1_id + 1]:
+ assert_raises_rpc_error(-1, "Peer does not exist", self.nodes[0].getblockfrompeer, short_tip, peer_id)
self.log.info("Fetching from pre-segwit peer generates error")
self.nodes[0].add_p2p_connection(P2PInterface(), services=P2P_SERVICES & ~NODE_WITNESS)
diff --git a/test/functional/rpc_getdescriptorinfo.py b/test/functional/rpc_getdescriptorinfo.py
index 5e6fd66aab..1b0f411e52 100755
--- a/test/functional/rpc_getdescriptorinfo.py
+++ b/test/functional/rpc_getdescriptorinfo.py
@@ -29,7 +29,7 @@ class DescriptorTest(BitcoinTestFramework):
def run_test(self):
assert_raises_rpc_error(-1, 'getdescriptorinfo', self.nodes[0].getdescriptorinfo)
- assert_raises_rpc_error(-3, 'Expected type string', self.nodes[0].getdescriptorinfo, 1)
+ assert_raises_rpc_error(-3, 'JSON value of type number is not of expected type string', self.nodes[0].getdescriptorinfo, 1)
assert_raises_rpc_error(-5, "'' is not a valid descriptor function", self.nodes[0].getdescriptorinfo, "")
# P2PK output with the specified public key.
diff --git a/test/functional/rpc_help.py b/test/functional/rpc_help.py
index 3b6413d4a6..f683577c47 100755
--- a/test/functional/rpc_help.py
+++ b/test/functional/rpc_help.py
@@ -92,7 +92,7 @@ class HelpRpcTest(BitcoinTestFramework):
assert_raises_rpc_error(-1, 'help', node.help, 'foo', 'bar')
# invalid argument
- assert_raises_rpc_error(-1, 'JSON value is not a string as expected', node.help, 0)
+ assert_raises_rpc_error(-3, "JSON value of type number is not of expected type string", node.help, 0)
# help of unknown command
assert_equal(node.help('foo'), 'help: unknown command: foo')
diff --git a/test/functional/rpc_invalidateblock.py b/test/functional/rpc_invalidateblock.py
index f1c2537ef9..1e33e7ca9c 100755
--- a/test/functional/rpc_invalidateblock.py
+++ b/test/functional/rpc_invalidateblock.py
@@ -8,6 +8,7 @@ from test_framework.test_framework import BitcoinTestFramework
from test_framework.address import ADDRESS_BCRT1_UNSPENDABLE_DESCRIPTOR
from test_framework.util import (
assert_equal,
+ assert_raises_rpc_error,
)
@@ -83,6 +84,10 @@ class InvalidateTest(BitcoinTestFramework):
# Should be back at the tip by now
assert_equal(self.nodes[1].getbestblockhash(), blocks[-1])
+ self.log.info("Verify that invalidating an unknown block throws an error")
+ assert_raises_rpc_error(-5, "Block not found", self.nodes[1].invalidateblock, "00" * 32)
+ assert_equal(self.nodes[1].getbestblockhash(), blocks[-1])
+
if __name__ == '__main__':
InvalidateTest().main()
diff --git a/test/functional/rpc_packages.py b/test/functional/rpc_packages.py
index 63533affd0..9a563cbf5f 100755
--- a/test/functional/rpc_packages.py
+++ b/test/functional/rpc_packages.py
@@ -10,21 +10,25 @@ import random
from test_framework.address import ADDRESS_BCRT1_P2WSH_OP_TRUE
from test_framework.test_framework import BitcoinTestFramework
from test_framework.messages import (
- BIP125_SEQUENCE_NUMBER,
+ MAX_BIP125_RBF_SEQUENCE,
COIN,
CTxInWitness,
tx_from_hex,
)
+from test_framework.p2p import P2PTxInvStore
from test_framework.script import (
CScript,
OP_TRUE,
)
from test_framework.util import (
assert_equal,
+ assert_fee_amount,
+ assert_raises_rpc_error,
)
from test_framework.wallet import (
create_child_with_parents,
create_raw_chain,
+ DEFAULT_FEE,
make_chain,
)
@@ -51,7 +55,7 @@ class RPCPackagesTest(BitcoinTestFramework):
self.address = node.get_deterministic_priv_key().address
self.coins = []
# The last 100 coinbase transactions are premature
- for b in self.generatetoaddress(node, 200, self.address)[:100]:
+ for b in self.generatetoaddress(node, 220, self.address)[:-100]:
coinbase = node.getblock(blockhash=b, verbosity=2)["tx"][0]
self.coins.append({
"txid": coinbase["txid"],
@@ -82,7 +86,7 @@ class RPCPackagesTest(BitcoinTestFramework):
self.test_multiple_parents()
self.test_conflicting()
self.test_rbf()
-
+ self.test_submitpackage()
def test_independent(self):
self.log.info("Test multiple independent transactions in a package")
@@ -132,8 +136,7 @@ class RPCPackagesTest(BitcoinTestFramework):
def test_chain(self):
node = self.nodes[0]
- first_coin = self.coins.pop()
- (chain_hex, chain_txns) = create_raw_chain(node, first_coin, self.address, self.privkeys)
+ (chain_hex, chain_txns) = create_raw_chain(node, self.coins.pop(), self.address, self.privkeys)
self.log.info("Check that testmempoolaccept requires packages to be sorted by dependency")
assert_equal(node.testmempoolaccept(rawtxs=chain_hex[::-1]),
[{"txid": tx.rehash(), "wtxid": tx.getwtxid(), "package-error": "package-not-sorted"} for tx in chain_txns[::-1]])
@@ -270,7 +273,7 @@ class RPCPackagesTest(BitcoinTestFramework):
def test_rbf(self):
node = self.nodes[0]
coin = self.coins.pop()
- inputs = [{"txid": coin["txid"], "vout": 0, "sequence": BIP125_SEQUENCE_NUMBER}]
+ inputs = [{"txid": coin["txid"], "vout": 0, "sequence": MAX_BIP125_RBF_SEQUENCE}]
fee = Decimal('0.00125000')
output = {node.get_deterministic_priv_key().address: 50 - fee}
raw_replaceable_tx = node.createrawtransaction(inputs, output)
@@ -306,5 +309,127 @@ class RPCPackagesTest(BitcoinTestFramework):
}]
self.assert_testres_equal(self.independent_txns_hex + [signed_replacement_tx["hex"]], testres_rbf_package)
+ def assert_equal_package_results(self, node, testmempoolaccept_result, submitpackage_result):
+ """Assert that a successful submitpackage result is consistent with testmempoolaccept
+ results and getmempoolentry info. Note that the result structs are different and, due to
+ policy differences between testmempoolaccept and submitpackage (i.e. package feerate),
+ some information may be different.
+ """
+ for testres_tx in testmempoolaccept_result:
+ # Grab this result from the submitpackage_result
+ submitres_tx = submitpackage_result["tx-results"][testres_tx["wtxid"]]
+ assert_equal(submitres_tx["txid"], testres_tx["txid"])
+ # No "allowed" if the tx was already in the mempool
+ if "allowed" in testres_tx and testres_tx["allowed"]:
+ assert_equal(submitres_tx["vsize"], testres_tx["vsize"])
+ assert_equal(submitres_tx["fees"]["base"], testres_tx["fees"]["base"])
+ entry_info = node.getmempoolentry(submitres_tx["txid"])
+ assert_equal(submitres_tx["vsize"], entry_info["vsize"])
+ assert_equal(submitres_tx["fees"]["base"], entry_info["fees"]["base"])
+
+ def test_submit_child_with_parents(self, num_parents, partial_submit):
+ node = self.nodes[0]
+ peer = node.add_p2p_connection(P2PTxInvStore())
+ # Test a package with num_parents parents and 1 child transaction.
+ package_hex = []
+ package_txns = []
+ values = []
+ scripts = []
+ for _ in range(num_parents):
+ parent_coin = self.coins.pop()
+ value = parent_coin["amount"]
+ (tx, txhex, value, spk) = make_chain(node, self.address, self.privkeys, parent_coin["txid"], value)
+ package_hex.append(txhex)
+ package_txns.append(tx)
+ values.append(value)
+ scripts.append(spk)
+ if partial_submit and random.choice([True, False]):
+ node.sendrawtransaction(txhex)
+ child_hex = create_child_with_parents(node, self.address, self.privkeys, package_txns, values, scripts)
+ package_hex.append(child_hex)
+ package_txns.append(tx_from_hex(child_hex))
+
+ testmempoolaccept_result = node.testmempoolaccept(rawtxs=package_hex)
+ submitpackage_result = node.submitpackage(package=package_hex)
+
+ # Check that each result is present, with the correct size and fees
+ for i in range(num_parents + 1):
+ tx = package_txns[i]
+ wtxid = tx.getwtxid()
+ assert wtxid in submitpackage_result["tx-results"]
+ tx_result = submitpackage_result["tx-results"][wtxid]
+ assert_equal(tx_result, {
+ "txid": tx.rehash(),
+ "vsize": tx.get_vsize(),
+ "fees": {
+ "base": DEFAULT_FEE,
+ }
+ })
+
+ # submitpackage result should be consistent with testmempoolaccept and getmempoolentry
+ self.assert_equal_package_results(node, testmempoolaccept_result, submitpackage_result)
+
+ # Package feerate is calculated for the remaining transactions after deduplication and
+ # individual submission. If only 0 or 1 transaction is left, e.g. because all transactions
+ # had high-feerates or were already in the mempool, no package feerate is provided.
+ # In this case, since all of the parents have high fees, each is accepted individually.
+ assert "package-feerate" not in submitpackage_result
+
+ # The node should announce each transaction. No guarantees for propagation.
+ peer.wait_for_broadcast([tx.getwtxid() for tx in package_txns])
+ self.generate(node, 1)
+
+
+ def test_submit_cpfp(self):
+ node = self.nodes[0]
+ peer = node.add_p2p_connection(P2PTxInvStore())
+
+ # 2 parent 1 child CPFP. First parent pays high fees, second parent pays 0 fees and is
+ # fee-bumped by the child.
+ coin_rich = self.coins.pop()
+ coin_poor = self.coins.pop()
+ tx_rich, hex_rich, value_rich, spk_rich = make_chain(node, self.address, self.privkeys, coin_rich["txid"], coin_rich["amount"])
+ tx_poor, hex_poor, value_poor, spk_poor = make_chain(node, self.address, self.privkeys, coin_poor["txid"], coin_poor["amount"], fee=0)
+ package_txns = [tx_rich, tx_poor]
+ hex_child = create_child_with_parents(node, self.address, self.privkeys, package_txns, [value_rich, value_poor], [spk_rich, spk_poor])
+ tx_child = tx_from_hex(hex_child)
+ package_txns.append(tx_child)
+
+ submitpackage_result = node.submitpackage([hex_rich, hex_poor, hex_child])
+
+ rich_parent_result = submitpackage_result["tx-results"][tx_rich.getwtxid()]
+ poor_parent_result = submitpackage_result["tx-results"][tx_poor.getwtxid()]
+ child_result = submitpackage_result["tx-results"][tx_child.getwtxid()]
+ assert_equal(rich_parent_result["fees"]["base"], DEFAULT_FEE)
+ assert_equal(poor_parent_result["fees"]["base"], 0)
+ assert_equal(child_result["fees"]["base"], DEFAULT_FEE)
+ # Package feerate is calculated for the remaining transactions after deduplication and
+ # individual submission. Since this package had a 0-fee parent, package feerate must have
+ # been used and returned.
+ assert "package-feerate" in submitpackage_result
+ assert_fee_amount(DEFAULT_FEE, rich_parent_result["vsize"] + child_result["vsize"], submitpackage_result["package-feerate"])
+
+ # The node will broadcast each transaction, still abiding by its peer's fee filter
+ peer.wait_for_broadcast([tx.getwtxid() for tx in package_txns])
+ self.generate(node, 1)
+
+
+ def test_submitpackage(self):
+ node = self.nodes[0]
+
+ self.log.info("Submitpackage valid packages with 1 child and some number of parents")
+ for num_parents in [1, 2, 24]:
+ self.test_submit_child_with_parents(num_parents, False)
+ self.test_submit_child_with_parents(num_parents, True)
+
+ self.log.info("Submitpackage valid packages with CPFP")
+ self.test_submit_cpfp()
+
+ self.log.info("Submitpackage only allows packages of 1 child with its parents")
+ # Chain of 3 transactions has too many generations
+ chain_hex, _ = create_raw_chain(node, self.coins.pop(), self.address, self.privkeys, 3)
+ assert_raises_rpc_error(-25, "not-child-with-parents", node.submitpackage, chain_hex)
+
+
if __name__ == "__main__":
RPCPackagesTest().main()
diff --git a/test/functional/rpc_psbt.py b/test/functional/rpc_psbt.py
index d2a888fd31..3b78a7d095 100755
--- a/test/functional/rpc_psbt.py
+++ b/test/functional/rpc_psbt.py
@@ -11,9 +11,26 @@ from itertools import product
from test_framework.descriptors import descsum_create
from test_framework.key import ECKey, H_POINT
from test_framework.messages import (
- ser_compact_size,
+ COutPoint,
+ CTransaction,
+ CTxIn,
+ CTxOut,
+ MAX_BIP125_RBF_SEQUENCE,
WITNESS_SCALE_FACTOR,
+ ser_compact_size,
+)
+from test_framework.psbt import (
+ PSBT,
+ PSBTMap,
+ PSBT_GLOBAL_UNSIGNED_TX,
+ PSBT_IN_RIPEMD160,
+ PSBT_IN_SHA256,
+ PSBT_IN_HASH160,
+ PSBT_IN_HASH256,
+ PSBT_IN_WITNESS_UTXO,
+ PSBT_OUT_TAP_TREE,
)
+from test_framework.script import CScript, OP_TRUE
from test_framework.test_framework import BitcoinTestFramework
from test_framework.util import (
assert_approx,
@@ -21,13 +38,14 @@ from test_framework.util import (
assert_greater_than,
assert_raises_rpc_error,
find_output,
+ find_vout_for_address,
+ random_bytes,
)
from test_framework.wallet_util import bytes_to_wif
import json
import os
-MAX_BIP125_RBF_SEQUENCE = 0xfffffffd
# Create one-input, one-output, no-fee transaction:
class PSBTTest(BitcoinTestFramework):
@@ -435,6 +453,7 @@ class PSBTTest(BitcoinTestFramework):
with open(os.path.join(os.path.dirname(os.path.realpath(__file__)), 'data/rpc_psbt.json'), encoding='utf-8') as f:
d = json.load(f)
invalids = d['invalid']
+ invalid_with_msgs = d["invalid_with_msg"]
valids = d['valid']
creators = d['creator']
signers = d['signer']
@@ -445,6 +464,9 @@ class PSBTTest(BitcoinTestFramework):
# Invalid PSBTs
for invalid in invalids:
assert_raises_rpc_error(-22, "TX decode failed", self.nodes[0].decodepsbt, invalid)
+ for invalid in invalid_with_msgs:
+ psbt, msg = invalid
+ assert_raises_rpc_error(-22, f"TX decode failed {msg}", self.nodes[0].decodepsbt, psbt)
# Valid PSBTs
for valid in valids:
@@ -452,7 +474,7 @@ class PSBTTest(BitcoinTestFramework):
# Creator Tests
for creator in creators:
- created_tx = self.nodes[0].createpsbt(creator['inputs'], creator['outputs'])
+ created_tx = self.nodes[0].createpsbt(inputs=creator['inputs'], outputs=creator['outputs'], replaceable=False)
assert_equal(created_tx, creator['result'])
# Signer tests
@@ -760,9 +782,90 @@ class PSBTTest(BitcoinTestFramework):
self.generate(self.nodes[0], 1)
self.nodes[0].importdescriptors([{"desc": descsum_create("tr({})".format(privkey)), "timestamp":"now"}])
- psbt = watchonly.sendall([wallet.getnewaddress()])["psbt"]
+ psbt = watchonly.sendall([wallet.getnewaddress(), addr])["psbt"]
psbt = self.nodes[0].walletprocesspsbt(psbt)["psbt"]
- self.nodes[0].sendrawtransaction(self.nodes[0].finalizepsbt(psbt)["hex"])
+ txid = self.nodes[0].sendrawtransaction(self.nodes[0].finalizepsbt(psbt)["hex"])
+ vout = find_vout_for_address(self.nodes[0], txid, addr)
+
+ # Make sure tap tree is in psbt
+ parsed_psbt = PSBT.from_base64(psbt)
+ assert_greater_than(len(parsed_psbt.o[vout].map[PSBT_OUT_TAP_TREE]), 0)
+ assert "taproot_tree" in self.nodes[0].decodepsbt(psbt)["outputs"][vout]
+ parsed_psbt.make_blank()
+ comb_psbt = self.nodes[0].combinepsbt([psbt, parsed_psbt.to_base64()])
+ assert_equal(comb_psbt, psbt)
+
+ self.log.info("Test that walletprocesspsbt both updates and signs a non-updated psbt containing Taproot inputs")
+ addr = self.nodes[0].getnewaddress("", "bech32m")
+ txid = self.nodes[0].sendtoaddress(addr, 1)
+ vout = find_vout_for_address(self.nodes[0], txid, addr)
+ psbt = self.nodes[0].createpsbt([{"txid": txid, "vout": vout}], [{self.nodes[0].getnewaddress(): 0.9999}])
+ signed = self.nodes[0].walletprocesspsbt(psbt)
+ rawtx = self.nodes[0].finalizepsbt(signed["psbt"])["hex"]
+ self.nodes[0].sendrawtransaction(rawtx)
+ self.generate(self.nodes[0], 1)
+
+ # Make sure tap tree is not in psbt
+ parsed_psbt = PSBT.from_base64(psbt)
+ assert PSBT_OUT_TAP_TREE not in parsed_psbt.o[0].map
+ assert "taproot_tree" not in self.nodes[0].decodepsbt(psbt)["outputs"][0]
+ parsed_psbt.make_blank()
+ comb_psbt = self.nodes[0].combinepsbt([psbt, parsed_psbt.to_base64()])
+ assert_equal(comb_psbt, psbt)
+
+ self.log.info("Test decoding PSBT with per-input preimage types")
+ # note that the decodepsbt RPC doesn't check whether preimages and hashes match
+ hash_ripemd160, preimage_ripemd160 = random_bytes(20), random_bytes(50)
+ hash_sha256, preimage_sha256 = random_bytes(32), random_bytes(50)
+ hash_hash160, preimage_hash160 = random_bytes(20), random_bytes(50)
+ hash_hash256, preimage_hash256 = random_bytes(32), random_bytes(50)
+
+ tx = CTransaction()
+ tx.vin = [CTxIn(outpoint=COutPoint(hash=int('aa' * 32, 16), n=0), scriptSig=b""),
+ CTxIn(outpoint=COutPoint(hash=int('bb' * 32, 16), n=0), scriptSig=b""),
+ CTxIn(outpoint=COutPoint(hash=int('cc' * 32, 16), n=0), scriptSig=b""),
+ CTxIn(outpoint=COutPoint(hash=int('dd' * 32, 16), n=0), scriptSig=b"")]
+ tx.vout = [CTxOut(nValue=0, scriptPubKey=b"")]
+ psbt = PSBT()
+ psbt.g = PSBTMap({PSBT_GLOBAL_UNSIGNED_TX: tx.serialize()})
+ psbt.i = [PSBTMap({bytes([PSBT_IN_RIPEMD160]) + hash_ripemd160: preimage_ripemd160}),
+ PSBTMap({bytes([PSBT_IN_SHA256]) + hash_sha256: preimage_sha256}),
+ PSBTMap({bytes([PSBT_IN_HASH160]) + hash_hash160: preimage_hash160}),
+ PSBTMap({bytes([PSBT_IN_HASH256]) + hash_hash256: preimage_hash256})]
+ psbt.o = [PSBTMap()]
+ res_inputs = self.nodes[0].decodepsbt(psbt.to_base64())["inputs"]
+ assert_equal(len(res_inputs), 4)
+ preimage_keys = ["ripemd160_preimages", "sha256_preimages", "hash160_preimages", "hash256_preimages"]
+ expected_hashes = [hash_ripemd160, hash_sha256, hash_hash160, hash_hash256]
+ expected_preimages = [preimage_ripemd160, preimage_sha256, preimage_hash160, preimage_hash256]
+ for res_input, preimage_key, hash, preimage in zip(res_inputs, preimage_keys, expected_hashes, expected_preimages):
+ assert preimage_key in res_input
+ assert_equal(len(res_input[preimage_key]), 1)
+ assert hash.hex() in res_input[preimage_key]
+ assert_equal(res_input[preimage_key][hash.hex()], preimage.hex())
+
+ self.log.info("Test that combining PSBTs with different transactions fails")
+ tx = CTransaction()
+ tx.vin = [CTxIn(outpoint=COutPoint(hash=int('aa' * 32, 16), n=0), scriptSig=b"")]
+ tx.vout = [CTxOut(nValue=0, scriptPubKey=b"")]
+ psbt1 = PSBT(g=PSBTMap({PSBT_GLOBAL_UNSIGNED_TX: tx.serialize()}), i=[PSBTMap()], o=[PSBTMap()]).to_base64()
+ tx.vout[0].nValue += 1 # slightly modify tx
+ psbt2 = PSBT(g=PSBTMap({PSBT_GLOBAL_UNSIGNED_TX: tx.serialize()}), i=[PSBTMap()], o=[PSBTMap()]).to_base64()
+ assert_raises_rpc_error(-8, "PSBTs not compatible (different transactions)", self.nodes[0].combinepsbt, [psbt1, psbt2])
+ assert_equal(self.nodes[0].combinepsbt([psbt1, psbt1]), psbt1)
+
+ self.log.info("Test that PSBT inputs are being checked via script execution")
+ acs_prevout = CTxOut(nValue=0, scriptPubKey=CScript([OP_TRUE]))
+ tx = CTransaction()
+ tx.vin = [CTxIn(outpoint=COutPoint(hash=int('dd' * 32, 16), n=0), scriptSig=b"")]
+ tx.vout = [CTxOut(nValue=0, scriptPubKey=b"")]
+ psbt = PSBT()
+ psbt.g = PSBTMap({PSBT_GLOBAL_UNSIGNED_TX: tx.serialize()})
+ psbt.i = [PSBTMap({bytes([PSBT_IN_WITNESS_UTXO]) : acs_prevout.serialize()})]
+ psbt.o = [PSBTMap()]
+ assert_equal(self.nodes[0].finalizepsbt(psbt.to_base64()),
+ {'hex': '0200000001dddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddd0000000000000000000100000000000000000000000000', 'complete': True})
+
if __name__ == '__main__':
PSBTTest().main()
diff --git a/test/functional/rpc_rawtransaction.py b/test/functional/rpc_rawtransaction.py
index 26a5da85b7..930aaaa897 100755
--- a/test/functional/rpc_rawtransaction.py
+++ b/test/functional/rpc_rawtransaction.py
@@ -17,7 +17,7 @@ from decimal import Decimal
from test_framework.blocktools import COINBASE_MATURITY
from test_framework.messages import (
- BIP125_SEQUENCE_NUMBER,
+ MAX_BIP125_RBF_SEQUENCE,
CTransaction,
tx_from_hex,
)
@@ -124,13 +124,13 @@ class RawTransactionsTest(BitcoinTestFramework):
# 6. invalid parameters - supply txid and invalid boolean values (strings) for verbose
for value in ["True", "False"]:
- assert_raises_rpc_error(-1, "not a boolean", self.nodes[n].getrawtransaction, txid=txId, verbose=value)
+ assert_raises_rpc_error(-3, "not of expected type bool", self.nodes[n].getrawtransaction, txid=txId, verbose=value)
# 7. invalid parameters - supply txid and empty array
- assert_raises_rpc_error(-1, "not a boolean", self.nodes[n].getrawtransaction, txId, [])
+ assert_raises_rpc_error(-3, "not of expected type bool", self.nodes[n].getrawtransaction, txId, [])
# 8. invalid parameters - supply txid and empty dict
- assert_raises_rpc_error(-1, "not a boolean", self.nodes[n].getrawtransaction, txId, {})
+ assert_raises_rpc_error(-3, "not of expected type bool", self.nodes[n].getrawtransaction, txId, {})
# Make a tx by sending, then generate 2 blocks; block1 has the tx in it
tx = self.wallet.send_self_transfer(from_node=self.nodes[2])['txid']
@@ -152,7 +152,7 @@ class RawTransactionsTest(BitcoinTestFramework):
# We should not get the tx if we provide an unrelated block
assert_raises_rpc_error(-5, "No such transaction found", self.nodes[n].getrawtransaction, txid=tx, blockhash=block2)
# An invalid block hash should raise the correct errors
- assert_raises_rpc_error(-1, "JSON value is not a string as expected", self.nodes[n].getrawtransaction, txid=tx, blockhash=True)
+ assert_raises_rpc_error(-3, "JSON value of type bool is not of expected type string", self.nodes[n].getrawtransaction, txid=tx, blockhash=True)
assert_raises_rpc_error(-8, "parameter 3 must be of length 64 (not 6, for 'foobar')", self.nodes[n].getrawtransaction, txid=tx, blockhash="foobar")
assert_raises_rpc_error(-8, "parameter 3 must be of length 64 (not 8, for 'abcd1234')", self.nodes[n].getrawtransaction, txid=tx, blockhash="abcd1234")
foo = "ZZZ0000000000000000000000000000000000000000000000000000000000000"
@@ -180,9 +180,9 @@ class RawTransactionsTest(BitcoinTestFramework):
assert_raises_rpc_error(-1, "createrawtransaction", self.nodes[0].createrawtransaction, [], {}, 0, False, 'foo')
# Test `createrawtransaction` invalid `inputs`
- assert_raises_rpc_error(-3, "Expected type array", self.nodes[0].createrawtransaction, 'foo', {})
- assert_raises_rpc_error(-1, "JSON value is not an object as expected", self.nodes[0].createrawtransaction, ['foo'], {})
- assert_raises_rpc_error(-1, "JSON value is not a string as expected", self.nodes[0].createrawtransaction, [{}], {})
+ assert_raises_rpc_error(-3, "JSON value of type string is not of expected type array", self.nodes[0].createrawtransaction, 'foo', {})
+ assert_raises_rpc_error(-3, "JSON value of type string is not of expected type object", self.nodes[0].createrawtransaction, ['foo'], {})
+ assert_raises_rpc_error(-3, "JSON value of type null is not of expected type string", self.nodes[0].createrawtransaction, [{}], {})
assert_raises_rpc_error(-8, "txid must be of length 64 (not 3, for 'foo')", self.nodes[0].createrawtransaction, [{'txid': 'foo'}], {})
txid = "ZZZ7bb8b1697ea987f3b223ba7819250cae33efacb068d23dc24859824a77844"
assert_raises_rpc_error(-8, f"txid must be hexadecimal string (not '{txid}')", self.nodes[0].createrawtransaction, [{'txid': txid}], {})
@@ -207,7 +207,7 @@ class RawTransactionsTest(BitcoinTestFramework):
# Test `createrawtransaction` invalid `outputs`
address = getnewdestination()[2]
- assert_raises_rpc_error(-1, "JSON value is not an array as expected", self.nodes[0].createrawtransaction, [], 'foo')
+ assert_raises_rpc_error(-3, "JSON value of type string is not of expected type array", self.nodes[0].createrawtransaction, [], 'foo')
self.nodes[0].createrawtransaction(inputs=[], outputs={}) # Should not throw for backwards compatibility
self.nodes[0].createrawtransaction(inputs=[], outputs=[])
assert_raises_rpc_error(-8, "Data must be hexadecimal string", self.nodes[0].createrawtransaction, [], {'data': 'foo'})
@@ -223,15 +223,15 @@ class RawTransactionsTest(BitcoinTestFramework):
# Test `createrawtransaction` mismatch between sequence number(s) and `replaceable` option
assert_raises_rpc_error(-8, "Invalid parameter combination: Sequence number(s) contradict replaceable option",
- self.nodes[0].createrawtransaction, [{'txid': TXID, 'vout': 0, 'sequence': BIP125_SEQUENCE_NUMBER+1}], {}, 0, True)
+ self.nodes[0].createrawtransaction, [{'txid': TXID, 'vout': 0, 'sequence': MAX_BIP125_RBF_SEQUENCE+1}], {}, 0, True)
# Test `createrawtransaction` invalid `locktime`
- assert_raises_rpc_error(-3, "Expected type number", self.nodes[0].createrawtransaction, [], {}, 'foo')
+ assert_raises_rpc_error(-3, "JSON value of type string is not of expected type number", self.nodes[0].createrawtransaction, [], {}, 'foo')
assert_raises_rpc_error(-8, "Invalid parameter, locktime out of range", self.nodes[0].createrawtransaction, [], {}, -1)
assert_raises_rpc_error(-8, "Invalid parameter, locktime out of range", self.nodes[0].createrawtransaction, [], {}, 4294967296)
# Test `createrawtransaction` invalid `replaceable`
- assert_raises_rpc_error(-3, "Expected type bool", self.nodes[0].createrawtransaction, [], {}, 0, 'foo')
+ assert_raises_rpc_error(-3, "JSON value of type string is not of expected type bool", self.nodes[0].createrawtransaction, [], {}, 0, 'foo')
# Test that createrawtransaction accepts an array and object as outputs
# One output
diff --git a/test/functional/rpc_scanblocks.py b/test/functional/rpc_scanblocks.py
new file mode 100755
index 0000000000..39f091fd1a
--- /dev/null
+++ b/test/functional/rpc_scanblocks.py
@@ -0,0 +1,103 @@
+#!/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 the scanblocks RPC call."""
+from test_framework.messages import COIN
+from test_framework.test_framework import BitcoinTestFramework
+from test_framework.util import (
+ assert_equal,
+ assert_raises_rpc_error,
+)
+from test_framework.wallet import (
+ MiniWallet,
+ getnewdestination,
+)
+
+
+class ScanblocksTest(BitcoinTestFramework):
+ def set_test_params(self):
+ self.num_nodes = 2
+ self.extra_args = [["-blockfilterindex=1"], []]
+
+ def run_test(self):
+ node = self.nodes[0]
+ wallet = MiniWallet(node)
+ wallet.rescan_utxos()
+
+ # send 1.0, mempool only
+ _, spk_1, addr_1 = getnewdestination()
+ wallet.send_to(from_node=node, scriptPubKey=spk_1, amount=1 * COIN)
+
+ parent_key = "tpubD6NzVbkrYhZ4WaWSyoBvQwbpLkojyoTZPRsgXELWz3Popb3qkjcJyJUGLnL4qHHoQvao8ESaAstxYSnhyswJ76uZPStJRJCTKvosUCJZL5B"
+ # send 1.0, mempool only
+ # childkey 5 of `parent_key`
+ wallet.send_to(from_node=node,
+ scriptPubKey=bytes.fromhex(node.validateaddress("mkS4HXoTYWRTescLGaUTGbtTTYX5EjJyEE")['scriptPubKey']),
+ amount=1 * COIN)
+
+ # mine a block and assure that the mined blockhash is in the filterresult
+ blockhash = self.generate(node, 1)[0]
+ height = node.getblockheader(blockhash)['height']
+ self.wait_until(lambda: all(i["synced"] for i in node.getindexinfo().values()))
+
+ out = node.scanblocks("start", [f"addr({addr_1})"])
+ assert(blockhash in out['relevant_blocks'])
+ assert_equal(height, out['to_height'])
+ assert_equal(0, out['from_height'])
+
+ # mine another block
+ blockhash_new = self.generate(node, 1)[0]
+ height_new = node.getblockheader(blockhash_new)['height']
+
+ # make sure the blockhash is not in the filter result if we set the start_height
+ # to the just mined block (unlikely to hit a false positive)
+ assert(blockhash not in node.scanblocks(
+ "start", [f"addr({addr_1})"], height_new)['relevant_blocks'])
+
+ # make sure the blockhash is present when using the first mined block as start_height
+ assert(blockhash in node.scanblocks(
+ "start", [f"addr({addr_1})"], height)['relevant_blocks'])
+
+ # also test the stop height
+ assert(blockhash in node.scanblocks(
+ "start", [f"addr({addr_1})"], height, height)['relevant_blocks'])
+
+ # use the stop_height to exclude the relevant block
+ assert(blockhash not in node.scanblocks(
+ "start", [f"addr({addr_1})"], 0, height - 1)['relevant_blocks'])
+
+ # make sure the blockhash is present when using the first mined block as start_height
+ assert(blockhash in node.scanblocks(
+ "start", [{"desc": f"pkh({parent_key}/*)", "range": [0, 100]}], height)['relevant_blocks'])
+
+ # test node with disabled blockfilterindex
+ assert_raises_rpc_error(-1, "Index is not enabled for filtertype basic",
+ self.nodes[1].scanblocks, "start", [f"addr({addr_1})"])
+
+ # test unknown filtertype
+ assert_raises_rpc_error(-5, "Unknown filtertype",
+ node.scanblocks, "start", [f"addr({addr_1})"], 0, 10, "extended")
+
+ # test invalid start_height
+ assert_raises_rpc_error(-1, "Invalid start_height",
+ node.scanblocks, "start", [f"addr({addr_1})"], 100000000)
+
+ # test invalid stop_height
+ assert_raises_rpc_error(-1, "Invalid stop_height",
+ node.scanblocks, "start", [f"addr({addr_1})"], 10, 0)
+ assert_raises_rpc_error(-1, "Invalid stop_height",
+ node.scanblocks, "start", [f"addr({addr_1})"], 10, 100000000)
+
+ # test accessing the status (must be empty)
+ assert_equal(node.scanblocks("status"), None)
+
+ # test aborting the current scan (there is no, must return false)
+ assert_equal(node.scanblocks("abort"), False)
+
+ # test invalid command
+ assert_raises_rpc_error(-8, "Invalid command", node.scanblocks, "foobar")
+
+
+if __name__ == '__main__':
+ ScanblocksTest().main()
diff --git a/test/functional/rpc_scantxoutset.py b/test/functional/rpc_scantxoutset.py
index acb6d3ea4a..6eb5b493b9 100755
--- a/test/functional/rpc_scantxoutset.py
+++ b/test/functional/rpc_scantxoutset.py
@@ -33,6 +33,9 @@ class ScantxoutsetTest(BitcoinTestFramework):
self.wallet = MiniWallet(self.nodes[0])
self.wallet.rescan_utxos()
+ self.log.info("Test if we find coinbase outputs.")
+ assert_equal(sum(u["coinbase"] for u in self.nodes[0].scantxoutset("start", [self.wallet.get_descriptor()])["unspents"]), 49)
+
self.log.info("Create UTXOs...")
pubk1, spk_P2SH_SEGWIT, addr_P2SH_SEGWIT = getnewdestination("p2sh-segwit")
pubk2, spk_LEGACY, addr_LEGACY = getnewdestination("legacy")
diff --git a/test/functional/rpc_signer.py b/test/functional/rpc_signer.py
index f1107197c5..de17b2b929 100755
--- a/test/functional/rpc_signer.py
+++ b/test/functional/rpc_signer.py
@@ -77,10 +77,7 @@ class RPCSignerTest(BitcoinTestFramework):
)
self.clear_mock_result(self.nodes[1])
- result = self.nodes[1].enumeratesigners()
- assert_equal(len(result['signers']), 2)
- assert_equal(result['signers'][0]["fingerprint"], "00000001")
- assert_equal(result['signers'][0]["name"], "trezor_t")
+ assert_equal({'fingerprint': '00000001', 'name': 'trezor_t'} in self.nodes[1].enumeratesigners()['signers'], True)
if __name__ == '__main__':
RPCSignerTest().main()
diff --git a/test/functional/rpc_signmessagewithprivkey.py b/test/functional/rpc_signmessagewithprivkey.py
index 80555eab75..6635da150f 100755
--- a/test/functional/rpc_signmessagewithprivkey.py
+++ b/test/functional/rpc_signmessagewithprivkey.py
@@ -4,27 +4,44 @@
# file COPYING or http://www.opensource.org/licenses/mit-license.php.
"""Test RPC commands for signing messages with private key."""
+from test_framework.descriptors import (
+ descsum_create,
+)
from test_framework.test_framework import BitcoinTestFramework
from test_framework.util import (
assert_equal,
assert_raises_rpc_error,
)
+
class SignMessagesWithPrivTest(BitcoinTestFramework):
def set_test_params(self):
self.setup_clean_chain = True
self.num_nodes = 1
+ def addresses_from_privkey(self, priv_key):
+ '''Return addresses for a given WIF private key in legacy (P2PKH),
+ nested segwit (P2SH-P2WPKH) and native segwit (P2WPKH) formats.'''
+ descriptors = f'pkh({priv_key})', f'sh(wpkh({priv_key}))', f'wpkh({priv_key})'
+ return [self.nodes[0].deriveaddresses(descsum_create(desc))[0] for desc in descriptors]
+
def run_test(self):
message = 'This is just a test message'
self.log.info('test signing with priv_key')
priv_key = 'cUeKHd5orzT3mz8P9pxyREHfsWtVfgsfDjiZZBcjUBAaGk1BTj7N'
- address = 'mpLQjfK79b7CCV4VMJWEWAj5Mpx8Up5zxB'
expected_signature = 'INbVnW4e6PeRmsv2Qgu8NuopvrVjkcxob+sX8OcZG0SALhWybUjzMLPdAsXI46YZGb0KQTRii+wWIQzRpG/U+S0='
signature = self.nodes[0].signmessagewithprivkey(priv_key, message)
assert_equal(expected_signature, signature)
- assert self.nodes[0].verifymessage(address, signature, message)
+
+ self.log.info('test that verifying with P2PKH address succeeds')
+ addresses = self.addresses_from_privkey(priv_key)
+ assert_equal(addresses[0], 'mpLQjfK79b7CCV4VMJWEWAj5Mpx8Up5zxB')
+ assert self.nodes[0].verifymessage(addresses[0], signature, message)
+
+ self.log.info('test that verifying with non-P2PKH addresses throws error')
+ for non_p2pkh_address in addresses[1:]:
+ assert_raises_rpc_error(-3, "Address does not refer to key", self.nodes[0].verifymessage, non_p2pkh_address, signature, message)
self.log.info('test parameter validity and error codes')
# signmessagewithprivkey has two required parameters
@@ -41,5 +58,6 @@ class SignMessagesWithPrivTest(BitcoinTestFramework):
# malformed signature provided
assert_raises_rpc_error(-3, "Malformed base64 encoding", self.nodes[0].verifymessage, 'mpLQjfK79b7CCV4VMJWEWAj5Mpx8Up5zxB', "invalid_sig", message)
+
if __name__ == '__main__':
SignMessagesWithPrivTest().main()
diff --git a/test/functional/rpc_signrawtransactionwithkey.py b/test/functional/rpc_signrawtransactionwithkey.py
new file mode 100755
index 0000000000..0da5a99fdb
--- /dev/null
+++ b/test/functional/rpc_signrawtransactionwithkey.py
@@ -0,0 +1,140 @@
+#!/usr/bin/env python3
+# Copyright (c) 2015-2022 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 transaction signing using the signrawtransactionwithkey RPC."""
+
+from test_framework.blocktools import (
+ COINBASE_MATURITY,
+)
+from test_framework.address import (
+ script_to_p2sh,
+)
+from test_framework.key import ECKey
+from test_framework.test_framework import BitcoinTestFramework
+from test_framework.util import (
+ assert_equal,
+ find_vout_for_address,
+)
+from test_framework.script_util import (
+ key_to_p2pk_script,
+ key_to_p2pkh_script,
+ script_to_p2sh_p2wsh_script,
+ script_to_p2wsh_script,
+)
+from test_framework.wallet_util import (
+ bytes_to_wif,
+)
+
+from decimal import (
+ Decimal,
+)
+from test_framework.wallet import (
+ getnewdestination,
+)
+
+
+class SignRawTransactionWithKeyTest(BitcoinTestFramework):
+ def set_test_params(self):
+ self.setup_clean_chain = True
+ self.num_nodes = 2
+
+ def send_to_address(self, addr, amount):
+ input = {"txid": self.nodes[0].getblock(self.block_hash[self.blk_idx])["tx"][0], "vout": 0}
+ output = {addr: amount}
+ self.blk_idx += 1
+ rawtx = self.nodes[0].createrawtransaction([input], output)
+ txid = self.nodes[0].sendrawtransaction(self.nodes[0].signrawtransactionwithkey(rawtx, [self.nodes[0].get_deterministic_priv_key().key])["hex"], 0)
+ return txid
+
+ def successful_signing_test(self):
+ """Create and sign a valid raw transaction with one input.
+
+ Expected results:
+
+ 1) The transaction has a complete set of signatures
+ 2) No script verification error occurred"""
+ self.log.info("Test valid raw transaction with one input")
+ privKeys = ['cUeKHd5orzT3mz8P9pxyREHfsWtVfgsfDjiZZBcjUBAaGk1BTj7N', 'cVKpPfVKSJxKqVpE9awvXNWuLHCa5j5tiE7K6zbUSptFpTEtiFrA']
+
+ inputs = [
+ # Valid pay-to-pubkey scripts
+ {'txid': '9b907ef1e3c26fc71fe4a4b3580bc75264112f95050014157059c736f0202e71', 'vout': 0,
+ 'scriptPubKey': '76a91460baa0f494b38ce3c940dea67f3804dc52d1fb9488ac'},
+ {'txid': '83a4f6a6b73660e13ee6cb3c6063fa3759c50c9b7521d0536022961898f4fb02', 'vout': 0,
+ 'scriptPubKey': '76a914669b857c03a5ed269d5d85a1ffac9ed5d663072788ac'},
+ ]
+
+ outputs = {'mpLQjfK79b7CCV4VMJWEWAj5Mpx8Up5zxB': 0.1}
+
+ rawTx = self.nodes[0].createrawtransaction(inputs, outputs)
+ rawTxSigned = self.nodes[0].signrawtransactionwithkey(rawTx, privKeys, inputs)
+
+ # 1) The transaction has a complete set of signatures
+ assert rawTxSigned['complete']
+
+ # 2) No script verification error occurred
+ assert 'errors' not in rawTxSigned
+
+ def witness_script_test(self):
+ self.log.info("Test signing transaction to P2SH-P2WSH addresses without wallet")
+ # Create a new P2SH-P2WSH 1-of-1 multisig address:
+ eckey = ECKey()
+ eckey.generate()
+ embedded_privkey = bytes_to_wif(eckey.get_bytes())
+ embedded_pubkey = eckey.get_pubkey().get_bytes().hex()
+ p2sh_p2wsh_address = self.nodes[1].createmultisig(1, [embedded_pubkey], "p2sh-segwit")
+ # send transaction to P2SH-P2WSH 1-of-1 multisig address
+ self.block_hash = self.generate(self.nodes[0], COINBASE_MATURITY + 1)
+ self.blk_idx = 0
+ self.send_to_address(p2sh_p2wsh_address["address"], 49.999)
+ self.generate(self.nodes[0], 1)
+ # Get the UTXO info from scantxoutset
+ unspent_output = self.nodes[1].scantxoutset('start', [p2sh_p2wsh_address['descriptor']])['unspents'][0]
+ spk = script_to_p2sh_p2wsh_script(p2sh_p2wsh_address['redeemScript']).hex()
+ unspent_output['witnessScript'] = p2sh_p2wsh_address['redeemScript']
+ unspent_output['redeemScript'] = script_to_p2wsh_script(unspent_output['witnessScript']).hex()
+ assert_equal(spk, unspent_output['scriptPubKey'])
+ # Now create and sign a transaction spending that output on node[0], which doesn't know the scripts or keys
+ spending_tx = self.nodes[0].createrawtransaction([unspent_output], {getnewdestination()[2]: Decimal("49.998")})
+ spending_tx_signed = self.nodes[0].signrawtransactionwithkey(spending_tx, [embedded_privkey], [unspent_output])
+ # Check the signing completed successfully
+ assert 'complete' in spending_tx_signed
+ assert_equal(spending_tx_signed['complete'], True)
+
+ # Now test with P2PKH and P2PK scripts as the witnessScript
+ for tx_type in ['P2PKH', 'P2PK']: # these tests are order-independent
+ self.verify_txn_with_witness_script(tx_type)
+
+ def verify_txn_with_witness_script(self, tx_type):
+ self.log.info("Test with a {} script as the witnessScript".format(tx_type))
+ eckey = ECKey()
+ eckey.generate()
+ embedded_privkey = bytes_to_wif(eckey.get_bytes())
+ embedded_pubkey = eckey.get_pubkey().get_bytes().hex()
+ witness_script = {
+ 'P2PKH': key_to_p2pkh_script(embedded_pubkey).hex(),
+ 'P2PK': key_to_p2pk_script(embedded_pubkey).hex()
+ }.get(tx_type, "Invalid tx_type")
+ redeem_script = script_to_p2wsh_script(witness_script).hex()
+ addr = script_to_p2sh(redeem_script)
+ script_pub_key = self.nodes[1].validateaddress(addr)['scriptPubKey']
+ # Fund that address
+ txid = self.send_to_address(addr, 10)
+ vout = find_vout_for_address(self.nodes[0], txid, addr)
+ self.generate(self.nodes[0], 1)
+ # Now create and sign a transaction spending that output on node[0], which doesn't know the scripts or keys
+ spending_tx = self.nodes[0].createrawtransaction([{'txid': txid, 'vout': vout}], {getnewdestination()[2]: Decimal("9.999")})
+ spending_tx_signed = self.nodes[0].signrawtransactionwithkey(spending_tx, [embedded_privkey], [{'txid': txid, 'vout': vout, 'scriptPubKey': script_pub_key, 'redeemScript': redeem_script, 'witnessScript': witness_script, 'amount': 10}])
+ # Check the signing completed successfully
+ assert 'complete' in spending_tx_signed
+ assert_equal(spending_tx_signed['complete'], True)
+ self.nodes[0].sendrawtransaction(spending_tx_signed['hex'])
+
+ def run_test(self):
+ self.successful_signing_test()
+ self.witness_script_test()
+
+
+if __name__ == '__main__':
+ SignRawTransactionWithKeyTest().main()
diff --git a/test/functional/test_framework/blocktools.py b/test/functional/test_framework/blocktools.py
index 40fcbf7761..574ea10356 100644
--- a/test/functional/test_framework/blocktools.py
+++ b/test/functional/test_framework/blocktools.py
@@ -162,30 +162,6 @@ def create_tx_with_script(prevtx, n, script_sig=b"", *, amount, script_pub_key=C
tx.calc_sha256()
return tx
-def create_transaction(node, txid, to_address, *, amount):
- """ Return signed transaction spending the first output of the
- input txid. Note that the node must have a wallet that can
- sign for the output that is being spent.
- """
- raw_tx = create_raw_transaction(node, txid, to_address, amount=amount)
- tx = tx_from_hex(raw_tx)
- return tx
-
-def create_raw_transaction(node, txid, to_address, *, amount):
- """ Return raw signed transaction spending the first output of the
- input txid. Note that the node must have a wallet that can sign
- for the output that is being spent.
- """
- psbt = node.createpsbt(inputs=[{"txid": txid, "vout": 0}], outputs={to_address: amount})
- for _ in range(2):
- for w in node.listwallets():
- wrpc = node.get_wallet_rpc(w)
- signed_psbt = wrpc.walletprocesspsbt(psbt)
- psbt = signed_psbt['psbt']
- final_psbt = node.finalizepsbt(psbt)
- assert_equal(final_psbt["complete"], True)
- return final_psbt['hex']
-
def get_legacy_sigopcount_block(block, accurate=True):
count = 0
for tx in block.vtx:
diff --git a/test/functional/test_framework/messages.py b/test/functional/test_framework/messages.py
index aae44c0ac0..252b49cc6d 100755
--- a/test/functional/test_framework/messages.py
+++ b/test/functional/test_framework/messages.py
@@ -39,7 +39,7 @@ MAX_BLOOM_HASH_FUNCS = 50
COIN = 100000000 # 1 btc in satoshis
MAX_MONEY = 21000000 * COIN
-BIP125_SEQUENCE_NUMBER = 0xfffffffd # Sequence number that is rbf-opt-in (BIP 125) and csv-opt-out (BIP 68)
+MAX_BIP125_RBF_SEQUENCE = 0xfffffffd # Sequence number that is rbf-opt-in (BIP 125) and csv-opt-out (BIP 68)
SEQUENCE_FINAL = 0xffffffff # Sequence number that disables nLockTime if set for every input of a tx
MAX_PROTOCOL_MESSAGE_LENGTH = 4000000 # Maximum length of incoming protocol messages
@@ -65,6 +65,13 @@ FILTER_TYPE_BASIC = 0
WITNESS_SCALE_FACTOR = 4
+DEFAULT_ANCESTOR_LIMIT = 25 # default max number of in-mempool ancestors
+DEFAULT_DESCENDANT_LIMIT = 25 # default max number of in-mempool descendants
+
+# Default setting for -datacarriersize. 80 bytes of data, +1 for OP_RETURN, +2 for the pushdata opcodes.
+MAX_OP_RETURN_RELAY = 83
+
+DEFAULT_MEMPOOL_EXPIRY_HOURS = 336 # hours
def sha256(s):
return hashlib.sha256(s).digest()
@@ -208,6 +215,20 @@ def tx_from_hex(hex_string):
return from_hex(CTransaction(), hex_string)
+# like from_hex, but without the hex part
+def from_binary(cls, stream):
+ """deserialize a binary stream (or bytes object) into an object"""
+ # handle bytes object by turning it into a stream
+ was_bytes = isinstance(stream, bytes)
+ if was_bytes:
+ stream = BytesIO(stream)
+ obj = cls()
+ obj.deserialize(stream)
+ if was_bytes:
+ assert len(stream.read()) == 0
+ return obj
+
+
# Objects that map to bitcoind objects, which can be serialized/deserialized
@@ -1817,3 +1838,31 @@ class msg_cfcheckpt:
def __repr__(self):
return "msg_cfcheckpt(filter_type={:#x}, stop_hash={:x})".format(
self.filter_type, self.stop_hash)
+
+class msg_sendtxrcncl:
+ __slots__ = ("initiator", "responder", "version", "salt")
+ msgtype = b"sendtxrcncl"
+
+ def __init__(self):
+ self.initiator = False
+ self.responder = False
+ self.version = 0
+ self.salt = 0
+
+ def deserialize(self, f):
+ self.initiator = struct.unpack("<?", f.read(1))[0]
+ self.responder = struct.unpack("<?", f.read(1))[0]
+ self.version = struct.unpack("<I", f.read(4))[0]
+ self.salt = struct.unpack("<Q", f.read(8))[0]
+
+ def serialize(self):
+ r = b""
+ r += struct.pack("<?", self.initiator)
+ r += struct.pack("<?", self.responder)
+ r += struct.pack("<I", self.version)
+ r += struct.pack("<Q", self.salt)
+ return r
+
+ def __repr__(self):
+ return "msg_sendtxrcncl(initiator=%i, responder=%i, version=%lu, salt=%lu)" %\
+ (self.initiator, self.responder, self.version, self.salt)
diff --git a/test/functional/test_framework/p2p.py b/test/functional/test_framework/p2p.py
index fc72a9ab73..05b46e630c 100755
--- a/test/functional/test_framework/p2p.py
+++ b/test/functional/test_framework/p2p.py
@@ -62,6 +62,7 @@ from test_framework.messages import (
msg_sendaddrv2,
msg_sendcmpct,
msg_sendheaders,
+ msg_sendtxrcncl,
msg_tx,
MSG_TX,
MSG_TYPE_MASK,
@@ -126,6 +127,7 @@ MESSAGEMAP = {
b"sendaddrv2": msg_sendaddrv2,
b"sendcmpct": msg_sendcmpct,
b"sendheaders": msg_sendheaders,
+ b"sendtxrcncl": msg_sendtxrcncl,
b"tx": msg_tx,
b"verack": msg_verack,
b"version": msg_version,
@@ -421,6 +423,7 @@ class P2PInterface(P2PConnection):
def on_sendaddrv2(self, message): pass
def on_sendcmpct(self, message): pass
def on_sendheaders(self, message): pass
+ def on_sendtxrcncl(self, message): pass
def on_tx(self, message): pass
def on_wtxidrelay(self, message): pass
@@ -446,6 +449,7 @@ class P2PInterface(P2PConnection):
self.send_message(msg_sendaddrv2())
self.send_message(msg_verack())
self.nServices = message.nServices
+ self.relay = message.relay
self.send_message(msg_getaddr())
# Connection helper methods
diff --git a/test/functional/test_framework/psbt.py b/test/functional/test_framework/psbt.py
new file mode 100644
index 0000000000..3a5b4ec74d
--- /dev/null
+++ b/test/functional/test_framework/psbt.py
@@ -0,0 +1,140 @@
+#!/usr/bin/env python3
+# Copyright (c) 2022 The Bitcoin Core developers
+# Distributed under the MIT software license, see the accompanying
+# file COPYING or http://www.opensource.org/licenses/mit-license.php.
+
+import base64
+
+from .messages import (
+ CTransaction,
+ deser_string,
+ from_binary,
+ ser_compact_size,
+)
+
+
+# global types
+PSBT_GLOBAL_UNSIGNED_TX = 0x00
+PSBT_GLOBAL_XPUB = 0x01
+PSBT_GLOBAL_TX_VERSION = 0x02
+PSBT_GLOBAL_FALLBACK_LOCKTIME = 0x03
+PSBT_GLOBAL_INPUT_COUNT = 0x04
+PSBT_GLOBAL_OUTPUT_COUNT = 0x05
+PSBT_GLOBAL_TX_MODIFIABLE = 0x06
+PSBT_GLOBAL_VERSION = 0xfb
+PSBT_GLOBAL_PROPRIETARY = 0xfc
+
+# per-input types
+PSBT_IN_NON_WITNESS_UTXO = 0x00
+PSBT_IN_WITNESS_UTXO = 0x01
+PSBT_IN_PARTIAL_SIG = 0x02
+PSBT_IN_SIGHASH_TYPE = 0x03
+PSBT_IN_REDEEM_SCRIPT = 0x04
+PSBT_IN_WITNESS_SCRIPT = 0x05
+PSBT_IN_BIP32_DERIVATION = 0x06
+PSBT_IN_FINAL_SCRIPTSIG = 0x07
+PSBT_IN_FINAL_SCRIPTWITNESS = 0x08
+PSBT_IN_POR_COMMITMENT = 0x09
+PSBT_IN_RIPEMD160 = 0x0a
+PSBT_IN_SHA256 = 0x0b
+PSBT_IN_HASH160 = 0x0c
+PSBT_IN_HASH256 = 0x0d
+PSBT_IN_PREVIOUS_TXID = 0x0e
+PSBT_IN_OUTPUT_INDEX = 0x0f
+PSBT_IN_SEQUENCE = 0x10
+PSBT_IN_REQUIRED_TIME_LOCKTIME = 0x11
+PSBT_IN_REQUIRED_HEIGHT_LOCKTIME = 0x12
+PSBT_IN_TAP_KEY_SIG = 0x13
+PSBT_IN_TAP_SCRIPT_SIG = 0x14
+PSBT_IN_TAP_LEAF_SCRIPT = 0x15
+PSBT_IN_TAP_BIP32_DERIVATION = 0x16
+PSBT_IN_TAP_INTERNAL_KEY = 0x17
+PSBT_IN_TAP_MERKLE_ROOT = 0x18
+PSBT_IN_PROPRIETARY = 0xfc
+
+# per-output types
+PSBT_OUT_REDEEM_SCRIPT = 0x00
+PSBT_OUT_WITNESS_SCRIPT = 0x01
+PSBT_OUT_BIP32_DERIVATION = 0x02
+PSBT_OUT_AMOUNT = 0x03
+PSBT_OUT_SCRIPT = 0x04
+PSBT_OUT_TAP_INTERNAL_KEY = 0x05
+PSBT_OUT_TAP_TREE = 0x06
+PSBT_OUT_TAP_BIP32_DERIVATION = 0x07
+PSBT_OUT_PROPRIETARY = 0xfc
+
+
+class PSBTMap:
+ """Class for serializing and deserializing PSBT maps"""
+
+ def __init__(self, map=None):
+ self.map = map if map is not None else {}
+
+ def deserialize(self, f):
+ m = {}
+ while True:
+ k = deser_string(f)
+ if len(k) == 0:
+ break
+ v = deser_string(f)
+ if len(k) == 1:
+ k = k[0]
+ assert k not in m
+ m[k] = v
+ self.map = m
+
+ def serialize(self):
+ m = b""
+ for k,v in self.map.items():
+ if isinstance(k, int) and 0 <= k and k <= 255:
+ k = bytes([k])
+ m += ser_compact_size(len(k)) + k
+ m += ser_compact_size(len(v)) + v
+ m += b"\x00"
+ return m
+
+class PSBT:
+ """Class for serializing and deserializing PSBTs"""
+
+ def __init__(self, *, g=None, i=None, o=None):
+ self.g = g if g is not None else PSBTMap()
+ self.i = i if i is not None else []
+ self.o = o if o is not None else []
+ self.tx = None
+
+ def deserialize(self, f):
+ assert f.read(5) == b"psbt\xff"
+ self.g = from_binary(PSBTMap, f)
+ assert 0 in self.g.map
+ self.tx = from_binary(CTransaction, self.g.map[0])
+ self.i = [from_binary(PSBTMap, f) for _ in self.tx.vin]
+ self.o = [from_binary(PSBTMap, f) for _ in self.tx.vout]
+ return self
+
+ def serialize(self):
+ assert isinstance(self.g, PSBTMap)
+ assert isinstance(self.i, list) and all(isinstance(x, PSBTMap) for x in self.i)
+ assert isinstance(self.o, list) and all(isinstance(x, PSBTMap) for x in self.o)
+ assert 0 in self.g.map
+ tx = from_binary(CTransaction, self.g.map[0])
+ assert len(tx.vin) == len(self.i)
+ assert len(tx.vout) == len(self.o)
+
+ psbt = [x.serialize() for x in [self.g] + self.i + self.o]
+ return b"psbt\xff" + b"".join(psbt)
+
+ def make_blank(self):
+ """
+ Remove all fields except for PSBT_GLOBAL_UNSIGNED_TX
+ """
+ for m in self.i + self.o:
+ m.map.clear()
+
+ self.g = PSBTMap(map={0: self.g.map[0]})
+
+ def to_base64(self):
+ return base64.b64encode(self.serialize()).decode("utf8")
+
+ @classmethod
+ def from_base64(cls, b64psbt):
+ return from_binary(cls, base64.b64decode(b64psbt))
diff --git a/test/functional/test_framework/test_framework.py b/test/functional/test_framework/test_framework.py
index c880aabd21..b1164b98fd 100755
--- a/test/functional/test_framework/test_framework.py
+++ b/test/functional/test_framework/test_framework.py
@@ -596,24 +596,24 @@ class BitcoinTestFramework(metaclass=BitcoinTestMetaClass):
self.wait_until(lambda: sum(peer['bytesrecv_per_msg'].pop('verack', 0) == 24 for peer in to_connection.getpeerinfo()) == to_num_peers)
def disconnect_nodes(self, a, b):
- def disconnect_nodes_helper(from_connection, node_num):
- def get_peer_ids():
+ def disconnect_nodes_helper(node_a, node_b):
+ def get_peer_ids(from_connection, node_num):
result = []
for peer in from_connection.getpeerinfo():
if "testnode{}".format(node_num) in peer['subver']:
result.append(peer['id'])
return result
- peer_ids = get_peer_ids()
+ peer_ids = get_peer_ids(node_a, node_b.index)
if not peer_ids:
self.log.warning("disconnect_nodes: {} and {} were not connected".format(
- from_connection.index,
- node_num,
+ node_a.index,
+ node_b.index,
))
return
for peer_id in peer_ids:
try:
- from_connection.disconnectnode(nodeid=peer_id)
+ node_a.disconnectnode(nodeid=peer_id)
except JSONRPCException as e:
# If this node is disconnected between calculating the peer id
# and issuing the disconnect, don't worry about it.
@@ -622,9 +622,10 @@ class BitcoinTestFramework(metaclass=BitcoinTestMetaClass):
raise
# wait to disconnect
- self.wait_until(lambda: not get_peer_ids(), timeout=5)
+ self.wait_until(lambda: not get_peer_ids(node_a, node_b.index), timeout=5)
+ self.wait_until(lambda: not get_peer_ids(node_b, node_a.index), timeout=5)
- disconnect_nodes_helper(self.nodes[a], b)
+ disconnect_nodes_helper(self.nodes[a], self.nodes[b])
def split_network(self):
"""
diff --git a/test/functional/test_framework/test_node.py b/test/functional/test_framework/test_node.py
index 03f6c8adea..2367a9a8fa 100755
--- a/test/functional/test_framework/test_node.py
+++ b/test/functional/test_framework/test_node.py
@@ -118,6 +118,8 @@ class TestNode():
self.args.append("-logthreadnames")
if self.version_is_at_least(219900):
self.args.append("-logsourcelocations")
+ if self.version_is_at_least(239000):
+ self.args.append("-loglevel=trace")
self.cli = TestNodeCLI(bitcoin_cli, self.datadir)
self.use_cli = use_cli
@@ -616,7 +618,7 @@ class TestNode():
return p2p_conn
- def add_outbound_p2p_connection(self, p2p_conn, *, p2p_idx, connection_type="outbound-full-relay", **kwargs):
+ def add_outbound_p2p_connection(self, p2p_conn, *, wait_for_verack=True, p2p_idx, connection_type="outbound-full-relay", **kwargs):
"""Add an outbound p2p connection from node. Must be an
"outbound-full-relay", "block-relay-only", "addr-fetch" or "feeler" connection.
@@ -638,8 +640,9 @@ class TestNode():
p2p_conn.wait_for_connect()
self.p2ps.append(p2p_conn)
- p2p_conn.wait_for_verack()
- p2p_conn.sync_with_ping()
+ if wait_for_verack:
+ p2p_conn.wait_for_verack()
+ p2p_conn.sync_with_ping()
return p2p_conn
diff --git a/test/functional/test_framework/util.py b/test/functional/test_framework/util.py
index 6a588275ea..bfc835f272 100644
--- a/test/functional/test_framework/util.py
+++ b/test/functional/test_framework/util.py
@@ -12,6 +12,7 @@ import inspect
import json
import logging
import os
+import random
import re
import time
import unittest
@@ -28,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:
@@ -286,6 +291,13 @@ def sha256sum_file(filename):
d = f.read(4096)
return h.digest()
+
+# TODO: Remove and use random.randbytes(n) instead, available in Python 3.9
+def random_bytes(n):
+ """Return a random bytes object of length n."""
+ return bytes(random.getrandbits(8) for i in range(n))
+
+
# RPC/P2P connection constants and functions
############################################
@@ -499,38 +511,26 @@ def chain_transaction(node, parent_txids, vouts, value, fee, num_outputs):
# Create large OP_RETURN txouts that can be appended to a transaction
-# to make it large (helper for constructing large transactions).
+# to make it large (helper for constructing large transactions). The
+# total serialized size of the txouts is about 66k vbytes.
def gen_return_txouts():
- # Some pre-processing to create a bunch of OP_RETURN txouts to insert into transactions we create
- # So we have big transactions (and therefore can't fit very many into each block)
- # create one script_pubkey
- script_pubkey = "6a4d0200" # OP_RETURN OP_PUSH2 512 bytes
- for _ in range(512):
- script_pubkey = script_pubkey + "01"
- # concatenate 128 txouts of above script_pubkey which we'll insert before the txout for change
- txouts = []
from .messages import CTxOut
- txout = CTxOut()
- txout.nValue = 0
- txout.scriptPubKey = bytes.fromhex(script_pubkey)
- for _ in range(128):
- txouts.append(txout)
+ from .script import CScript, OP_RETURN
+ txouts = [CTxOut(nValue=0, scriptPubKey=CScript([OP_RETURN, b'\x01'*67437]))]
+ assert_equal(sum([len(txout.serialize()) for txout in txouts]), 67456)
return txouts
# Create a spend of each passed-in utxo, splicing in "txouts" to each raw
# transaction to make it large. See gen_return_txouts() above.
def create_lots_of_big_transactions(mini_wallet, node, fee, tx_batch_size, txouts, utxos=None):
- from .messages import COIN
- fee_sats = int(fee * COIN)
txids = []
use_internal_utxos = utxos is None
for _ in range(tx_batch_size):
tx = mini_wallet.create_self_transfer(
utxo_to_spend=None if use_internal_utxos else utxos.pop(),
- fee_rate=0,
+ fee=fee,
)["tx"]
- tx.vout[0].nValue -= fee_sats
tx.vout.extend(txouts)
res = node.testmempoolaccept([tx.serialize().hex()])[0]
assert_equal(res['fees']['base'], fee)
diff --git a/test/functional/test_framework/wallet.py b/test/functional/test_framework/wallet.py
index 68d5dfa880..374fda5c23 100644
--- a/test/functional/test_framework/wallet.py
+++ b/test/functional/test_framework/wallet.py
@@ -7,7 +7,6 @@
from copy import deepcopy
from decimal import Decimal
from enum import Enum
-from random import choice
from typing import (
Any,
List,
@@ -40,6 +39,7 @@ from test_framework.script import (
LegacySignatureHash,
LEAF_VERSION_TAPSCRIPT,
OP_NOP,
+ OP_RETURN,
OP_TRUE,
SIGHASH_ALL,
taproot_construct,
@@ -104,6 +104,18 @@ class MiniWallet:
def _create_utxo(self, *, txid, vout, value, height):
return {"txid": txid, "vout": vout, "value": value, "height": height}
+ def _bulk_tx(self, tx, target_weight):
+ """Pad a transaction with extra outputs until it reaches a target weight (or higher).
+ returns the tx
+ """
+ tx.vout.append(CTxOut(nValue=0, scriptPubKey=CScript([OP_RETURN, b'a'])))
+ dummy_vbytes = (target_weight - tx.get_weight() + 3) // 4
+ tx.vout[-1].scriptPubKey = CScript([OP_RETURN, b'a' * dummy_vbytes])
+ # Lower bound should always be off by at most 3
+ assert_greater_than_or_equal(tx.get_weight(), target_weight)
+ # Higher bound should always be off by at most 3 + 12 weight (for encoding the length)
+ assert_greater_than_or_equal(target_weight + 15, tx.get_weight())
+
def get_balance(self):
return sum(u['value'] for u in self._utxos)
@@ -197,7 +209,7 @@ class MiniWallet:
return utxos
def send_self_transfer(self, *, from_node, **kwargs):
- """Create and send a tx with the specified fee_rate. Fee may be exact or at most one satoshi higher than needed."""
+ """Call create_self_transfer and send the transaction."""
tx = self.create_self_transfer(**kwargs)
self.sendrawtransaction(from_node=from_node, tx_hex=tx['hex'])
return tx
@@ -232,21 +244,28 @@ class MiniWallet:
*,
utxos_to_spend: Optional[List[dict]] = None,
num_outputs=1,
+ amount_per_output=0,
sequence=0,
fee_per_output=1000,
+ target_weight=0
):
"""
Create and return a transaction that spends the given UTXOs and creates a
- certain number of outputs with equal amounts.
+ certain number of outputs with equal amounts. The output amounts can be
+ set by amount_per_output or automatically calculated with a fee_per_output.
"""
utxos_to_spend = utxos_to_spend or [self.get_utxo()]
+ sequence = [sequence] * len(utxos_to_spend) if type(sequence) is int else sequence
+ assert_equal(len(utxos_to_spend), len(sequence))
# create simple tx template (1 input, 1 output)
tx = self.create_self_transfer(
fee_rate=0,
- utxo_to_spend=utxos_to_spend[0], sequence=sequence)["tx"]
+ utxo_to_spend=utxos_to_spend[0])["tx"]
# duplicate inputs, witnesses and outputs
tx.vin = [deepcopy(tx.vin[0]) for _ in range(len(utxos_to_spend))]
+ for txin, seq in zip(tx.vin, sequence):
+ txin.nSequence = seq
tx.wit.vtxinwit = [deepcopy(tx.wit.vtxinwit[0]) for _ in range(len(utxos_to_spend))]
tx.vout = [deepcopy(tx.vout[0]) for _ in range(num_outputs)]
@@ -258,7 +277,11 @@ class MiniWallet:
inputs_value_total = sum([int(COIN * utxo['value']) for utxo in utxos_to_spend])
outputs_value_total = inputs_value_total - fee_per_output * num_outputs
for o in tx.vout:
- o.nValue = outputs_value_total // num_outputs
+ o.nValue = amount_per_output or (outputs_value_total // num_outputs)
+
+ if target_weight:
+ self._bulk_tx(tx, target_weight)
+
txid = tx.rehash()
return {
"new_utxos": [self._create_utxo(
@@ -272,21 +295,23 @@ class MiniWallet:
"tx": tx,
}
- def create_self_transfer(self, *, fee_rate=Decimal("0.003"), utxo_to_spend=None, locktime=0, sequence=0):
- """Create and return a tx with the specified fee_rate. Fee may be exact or at most one satoshi higher than needed."""
+ def create_self_transfer(self, *, fee_rate=Decimal("0.003"), fee=Decimal("0"), utxo_to_spend=None, locktime=0, sequence=0, target_weight=0):
+ """Create and return a tx with the specified fee. If fee is 0, use fee_rate, where the resulting fee may be exact or at most one satoshi higher than needed."""
utxo_to_spend = utxo_to_spend or self.get_utxo()
+ assert fee_rate >= 0
+ assert fee >= 0
if self._mode in (MiniWalletMode.RAW_OP_TRUE, MiniWalletMode.ADDRESS_OP_TRUE):
vsize = Decimal(104) # anyone-can-spend
elif self._mode == MiniWalletMode.RAW_P2PK:
vsize = Decimal(168) # P2PK (73 bytes scriptSig + 35 bytes scriptPubKey + 60 bytes other)
else:
assert False
- send_value = utxo_to_spend["value"] - (fee_rate * vsize / 1000)
+ send_value = utxo_to_spend["value"] - (fee or (fee_rate * vsize / 1000))
assert send_value > 0
tx = CTransaction()
tx.vin = [CTxIn(COutPoint(int(utxo_to_spend['txid'], 16), utxo_to_spend['vout']), nSequence=sequence)]
- tx.vout = [CTxOut(int(COIN * send_value), self._scriptPubKey)]
+ tx.vout = [CTxOut(int(COIN * send_value), bytearray(self._scriptPubKey))]
tx.nLockTime = locktime
if self._mode == MiniWalletMode.RAW_P2PK:
self.sign_tx(tx)
@@ -297,9 +322,13 @@ class MiniWallet:
tx.wit.vtxinwit[0].scriptWitness.stack = [CScript([OP_TRUE]), bytes([LEAF_VERSION_TAPSCRIPT]) + self._internal_key]
else:
assert False
- tx_hex = tx.serialize().hex()
assert_equal(tx.get_vsize(), vsize)
+
+ if target_weight:
+ self._bulk_tx(tx, target_weight)
+
+ tx_hex = tx.serialize().hex()
new_utxo = self._create_utxo(txid=tx.rehash(), vout=0, value=send_value, height=0)
return {"txid": new_utxo["txid"], "wtxid": tx.getwtxid(), "hex": tx_hex, "tx": tx, "new_utxo": new_utxo}
@@ -309,6 +338,17 @@ class MiniWallet:
self.scan_tx(from_node.decoderawtransaction(tx_hex))
return txid
+ def send_self_transfer_chain(self, *, from_node, chain_length, utxo_to_spend=None):
+ """Create and send a "chain" of chain_length transactions. The nth transaction in
+ the chain is a child of the n-1th transaction and parent of the n+1th transaction.
+
+ Returns the chaintip (nth) utxo
+ """
+ chaintip_utxo = utxo_to_spend or self.get_utxo()
+ for _ in range(chain_length):
+ chaintip_utxo = self.send_self_transfer(utxo_to_spend=chaintip_utxo, from_node=from_node)["new_utxo"]
+ return chaintip_utxo
+
def getnewdestination(address_type='bech32m'):
"""Generate a random destination of the specified type and return the
@@ -401,23 +441,3 @@ def create_raw_chain(node, first_coin, address, privkeys, chain_length=25):
chain_txns.append(tx)
return (chain_hex, chain_txns)
-
-def bulk_transaction(tx, node, target_weight, privkeys, prevtxs=None):
- """Pad a transaction with extra outputs until it reaches a target weight (or higher).
- returns CTransaction object
- """
- tx_heavy = deepcopy(tx)
- assert_greater_than_or_equal(target_weight, tx_heavy.get_weight())
- while tx_heavy.get_weight() < target_weight:
- random_spk = "6a4d0200" # OP_RETURN OP_PUSH2 512 bytes
- for _ in range(512*2):
- random_spk += choice("0123456789ABCDEF")
- tx_heavy.vout.append(CTxOut(0, bytes.fromhex(random_spk)))
- # Re-sign the transaction
- if privkeys:
- signed = node.signrawtransactionwithkey(tx_heavy.serialize().hex(), privkeys, prevtxs)
- return tx_from_hex(signed["hex"])
- # OP_TRUE
- tx_heavy.wit.vtxinwit = [CTxInWitness()]
- tx_heavy.wit.vtxinwit[0].scriptWitness.stack = [CScript([OP_TRUE])]
- return tx_heavy
diff --git a/test/functional/test_runner.py b/test/functional/test_runner.py
index 6a44f9d21d..e20de8ea8e 100755
--- a/test/functional/test_runner.py
+++ b/test/functional/test_runner.py
@@ -130,6 +130,7 @@ BASE_SCRIPTS = [
'wallet_address_types.py --descriptors',
'feature_bip68_sequence.py',
'p2p_feefilter.py',
+ 'rpc_packages.py',
'feature_reindex.py',
'feature_abortnode.py',
# vv Tests less than 30s vv
@@ -155,6 +156,7 @@ BASE_SCRIPTS = [
'mempool_spend_coinbase.py',
'wallet_avoidreuse.py --legacy-wallet',
'wallet_avoidreuse.py --descriptors',
+ 'wallet_avoid_mixing_output_types.py --descriptors',
'mempool_reorg.py',
'mempool_persist.py',
'p2p_block_sync.py',
@@ -181,8 +183,10 @@ BASE_SCRIPTS = [
'rpc_whitelist.py',
'feature_proxy.py',
'feature_syscall_sandbox.py',
- 'rpc_signrawtransaction.py --legacy-wallet',
- 'rpc_signrawtransaction.py --descriptors',
+ 'wallet_signrawtransactionwithwallet.py --legacy-wallet',
+ 'wallet_signrawtransactionwithwallet.py --descriptors',
+ 'rpc_signrawtransactionwithkey.py',
+ 'p2p_headers_sync_with_minchainwork.py',
'rpc_rawtransaction.py --legacy-wallet',
'wallet_groups.py --legacy-wallet',
'wallet_transactiontime_rescan.py --descriptors',
@@ -203,6 +207,7 @@ BASE_SCRIPTS = [
'wallet_keypool.py --legacy-wallet',
'wallet_keypool.py --descriptors',
'wallet_descriptor.py --descriptors',
+ 'wallet_miniscript.py',
'feature_maxtipage.py',
'p2p_nobloomfilter_messages.py',
'p2p_filter.py',
@@ -226,12 +231,10 @@ BASE_SCRIPTS = [
'rpc_getblockfrompeer.py',
'rpc_invalidateblock.py',
'feature_utxo_set_hash.py',
- 'feature_rbf.py --legacy-wallet',
- 'feature_rbf.py --descriptors',
+ 'feature_rbf.py',
'mempool_packages.py',
'mempool_package_onemore.py',
'rpc_createmultisig.py',
- 'rpc_packages.py',
'mempool_package_limits.py',
'feature_versionbits_warning.py',
'rpc_preciousblock.py',
@@ -244,8 +247,8 @@ BASE_SCRIPTS = [
'rpc_generate.py',
'wallet_balance.py --legacy-wallet',
'wallet_balance.py --descriptors',
- 'feature_nulldummy.py --legacy-wallet',
- 'feature_nulldummy.py --descriptors',
+ 'p2p_initial_headers_sync.py',
+ 'feature_nulldummy.py',
'mempool_accept.py',
'mempool_expiry.py',
'wallet_import_rescan.py --legacy-wallet',
@@ -263,6 +266,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',
@@ -272,6 +277,7 @@ BASE_SCRIPTS = [
'feature_dersig.py',
'feature_cltv.py',
'rpc_uptime.py',
+ 'feature_discover.py',
'wallet_resendwallettransactions.py --legacy-wallet',
'wallet_resendwallettransactions.py --descriptors',
'wallet_fallbackfee.py --legacy-wallet',
@@ -311,11 +317,14 @@ BASE_SCRIPTS = [
'rpc_deriveaddresses.py',
'rpc_deriveaddresses.py --usecli',
'p2p_ping.py',
+ 'rpc_scanblocks.py',
+ 'p2p_sendtxrcncl.py',
'rpc_scantxoutset.py',
'feature_txindex_compatibility.py',
'feature_unsupported_utxo_db.py',
'feature_logging.py',
'feature_anchors.py',
+ 'mempool_datacarrier.py',
'feature_coinstatsindex.py',
'wallet_orphanedreward.py',
'wallet_timelock.py',
@@ -324,6 +333,7 @@ BASE_SCRIPTS = [
'feature_blocksdir.py',
'wallet_startup.py',
'p2p_i2p_ports.py',
+ 'p2p_i2p_sessions.py',
'feature_config_args.py',
'feature_presegwit_node_upgrade.py',
'feature_settings.py',
@@ -333,6 +343,7 @@ BASE_SCRIPTS = [
'feature_dirsymlinks.py',
'feature_help.py',
'feature_shutdown.py',
+ 'wallet_migration.py',
'p2p_ibd_txrelay.py',
# Don't append tests at the end to avoid merge conflicts
# Put them in a random line within the section that fits their approximate run-time
@@ -546,14 +557,14 @@ def run_tests(*, test_list, src_dir, build_dir, tmpdir, jobs=1, enable_coverage=
while i < test_count:
if failfast and not all_passed:
break
- for test_result, testdir, stdout, stderr in job_queue.get_next():
+ for test_result, testdir, stdout, stderr, skip_reason in job_queue.get_next():
test_results.append(test_result)
i += 1
done_str = "{}/{} - {}{}{}".format(i, test_count, BOLD[1], test_result.name, BOLD[0])
if test_result.status == "Passed":
logging.debug("%s passed, Duration: %s s" % (done_str, test_result.time))
elif test_result.status == "Skipped":
- logging.debug("%s skipped" % (done_str))
+ logging.debug(f"{done_str} skipped ({skip_reason})")
else:
all_passed = False
print("%s failed, Duration: %s s\n" % (done_str, test_result.time))
@@ -677,10 +688,12 @@ class TestHandler:
log_out.seek(0), log_err.seek(0)
[stdout, stderr] = [log_file.read().decode('utf-8') for log_file in (log_out, log_err)]
log_out.close(), log_err.close()
+ skip_reason = None
if proc.returncode == TEST_EXIT_PASSED and stderr == "":
status = "Passed"
elif proc.returncode == TEST_EXIT_SKIPPED:
status = "Skipped"
+ skip_reason = re.search(r"Test Skipped: (.*)", stdout).group(1)
else:
status = "Failed"
self.num_running -= 1
@@ -689,7 +702,7 @@ class TestHandler:
clearline = '\r' + (' ' * dot_count) + '\r'
print(clearline, end='', flush=True)
dot_count = 0
- ret.append((TestResult(name, status, int(time.time() - start_time)), testdir, stdout, stderr))
+ ret.append((TestResult(name, status, int(time.time() - start_time)), testdir, stdout, stderr, skip_reason))
if ret:
return ret
if self.use_term_control:
diff --git a/test/functional/tool_wallet.py b/test/functional/tool_wallet.py
index 2cb9dc4523..1e5ce513cb 100755
--- a/test/functional/tool_wallet.py
+++ b/test/functional/tool_wallet.py
@@ -68,7 +68,7 @@ class ToolWalletTest(BitcoinTestFramework):
result = 'unchanged' if new == old else 'increased!'
self.log.debug('Wallet file timestamp {}'.format(result))
- def get_expected_info_output(self, name="", transactions=0, keypool=2, address=0):
+ def get_expected_info_output(self, name="", transactions=0, keypool=2, address=0, imported_privs=0):
wallet_name = self.default_wallet_name if name == "" else name
if self.options.descriptors:
output_types = 4 # p2pkh, p2sh, segwit, bech32m
@@ -83,7 +83,7 @@ class ToolWalletTest(BitcoinTestFramework):
Keypool Size: %d
Transactions: %d
Address Book: %d
- ''' % (wallet_name, keypool * output_types, transactions, address))
+ ''' % (wallet_name, keypool * output_types, transactions, imported_privs * 3 + address))
else:
output_types = 3 # p2pkh, p2sh, segwit. Legacy wallets do not support bech32m.
return textwrap.dedent('''\
@@ -97,7 +97,7 @@ class ToolWalletTest(BitcoinTestFramework):
Keypool Size: %d
Transactions: %d
Address Book: %d
- ''' % (wallet_name, keypool, transactions, address * output_types))
+ ''' % (wallet_name, keypool, transactions, (address + imported_privs) * output_types))
def read_dump(self, filename):
dump = OrderedDict()
@@ -219,7 +219,7 @@ class ToolWalletTest(BitcoinTestFramework):
# shasum_before = self.wallet_shasum()
timestamp_before = self.wallet_timestamp()
self.log.debug('Wallet file timestamp before calling info: {}'.format(timestamp_before))
- out = self.get_expected_info_output(address=1)
+ out = self.get_expected_info_output(imported_privs=1)
self.assert_tool_output(out, '-wallet=' + self.default_wallet_name, 'info')
timestamp_after = self.wallet_timestamp()
self.log.debug('Wallet file timestamp after calling info: {}'.format(timestamp_after))
@@ -250,7 +250,7 @@ class ToolWalletTest(BitcoinTestFramework):
shasum_before = self.wallet_shasum()
timestamp_before = self.wallet_timestamp()
self.log.debug('Wallet file timestamp before calling info: {}'.format(timestamp_before))
- out = self.get_expected_info_output(transactions=1, address=1)
+ out = self.get_expected_info_output(transactions=1, imported_privs=1)
self.assert_tool_output(out, '-wallet=' + self.default_wallet_name, 'info')
shasum_after = self.wallet_shasum()
timestamp_after = self.wallet_timestamp()
diff --git a/test/functional/wallet_abandonconflict.py b/test/functional/wallet_abandonconflict.py
index 36fcdb36d6..d7850b41ac 100755
--- a/test/functional/wallet_abandonconflict.py
+++ b/test/functional/wallet_abandonconflict.py
@@ -24,6 +24,9 @@ class AbandonConflictTest(BitcoinTestFramework):
def set_test_params(self):
self.num_nodes = 2
self.extra_args = [["-minrelaytxfee=0.00001"], []]
+ # whitelist peers to speed up tx relay / mempool sync
+ for args in self.extra_args:
+ args.append("-whitelist=noban@127.0.0.1")
def skip_test_if_missing_module(self):
self.skip_if_no_wallet()
diff --git a/test/functional/wallet_address_types.py b/test/functional/wallet_address_types.py
index f7c80f805c..5b836f693f 100755
--- a/test/functional/wallet_address_types.py
+++ b/test/functional/wallet_address_types.py
@@ -345,31 +345,19 @@ class AddressTypeTest(BitcoinTestFramework):
self.log.info("Nodes with addresstype=legacy never use a P2WPKH change output (unless changetype is set otherwise):")
self.test_change_output_type(0, [to_address_bech32_1], 'legacy')
- if self.options.descriptors:
- self.log.info("Nodes with addresstype=p2sh-segwit match the change output")
- self.test_change_output_type(1, [to_address_p2sh], 'p2sh-segwit')
- self.test_change_output_type(1, [to_address_bech32_1], 'bech32')
- self.test_change_output_type(1, [to_address_p2sh, to_address_bech32_1], 'bech32')
- self.test_change_output_type(1, [to_address_bech32_1, to_address_bech32_2], 'bech32')
- else:
- self.log.info("Nodes with addresstype=p2sh-segwit match the change output")
- self.test_change_output_type(1, [to_address_p2sh], 'p2sh-segwit')
- self.test_change_output_type(1, [to_address_bech32_1], 'bech32')
- self.test_change_output_type(1, [to_address_p2sh, to_address_bech32_1], 'bech32')
- self.test_change_output_type(1, [to_address_bech32_1, to_address_bech32_2], 'bech32')
+ self.log.info("Nodes with addresstype=p2sh-segwit match the change output")
+ self.test_change_output_type(1, [to_address_p2sh], 'p2sh-segwit')
+ self.test_change_output_type(1, [to_address_bech32_1], 'bech32')
+ self.test_change_output_type(1, [to_address_p2sh, to_address_bech32_1], 'bech32')
+ self.test_change_output_type(1, [to_address_bech32_1, to_address_bech32_2], 'bech32')
self.log.info("Nodes with change_type=bech32 always use a P2WPKH change output:")
self.test_change_output_type(2, [to_address_bech32_1], 'bech32')
self.test_change_output_type(2, [to_address_p2sh], 'bech32')
- if self.options.descriptors:
- self.log.info("Nodes with addresstype=bech32 match the change output (unless changetype is set otherwise):")
- self.test_change_output_type(3, [to_address_bech32_1], 'bech32')
- self.test_change_output_type(3, [to_address_p2sh], 'p2sh-segwit')
- else:
- self.log.info("Nodes with addresstype=bech32 match the change output (unless changetype is set otherwise):")
- self.test_change_output_type(3, [to_address_bech32_1], 'bech32')
- self.test_change_output_type(3, [to_address_p2sh], 'p2sh-segwit')
+ self.log.info("Nodes with addresstype=bech32 match the change output (unless changetype is set otherwise):")
+ self.test_change_output_type(3, [to_address_bech32_1], 'bech32')
+ self.test_change_output_type(3, [to_address_p2sh], 'p2sh-segwit')
self.log.info('getrawchangeaddress defaults to addresstype if -changetype is not set and argument is absent')
self.test_address(3, self.nodes[3].getrawchangeaddress(), multisig=False, typ='bech32')
diff --git a/test/functional/wallet_avoid_mixing_output_types.py b/test/functional/wallet_avoid_mixing_output_types.py
new file mode 100755
index 0000000000..cad9d02808
--- /dev/null
+++ b/test/functional/wallet_avoid_mixing_output_types.py
@@ -0,0 +1,177 @@
+#!/usr/bin/env python3
+# Copyright (c) 2022 The Bitcoin Core developers
+# Distributed under the MIT software license, see the accompanying
+# file COPYING or https://www.opensource.org/licenses/mit-license.php.
+"""Test output type mixing during coin selection
+
+A wallet may have different types of UTXOs to choose from during coin selection,
+where output type is one of the following:
+ - BECH32M
+ - BECH32
+ - P2SH-SEGWIT
+ - LEGACY
+
+This test verifies that mixing different output types is avoided unless
+absolutely necessary. Both wallets start with zero funds. Alice mines
+enough blocks to have spendable coinbase outputs. Alice sends three
+random value payments which sum to 10BTC for each output type to Bob,
+for a total of 40BTC in Bob's wallet.
+
+Bob then sends random valued payments back to Alice, some of which need
+unconfirmed change, and we verify that none of these payments contain mixed
+inputs. Finally, Bob sends the remainder of his funds, which requires mixing.
+
+The payment values are random, but chosen such that they sum up to a specified
+total. This ensures we are not relying on specific values for the UTXOs,
+but still know when to expect mixing due to the wallet being close to empty.
+
+"""
+
+import random
+from test_framework.test_framework import BitcoinTestFramework
+from test_framework.blocktools import COINBASE_MATURITY
+
+ADDRESS_TYPES = [
+ "bech32m",
+ "bech32",
+ "p2sh-segwit",
+ "legacy",
+]
+
+
+def is_bech32_address(node, addr):
+ """Check if an address contains a bech32 output."""
+ addr_info = node.getaddressinfo(addr)
+ return addr_info['desc'].startswith('wpkh(')
+
+
+def is_bech32m_address(node, addr):
+ """Check if an address contains a bech32m output."""
+ addr_info = node.getaddressinfo(addr)
+ return addr_info['desc'].startswith('tr(')
+
+
+def is_p2sh_segwit_address(node, addr):
+ """Check if an address contains a P2SH-Segwit output.
+ Note: this function does not actually determine the type
+ of P2SH output, but is sufficient for this test in that
+ we are only generating P2SH-Segwit outputs.
+ """
+ addr_info = node.getaddressinfo(addr)
+ return addr_info['desc'].startswith('sh(wpkh(')
+
+
+def is_legacy_address(node, addr):
+ """Check if an address contains a legacy output."""
+ addr_info = node.getaddressinfo(addr)
+ return addr_info['desc'].startswith('pkh(')
+
+
+def is_same_type(node, tx):
+ """Check that all inputs are of the same OutputType"""
+ vins = node.getrawtransaction(tx, True)['vin']
+ inputs = []
+ for vin in vins:
+ prev_tx, n = vin['txid'], vin['vout']
+ inputs.append(
+ node.getrawtransaction(
+ prev_tx,
+ True,
+ )['vout'][n]['scriptPubKey']['address']
+ )
+ has_legacy = False
+ has_p2sh = False
+ has_bech32 = False
+ has_bech32m = False
+
+ for addr in inputs:
+ if is_legacy_address(node, addr):
+ has_legacy = True
+ if is_p2sh_segwit_address(node, addr):
+ has_p2sh = True
+ if is_bech32_address(node, addr):
+ has_bech32 = True
+ if is_bech32m_address(node, addr):
+ has_bech32m = True
+
+ return (sum([has_legacy, has_p2sh, has_bech32, has_bech32m]) == 1)
+
+
+def generate_payment_values(n, m):
+ """Return a randomly chosen list of n positive integers summing to m.
+ Each such list is equally likely to occur."""
+
+ dividers = sorted(random.sample(range(1, m), n - 1))
+ return [a - b for a, b in zip(dividers + [m], [0] + dividers)]
+
+
+class AddressInputTypeGrouping(BitcoinTestFramework):
+ def set_test_params(self):
+ self.setup_clean_chain = True
+ self.num_nodes = 2
+ self.extra_args = [
+ [
+ "-addresstype=bech32",
+ "-whitelist=noban@127.0.0.1",
+ "-txindex",
+ ],
+ [
+ "-addresstype=p2sh-segwit",
+ "-whitelist=noban@127.0.0.1",
+ "-txindex",
+ ],
+ ]
+
+ def skip_test_if_missing_module(self):
+ self.skip_if_no_wallet()
+ self.skip_if_no_sqlite()
+
+ def make_payment(self, A, B, v, addr_type):
+ fee_rate = random.randint(1, 20)
+ self.log.debug(f"Making payment of {v} BTC at fee_rate {fee_rate}")
+ tx = B.sendtoaddress(
+ address=A.getnewaddress(address_type=addr_type),
+ amount=v,
+ fee_rate=fee_rate,
+ )
+ return tx
+
+ def run_test(self):
+
+ # alias self.nodes[i] to A, B for readability
+ A, B = self.nodes[0], self.nodes[1]
+ self.generate(A, COINBASE_MATURITY + 5)
+
+ self.log.info("Creating mixed UTXOs in B's wallet")
+ for v in generate_payment_values(3, 10):
+ self.log.debug(f"Making payment of {v} BTC to legacy")
+ A.sendtoaddress(B.getnewaddress(address_type="legacy"), v)
+
+ for v in generate_payment_values(3, 10):
+ self.log.debug(f"Making payment of {v} BTC to p2sh")
+ A.sendtoaddress(B.getnewaddress(address_type="p2sh-segwit"), v)
+
+ for v in generate_payment_values(3, 10):
+ self.log.debug(f"Making payment of {v} BTC to bech32")
+ A.sendtoaddress(B.getnewaddress(address_type="bech32"), v)
+
+ for v in generate_payment_values(3, 10):
+ self.log.debug(f"Making payment of {v} BTC to bech32m")
+ A.sendtoaddress(B.getnewaddress(address_type="bech32m"), v)
+
+ self.generate(A, 1)
+
+ self.log.info("Sending payments from B to A")
+ for v in generate_payment_values(5, 9):
+ tx = self.make_payment(
+ A, B, v, random.choice(ADDRESS_TYPES)
+ )
+ self.generate(A, 1)
+ assert is_same_type(B, tx)
+
+ tx = self.make_payment(A, B, 30.99, random.choice(ADDRESS_TYPES))
+ assert not is_same_type(B, tx)
+
+
+if __name__ == '__main__':
+ AddressInputTypeGrouping().main()
diff --git a/test/functional/wallet_balance.py b/test/functional/wallet_balance.py
index 0c93821e7f..ec58ace4a2 100755
--- a/test/functional/wallet_balance.py
+++ b/test/functional/wallet_balance.py
@@ -55,6 +55,9 @@ class WalletTest(BitcoinTestFramework):
['-limitdescendantcount=3', '-walletrejectlongchains=0'],
[],
]
+ # whitelist peers to speed up tx relay / mempool sync
+ for args in self.extra_args:
+ args.append("-whitelist=noban@127.0.0.1")
def skip_test_if_missing_module(self):
self.skip_if_no_wallet()
@@ -263,7 +266,6 @@ class WalletTest(BitcoinTestFramework):
self.nodes[1].invalidateblock(block_reorg)
assert_equal(self.nodes[0].getbalance(minconf=0), 0) # wallet txs not in the mempool are untrusted
self.generatetoaddress(self.nodes[0], 1, ADDRESS_WATCHONLY, sync_fun=self.no_op)
- assert_equal(self.nodes[0].getbalance(minconf=0), 0) # wallet txs not in the mempool are untrusted
# Now confirm tx_orig
self.restart_node(1, ['-persistmempool=0'])
@@ -273,6 +275,26 @@ class WalletTest(BitcoinTestFramework):
self.generatetoaddress(self.nodes[1], 1, ADDRESS_WATCHONLY)
assert_equal(self.nodes[0].getbalance(minconf=0), total_amount + 1) # The reorg recovered our fee of 1 coin
+ if not self.options.descriptors:
+ self.log.info('Check if mempool is taken into account after import*')
+ address = self.nodes[0].getnewaddress()
+ privkey = self.nodes[0].dumpprivkey(address)
+ self.nodes[0].sendtoaddress(address, 0.1)
+ self.nodes[0].unloadwallet('')
+ # check importaddress on fresh wallet
+ self.nodes[0].createwallet('w1', False, True)
+ self.nodes[0].importaddress(address)
+ assert_equal(self.nodes[0].getbalances()['mine']['untrusted_pending'], 0)
+ assert_equal(self.nodes[0].getbalances()['watchonly']['untrusted_pending'], Decimal('0.1'))
+ self.nodes[0].importprivkey(privkey)
+ assert_equal(self.nodes[0].getbalances()['mine']['untrusted_pending'], Decimal('0.1'))
+ assert_equal(self.nodes[0].getbalances()['watchonly']['untrusted_pending'], 0)
+ self.nodes[0].unloadwallet('w1')
+ # check importprivkey on fresh wallet
+ self.nodes[0].createwallet('w2', False, True)
+ self.nodes[0].importprivkey(privkey)
+ assert_equal(self.nodes[0].getbalances()['mine']['untrusted_pending'], Decimal('0.1'))
+
if __name__ == '__main__':
WalletTest().main()
diff --git a/test/functional/wallet_basic.py b/test/functional/wallet_basic.py
index a6c93ba5f9..20c577ceb3 100755
--- a/test/functional/wallet_basic.py
+++ b/test/functional/wallet_basic.py
@@ -7,6 +7,7 @@ from decimal import Decimal
from itertools import product
from test_framework.blocktools import COINBASE_MATURITY
+from test_framework.descriptors import descsum_create
from test_framework.test_framework import BitcoinTestFramework
from test_framework.util import (
assert_array_result,
@@ -25,7 +26,7 @@ class WalletTest(BitcoinTestFramework):
def set_test_params(self):
self.num_nodes = 4
self.extra_args = [[
- "-acceptnonstdtxn=1", "-walletrejectlongchains=0"
+ "-dustrelayfee=0", "-walletrejectlongchains=0", "-whitelist=noban@127.0.0.1"
]] * self.num_nodes
self.setup_clean_chain = True
self.supports_cli = False
@@ -414,7 +415,7 @@ class WalletTest(BitcoinTestFramework):
assert_raises_rpc_error(-3, "Invalid amount", self.nodes[0].sendtoaddress, self.nodes[2].getnewaddress(), "1f-4")
# This will raise an exception since generate does not accept a string
- assert_raises_rpc_error(-1, "not an integer", self.generate, self.nodes[0], "2")
+ assert_raises_rpc_error(-3, "not of expected type number", self.generate, self.nodes[0], "2")
if not self.options.descriptors:
@@ -584,15 +585,9 @@ class WalletTest(BitcoinTestFramework):
# ==Check that wallet prefers to use coins that don't exceed mempool limits =====
- # Get all non-zero utxos together
+ # Get all non-zero utxos together and split into two chains
chain_addrs = [self.nodes[0].getnewaddress(), self.nodes[0].getnewaddress()]
- singletxid = self.nodes[0].sendtoaddress(chain_addrs[0], self.nodes[0].getbalance(), "", "", True)
- self.generate(self.nodes[0], 1, sync_fun=self.no_op)
- node0_balance = self.nodes[0].getbalance()
- # Split into two chains
- rawtx = self.nodes[0].createrawtransaction([{"txid": singletxid, "vout": 0}], {chain_addrs[0]: node0_balance / 2 - Decimal('0.01'), chain_addrs[1]: node0_balance / 2 - Decimal('0.01')})
- signedtx = self.nodes[0].signrawtransactionwithwallet(rawtx)
- singletxid = self.nodes[0].sendrawtransaction(hexstring=signedtx["hex"], maxfeerate=0)
+ self.nodes[0].sendall(recipients=chain_addrs)
self.generate(self.nodes[0], 1, sync_fun=self.no_op)
# Make a long chain of unconfirmed payments without hitting mempool limit
@@ -700,6 +695,39 @@ class WalletTest(BitcoinTestFramework):
txid_feeReason_four = self.nodes[2].sendmany(dummy='', amounts={address: 5}, verbose=False)
assert_equal(self.nodes[2].gettransaction(txid_feeReason_four)['txid'], txid_feeReason_four)
+ if self.options.descriptors:
+ self.log.info("Testing 'listunspent' outputs the parent descriptor(s) of coins")
+ # Create two multisig descriptors, and send a UTxO each.
+ multi_a = descsum_create("wsh(multi(1,tpubD6NzVbkrYhZ4YBNjUo96Jxd1u4XKWgnoc7LsA1jz3Yc2NiDbhtfBhaBtemB73n9V5vtJHwU6FVXwggTbeoJWQ1rzdz8ysDuQkpnaHyvnvzR/*,tpubD6NzVbkrYhZ4YHdDGMAYGaWxMSC1B6tPRTHuU5t3BcfcS3nrF523iFm5waFd1pP3ZvJt4Jr8XmCmsTBNx5suhcSgtzpGjGMASR3tau1hJz4/*))")
+ multi_b = descsum_create("wsh(multi(1,tpubD6NzVbkrYhZ4YHdDGMAYGaWxMSC1B6tPRTHuU5t3BcfcS3nrF523iFm5waFd1pP3ZvJt4Jr8XmCmsTBNx5suhcSgtzpGjGMASR3tau1hJz4/*,tpubD6NzVbkrYhZ4Y2RLiuEzNQkntjmsLpPYDm3LTRBYynUQtDtpzeUKAcb9sYthSFL3YR74cdFgF5mW8yKxv2W2CWuZDFR2dUpE5PF9kbrVXNZ/*))")
+ addr_a = self.nodes[0].deriveaddresses(multi_a, 0)[0]
+ addr_b = self.nodes[0].deriveaddresses(multi_b, 0)[0]
+ txid_a = self.nodes[0].sendtoaddress(addr_a, 0.01)
+ txid_b = self.nodes[0].sendtoaddress(addr_b, 0.01)
+ self.generate(self.nodes[0], 1, sync_fun=self.no_op)
+ # Now import the descriptors, make sure we can identify on which descriptor each coin was received.
+ self.nodes[0].createwallet(wallet_name="wo", descriptors=True, disable_private_keys=True)
+ wo_wallet = self.nodes[0].get_wallet_rpc("wo")
+ wo_wallet.importdescriptors([
+ {
+ "desc": multi_a,
+ "active": False,
+ "timestamp": "now",
+ },
+ {
+ "desc": multi_b,
+ "active": False,
+ "timestamp": "now",
+ },
+ ])
+ coins = wo_wallet.listunspent(minconf=0)
+ assert_equal(len(coins), 2)
+ coin_a = next(c for c in coins if c["txid"] == txid_a)
+ assert_equal(coin_a["parent_descs"][0], multi_a)
+ coin_b = next(c for c in coins if c["txid"] == txid_b)
+ assert_equal(coin_b["parent_descs"][0], multi_b)
+ self.nodes[0].unloadwallet("wo")
+
if __name__ == '__main__':
WalletTest().main()
diff --git a/test/functional/wallet_bumpfee.py b/test/functional/wallet_bumpfee.py
index 3b23ee8e94..158ef66110 100755
--- a/test/functional/wallet_bumpfee.py
+++ b/test/functional/wallet_bumpfee.py
@@ -23,7 +23,7 @@ from test_framework.blocktools import (
send_to_witness,
)
from test_framework.messages import (
- BIP125_SEQUENCE_NUMBER,
+ MAX_BIP125_RBF_SEQUENCE,
)
from test_framework.test_framework import BitcoinTestFramework
from test_framework.util import (
@@ -53,6 +53,7 @@ class BumpFeeTest(BitcoinTestFramework):
"-walletrbf={}".format(i),
"-mintxfee=0.00002",
"-addresstype=bech32",
+ "-whitelist=noban@127.0.0.1",
] for i in range(self.num_nodes)]
def skip_test_if_missing_module(self):
@@ -86,12 +87,13 @@ class BumpFeeTest(BitcoinTestFramework):
self.test_invalid_parameters(rbf_node, peer_node, dest_address)
test_segwit_bumpfee_succeeds(self, rbf_node, dest_address)
test_nonrbf_bumpfee_fails(self, peer_node, dest_address)
- test_notmine_bumpfee_fails(self, rbf_node, peer_node, dest_address)
+ test_notmine_bumpfee(self, rbf_node, peer_node, dest_address)
test_bumpfee_with_descendant_fails(self, rbf_node, rbf_node_address, dest_address)
test_dust_to_fee(self, rbf_node, dest_address)
test_watchonly_psbt(self, peer_node, rbf_node, dest_address)
test_rebumping(self, rbf_node, dest_address)
test_rebumping_not_replaceable(self, rbf_node, dest_address)
+ test_bumpfee_already_spent(self, rbf_node, dest_address)
test_unconfirmed_not_spendable(self, rbf_node, rbf_node_address)
test_bumpfee_metadata(self, rbf_node, dest_address)
test_locked_wallet_fails(self, rbf_node, dest_address)
@@ -212,7 +214,7 @@ def test_segwit_bumpfee_succeeds(self, rbf_node, dest_address):
rbfraw = rbf_node.createrawtransaction([{
'txid': segwitid,
'vout': 0,
- "sequence": BIP125_SEQUENCE_NUMBER
+ "sequence": MAX_BIP125_RBF_SEQUENCE
}], {dest_address: Decimal("0.0005"),
rbf_node.getrawchangeaddress(): Decimal("0.0003")})
rbfsigned = rbf_node.signrawtransactionwithwallet(rbfraw)
@@ -228,11 +230,11 @@ def test_segwit_bumpfee_succeeds(self, rbf_node, dest_address):
def test_nonrbf_bumpfee_fails(self, peer_node, dest_address):
self.log.info('Test that we cannot replace a non RBF transaction')
not_rbfid = peer_node.sendtoaddress(dest_address, Decimal("0.00090000"))
- assert_raises_rpc_error(-4, "not BIP 125 replaceable", peer_node.bumpfee, not_rbfid)
+ assert_raises_rpc_error(-4, "Transaction is not BIP 125 replaceable", peer_node.bumpfee, not_rbfid)
self.clear_mempool()
-def test_notmine_bumpfee_fails(self, rbf_node, peer_node, dest_address):
+def test_notmine_bumpfee(self, rbf_node, peer_node, dest_address):
self.log.info('Test that it cannot bump fee if non-owned inputs are included')
# here, the rbftx has a peer_node coin and then adds a rbf_node input
# Note that this test depends upon the RPC code checking input ownership prior to change outputs
@@ -243,15 +245,34 @@ def test_notmine_bumpfee_fails(self, rbf_node, peer_node, dest_address):
"txid": utxo["txid"],
"vout": utxo["vout"],
"address": utxo["address"],
- "sequence": BIP125_SEQUENCE_NUMBER
+ "sequence": MAX_BIP125_RBF_SEQUENCE
} for utxo in utxos]
output_val = sum(utxo["amount"] for utxo in utxos) - fee
rawtx = rbf_node.createrawtransaction(inputs, {dest_address: output_val})
signedtx = rbf_node.signrawtransactionwithwallet(rawtx)
signedtx = peer_node.signrawtransactionwithwallet(signedtx["hex"])
rbfid = rbf_node.sendrawtransaction(signedtx["hex"])
+ entry = rbf_node.getmempoolentry(rbfid)
+ old_fee = entry["fees"]["base"]
+ old_feerate = int(old_fee / entry["vsize"] * Decimal(1e8))
assert_raises_rpc_error(-4, "Transaction contains inputs that don't belong to this wallet",
rbf_node.bumpfee, rbfid)
+
+ def finish_psbtbumpfee(psbt):
+ psbt = rbf_node.walletprocesspsbt(psbt)
+ psbt = peer_node.walletprocesspsbt(psbt["psbt"])
+ final = rbf_node.finalizepsbt(psbt["psbt"])
+ res = rbf_node.testmempoolaccept([final["hex"]])
+ assert res[0]["allowed"]
+ assert_greater_than(res[0]["fees"]["base"], old_fee)
+
+ self.log.info("Test that psbtbumpfee works for non-owned inputs")
+ psbt = rbf_node.psbtbumpfee(txid=rbfid)
+ finish_psbtbumpfee(psbt["psbt"])
+
+ psbt = rbf_node.psbtbumpfee(txid=rbfid, options={"fee_rate": old_feerate + 10})
+ finish_psbtbumpfee(psbt["psbt"])
+
self.clear_mempool()
@@ -479,7 +500,8 @@ def test_rebumping(self, rbf_node, dest_address):
self.log.info('Test that re-bumping the original tx fails, but bumping successor works')
rbfid = spend_one_input(rbf_node, dest_address)
bumped = rbf_node.bumpfee(rbfid, {"fee_rate": ECONOMICAL})
- assert_raises_rpc_error(-4, "already bumped", rbf_node.bumpfee, rbfid, {"fee_rate": NORMAL})
+ assert_raises_rpc_error(-4, f"Cannot bump transaction {rbfid} which was already bumped by transaction {bumped['txid']}",
+ rbf_node.bumpfee, rbfid, {"fee_rate": NORMAL})
rbf_node.bumpfee(bumped["txid"], {"fee_rate": NORMAL})
self.clear_mempool()
@@ -493,6 +515,15 @@ def test_rebumping_not_replaceable(self, rbf_node, dest_address):
self.clear_mempool()
+def test_bumpfee_already_spent(self, rbf_node, dest_address):
+ self.log.info('Test that bumping tx with already spent coin fails')
+ txid = spend_one_input(rbf_node, dest_address)
+ self.generate(rbf_node, 1) # spend coin simply by mining block with tx
+ spent_input = rbf_node.gettransaction(txid=txid, verbose=True)['decoded']['vin'][0]
+ assert_raises_rpc_error(-1, f"{spent_input['txid']}:{spent_input['vout']} is already spent",
+ rbf_node.bumpfee, txid, {"fee_rate": NORMAL})
+
+
def test_unconfirmed_not_spendable(self, rbf_node, rbf_node_address):
self.log.info('Test that unconfirmed outputs from bumped txns are not spendable')
rbfid = spend_one_input(rbf_node, rbf_node_address)
@@ -578,7 +609,7 @@ def test_change_script_match(self, rbf_node, dest_address):
def spend_one_input(node, dest_address, change_size=Decimal("0.00049000")):
tx_input = dict(
- sequence=BIP125_SEQUENCE_NUMBER, **next(u for u in node.listunspent() if u["amount"] == Decimal("0.00100000")))
+ sequence=MAX_BIP125_RBF_SEQUENCE, **next(u for u in node.listunspent() if u["amount"] == Decimal("0.00100000")))
destinations = {dest_address: Decimal("0.00050000")}
if change_size > 0:
destinations[node.getrawchangeaddress()] = change_size
@@ -604,7 +635,7 @@ def test_no_more_inputs_fails(self, rbf_node, dest_address):
# feerate rbf requires confirmed outputs when change output doesn't exist or is insufficient
self.generatetoaddress(rbf_node, 1, dest_address)
# spend all funds, no change output
- rbfid = rbf_node.sendtoaddress(rbf_node.getnewaddress(), rbf_node.getbalance(), "", "", True)
+ rbfid = rbf_node.sendall(recipients=[rbf_node.getnewaddress()])['txid']
assert_raises_rpc_error(-4, "Unable to create transaction. Insufficient funds", rbf_node.bumpfee, rbfid)
self.clear_mempool()
diff --git a/test/functional/wallet_coinbase_category.py b/test/functional/wallet_coinbase_category.py
index 5a6b6cee59..c2a8e612cf 100755
--- a/test/functional/wallet_coinbase_category.py
+++ b/test/functional/wallet_coinbase_category.py
@@ -15,6 +15,7 @@ from test_framework.util import (
class CoinbaseCategoryTest(BitcoinTestFramework):
def set_test_params(self):
self.num_nodes = 1
+ self.setup_clean_chain = True
def skip_test_if_missing_module(self):
self.skip_if_no_wallet()
diff --git a/test/functional/wallet_encryption.py b/test/functional/wallet_encryption.py
index 0c9106f800..37c1c4bff3 100755
--- a/test/functional/wallet_encryption.py
+++ b/test/functional/wallet_encryption.py
@@ -9,8 +9,7 @@ import time
from test_framework.test_framework import BitcoinTestFramework
from test_framework.util import (
assert_raises_rpc_error,
- assert_greater_than,
- assert_greater_than_or_equal,
+ assert_equal,
)
@@ -76,21 +75,18 @@ class WalletEncryptionTest(BitcoinTestFramework):
self.log.info('Check a timeout less than the limit')
MAX_VALUE = 100000000
- expected_time = int(time.time()) + MAX_VALUE - 600
+ now = int(time.time())
+ self.nodes[0].setmocktime(now)
+ expected_time = now + MAX_VALUE - 600
self.nodes[0].walletpassphrase(passphrase2, MAX_VALUE - 600)
- # give buffer for walletpassphrase, since it iterates over all encrypted keys
- expected_time_with_buffer = time.time() + MAX_VALUE - 600
actual_time = self.nodes[0].getwalletinfo()['unlocked_until']
- assert_greater_than_or_equal(actual_time, expected_time)
- assert_greater_than(expected_time_with_buffer, actual_time)
+ assert_equal(actual_time, expected_time)
self.log.info('Check a timeout greater than the limit')
- expected_time = int(time.time()) + MAX_VALUE - 1
+ expected_time = now + MAX_VALUE
self.nodes[0].walletpassphrase(passphrase2, MAX_VALUE + 1000)
- expected_time_with_buffer = time.time() + MAX_VALUE
actual_time = self.nodes[0].getwalletinfo()['unlocked_until']
- assert_greater_than_or_equal(actual_time, expected_time)
- assert_greater_than(expected_time_with_buffer, actual_time)
+ assert_equal(actual_time, expected_time)
if __name__ == '__main__':
diff --git a/test/functional/wallet_groups.py b/test/functional/wallet_groups.py
index eb305c5fa2..e5e4cf03bf 100755
--- a/test/functional/wallet_groups.py
+++ b/test/functional/wallet_groups.py
@@ -26,6 +26,11 @@ class WalletGroupTest(BitcoinTestFramework):
["-maxapsfee=0.00002719"],
["-maxapsfee=0.00002720"],
]
+
+ for args in self.extra_args:
+ args.append("-whitelist=noban@127.0.0.1") # whitelist peers to speed up tx relay / mempool sync
+ args.append(f"-paytxfee={20 * 1e3 / 1e8}") # apply feerate of 20 sats/vB across all nodes
+
self.rpc_timeout = 480
def skip_test_if_missing_module(self):
@@ -150,7 +155,7 @@ class WalletGroupTest(BitcoinTestFramework):
assert_equal(2, len(tx6["vout"]))
# Empty out node2's wallet
- self.nodes[2].sendtoaddress(address=self.nodes[0].getnewaddress(), amount=self.nodes[2].getbalance(), subtractfeefromamount=True)
+ self.nodes[2].sendall(recipients=[self.nodes[0].getnewaddress()])
self.sync_all()
self.generate(self.nodes[0], 1)
diff --git a/test/functional/wallet_hd.py b/test/functional/wallet_hd.py
index ac878ea0aa..220c856498 100755
--- a/test/functional/wallet_hd.py
+++ b/test/functional/wallet_hd.py
@@ -20,6 +20,10 @@ class WalletHDTest(BitcoinTestFramework):
self.setup_clean_chain = True
self.num_nodes = 2
self.extra_args = [[], ['-keypool=0']]
+ # whitelist peers to speed up tx relay / mempool sync
+ for args in self.extra_args:
+ args.append("-whitelist=noban@127.0.0.1")
+
self.supports_cli = False
def skip_test_if_missing_module(self):
@@ -173,8 +177,8 @@ class WalletHDTest(BitcoinTestFramework):
# Sethdseed parameter validity
assert_raises_rpc_error(-1, 'sethdseed', self.nodes[0].sethdseed, False, new_seed, 0)
assert_raises_rpc_error(-5, "Invalid private key", self.nodes[1].sethdseed, False, "not_wif")
- assert_raises_rpc_error(-1, "JSON value is not a boolean as expected", self.nodes[1].sethdseed, "Not_bool")
- assert_raises_rpc_error(-1, "JSON value is not a string as expected", self.nodes[1].sethdseed, False, True)
+ assert_raises_rpc_error(-3, "JSON value of type string is not of expected type bool", self.nodes[1].sethdseed, "Not_bool")
+ assert_raises_rpc_error(-3, "JSON value of type bool is not of expected type string", self.nodes[1].sethdseed, False, True)
assert_raises_rpc_error(-5, "Already have this key", self.nodes[1].sethdseed, False, new_seed)
assert_raises_rpc_error(-5, "Already have this key", self.nodes[1].sethdseed, False, self.nodes[1].dumpprivkey(self.nodes[1].getnewaddress()))
diff --git a/test/functional/wallet_import_rescan.py b/test/functional/wallet_import_rescan.py
index d9acc8cea5..085ad51c79 100755
--- a/test/functional/wallet_import_rescan.py
+++ b/test/functional/wallet_import_rescan.py
@@ -87,6 +87,7 @@ class Variant(collections.namedtuple("Variant", "call data address_type rescan p
assert_equal(len(txs), self.expected_txs)
addresses = self.node.listreceivedbyaddress(minconf=0, include_watchonly=True, address_filter=self.address['address'])
+
if self.expected_txs:
assert_equal(len(addresses[0]["txids"]), self.expected_txs)
@@ -98,13 +99,18 @@ class Variant(collections.namedtuple("Variant", "call data address_type rescan p
assert_equal(tx["category"], "receive")
assert_equal(tx["label"], self.label)
assert_equal(tx["txid"], txid)
- assert_equal(tx["confirmations"], 1 + current_height - confirmation_height)
- assert "trusted" not in tx
+
+ # If no confirmation height is given, the tx is still in the
+ # mempool.
+ confirmations = (1 + current_height - confirmation_height) if confirmation_height else 0
+ assert_equal(tx["confirmations"], confirmations)
+ if confirmations:
+ assert "trusted" not in tx
address, = [ad for ad in addresses if txid in ad["txids"]]
assert_equal(address["address"], self.address["address"])
assert_equal(address["amount"], self.expected_balance)
- assert_equal(address["confirmations"], 1 + current_height - confirmation_height)
+ assert_equal(address["confirmations"], confirmations)
# Verify the transaction is correctly marked watchonly depending on
# whether the transaction pays to an imported public key or
# imported private key. The test setup ensures that transaction
@@ -162,11 +168,12 @@ class ImportRescanTest(BitcoinTestFramework):
self.import_deterministic_coinbase_privkeys()
self.stop_nodes()
- self.start_nodes()
+ self.start_nodes(extra_args=[["-whitelist=noban@127.0.0.1"]] * self.num_nodes)
for i in range(1, self.num_nodes):
self.connect_nodes(i, 0)
def run_test(self):
+
# Create one transaction on node 0 with a unique amount for
# each possible type of wallet import RPC.
for i, variant in enumerate(IMPORT_VARIANTS):
@@ -207,7 +214,7 @@ class ImportRescanTest(BitcoinTestFramework):
variant.check()
# Create new transactions sending to each address.
- for i, variant in enumerate(IMPORT_VARIANTS):
+ for variant in IMPORT_VARIANTS:
variant.sent_amount = get_rand_amount()
variant.sent_txid = self.nodes[0].sendtoaddress(variant.address["address"], variant.sent_amount)
self.generate(self.nodes[0], 1) # Generate one block for each send
@@ -223,6 +230,46 @@ class ImportRescanTest(BitcoinTestFramework):
variant.expected_txs += 1
variant.check(variant.sent_txid, variant.sent_amount, variant.confirmation_height)
+ self.log.info('Test that the mempool is rescanned as well if the rescan parameter is set to true')
+
+ # The late timestamp and pruned variants are not necessary when testing mempool rescan
+ mempool_variants = [variant for variant in IMPORT_VARIANTS if variant.rescan != Rescan.late_timestamp and not variant.prune]
+ # No further blocks are mined so the timestamp will stay the same
+ timestamp = self.nodes[0].getblockheader(self.nodes[0].getbestblockhash())["time"]
+
+ # Create one transaction on node 0 with a unique amount for
+ # each possible type of wallet import RPC.
+ for i, variant in enumerate(mempool_variants):
+ variant.label = "mempool label {} {}".format(i, variant)
+ variant.address = self.nodes[1].getaddressinfo(self.nodes[1].getnewaddress(
+ label=variant.label,
+ address_type=variant.address_type.value,
+ ))
+ variant.key = self.nodes[1].dumpprivkey(variant.address["address"])
+ variant.initial_amount = get_rand_amount()
+ variant.initial_txid = self.nodes[0].sendtoaddress(variant.address["address"], variant.initial_amount)
+ variant.confirmation_height = 0
+ variant.timestamp = timestamp
+
+ assert_equal(len(self.nodes[0].getrawmempool()), len(mempool_variants))
+ self.sync_mempools()
+
+ # For each variation of wallet key import, invoke the import RPC and
+ # check the results from getbalance and listtransactions.
+ for variant in mempool_variants:
+ self.log.info('Run import for mempool variant {}'.format(variant))
+ expect_rescan = variant.rescan == Rescan.yes
+ variant.node = self.nodes[2 + IMPORT_NODES.index(ImportNode(variant.prune, expect_rescan))]
+ variant.do_import(variant.timestamp)
+ if expect_rescan:
+ variant.expected_balance = variant.initial_amount
+ variant.expected_txs = 1
+ variant.check(variant.initial_txid, variant.initial_amount)
+ else:
+ variant.expected_balance = 0
+ variant.expected_txs = 0
+ variant.check()
+
if __name__ == "__main__":
ImportRescanTest().main()
diff --git a/test/functional/wallet_importdescriptors.py b/test/functional/wallet_importdescriptors.py
index ff11f421a1..9744009af8 100755
--- a/test/functional/wallet_importdescriptors.py
+++ b/test/functional/wallet_importdescriptors.py
@@ -35,6 +35,9 @@ class ImportDescriptorsTest(BitcoinTestFramework):
self.extra_args = [["-addresstype=legacy"],
["-addresstype=bech32", "-keypool=5"]
]
+ # whitelist peers to speed up tx relay / mempool sync
+ for args in self.extra_args:
+ args.append("-whitelist=noban@127.0.0.1")
self.setup_clean_chain = True
self.wallet_names = []
@@ -480,7 +483,9 @@ class ImportDescriptorsTest(BitcoinTestFramework):
addr = wmulti_pub.getnewaddress('', 'bech32')
assert_equal(addr, 'bcrt1qp8s25ckjl7gr6x2q3dx3tn2pytwp05upkjztk6ey857tt50r5aeqn6mvr9') # Derived at m/84'/0'/0'/1
change_addr = wmulti_pub.getrawchangeaddress('bech32')
- assert_equal(change_addr, 'bcrt1qt9uhe3a9hnq7vajl7a094z4s3crm9ttf8zw3f5v9gr2nyd7e3lnsy44n8e')
+ assert_equal(change_addr, 'bcrt1qzxl0qz2t88kljdnkzg4n4gapr6kte26390gttrg79x66nt4p04fssj53nl')
+ assert(send_txid in self.nodes[0].getrawmempool(True))
+ assert(send_txid in (x['txid'] for x in wmulti_pub.listunspent(0)))
assert_equal(wmulti_pub.getwalletinfo()['keypoolsize'], 999)
# generate some utxos for next tests
diff --git a/test/functional/wallet_importmulti.py b/test/functional/wallet_importmulti.py
index 3953851491..62a1a3341d 100755
--- a/test/functional/wallet_importmulti.py
+++ b/test/functional/wallet_importmulti.py
@@ -874,6 +874,25 @@ class ImportMultiTest(BitcoinTestFramework):
addr = wrpc.getnewaddress('', 'bech32')
assert_equal(addr, addresses[i])
+ # Create wallet with passphrase
+ self.log.info('Test watchonly imports on a wallet with a passphrase, without unlocking')
+ self.nodes[1].createwallet(wallet_name='w1', blank=True, passphrase='pass')
+ wrpc = self.nodes[1].get_wallet_rpc('w1')
+ assert_raises_rpc_error(-13, "Please enter the wallet passphrase with walletpassphrase first.",
+ wrpc.importmulti, [{
+ 'desc': descsum_create('wpkh(' + pub1 + ')'),
+ "timestamp": "now",
+ }])
+
+ result = wrpc.importmulti(
+ [{
+ 'desc': descsum_create('wpkh(' + pub1 + ')'),
+ "timestamp": "now",
+ "watchonly": True,
+ }]
+ )
+ assert result[0]['success']
+
if __name__ == '__main__':
ImportMultiTest().main()
diff --git a/test/functional/wallet_listdescriptors.py b/test/functional/wallet_listdescriptors.py
index 202ef92887..d5372f5aee 100755
--- a/test/functional/wallet_listdescriptors.py
+++ b/test/functional/wallet_listdescriptors.py
@@ -52,6 +52,10 @@ class ListDescriptorsTest(BitcoinTestFramework):
assert item['range'] == [0, 0]
assert item['timestamp'] is not None
+ self.log.info('Test that descriptor strings are returned in lexicographically sorted order.')
+ descriptor_strings = [descriptor['desc'] for descriptor in result['descriptors']]
+ assert_equal(descriptor_strings, sorted(descriptor_strings))
+
self.log.info('Test descriptors with hardened derivations are listed in importable form.')
xprv = 'tprv8ZgxMBicQKsPeuVhWwi6wuMQGfPKi9Li5GtX35jVNknACgqe3CY4g5xgkfDDJcmtF7o1QnxWDRYw4H5P26PXq7sbcUkEqeR4fg3Kxp2tigg'
xpub_acc = 'tpubDCMVLhErorrAGfApiJSJzEKwqeaf2z3NrkVMxgYQjZLzMjXMBeRw2muGNYbvaekAE8rUFLftyEar4LdrG2wXyyTJQZ26zptmeTEjPTaATts'
diff --git a/test/functional/wallet_listreceivedby.py b/test/functional/wallet_listreceivedby.py
index db1d8eb54a..f1d7de2f27 100755
--- a/test/functional/wallet_listreceivedby.py
+++ b/test/functional/wallet_listreceivedby.py
@@ -18,6 +18,8 @@ from test_framework.wallet_util import test_address
class ReceivedByTest(BitcoinTestFramework):
def set_test_params(self):
self.num_nodes = 2
+ # whitelist peers to speed up tx relay / mempool sync
+ self.extra_args = [["-whitelist=noban@127.0.0.1"]] * self.num_nodes
def skip_test_if_missing_module(self):
self.skip_if_no_wallet()
@@ -57,6 +59,11 @@ class ReceivedByTest(BitcoinTestFramework):
{"address": empty_addr},
{"address": empty_addr, "label": "", "amount": 0, "confirmations": 0, "txids": []})
+ # No returned addy should be a change addr
+ for node in self.nodes:
+ for addr_obj in node.listreceivedbyaddress():
+ assert_equal(node.getaddressinfo(addr_obj["address"])["ischange"], False)
+
# Test Address filtering
# Only on addr
expected = {"address": addr, "label": "", "amount": Decimal("0.1"), "confirmations": 10, "txids": [txid, ]}
diff --git a/test/functional/wallet_listsinceblock.py b/test/functional/wallet_listsinceblock.py
index fc06565983..f259449bef 100755
--- a/test/functional/wallet_listsinceblock.py
+++ b/test/functional/wallet_listsinceblock.py
@@ -6,9 +6,10 @@
from test_framework.address import key_to_p2wpkh
from test_framework.blocktools import COINBASE_MATURITY
+from test_framework.descriptors import descsum_create
from test_framework.key import ECKey
from test_framework.test_framework import BitcoinTestFramework
-from test_framework.messages import BIP125_SEQUENCE_NUMBER
+from test_framework.messages import MAX_BIP125_RBF_SEQUENCE
from test_framework.util import (
assert_array_result,
assert_equal,
@@ -22,6 +23,8 @@ class ListSinceBlockTest(BitcoinTestFramework):
def set_test_params(self):
self.num_nodes = 4
self.setup_clean_chain = True
+ # whitelist peers to speed up tx relay / mempool sync
+ self.extra_args = [["-whitelist=noban@127.0.0.1"]] * self.num_nodes
def skip_test_if_missing_module(self):
self.skip_if_no_wallet()
@@ -39,6 +42,9 @@ class ListSinceBlockTest(BitcoinTestFramework):
self.test_double_send()
self.double_spends_filtered()
self.test_targetconfirmations()
+ if self.options.descriptors:
+ self.test_desc()
+ self.test_send_to_self()
def test_no_blockhash(self):
self.log.info("Test no blockhash")
@@ -346,7 +352,7 @@ class ListSinceBlockTest(BitcoinTestFramework):
dest_address = spending_node.getnewaddress()
tx_input = dict(
- sequence=BIP125_SEQUENCE_NUMBER, **next(u for u in spending_node.listunspent()))
+ sequence=MAX_BIP125_RBF_SEQUENCE, **next(u for u in spending_node.listunspent()))
rawtx = spending_node.createrawtransaction(
[tx_input], {dest_address: tx_input["amount"] - Decimal("0.00051000"),
spending_node.getrawchangeaddress(): Decimal("0.00050000")})
@@ -383,5 +389,65 @@ class ListSinceBlockTest(BitcoinTestFramework):
assert_equal(original_found, False)
assert_equal(double_found, False)
+ def test_desc(self):
+ """Make sure we can track coins by descriptor."""
+ self.log.info("Test descriptor lookup by scriptPubKey.")
+
+ # Create a watchonly wallet tracking two multisig descriptors.
+ multi_a = descsum_create("wsh(multi(1,tpubD6NzVbkrYhZ4YBNjUo96Jxd1u4XKWgnoc7LsA1jz3Yc2NiDbhtfBhaBtemB73n9V5vtJHwU6FVXwggTbeoJWQ1rzdz8ysDuQkpnaHyvnvzR/*,tpubD6NzVbkrYhZ4YHdDGMAYGaWxMSC1B6tPRTHuU5t3BcfcS3nrF523iFm5waFd1pP3ZvJt4Jr8XmCmsTBNx5suhcSgtzpGjGMASR3tau1hJz4/*))")
+ multi_b = descsum_create("wsh(multi(1,tpubD6NzVbkrYhZ4YHdDGMAYGaWxMSC1B6tPRTHuU5t3BcfcS3nrF523iFm5waFd1pP3ZvJt4Jr8XmCmsTBNx5suhcSgtzpGjGMASR3tau1hJz4/*,tpubD6NzVbkrYhZ4Y2RLiuEzNQkntjmsLpPYDm3LTRBYynUQtDtpzeUKAcb9sYthSFL3YR74cdFgF5mW8yKxv2W2CWuZDFR2dUpE5PF9kbrVXNZ/*))")
+ self.nodes[0].createwallet(wallet_name="wo", descriptors=True, disable_private_keys=True)
+ wo_wallet = self.nodes[0].get_wallet_rpc("wo")
+ wo_wallet.importdescriptors([
+ {
+ "desc": multi_a,
+ "active": False,
+ "timestamp": "now",
+ },
+ {
+ "desc": multi_b,
+ "active": False,
+ "timestamp": "now",
+ },
+ ])
+
+ # Send a coin to each descriptor.
+ assert_equal(len(wo_wallet.listsinceblock()["transactions"]), 0)
+ addr_a = self.nodes[0].deriveaddresses(multi_a, 0)[0]
+ addr_b = self.nodes[0].deriveaddresses(multi_b, 0)[0]
+ self.nodes[2].sendtoaddress(addr_a, 1)
+ self.nodes[2].sendtoaddress(addr_b, 2)
+ self.generate(self.nodes[2], 1)
+
+ # We can identify on which descriptor each coin was received.
+ coins = wo_wallet.listsinceblock()["transactions"]
+ assert_equal(len(coins), 2)
+ coin_a = next(c for c in coins if c["amount"] == 1)
+ assert_equal(coin_a["parent_descs"][0], multi_a)
+ coin_b = next(c for c in coins if c["amount"] == 2)
+ assert_equal(coin_b["parent_descs"][0], multi_b)
+
+ def test_send_to_self(self):
+ """We can make listsinceblock output our change outputs."""
+ self.log.info("Test the inclusion of change outputs in the output.")
+
+ # Create a UTxO paying to one of our change addresses.
+ block_hash = self.nodes[2].getbestblockhash()
+ addr = self.nodes[2].getrawchangeaddress()
+ self.nodes[2].sendtoaddress(addr, 1)
+
+ # If we don't list change, we won't have an entry for it.
+ coins = self.nodes[2].listsinceblock(blockhash=block_hash)["transactions"]
+ assert not any(c["address"] == addr for c in coins)
+
+ # Now if we list change, we'll get both the send (to a change address) and
+ # the actual change.
+ res = self.nodes[2].listsinceblock(blockhash=block_hash, include_change=True)
+ coins = [entry for entry in res["transactions"] if entry["category"] == "receive"]
+ assert_equal(len(coins), 2)
+ assert any(c["address"] == addr for c in coins)
+ assert all(self.nodes[2].getaddressinfo(c["address"])["ischange"] for c in coins)
+
+
if __name__ == '__main__':
ListSinceBlockTest().main()
diff --git a/test/functional/wallet_listtransactions.py b/test/functional/wallet_listtransactions.py
index f75877f256..7c16b6328d 100755
--- a/test/functional/wallet_listtransactions.py
+++ b/test/functional/wallet_listtransactions.py
@@ -25,7 +25,7 @@ class ListTransactionsTest(BitcoinTestFramework):
self.num_nodes = 3
# This test isn't testing txn relay/timing, so set whitelist on the
# peers for instant txn relay. This speeds up the test run time 2-3x.
- self.extra_args = [["-whitelist=noban@127.0.0.1"]] * self.num_nodes
+ self.extra_args = [["-whitelist=noban@127.0.0.1", "-walletrbf=0"]] * self.num_nodes
def skip_test_if_missing_module(self):
self.skip_if_no_wallet()
@@ -146,7 +146,7 @@ class ListTransactionsTest(BitcoinTestFramework):
# Create tx2 using createrawtransaction
inputs = [{"txid": utxo_to_use["txid"], "vout": utxo_to_use["vout"]}]
outputs = {self.nodes[0].getnewaddress(): 0.999}
- tx2 = self.nodes[1].createrawtransaction(inputs, outputs)
+ tx2 = self.nodes[1].createrawtransaction(inputs=inputs, outputs=outputs, replaceable=False)
tx2_signed = self.nodes[1].signrawtransactionwithwallet(tx2)["hex"]
txid_2 = self.nodes[1].sendrawtransaction(tx2_signed)
@@ -178,7 +178,7 @@ class ListTransactionsTest(BitcoinTestFramework):
utxo_to_use = get_unconfirmed_utxo_entry(self.nodes[1], txid_3)
inputs = [{"txid": txid_3, "vout": utxo_to_use["vout"]}]
outputs = {self.nodes[0].getnewaddress(): 0.997}
- tx4 = self.nodes[1].createrawtransaction(inputs, outputs)
+ tx4 = self.nodes[1].createrawtransaction(inputs=inputs, outputs=outputs, replaceable=False)
tx4_signed = self.nodes[1].signrawtransactionwithwallet(tx4)["hex"]
txid_4 = self.nodes[1].sendrawtransaction(tx4_signed)
diff --git a/test/functional/wallet_migration.py b/test/functional/wallet_migration.py
new file mode 100755
index 0000000000..3c1cb6ac32
--- /dev/null
+++ b/test/functional/wallet_migration.py
@@ -0,0 +1,407 @@
+#!/usr/bin/env python3
+# Copyright (c) 2020 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 Migrating a wallet from legacy to descriptor."""
+
+import os
+import random
+from test_framework.descriptors import descsum_create
+from test_framework.test_framework import BitcoinTestFramework
+from test_framework.util import (
+ assert_equal,
+ assert_raises_rpc_error,
+ find_vout_for_address,
+)
+from test_framework.wallet_util import (
+ get_generate_key,
+)
+
+
+class WalletMigrationTest(BitcoinTestFramework):
+ def set_test_params(self):
+ self.setup_clean_chain = True
+ self.num_nodes = 1
+ self.extra_args = [[]]
+ self.supports_cli = False
+
+ def skip_test_if_missing_module(self):
+ self.skip_if_no_wallet()
+ self.skip_if_no_sqlite()
+ self.skip_if_no_bdb()
+
+ def assert_is_sqlite(self, wallet_name):
+ wallet_file_path = os.path.join(self.nodes[0].datadir, "regtest/wallets", wallet_name, self.wallet_data_filename)
+ with open(wallet_file_path, 'rb') as f:
+ file_magic = f.read(16)
+ assert_equal(file_magic, b'SQLite format 3\x00')
+ assert_equal(self.nodes[0].get_wallet_rpc(wallet_name).getwalletinfo()["format"], "sqlite")
+
+ def create_legacy_wallet(self, wallet_name):
+ self.nodes[0].createwallet(wallet_name=wallet_name)
+ wallet = self.nodes[0].get_wallet_rpc(wallet_name)
+ assert_equal(wallet.getwalletinfo()["descriptors"], False)
+ assert_equal(wallet.getwalletinfo()["format"], "bdb")
+ return wallet
+
+ def assert_addr_info_equal(self, addr_info, addr_info_old):
+ assert_equal(addr_info["address"], addr_info_old["address"])
+ assert_equal(addr_info["scriptPubKey"], addr_info_old["scriptPubKey"])
+ assert_equal(addr_info["ismine"], addr_info_old["ismine"])
+ assert_equal(addr_info["hdkeypath"], addr_info_old["hdkeypath"])
+ assert_equal(addr_info["solvable"], addr_info_old["solvable"])
+ assert_equal(addr_info["ischange"], addr_info_old["ischange"])
+ assert_equal(addr_info["hdmasterfingerprint"], addr_info_old["hdmasterfingerprint"])
+
+ def assert_list_txs_equal(self, received_list_txs, expected_list_txs):
+ for d in received_list_txs:
+ if "parent_descs" in d:
+ del d["parent_descs"]
+ for d in expected_list_txs:
+ if "parent_descs" in d:
+ del d["parent_descs"]
+ assert_equal(received_list_txs, expected_list_txs)
+
+ def test_basic(self):
+ default = self.nodes[0].get_wallet_rpc(self.default_wallet_name)
+
+ self.log.info("Test migration of a basic keys only wallet without balance")
+ basic0 = self.create_legacy_wallet("basic0")
+
+ addr = basic0.getnewaddress()
+ change = basic0.getrawchangeaddress()
+
+ old_addr_info = basic0.getaddressinfo(addr)
+ old_change_addr_info = basic0.getaddressinfo(change)
+ assert_equal(old_addr_info["ismine"], True)
+ assert_equal(old_addr_info["hdkeypath"], "m/0'/0'/0'")
+ assert_equal(old_change_addr_info["ismine"], True)
+ assert_equal(old_change_addr_info["hdkeypath"], "m/0'/1'/0'")
+
+ # Note: migration could take a while.
+ basic0.migratewallet()
+
+ # Verify created descriptors
+ assert_equal(basic0.getwalletinfo()["descriptors"], True)
+ self.assert_is_sqlite("basic0")
+
+ # The wallet should create the following descriptors:
+ # * BIP32 descriptors in the form of "0'/0'/*" and "0'/1'/*" (2 descriptors)
+ # * BIP44 descriptors in the form of "44'/1'/0'/0/*" and "44'/1'/0'/1/*" (2 descriptors)
+ # * BIP49 descriptors, P2SH(P2WPKH), in the form of "86'/1'/0'/0/*" and "86'/1'/0'/1/*" (2 descriptors)
+ # * BIP84 descriptors, P2WPKH, in the form of "84'/1'/0'/1/*" and "84'/1'/0'/1/*" (2 descriptors)
+ # * BIP86 descriptors, P2TR, in the form of "86'/1'/0'/0/*" and "86'/1'/0'/1/*" (2 descriptors)
+ # * A combo(PK) descriptor for the wallet master key.
+ # So, should have a total of 11 descriptors on it.
+ assert_equal(len(basic0.listdescriptors()["descriptors"]), 11)
+
+ # Compare addresses info
+ addr_info = basic0.getaddressinfo(addr)
+ change_addr_info = basic0.getaddressinfo(change)
+ self.assert_addr_info_equal(addr_info, old_addr_info)
+ self.assert_addr_info_equal(change_addr_info, old_change_addr_info)
+
+ addr_info = basic0.getaddressinfo(basic0.getnewaddress("", "bech32"))
+ assert_equal(addr_info["hdkeypath"], "m/84'/1'/0'/0/0")
+
+ self.log.info("Test migration of a basic keys only wallet with a balance")
+ basic1 = self.create_legacy_wallet("basic1")
+
+ for _ in range(0, 10):
+ default.sendtoaddress(basic1.getnewaddress(), 1)
+
+ self.generate(self.nodes[0], 1)
+
+ for _ in range(0, 5):
+ basic1.sendtoaddress(default.getnewaddress(), 0.5)
+
+ self.generate(self.nodes[0], 1)
+ bal = basic1.getbalance()
+ txs = basic1.listtransactions()
+
+ basic1.migratewallet()
+ assert_equal(basic1.getwalletinfo()["descriptors"], True)
+ self.assert_is_sqlite("basic1")
+ assert_equal(basic1.getbalance(), bal)
+ self.assert_list_txs_equal(basic1.listtransactions(), txs)
+
+ # restart node and verify that everything is still there
+ self.restart_node(0)
+ default = self.nodes[0].get_wallet_rpc(self.default_wallet_name)
+ self.nodes[0].loadwallet("basic1")
+ basic1 = self.nodes[0].get_wallet_rpc("basic1")
+ assert_equal(basic1.getwalletinfo()["descriptors"], True)
+ self.assert_is_sqlite("basic1")
+ assert_equal(basic1.getbalance(), bal)
+ self.assert_list_txs_equal(basic1.listtransactions(), txs)
+
+ self.log.info("Test migration of a wallet with balance received on the seed")
+ basic2 = self.create_legacy_wallet("basic2")
+ basic2_seed = get_generate_key()
+ basic2.sethdseed(True, basic2_seed.privkey)
+ assert_equal(basic2.getbalance(), 0)
+
+ # Receive coins on different output types for the same seed
+ basic2_balance = 0
+ for addr in [basic2_seed.p2pkh_addr, basic2_seed.p2wpkh_addr, basic2_seed.p2sh_p2wpkh_addr]:
+ send_value = random.randint(1, 4)
+ default.sendtoaddress(addr, send_value)
+ basic2_balance += send_value
+ self.generate(self.nodes[0], 1)
+ assert_equal(basic2.getbalance(), basic2_balance)
+ basic2_txs = basic2.listtransactions()
+
+ # Now migrate and test that we still see have the same balance/transactions
+ basic2.migratewallet()
+ assert_equal(basic2.getwalletinfo()["descriptors"], True)
+ self.assert_is_sqlite("basic2")
+ assert_equal(basic2.getbalance(), basic2_balance)
+ self.assert_list_txs_equal(basic2.listtransactions(), basic2_txs)
+
+ def test_multisig(self):
+ default = self.nodes[0].get_wallet_rpc(self.default_wallet_name)
+
+ # Contrived case where all the multisig keys are in a single wallet
+ self.log.info("Test migration of a wallet with all keys for a multisig")
+ multisig0 = self.create_legacy_wallet("multisig0")
+ addr1 = multisig0.getnewaddress()
+ addr2 = multisig0.getnewaddress()
+ addr3 = multisig0.getnewaddress()
+
+ ms_info = multisig0.addmultisigaddress(2, [addr1, addr2, addr3])
+
+ multisig0.migratewallet()
+ assert_equal(multisig0.getwalletinfo()["descriptors"], True)
+ self.assert_is_sqlite("multisig0")
+ ms_addr_info = multisig0.getaddressinfo(ms_info["address"])
+ assert_equal(ms_addr_info["ismine"], True)
+ assert_equal(ms_addr_info["desc"], ms_info["descriptor"])
+ assert_equal("multisig0_watchonly" in self.nodes[0].listwallets(), False)
+ assert_equal("multisig0_solvables" in self.nodes[0].listwallets(), False)
+
+ pub1 = multisig0.getaddressinfo(addr1)["pubkey"]
+ pub2 = multisig0.getaddressinfo(addr2)["pubkey"]
+
+ # Some keys in multisig do not belong to this wallet
+ self.log.info("Test migration of a wallet that has some keys in a multisig")
+ self.nodes[0].createwallet(wallet_name="multisig1")
+ multisig1 = self.nodes[0].get_wallet_rpc("multisig1")
+ ms_info = multisig1.addmultisigaddress(2, [multisig1.getnewaddress(), pub1, pub2])
+ ms_info2 = multisig1.addmultisigaddress(2, [multisig1.getnewaddress(), pub1, pub2])
+ assert_equal(multisig1.getwalletinfo()["descriptors"], False)
+
+ addr1 = ms_info["address"]
+ addr2 = ms_info2["address"]
+ txid = default.sendtoaddress(addr1, 10)
+ multisig1.importaddress(addr1)
+ assert_equal(multisig1.getaddressinfo(addr1)["ismine"], False)
+ assert_equal(multisig1.getaddressinfo(addr1)["iswatchonly"], True)
+ assert_equal(multisig1.getaddressinfo(addr1)["solvable"], True)
+ self.generate(self.nodes[0], 1)
+ multisig1.gettransaction(txid)
+ assert_equal(multisig1.getbalances()["watchonly"]["trusted"], 10)
+ assert_equal(multisig1.getaddressinfo(addr2)["ismine"], False)
+ assert_equal(multisig1.getaddressinfo(addr2)["iswatchonly"], False)
+ assert_equal(multisig1.getaddressinfo(addr2)["solvable"], True)
+
+ # Migrating multisig1 should see the multisig is no longer part of multisig1
+ # A new wallet multisig1_watchonly is created which has the multisig address
+ # Transaction to multisig is in multisig1_watchonly and not multisig1
+ multisig1.migratewallet()
+ assert_equal(multisig1.getwalletinfo()["descriptors"], True)
+ self.assert_is_sqlite("multisig1")
+ assert_equal(multisig1.getaddressinfo(addr1)["ismine"], False)
+ assert_equal(multisig1.getaddressinfo(addr1)["iswatchonly"], False)
+ assert_equal(multisig1.getaddressinfo(addr1)["solvable"], False)
+ assert_raises_rpc_error(-5, "Invalid or non-wallet transaction id", multisig1.gettransaction, txid)
+ assert_equal(multisig1.getbalance(), 0)
+ assert_equal(multisig1.listtransactions(), [])
+
+ assert_equal("multisig1_watchonly" in self.nodes[0].listwallets(), True)
+ ms1_watchonly = self.nodes[0].get_wallet_rpc("multisig1_watchonly")
+ ms1_wallet_info = ms1_watchonly.getwalletinfo()
+ assert_equal(ms1_wallet_info['descriptors'], True)
+ assert_equal(ms1_wallet_info['private_keys_enabled'], False)
+ self.assert_is_sqlite("multisig1_watchonly")
+ assert_equal(ms1_watchonly.getaddressinfo(addr1)["ismine"], True)
+ assert_equal(ms1_watchonly.getaddressinfo(addr1)["solvable"], True)
+ # Because addr2 was not being watched, it isn't in multisig1_watchonly but rather multisig1_solvables
+ assert_equal(ms1_watchonly.getaddressinfo(addr2)["ismine"], False)
+ assert_equal(ms1_watchonly.getaddressinfo(addr2)["solvable"], False)
+ ms1_watchonly.gettransaction(txid)
+ assert_equal(ms1_watchonly.getbalance(), 10)
+
+ # Migrating multisig1 should see the second multisig is no longer part of multisig1
+ # A new wallet multisig1_solvables is created which has the second address
+ # This should have no transactions
+ assert_equal("multisig1_solvables" in self.nodes[0].listwallets(), True)
+ ms1_solvable = self.nodes[0].get_wallet_rpc("multisig1_solvables")
+ ms1_wallet_info = ms1_solvable.getwalletinfo()
+ assert_equal(ms1_wallet_info['descriptors'], True)
+ assert_equal(ms1_wallet_info['private_keys_enabled'], False)
+ self.assert_is_sqlite("multisig1_solvables")
+ assert_equal(ms1_solvable.getaddressinfo(addr1)["ismine"], False)
+ assert_equal(ms1_solvable.getaddressinfo(addr1)["solvable"], False)
+ assert_equal(ms1_solvable.getaddressinfo(addr2)["ismine"], True)
+ assert_equal(ms1_solvable.getaddressinfo(addr2)["solvable"], True)
+ assert_equal(ms1_solvable.getbalance(), 0)
+ assert_equal(ms1_solvable.listtransactions(), [])
+
+
+ def test_other_watchonly(self):
+ default = self.nodes[0].get_wallet_rpc(self.default_wallet_name)
+
+ # Wallet with an imported address. Should be the same thing as the multisig test
+ self.log.info("Test migration of a wallet with watchonly imports")
+ self.nodes[0].createwallet(wallet_name="imports0")
+ imports0 = self.nodes[0].get_wallet_rpc("imports0")
+ assert_equal(imports0.getwalletinfo()["descriptors"], False)
+
+ # Exteranl address label
+ imports0.setlabel(default.getnewaddress(), "external")
+
+ # Normal non-watchonly tx
+ received_addr = imports0.getnewaddress()
+ imports0.setlabel(received_addr, "Receiving")
+ received_txid = default.sendtoaddress(received_addr, 10)
+
+ # Watchonly tx
+ import_addr = default.getnewaddress()
+ imports0.importaddress(import_addr)
+ imports0.setlabel(import_addr, "imported")
+ received_watchonly_txid = default.sendtoaddress(import_addr, 10)
+
+ # Received watchonly tx that is then spent
+ import_sent_addr = default.getnewaddress()
+ imports0.importaddress(import_sent_addr)
+ received_sent_watchonly_txid = default.sendtoaddress(import_sent_addr, 10)
+ received_sent_watchonly_vout = find_vout_for_address(self.nodes[0], received_sent_watchonly_txid, import_sent_addr)
+ send = default.sendall(recipients=[default.getnewaddress()], options={"inputs": [{"txid": received_sent_watchonly_txid, "vout": received_sent_watchonly_vout}]})
+ sent_watchonly_txid = send["txid"]
+
+ self.generate(self.nodes[0], 1)
+
+ balances = imports0.getbalances()
+ spendable_bal = balances["mine"]["trusted"]
+ watchonly_bal = balances["watchonly"]["trusted"]
+ assert_equal(len(imports0.listtransactions(include_watchonly=True)), 4)
+
+ # Migrate
+ imports0.migratewallet()
+ assert_equal(imports0.getwalletinfo()["descriptors"], True)
+ self.assert_is_sqlite("imports0")
+ assert_raises_rpc_error(-5, "Invalid or non-wallet transaction id", imports0.gettransaction, received_watchonly_txid)
+ assert_raises_rpc_error(-5, "Invalid or non-wallet transaction id", imports0.gettransaction, received_sent_watchonly_txid)
+ assert_raises_rpc_error(-5, "Invalid or non-wallet transaction id", imports0.gettransaction, sent_watchonly_txid)
+ assert_equal(len(imports0.listtransactions(include_watchonly=True)), 1)
+ imports0.gettransaction(received_txid)
+ assert_equal(imports0.getbalance(), spendable_bal)
+
+ assert_equal("imports0_watchonly" in self.nodes[0].listwallets(), True)
+ watchonly = self.nodes[0].get_wallet_rpc("imports0_watchonly")
+ watchonly_info = watchonly.getwalletinfo()
+ assert_equal(watchonly_info["descriptors"], True)
+ self.assert_is_sqlite("imports0_watchonly")
+ assert_equal(watchonly_info["private_keys_enabled"], False)
+ watchonly.gettransaction(received_watchonly_txid)
+ watchonly.gettransaction(received_sent_watchonly_txid)
+ watchonly.gettransaction(sent_watchonly_txid)
+ assert_equal(watchonly.getbalance(), watchonly_bal)
+ assert_raises_rpc_error(-5, "Invalid or non-wallet transaction id", watchonly.gettransaction, received_txid)
+ assert_equal(len(watchonly.listtransactions(include_watchonly=True)), 3)
+
+ def test_no_privkeys(self):
+ default = self.nodes[0].get_wallet_rpc(self.default_wallet_name)
+
+ # Migrating an actual watchonly wallet should not create a new watchonly wallet
+ self.log.info("Test migration of a pure watchonly wallet")
+ self.nodes[0].createwallet(wallet_name="watchonly0", disable_private_keys=True)
+ watchonly0 = self.nodes[0].get_wallet_rpc("watchonly0")
+ info = watchonly0.getwalletinfo()
+ assert_equal(info["descriptors"], False)
+ assert_equal(info["private_keys_enabled"], False)
+
+ addr = default.getnewaddress()
+ desc = default.getaddressinfo(addr)["desc"]
+ res = watchonly0.importmulti([
+ {
+ "desc": desc,
+ "watchonly": True,
+ "timestamp": "now",
+ }])
+ assert_equal(res[0]['success'], True)
+ default.sendtoaddress(addr, 10)
+ self.generate(self.nodes[0], 1)
+
+ watchonly0.migratewallet()
+ assert_equal("watchonly0_watchonly" in self.nodes[0].listwallets(), False)
+ info = watchonly0.getwalletinfo()
+ assert_equal(info["descriptors"], True)
+ assert_equal(info["private_keys_enabled"], False)
+ self.assert_is_sqlite("watchonly0")
+
+ # Migrating a wallet with pubkeys added to the keypool
+ self.log.info("Test migration of a pure watchonly wallet with pubkeys in keypool")
+ self.nodes[0].createwallet(wallet_name="watchonly1", disable_private_keys=True)
+ watchonly1 = self.nodes[0].get_wallet_rpc("watchonly1")
+ info = watchonly1.getwalletinfo()
+ assert_equal(info["descriptors"], False)
+ assert_equal(info["private_keys_enabled"], False)
+
+ addr1 = default.getnewaddress(address_type="bech32")
+ addr2 = default.getnewaddress(address_type="bech32")
+ desc1 = default.getaddressinfo(addr1)["desc"]
+ desc2 = default.getaddressinfo(addr2)["desc"]
+ res = watchonly1.importmulti([
+ {
+ "desc": desc1,
+ "keypool": True,
+ "timestamp": "now",
+ },
+ {
+ "desc": desc2,
+ "keypool": True,
+ "timestamp": "now",
+ }
+ ])
+ assert_equal(res[0]["success"], True)
+ assert_equal(res[1]["success"], True)
+ # Before migrating, we can fetch addr1 from the keypool
+ assert_equal(watchonly1.getnewaddress(address_type="bech32"), addr1)
+
+ watchonly1.migratewallet()
+ info = watchonly1.getwalletinfo()
+ assert_equal(info["descriptors"], True)
+ assert_equal(info["private_keys_enabled"], False)
+ self.assert_is_sqlite("watchonly1")
+ # After migrating, the "keypool" is empty
+ assert_raises_rpc_error(-4, "Error: This wallet has no available keys", watchonly1.getnewaddress)
+
+ def test_pk_coinbases(self):
+ self.log.info("Test migration of a wallet using old pk() coinbases")
+ wallet = self.create_legacy_wallet("pkcb")
+
+ addr = wallet.getnewaddress()
+ addr_info = wallet.getaddressinfo(addr)
+ desc = descsum_create("pk(" + addr_info["pubkey"] + ")")
+
+ self.nodes[0].generatetodescriptor(1, desc, invalid_call=False)
+
+ bals = wallet.getbalances()
+
+ wallet.migratewallet()
+
+ assert_equal(bals, wallet.getbalances())
+
+ def run_test(self):
+ self.generate(self.nodes[0], 101)
+
+ # TODO: Test the actual records in the wallet for these tests too. The behavior may be correct, but the data written may not be what we actually want
+ self.test_basic()
+ self.test_multisig()
+ self.test_other_watchonly()
+ self.test_no_privkeys()
+ self.test_pk_coinbases()
+
+if __name__ == '__main__':
+ WalletMigrationTest().main()
diff --git a/test/functional/wallet_miniscript.py b/test/functional/wallet_miniscript.py
new file mode 100755
index 0000000000..2252f1e424
--- /dev/null
+++ b/test/functional/wallet_miniscript.py
@@ -0,0 +1,93 @@
+#!/usr/bin/env python3
+# Copyright (c) 2022 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 Miniscript descriptors integration in the wallet."""
+
+from test_framework.descriptors import descsum_create
+from test_framework.test_framework import BitcoinTestFramework
+from test_framework.util import assert_equal
+
+
+MINISCRIPTS = [
+ # One of two keys
+ "or_b(pk(tpubD6NzVbkrYhZ4XRMcMFMMFvzVt6jaDAtjZhD7JLwdPdMm9xa76DnxYYP7w9TZGJDVFkek3ArwVsuacheqqPog8TH5iBCX1wuig8PLXim4n9a/*),s:pk(tpubD6NzVbkrYhZ4WsqRzDmkL82SWcu42JzUvKWzrJHQ8EC2vEHRHkXj1De93sD3biLrKd8XGnamXURGjMbYavbszVDXpjXV2cGUERucLJkE6cy/*))",
+ # A script similar (same spending policy) to BOLT3's offered HTLC (with anchor outputs)
+ "or_d(pk(tpubD6NzVbkrYhZ4XRMcMFMMFvzVt6jaDAtjZhD7JLwdPdMm9xa76DnxYYP7w9TZGJDVFkek3ArwVsuacheqqPog8TH5iBCX1wuig8PLXim4n9a/*),and_v(and_v(v:pk(tpubD6NzVbkrYhZ4WsqRzDmkL82SWcu42JzUvKWzrJHQ8EC2vEHRHkXj1De93sD3biLrKd8XGnamXURGjMbYavbszVDXpjXV2cGUERucLJkE6cy/*),or_c(pk(tpubD6NzVbkrYhZ4YNwtTWrKRJQzQX3PjPKeUQg1gYh1hiLMkk1cw8SRLgB1yb7JzE8bHKNt6EcZXkJ6AqpCZL1aaRSjnG36mLgbQvJZBNsjWnG/*),v:hash160(7f999c905d5e35cefd0a37673f746eb13fba3640))),older(1)))",
+ # A Revault Unvault policy with the older() replaced by an after()
+ "andor(multi(2,tpubD6NzVbkrYhZ4YMQC15JS7QcrsAyfGrGiykweqMmPxTkEVScu7vCZLNpPXW1XphHwzsgmqdHWDQAfucbM72EEB1ZEyfgZxYvkZjYVXx1xS9p/*,tpubD6NzVbkrYhZ4WkCyc7E3z6g6NkypHMiecnwc4DpWHTPqFdteRGkEKukdrSSyJGNnGrHNMfy4BCw2UXo5soYRCtCDDfy4q8pc8oyB7RgTFv8/*),and_v(v:multi(4,030f64b922aee2fd597f104bc6cb3b670f1ca2c6c49b1071a1a6c010575d94fe5a,02abe475b199ec3d62fa576faee16a334fdb86ffb26dce75becebaaedf328ac3fe,0314f3dc33595b0d016bb522f6fe3a67680723d842c1b9b8ae6b59fdd8ab5cccb4,025eba3305bd3c829e4e1551aac7358e4178832c739e4fc4729effe428de0398ab),after(424242)),thresh(4,pkh(tpubD6NzVbkrYhZ4YVrNggiT2ptVHwnFbLBqDkCtV5HkxR4WtcRLAQReKTkqZGNcV6GE7cQsmpBzzSzhk16DUwB1gn1L7ZPnJF2dnNePP1uMBCY/*),a:pkh(tpubD6NzVbkrYhZ4YU9vM1s53UhD75UyJatx8EMzMZ3VUjR2FciNfLLkAw6a4pWACChzobTseNqdWk4G7ZdBqRDLtLSACKykTScmqibb1ZrCvJu/*),a:pkh(tpubD6NzVbkrYhZ4YUHcFfuH9iEBLiH8CBRJTpS7X3qjHmh82m1KCNbzs6w9gyK8oWHSZmKHWcakAXCGfbKg6xoCvKzQCWAHyxaC7QcWfmzyBf4/*),a:pkh(tpubD6NzVbkrYhZ4XXEmQtS3sgxpJbMyMg4McqRR1Af6ULzyrTRnhwjyr1etPD7svap9oFtJf4MM72brUb5o7uvF2Jyszc5c1t836fJW7SX2e8D/*)))",
+ # Liquid-like federated pegin with emergency recovery keys
+ "or_i(and_b(pk(029ffbe722b147f3035c87cb1c60b9a5947dd49c774cc31e94773478711a929ac0),a:and_b(pk(025f05815e3a1a8a83bfbb03ce016c9a2ee31066b98f567f6227df1d76ec4bd143),a:and_b(pk(025625f41e4a065efc06d5019cbbd56fe8c07595af1231e7cbc03fafb87ebb71ec),a:and_b(pk(02a27c8b850a00f67da3499b60562673dcf5fdfb82b7e17652a7ac54416812aefd),s:pk(03e618ec5f384d6e19ca9ebdb8e2119e5bef978285076828ce054e55c4daf473e2))))),and_v(v:thresh(2,pkh(tpubD6NzVbkrYhZ4YK67cd5fDe4fBVmGB2waTDrAt1q4ey9HPq9veHjWkw3VpbaCHCcWozjkhgAkWpFrxuPMUrmXVrLHMfEJ9auoZA6AS1g3grC/*),a:pkh(033841045a531e1adf9910a6ec279589a90b3b8a904ee64ffd692bd08a8996c1aa),a:pkh(02aebf2d10b040eb936a6f02f44ee82f8b34f5c1ccb20ff3949c2b28206b7c1068)),older(4209713)))",
+]
+
+
+class WalletMiniscriptTest(BitcoinTestFramework):
+ def set_test_params(self):
+ self.num_nodes = 1
+
+ def skip_test_if_missing_module(self):
+ self.skip_if_no_wallet()
+ self.skip_if_no_sqlite()
+
+ def watchonly_test(self, ms):
+ self.log.info(f"Importing Miniscript '{ms}'")
+ desc = descsum_create(f"wsh({ms})")
+ assert self.ms_wo_wallet.importdescriptors(
+ [
+ {
+ "desc": desc,
+ "active": True,
+ "range": 2,
+ "next_index": 0,
+ "timestamp": "now",
+ }
+ ]
+ )[0]["success"]
+
+ self.log.info("Testing we derive new addresses for it")
+ assert_equal(
+ self.ms_wo_wallet.getnewaddress(), self.funder.deriveaddresses(desc, 0)[0]
+ )
+ assert_equal(
+ self.ms_wo_wallet.getnewaddress(), self.funder.deriveaddresses(desc, 1)[1]
+ )
+
+ self.log.info("Testing we detect funds sent to one of them")
+ addr = self.ms_wo_wallet.getnewaddress()
+ txid = self.funder.sendtoaddress(addr, 0.01)
+ self.wait_until(
+ lambda: len(self.ms_wo_wallet.listunspent(minconf=0, addresses=[addr])) == 1
+ )
+ utxo = self.ms_wo_wallet.listunspent(minconf=0, addresses=[addr])[0]
+ assert utxo["txid"] == txid and not utxo["solvable"] # No satisfaction logic (yet)
+
+ def run_test(self):
+ self.log.info("Making a descriptor wallet")
+ self.funder = self.nodes[0].get_wallet_rpc(self.default_wallet_name)
+ self.nodes[0].createwallet(
+ wallet_name="ms_wo", descriptors=True, disable_private_keys=True
+ )
+ self.ms_wo_wallet = self.nodes[0].get_wallet_rpc("ms_wo")
+
+ # Sanity check we wouldn't let an insane Miniscript descriptor in
+ res = self.ms_wo_wallet.importdescriptors(
+ [
+ {
+ "desc": descsum_create(
+ "wsh(and_b(ripemd160(1fd9b55a054a2b3f658d97e6b84cf3ee00be429a),a:1))"
+ ),
+ "active": False,
+ "timestamp": "now",
+ }
+ ]
+ )[0]
+ assert not res["success"]
+ assert "is not sane: witnesses without signature exist" in res["error"]["message"]
+
+ # Test we can track any type of Miniscript
+ for ms in MINISCRIPTS:
+ self.watchonly_test(ms)
+
+
+if __name__ == "__main__":
+ WalletMiniscriptTest().main()
diff --git a/test/functional/wallet_multiwallet.py b/test/functional/wallet_multiwallet.py
index dcb82bbbe9..1c890d7207 100755
--- a/test/functional/wallet_multiwallet.py
+++ b/test/functional/wallet_multiwallet.py
@@ -356,7 +356,7 @@ class MultiWalletTest(BitcoinTestFramework):
self.log.info("Test dynamic wallet unloading")
# Test `unloadwallet` errors
- assert_raises_rpc_error(-1, "JSON value is not a string as expected", self.nodes[0].unloadwallet)
+ assert_raises_rpc_error(-3, "JSON value of type null is not of expected type string", self.nodes[0].unloadwallet)
assert_raises_rpc_error(-18, "Requested wallet does not exist or is not loaded", self.nodes[0].unloadwallet, "dummy")
assert_raises_rpc_error(-18, "Requested wallet does not exist or is not loaded", node.get_wallet_rpc("dummy").unloadwallet)
assert_raises_rpc_error(-8, "RPC endpoint wallet and wallet_name parameter specify different wallets", w1.unloadwallet, "w2"),
diff --git a/test/functional/wallet_resendwallettransactions.py b/test/functional/wallet_resendwallettransactions.py
index 6552bfe60c..b3d02fbfc9 100755
--- a/test/functional/wallet_resendwallettransactions.py
+++ b/test/functional/wallet_resendwallettransactions.py
@@ -9,10 +9,13 @@ from test_framework.blocktools import (
create_block,
create_coinbase,
)
+from test_framework.messages import DEFAULT_MEMPOOL_EXPIRY_HOURS
from test_framework.p2p import P2PTxInvStore
from test_framework.test_framework import BitcoinTestFramework
-from test_framework.util import assert_equal
-
+from test_framework.util import (
+ assert_equal,
+ assert_raises_rpc_error,
+)
class ResendWalletTransactionsTest(BitcoinTestFramework):
def set_test_params(self):
@@ -27,13 +30,9 @@ class ResendWalletTransactionsTest(BitcoinTestFramework):
peer_first = node.add_p2p_connection(P2PTxInvStore())
self.log.info("Create a new transaction and wait until it's broadcast")
- txid = node.sendtoaddress(node.getnewaddress(), 1)
-
- # Wallet rebroadcast is first scheduled 1 sec after startup (see
- # nNextResend in ResendWalletTransactions()). Tell scheduler to call
- # MaybeResendWalletTxn now to initialize nNextResend before the first
- # setmocktime call below.
- node.mockscheduler(1)
+ parent_utxo, indep_utxo = node.listunspent()[:2]
+ addr = node.getnewaddress()
+ txid = node.send(outputs=[{addr: 1}], options={"inputs": [parent_utxo]})["txid"]
# Can take a few seconds due to transaction trickling
peer_first.wait_for_broadcast([txid])
@@ -51,7 +50,7 @@ class ResendWalletTransactionsTest(BitcoinTestFramework):
block.solve()
node.submitblock(block.serialize().hex())
- # Set correct m_best_block_time, which is used in ResendWalletTransactions
+ # Set correct m_best_block_time, which is used in ResubmitWalletTransactions
node.syncwithvalidationinterfacequeue()
now = int(time.time())
@@ -60,20 +59,67 @@ class ResendWalletTransactionsTest(BitcoinTestFramework):
twelve_hrs = 12 * 60 * 60
two_min = 2 * 60
node.setmocktime(now + twelve_hrs - two_min)
- node.mockscheduler(1) # Tell scheduler to call MaybeResendWalletTxn now
+ node.mockscheduler(60) # Tell scheduler to call MaybeResendWalletTxs now
assert_equal(int(txid, 16) in peer_second.get_invs(), False)
self.log.info("Bump time & check that transaction is rebroadcast")
# Transaction should be rebroadcast approximately 24 hours in the future,
# but can range from 12-36. So bump 36 hours to be sure.
- with node.assert_debug_log(['ResendWalletTransactions: resubmit 1 unconfirmed transactions']):
+ with node.assert_debug_log(['resubmit 1 unconfirmed transactions']):
node.setmocktime(now + 36 * 60 * 60)
- # Tell scheduler to call MaybeResendWalletTxn now.
- node.mockscheduler(1)
+ # Tell scheduler to call MaybeResendWalletTxs now.
+ node.mockscheduler(60)
# Give some time for trickle to occur
node.setmocktime(now + 36 * 60 * 60 + 600)
peer_second.wait_for_broadcast([txid])
+ self.log.info("Chain of unconfirmed not-in-mempool txs are rebroadcast")
+ # This tests that the node broadcasts the parent transaction before the child transaction.
+ # To test that scenario, we need a method to reliably get a child transaction placed
+ # in mapWallet positioned before the parent. We cannot predict the position in mapWallet,
+ # but we can observe it using listreceivedbyaddress and other related RPCs.
+ #
+ # So we will create the child transaction, use listreceivedbyaddress to see what the
+ # ordering of mapWallet is, if the child is not before the parent, we will create a new
+ # child (via bumpfee) and remove the old child (via removeprunedfunds) until we get the
+ # ordering of child before parent.
+ child_txid = node.send(outputs=[{addr: 0.5}], options={"inputs": [{"txid":txid, "vout":0}]})["txid"]
+ while True:
+ txids = node.listreceivedbyaddress(minconf=0, address_filter=addr)[0]["txids"]
+ if txids == [child_txid, txid]:
+ break
+ bumped = node.bumpfee(child_txid)
+ # The scheduler queue creates a copy of the added tx after
+ # send/bumpfee and re-adds it to the wallet (undoing the next
+ # removeprunedfunds). So empty the scheduler queue:
+ node.syncwithvalidationinterfacequeue()
+ node.removeprunedfunds(child_txid)
+ child_txid = bumped["txid"]
+ entry_time = node.getmempoolentry(child_txid)["time"]
+
+ block_time = entry_time + 6 * 60
+ node.setmocktime(block_time)
+ block = create_block(int(node.getbestblockhash(), 16), create_coinbase(node.getblockcount() + 1), block_time)
+ block.solve()
+ node.submitblock(block.serialize().hex())
+ # Set correct m_best_block_time, which is used in ResubmitWalletTransactions
+ node.syncwithvalidationinterfacequeue()
+
+ # Evict these txs from the mempool
+ evict_time = block_time + 60 * 60 * DEFAULT_MEMPOOL_EXPIRY_HOURS + 5
+ node.setmocktime(evict_time)
+ indep_send = node.send(outputs=[{node.getnewaddress(): 1}], options={"inputs": [indep_utxo]})
+ node.getmempoolentry(indep_send["txid"])
+ assert_raises_rpc_error(-5, "Transaction not in mempool", node.getmempoolentry, txid)
+ assert_raises_rpc_error(-5, "Transaction not in mempool", node.getmempoolentry, child_txid)
+
+ # Rebroadcast and check that parent and child are both in the mempool
+ with node.assert_debug_log(['resubmit 2 unconfirmed transactions']):
+ node.setmocktime(evict_time + 36 * 60 * 60) # 36 hrs is the upper limit of the resend timer
+ node.mockscheduler(60)
+ node.getmempoolentry(txid)
+ node.getmempoolentry(child_txid)
+
if __name__ == '__main__':
ResendWalletTransactionsTest().main()
diff --git a/test/functional/wallet_sendall.py b/test/functional/wallet_sendall.py
index aa8d2a9d2c..4fe11455b1 100755
--- a/test/functional/wallet_sendall.py
+++ b/test/functional/wallet_sendall.py
@@ -221,6 +221,11 @@ class SendallTest(BitcoinTestFramework):
self.add_utxos([16, 5])
spent_utxo = self.wallet.listunspent()[0]
+ # fails on out of bounds vout
+ assert_raises_rpc_error(-8,
+ "Input not found. UTXO ({}:{}) is not part of wallet.".format(spent_utxo["txid"], 1000),
+ self.wallet.sendall, recipients=[self.remainder_target], options={"inputs": [{"txid": spent_utxo["txid"], "vout": 1000}]})
+
# fails on unconfirmed spent UTXO
self.wallet.sendall(recipients=[self.remainder_target])
assert_raises_rpc_error(-8,
@@ -264,6 +269,59 @@ class SendallTest(BitcoinTestFramework):
recipients=[self.remainder_target],
options={"inputs": [utxo], "send_max": True})
+ @cleanup
+ def sendall_fails_on_high_fee(self):
+ self.log.info("Test sendall fails if the transaction fee exceeds the maxtxfee")
+ self.add_utxos([21])
+
+ assert_raises_rpc_error(
+ -4,
+ "Fee exceeds maximum configured by user",
+ self.wallet.sendall,
+ recipients=[self.remainder_target],
+ fee_rate=100000)
+
+ @cleanup
+ def sendall_watchonly_specific_inputs(self):
+ self.log.info("Test sendall with a subset of UTXO pool in a watchonly wallet")
+ self.add_utxos([17, 4])
+ utxo = self.wallet.listunspent()[0]
+
+ self.nodes[0].createwallet(wallet_name="watching", disable_private_keys=True)
+ watchonly = self.nodes[0].get_wallet_rpc("watching")
+
+ import_req = [{
+ "desc": utxo["desc"],
+ "timestamp": 0,
+ }]
+ if self.options.descriptors:
+ watchonly.importdescriptors(import_req)
+ else:
+ watchonly.importmulti(import_req)
+
+ sendall_tx_receipt = watchonly.sendall(recipients=[self.remainder_target], options={"inputs": [utxo]})
+ psbt = sendall_tx_receipt["psbt"]
+ decoded = self.nodes[0].decodepsbt(psbt)
+ assert_equal(len(decoded["inputs"]), 1)
+ assert_equal(len(decoded["outputs"]), 1)
+ assert_equal(decoded["tx"]["vin"][0]["txid"], utxo["txid"])
+ assert_equal(decoded["tx"]["vin"][0]["vout"], utxo["vout"])
+ assert_equal(decoded["tx"]["vout"][0]["scriptPubKey"]["address"], self.remainder_target)
+
+ # This tests needs to be the last one otherwise @cleanup will fail with "Transaction too large" error
+ def sendall_fails_with_transaction_too_large(self):
+ self.log.info("Test that sendall fails if resulting transaction is too large")
+ # create many inputs
+ outputs = {self.wallet.getnewaddress(): 0.000025 for _ in range(1600)}
+ self.def_wallet.sendmany(amounts=outputs)
+ self.generate(self.nodes[0], 1)
+
+ assert_raises_rpc_error(
+ -4,
+ "Transaction too large.",
+ self.wallet.sendall,
+ recipients=[self.remainder_target])
+
def run_test(self):
self.nodes[0].createwallet("activewallet")
self.wallet = self.nodes[0].get_wallet_rpc("activewallet")
@@ -312,5 +370,14 @@ class SendallTest(BitcoinTestFramework):
# Sendall fails when using send_max while specifying inputs
self.sendall_fails_on_specific_inputs_with_send_max()
+ # Sendall fails when providing a fee that is too high
+ self.sendall_fails_on_high_fee()
+
+ # Sendall succeeds with watchonly wallets spending specific UTXOs
+ self.sendall_watchonly_specific_inputs()
+
+ # Sendall fails when many inputs result to too large transaction
+ self.sendall_fails_with_transaction_too_large()
+
if __name__ == '__main__':
SendallTest().main()
diff --git a/test/functional/wallet_signer.py b/test/functional/wallet_signer.py
index 4bb60a9f58..db3a8a2efa 100755
--- a/test/functional/wallet_signer.py
+++ b/test/functional/wallet_signer.py
@@ -32,6 +32,13 @@ class WalletSignerTest(BitcoinTestFramework):
else:
return path
+ def mock_multi_signers_path(self):
+ path = os.path.join(os.path.dirname(os.path.realpath(__file__)), 'mocks', 'multi_signers.py')
+ if platform.system() == "Windows":
+ return "py " + path
+ else:
+ return path
+
def set_test_params(self):
self.num_nodes = 2
# The experimental syscall sandbox feature (-sandbox) is not compatible with -signer (which
@@ -58,6 +65,8 @@ class WalletSignerTest(BitcoinTestFramework):
self.test_valid_signer()
self.restart_node(1, [f"-signer={self.mock_invalid_signer_path()}", "-keypool=10"])
self.test_invalid_signer()
+ self.restart_node(1, [f"-signer={self.mock_multi_signers_path()}", "-keypool=10"])
+ self.test_multiple_signers()
def test_valid_signer(self):
self.log.debug(f"-signer={self.mock_signer_path()}")
@@ -219,5 +228,11 @@ class WalletSignerTest(BitcoinTestFramework):
self.log.info('Test invalid external signer')
assert_raises_rpc_error(-1, "Invalid descriptor", self.nodes[1].createwallet, wallet_name='hww_invalid', disable_private_keys=True, descriptors=True, external_signer=True)
+ def test_multiple_signers(self):
+ self.log.debug(f"-signer={self.mock_multi_signers_path()}")
+ self.log.info('Test multiple external signers')
+
+ assert_raises_rpc_error(-1, "GetExternalSigner: More than one external signer found", self.nodes[1].createwallet, wallet_name='multi_hww', disable_private_keys=True, descriptors=True, external_signer=True)
+
if __name__ == '__main__':
WalletSignerTest().main()
diff --git a/test/functional/rpc_signrawtransaction.py b/test/functional/wallet_signrawtransactionwithwallet.py
index 8da2cfa72b..6b30386b7e 100755
--- a/test/functional/rpc_signrawtransaction.py
+++ b/test/functional/wallet_signrawtransactionwithwallet.py
@@ -2,16 +2,14 @@
# Copyright (c) 2015-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 transaction signing using the signrawtransaction* RPCs."""
+"""Test transaction signing using the signrawtransactionwithwallet RPC."""
from test_framework.blocktools import (
COINBASE_MATURITY,
)
from test_framework.address import (
- script_to_p2sh,
script_to_p2wsh,
)
-from test_framework.key import ECKey
from test_framework.test_framework import BitcoinTestFramework
from test_framework.util import (
assert_equal,
@@ -29,20 +27,13 @@ from test_framework.script import (
OP_DROP,
OP_TRUE,
)
-from test_framework.script_util import (
- key_to_p2pk_script,
- key_to_p2pkh_script,
- script_to_p2sh_p2wsh_script,
- script_to_p2wsh_script,
-)
-from test_framework.wallet_util import bytes_to_wif
from decimal import (
Decimal,
getcontext,
)
-class SignRawTransactionsTest(BitcoinTestFramework):
+class SignRawTransactionWithWalletTest(BitcoinTestFramework):
def set_test_params(self):
self.setup_clean_chain = True
self.num_nodes = 2
@@ -50,35 +41,6 @@ class SignRawTransactionsTest(BitcoinTestFramework):
def skip_test_if_missing_module(self):
self.skip_if_no_wallet()
- def successful_signing_test(self):
- """Create and sign a valid raw transaction with one input.
-
- Expected results:
-
- 1) The transaction has a complete set of signatures
- 2) No script verification error occurred"""
- self.log.info("Test valid raw transaction with one input")
- privKeys = ['cUeKHd5orzT3mz8P9pxyREHfsWtVfgsfDjiZZBcjUBAaGk1BTj7N', 'cVKpPfVKSJxKqVpE9awvXNWuLHCa5j5tiE7K6zbUSptFpTEtiFrA']
-
- inputs = [
- # Valid pay-to-pubkey scripts
- {'txid': '9b907ef1e3c26fc71fe4a4b3580bc75264112f95050014157059c736f0202e71', 'vout': 0,
- 'scriptPubKey': '76a91460baa0f494b38ce3c940dea67f3804dc52d1fb9488ac'},
- {'txid': '83a4f6a6b73660e13ee6cb3c6063fa3759c50c9b7521d0536022961898f4fb02', 'vout': 0,
- 'scriptPubKey': '76a914669b857c03a5ed269d5d85a1ffac9ed5d663072788ac'},
- ]
-
- outputs = {'mpLQjfK79b7CCV4VMJWEWAj5Mpx8Up5zxB': 0.1}
-
- rawTx = self.nodes[0].createrawtransaction(inputs, outputs)
- rawTxSigned = self.nodes[0].signrawtransactionwithkey(rawTx, privKeys, inputs)
-
- # 1) The transaction has a complete set of signatures
- assert rawTxSigned['complete']
-
- # 2) No script verification error occurred
- assert 'errors' not in rawTxSigned
-
def test_with_lock_outputs(self):
self.log.info("Test correct error reporting when trying to sign a locked output")
self.nodes[0].encryptwallet("password")
@@ -191,60 +153,6 @@ class SignRawTransactionsTest(BitcoinTestFramework):
assert_equal(signedtx["hex"], signedtx2["hex"])
self.nodes[0].walletlock()
- def witness_script_test(self):
- self.log.info("Test signing transaction to P2SH-P2WSH addresses without wallet")
- # Create a new P2SH-P2WSH 1-of-1 multisig address:
- eckey = ECKey()
- eckey.generate()
- embedded_privkey = bytes_to_wif(eckey.get_bytes())
- embedded_pubkey = eckey.get_pubkey().get_bytes().hex()
- p2sh_p2wsh_address = self.nodes[1].createmultisig(1, [embedded_pubkey], "p2sh-segwit")
- # send transaction to P2SH-P2WSH 1-of-1 multisig address
- self.generate(self.nodes[0], COINBASE_MATURITY + 1)
- self.nodes[0].sendtoaddress(p2sh_p2wsh_address["address"], 49.999)
- self.generate(self.nodes[0], 1)
- # Get the UTXO info from scantxoutset
- unspent_output = self.nodes[1].scantxoutset('start', [p2sh_p2wsh_address['descriptor']])['unspents'][0]
- spk = script_to_p2sh_p2wsh_script(p2sh_p2wsh_address['redeemScript']).hex()
- unspent_output['witnessScript'] = p2sh_p2wsh_address['redeemScript']
- unspent_output['redeemScript'] = script_to_p2wsh_script(unspent_output['witnessScript']).hex()
- assert_equal(spk, unspent_output['scriptPubKey'])
- # Now create and sign a transaction spending that output on node[0], which doesn't know the scripts or keys
- spending_tx = self.nodes[0].createrawtransaction([unspent_output], {self.nodes[1].get_wallet_rpc(self.default_wallet_name).getnewaddress(): Decimal("49.998")})
- spending_tx_signed = self.nodes[0].signrawtransactionwithkey(spending_tx, [embedded_privkey], [unspent_output])
- # Check the signing completed successfully
- assert 'complete' in spending_tx_signed
- assert_equal(spending_tx_signed['complete'], True)
-
- # Now test with P2PKH and P2PK scripts as the witnessScript
- for tx_type in ['P2PKH', 'P2PK']: # these tests are order-independent
- self.verify_txn_with_witness_script(tx_type)
-
- def verify_txn_with_witness_script(self, tx_type):
- self.log.info("Test with a {} script as the witnessScript".format(tx_type))
- eckey = ECKey()
- eckey.generate()
- embedded_privkey = bytes_to_wif(eckey.get_bytes())
- embedded_pubkey = eckey.get_pubkey().get_bytes().hex()
- witness_script = {
- 'P2PKH': key_to_p2pkh_script(embedded_pubkey).hex(),
- 'P2PK': key_to_p2pk_script(embedded_pubkey).hex()
- }.get(tx_type, "Invalid tx_type")
- redeem_script = script_to_p2wsh_script(witness_script).hex()
- addr = script_to_p2sh(redeem_script)
- script_pub_key = self.nodes[1].validateaddress(addr)['scriptPubKey']
- # Fund that address
- txid = self.nodes[0].sendtoaddress(addr, 10)
- vout = find_vout_for_address(self.nodes[0], txid, addr)
- self.generate(self.nodes[0], 1)
- # Now create and sign a transaction spending that output on node[0], which doesn't know the scripts or keys
- spending_tx = self.nodes[0].createrawtransaction([{'txid': txid, 'vout': vout}], {self.nodes[1].getnewaddress(): Decimal("9.999")})
- spending_tx_signed = self.nodes[0].signrawtransactionwithkey(spending_tx, [embedded_privkey], [{'txid': txid, 'vout': vout, 'scriptPubKey': script_pub_key, 'redeemScript': redeem_script, 'witnessScript': witness_script, 'amount': 10}])
- # Check the signing completed successfully
- assert 'complete' in spending_tx_signed
- assert_equal(spending_tx_signed['complete'], True)
- self.nodes[0].sendrawtransaction(spending_tx_signed['hex'])
-
def OP_1NEGATE_test(self):
self.log.info("Test OP_1NEGATE (0x4f) satisfies BIP62 minimal push standardness rule")
hex_str = (
@@ -385,9 +293,7 @@ class SignRawTransactionsTest(BitcoinTestFramework):
])
def run_test(self):
- self.successful_signing_test()
self.script_verification_error_test()
- self.witness_script_test()
self.OP_1NEGATE_test()
self.test_with_lock_outputs()
self.test_fully_signed_tx()
@@ -397,4 +303,4 @@ class SignRawTransactionsTest(BitcoinTestFramework):
if __name__ == '__main__':
- SignRawTransactionsTest().main()
+ SignRawTransactionWithWalletTest().main()
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()
diff --git a/test/functional/wallet_taproot.py b/test/functional/wallet_taproot.py
index c8d4a1da45..3c630ba433 100755
--- a/test/functional/wallet_taproot.py
+++ b/test/functional/wallet_taproot.py
@@ -20,6 +20,7 @@ from test_framework.script import (
OP_NUMEQUAL,
taproot_construct,
)
+from test_framework.segwit_addr import encode_segwit_address
# xprvs/xpubs, and m/* derived x-only pubkeys (created using independent implementation)
KEYS = [
@@ -182,6 +183,9 @@ def compute_taproot_address(pubkey, scripts):
"""Compute the address for a taproot output with given inner key and scripts."""
return output_key_to_p2tr(taproot_construct(pubkey, scripts).output_pubkey)
+def compute_raw_taproot_address(pubkey):
+ return encode_segwit_address("bcrt", 1, pubkey)
+
class WalletTaprootTest(BitcoinTestFramework):
"""Test generation and spending of P2TR address outputs."""
@@ -216,7 +220,12 @@ class WalletTaprootTest(BitcoinTestFramework):
args = []
for j in range(len(keys)):
args.append(keys[j]['pubs'][i])
- return compute_taproot_address(*treefn(*args))
+ tree = treefn(*args)
+ if isinstance(tree, tuple):
+ return compute_taproot_address(*tree)
+ if isinstance(tree, bytes):
+ return compute_raw_taproot_address(tree)
+ assert False
def do_test_addr(self, comment, pattern, privmap, treefn, keys):
self.log.info("Testing %s address derivation" % comment)
@@ -444,6 +453,12 @@ class WalletTaprootTest(BitcoinTestFramework):
[True, False],
lambda k1, k2: (key(k2), [multi_a(1, ([H_POINT] * rnd_pos) + [k1] + ([H_POINT] * (MAX_PUBKEYS_PER_MULTI_A - 1 - rnd_pos)))])
)
+ self.do_test(
+ "rawtr(XPRV)",
+ "rawtr($1/*)",
+ [True],
+ lambda k1: key(k1)
+ )
self.log.info("Sending everything back...")
diff --git a/test/functional/wallet_transactiontime_rescan.py b/test/functional/wallet_transactiontime_rescan.py
index 21941084a3..9caa1fa3d0 100755
--- a/test/functional/wallet_transactiontime_rescan.py
+++ b/test/functional/wallet_transactiontime_rescan.py
@@ -11,6 +11,7 @@ from test_framework.blocktools import COINBASE_MATURITY
from test_framework.test_framework import BitcoinTestFramework
from test_framework.util import (
assert_equal,
+ assert_raises_rpc_error,
set_node_times,
)
@@ -158,5 +159,11 @@ class TransactionTimeRescanTest(BitcoinTestFramework):
assert_equal(tx['time'], cur_time + ten_days + ten_days + ten_days)
+ self.log.info('Test handling of invalid parameters for rescanblockchain')
+ assert_raises_rpc_error(-8, "Invalid start_height", restorewo_wallet.rescanblockchain, -1, 10)
+ assert_raises_rpc_error(-8, "Invalid stop_height", restorewo_wallet.rescanblockchain, 1, -1)
+ assert_raises_rpc_error(-8, "stop_height must be greater than start_height", restorewo_wallet.rescanblockchain, 20, 10)
+
+
if __name__ == '__main__':
TransactionTimeRescanTest().main()