aboutsummaryrefslogtreecommitdiff
path: root/test
diff options
context:
space:
mode:
Diffstat (limited to 'test')
-rw-r--r--test/README.md26
-rwxr-xr-xtest/functional/feature_bip68_sequence.py7
-rwxr-xr-xtest/functional/feature_block.py2
-rwxr-xr-xtest/functional/feature_cltv.py24
-rwxr-xr-xtest/functional/feature_config_args.py22
-rwxr-xr-xtest/functional/feature_dersig.py19
-rwxr-xr-xtest/functional/feature_maxuploadtarget.py2
-rwxr-xr-xtest/functional/feature_nulldummy.py2
-rwxr-xr-xtest/functional/feature_pruning.py19
-rwxr-xr-xtest/functional/feature_rbf.py16
-rwxr-xr-xtest/functional/feature_segwit.py3
-rwxr-xr-xtest/functional/interface_rpc.py2
-rwxr-xr-xtest/functional/mempool_accept.py1
-rwxr-xr-xtest/functional/mempool_limit.py6
-rwxr-xr-xtest/functional/mining_prioritisetransaction.py5
-rwxr-xr-xtest/functional/p2p_compactblocks.py27
-rwxr-xr-xtest/functional/p2p_invalid_tx.py3
-rwxr-xr-xtest/functional/p2p_segwit.py42
-rwxr-xr-xtest/functional/rpc_createmultisig.py35
-rwxr-xr-xtest/functional/rpc_fundrawtransaction.py1
-rwxr-xr-xtest/functional/rpc_psbt.py45
-rwxr-xr-xtest/functional/rpc_users.py182
-rwxr-xr-xtest/functional/test_framework/messages.py15
-rw-r--r--test/functional/test_framework/util.py23
-rwxr-xr-xtest/functional/test_runner.py33
-rwxr-xr-xtest/functional/tool_wallet.py128
-rwxr-xr-xtest/functional/wallet_avoidreuse.py241
-rwxr-xr-xtest/functional/wallet_balance.py2
-rwxr-xr-xtest/functional/wallet_basic.py3
-rwxr-xr-xtest/functional/wallet_bumpfee.py16
-rwxr-xr-xtest/functional/wallet_create_tx.py39
-rwxr-xr-xtest/functional/wallet_encryption.py2
-rwxr-xr-xtest/functional/wallet_importmulti.py28
-rwxr-xr-xtest/functional/wallet_resendwallettransactions.py3
-rwxr-xr-xtest/fuzz/test_runner.py4
-rwxr-xr-xtest/lint/commit-script-check.sh8
-rwxr-xr-xtest/lint/extended-lint-all.sh26
-rwxr-xr-xtest/lint/extended-lint-cppcheck.sh80
-rwxr-xr-xtest/lint/lint-circular-dependencies.sh10
-rwxr-xr-xtest/lint/lint-format-strings.py3
-rwxr-xr-xtest/lint/lint-format-strings.sh1
-rwxr-xr-xtest/lint/lint-locale-dependence.sh6
-rwxr-xr-xtest/lint/lint-logs.sh1
-rwxr-xr-xtest/lint/lint-shell.sh16
-rw-r--r--test/lint/lint-spelling.ignore-words.txt7
-rw-r--r--test/sanitizer_suppressions/lsan3
46 files changed, 883 insertions, 306 deletions
diff --git a/test/README.md b/test/README.md
index 9d4351b1de..8f08b7afe4 100644
--- a/test/README.md
+++ b/test/README.md
@@ -13,8 +13,7 @@ bitcoin-tx.
- [lint](/test/lint/) which perform various static analysis checks.
The util tests are run as part of `make check` target. The functional
-tests and lint scripts are run by the travis continuous build process whenever a pull
-request is opened. All sets of tests can also be run locally.
+tests and lint scripts can be run as explained in the sections below.
# Running tests locally
@@ -50,6 +49,29 @@ You can run any combination (incl. duplicates) of tests by calling:
test/functional/test_runner.py <testname1> <testname2> <testname3> ...
```
+Wildcard test names can be passed, if the paths are coherent and the test runner
+is called from a `bash` shell or similar that does the globbing. For example,
+to run all the wallet tests:
+
+```
+test/functional/test_runner.py test/functional/wallet*
+functional/test_runner.py functional/wallet* (called from the test/ directory)
+test_runner.py wallet* (called from the test/functional/ directory)
+```
+
+but not
+
+```
+test/functional/test_runner.py wallet*
+```
+
+Combinations of wildcards can be passed:
+
+```
+test/functional/test_runner.py ./test/functional/tool* test/functional/mempool*
+test_runner.py tool* mempool*
+```
+
Run the regression test suite with:
```
diff --git a/test/functional/feature_bip68_sequence.py b/test/functional/feature_bip68_sequence.py
index fdb60fb0e8..f0bf09e172 100755
--- a/test/functional/feature_bip68_sequence.py
+++ b/test/functional/feature_bip68_sequence.py
@@ -29,7 +29,10 @@ NOT_FINAL_ERROR = "non-BIP68-final (code 64)"
class BIP68Test(BitcoinTestFramework):
def set_test_params(self):
self.num_nodes = 2
- self.extra_args = [[], ["-acceptnonstdtxn=0"]]
+ self.extra_args = [
+ ["-acceptnonstdtxn=1"],
+ ["-acceptnonstdtxn=0"],
+ ]
def skip_test_if_missing_module(self):
self.skip_if_no_wallet()
@@ -378,7 +381,7 @@ class BIP68Test(BitcoinTestFramework):
add_witness_commitment(block)
block.solve()
- self.nodes[0].submitblock(block.serialize(True).hex())
+ self.nodes[0].submitblock(block.serialize().hex())
assert_equal(self.nodes[0].getbestblockhash(), block.hash)
def activateCSV(self):
diff --git a/test/functional/feature_block.py b/test/functional/feature_block.py
index 3ad83cd2b3..b5eac88ba7 100755
--- a/test/functional/feature_block.py
+++ b/test/functional/feature_block.py
@@ -78,7 +78,7 @@ class FullBlockTest(BitcoinTestFramework):
def set_test_params(self):
self.num_nodes = 1
self.setup_clean_chain = True
- self.extra_args = [[]]
+ self.extra_args = [['-acceptnonstdtxn=1']] # This is a consensus block test, we don't care about tx policy
def run_test(self):
node = self.nodes[0] # convenience reference to the node
diff --git a/test/functional/feature_cltv.py b/test/functional/feature_cltv.py
index b16eafccca..af34f9f0db 100755
--- a/test/functional/feature_cltv.py
+++ b/test/functional/feature_cltv.py
@@ -57,16 +57,34 @@ def cltv_validate(node, tx, height):
class BIP65Test(BitcoinTestFramework):
def set_test_params(self):
self.num_nodes = 1
- self.extra_args = [['-whitelist=127.0.0.1', '-par=1']] # Use only one script thread to get the exact reject reason for testing
+ self.extra_args = [[
+ '-whitelist=127.0.0.1',
+ '-par=1', # Use only one script thread to get the exact reject reason for testing
+ '-acceptnonstdtxn=1', # cltv_invalidate is nonstandard
+ ]]
self.setup_clean_chain = True
self.rpc_timeout = 120
def skip_test_if_missing_module(self):
self.skip_if_no_wallet()
+ def test_cltv_info(self, *, is_active):
+ assert_equal(
+ next(s for s in self.nodes[0].getblockchaininfo()['softforks'] if s['id'] == 'bip65'),
+ {
+ "id": "bip65",
+ "version": 4,
+ "reject": {
+ "status": is_active
+ }
+ },
+ )
+
def run_test(self):
self.nodes[0].add_p2p_connection(P2PInterface())
+ self.test_cltv_info(is_active=False)
+
self.log.info("Mining %d blocks", CLTV_HEIGHT - 2)
self.coinbase_txids = [self.nodes[0].getblock(b)['tx'][0] for b in self.nodes[0].generate(CLTV_HEIGHT - 2)]
self.nodeaddress = self.nodes[0].getnewaddress()
@@ -86,7 +104,9 @@ class BIP65Test(BitcoinTestFramework):
block.hashMerkleRoot = block.calc_merkle_root()
block.solve()
+ self.test_cltv_info(is_active=False)
self.nodes[0].p2p.send_and_ping(msg_block(block))
+ self.test_cltv_info(is_active=False) # Not active as of current tip, but next block must obey rules
assert_equal(self.nodes[0].getbestblockhash(), block.hash)
self.log.info("Test that blocks must now be at least version 4")
@@ -135,7 +155,9 @@ class BIP65Test(BitcoinTestFramework):
block.hashMerkleRoot = block.calc_merkle_root()
block.solve()
+ self.test_cltv_info(is_active=False) # Not active as of current tip, but next block must obey rules
self.nodes[0].p2p.send_and_ping(msg_block(block))
+ self.test_cltv_info(is_active=True) # Active as of current tip
assert_equal(int(self.nodes[0].getbestblockhash(), 16), block.sha256)
diff --git a/test/functional/feature_config_args.py b/test/functional/feature_config_args.py
index 2b93e3c24d..7d5893641e 100755
--- a/test/functional/feature_config_args.py
+++ b/test/functional/feature_config_args.py
@@ -21,6 +21,16 @@ class ConfArgsTest(BitcoinTestFramework):
with open(os.path.join(self.nodes[0].datadir, 'bitcoin.conf'), 'a', encoding='utf-8') as conf:
conf.write('includeconf={}\n'.format(inc_conf_file_path))
+ self.nodes[0].assert_start_raises_init_error(
+ expected_msg='Error parsing command line arguments: Invalid parameter -dash_cli',
+ extra_args=['-dash_cli=1'],
+ )
+ 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('-dash=1\n')
self.nodes[0].assert_start_raises_init_error(expected_msg='Error reading configuration file: parse error on line 1: -dash=1, options in configuration file must be specified without leading -')
@@ -30,6 +40,11 @@ class ConfArgsTest(BitcoinTestFramework):
self.nodes[0].assert_start_raises_init_error(expected_msg='Error: Config setting for -wallet only applied on regtest network when in [regtest] section.')
with open(inc_conf_file_path, 'w', encoding='utf-8') as conf:
+ conf.write('regtest=0\n') # mainnet
+ conf.write('acceptnonstdtxn=1\n')
+ self.nodes[0].assert_start_raises_init_error(expected_msg='Error: acceptnonstdtxn is not currently supported for main chain')
+
+ with open(inc_conf_file_path, 'w', encoding='utf-8') as conf:
conf.write('nono\n')
self.nodes[0].assert_start_raises_init_error(expected_msg='Error reading configuration file: parse error on line 1: nono, if you intended to specify a negated option, use nono=1 instead')
@@ -61,9 +76,16 @@ class ConfArgsTest(BitcoinTestFramework):
with open(inc_conf_file2_path, 'w', encoding='utf-8') as conf:
conf.write('') # clear
+ def test_log_buffer(self):
+ with self.nodes[0].assert_debug_log(expected_msgs=['Warning: parsed potentially confusing double-negative -connect=0']):
+ self.start_node(0, extra_args=['-noconnect=0'])
+ self.stop_node(0)
+
def run_test(self):
self.stop_node(0)
+ self.test_log_buffer()
+
self.test_config_file_parser()
# Remove the -datadir argument so it doesn't override the config file
diff --git a/test/functional/feature_dersig.py b/test/functional/feature_dersig.py
index 7480e5c5ba..067e3be1f4 100755
--- a/test/functional/feature_dersig.py
+++ b/test/functional/feature_dersig.py
@@ -51,9 +51,23 @@ class BIP66Test(BitcoinTestFramework):
def skip_test_if_missing_module(self):
self.skip_if_no_wallet()
+ def test_dersig_info(self, *, is_active):
+ assert_equal(
+ next(s for s in self.nodes[0].getblockchaininfo()['softforks'] if s['id'] == 'bip66'),
+ {
+ "id": "bip66",
+ "version": 3,
+ "reject": {
+ "status": is_active
+ }
+ },
+ )
+
def run_test(self):
self.nodes[0].add_p2p_connection(P2PInterface())
+ self.test_dersig_info(is_active=False)
+
self.log.info("Mining %d blocks", DERSIG_HEIGHT - 2)
self.coinbase_txids = [self.nodes[0].getblock(b)['tx'][0] for b in self.nodes[0].generate(DERSIG_HEIGHT - 2)]
self.nodeaddress = self.nodes[0].getnewaddress()
@@ -74,7 +88,9 @@ class BIP66Test(BitcoinTestFramework):
block.rehash()
block.solve()
+ self.test_dersig_info(is_active=False)
self.nodes[0].p2p.send_and_ping(msg_block(block))
+ self.test_dersig_info(is_active=False) # Not active as of current tip, but next block must obey rules
assert_equal(self.nodes[0].getbestblockhash(), block.hash)
self.log.info("Test that blocks must now be at least version 3")
@@ -128,8 +144,11 @@ class BIP66Test(BitcoinTestFramework):
block.rehash()
block.solve()
+ self.test_dersig_info(is_active=False) # Not active as of current tip, but next block must obey rules
self.nodes[0].p2p.send_and_ping(msg_block(block))
+ self.test_dersig_info(is_active=True) # Active as of current tip
assert_equal(int(self.nodes[0].getbestblockhash(), 16), block.sha256)
+
if __name__ == '__main__':
BIP66Test().main()
diff --git a/test/functional/feature_maxuploadtarget.py b/test/functional/feature_maxuploadtarget.py
index 87c318de9a..180ea0e51d 100755
--- a/test/functional/feature_maxuploadtarget.py
+++ b/test/functional/feature_maxuploadtarget.py
@@ -35,7 +35,7 @@ class MaxUploadTest(BitcoinTestFramework):
def set_test_params(self):
self.setup_clean_chain = True
self.num_nodes = 1
- self.extra_args = [["-maxuploadtarget=800"]]
+ self.extra_args = [["-maxuploadtarget=800", "-acceptnonstdtxn=1"]]
# Cache for utxos, as the listunspent may take a long time later in the test
self.utxo_cache = []
diff --git a/test/functional/feature_nulldummy.py b/test/functional/feature_nulldummy.py
index a56c983ccc..60a703c48f 100755
--- a/test/functional/feature_nulldummy.py
+++ b/test/functional/feature_nulldummy.py
@@ -108,7 +108,7 @@ class NULLDUMMYTest(BitcoinTestFramework):
witness and add_witness_commitment(block)
block.rehash()
block.solve()
- node.submitblock(block.serialize(True).hex())
+ node.submitblock(block.serialize().hex())
if (accept):
assert_equal(node.getbestblockhash(), block.hash)
self.tip = block.sha256
diff --git a/test/functional/feature_pruning.py b/test/functional/feature_pruning.py
index e2b3b2d544..66c395d7a2 100755
--- a/test/functional/feature_pruning.py
+++ b/test/functional/feature_pruning.py
@@ -23,8 +23,6 @@ from test_framework.util import (
wait_until,
)
-MIN_BLOCKS_TO_KEEP = 288
-
# Rescans start at the earliest block up to 2 hours before a key timestamp, so
# the manual prune RPC avoids pruning blocks in the same window to be
# compatible with pruning based on key creation time.
@@ -273,20 +271,9 @@ class PruneTest(BitcoinTestFramework):
else:
return index
- def prune(index, expected_ret=None):
+ def prune(index):
ret = node.pruneblockchain(height=height(index))
- # Check the return value. When use_timestamp is True, just check
- # that the return value is less than or equal to the expected
- # value, because when more than one block is generated per second,
- # a timestamp will not be granular enough to uniquely identify an
- # individual block.
- if expected_ret is None:
- expected_ret = index
- if use_timestamp:
- assert_greater_than(ret, 0)
- assert_greater_than(expected_ret + 1, ret)
- else:
- assert_equal(ret, expected_ret)
+ assert_equal(ret, node.getblockchaininfo()['pruneheight'])
def has_block(index):
return os.path.isfile(os.path.join(self.nodes[node_number].datadir, "regtest", "blocks", "blk{:05}.dat".format(index)))
@@ -326,7 +313,7 @@ class PruneTest(BitcoinTestFramework):
assert not has_block(1), "blk00001.dat is still there, should be pruned by now"
# height=1000 should not prune anything more, because tip-288 is in blk00002.dat.
- prune(1000, 1001 - MIN_BLOCKS_TO_KEEP)
+ prune(1000)
assert has_block(2), "blk00002.dat is still there, should be pruned by now"
# advance the tip so blk00002.dat and blk00003.dat can be pruned (the last 288 blocks should now be in blk00004.dat)
diff --git a/test/functional/feature_rbf.py b/test/functional/feature_rbf.py
index ccba547a1c..fd79df0b07 100755
--- a/test/functional/feature_rbf.py
+++ b/test/functional/feature_rbf.py
@@ -64,19 +64,16 @@ def make_utxo(node, amount, confirmed=True, scriptPubKey=CScript([1])):
class ReplaceByFeeTest(BitcoinTestFramework):
def set_test_params(self):
- self.num_nodes = 2
+ self.num_nodes = 1
self.extra_args = [
[
+ "-acceptnonstdtxn=1",
"-maxorphantx=1000",
- "-whitelist=127.0.0.1",
"-limitancestorcount=50",
"-limitancestorsize=101",
"-limitdescendantcount=200",
"-limitdescendantsize=101",
],
- [
- "-mempoolreplacement=0",
- ],
]
def skip_test_if_missing_module(self):
@@ -148,16 +145,12 @@ class ReplaceByFeeTest(BitcoinTestFramework):
# This will raise an exception due to insufficient fee
assert_raises_rpc_error(-26, "insufficient fee", self.nodes[0].sendrawtransaction, tx1b_hex, 0)
- # This will raise an exception due to transaction replacement being disabled
- assert_raises_rpc_error(-26, "txn-mempool-conflict", self.nodes[1].sendrawtransaction, tx1b_hex, 0)
# Extra 0.1 BTC fee
tx1b = CTransaction()
tx1b.vin = [CTxIn(tx0_outpoint, nSequence=0)]
tx1b.vout = [CTxOut(int(0.9 * COIN), CScript([b'b' * 35]))]
tx1b_hex = txToHex(tx1b)
- # Replacement still disabled even with "enough fee"
- assert_raises_rpc_error(-26, "txn-mempool-conflict", self.nodes[1].sendrawtransaction, tx1b_hex, 0)
# Works when enabled
tx1b_txid = self.nodes[0].sendrawtransaction(tx1b_hex, 0)
@@ -168,11 +161,6 @@ class ReplaceByFeeTest(BitcoinTestFramework):
assert_equal(tx1b_hex, self.nodes[0].getrawtransaction(tx1b_txid))
- # Second node is running mempoolreplacement=0, will not replace originally-seen txn
- mempool = self.nodes[1].getrawmempool()
- assert tx1a_txid in mempool
- assert tx1b_txid not in mempool
-
def test_doublespend_chain(self):
"""Doublespend of a long chain"""
diff --git a/test/functional/feature_segwit.py b/test/functional/feature_segwit.py
index 2d4dd96a1d..a71d4071d5 100755
--- a/test/functional/feature_segwit.py
+++ b/test/functional/feature_segwit.py
@@ -53,17 +53,20 @@ class SegWitTest(BitcoinTestFramework):
# This test tests SegWit both pre and post-activation, so use the normal BIP9 activation.
self.extra_args = [
[
+ "-acceptnonstdtxn=1",
"-rpcserialversion=0",
"-vbparams=segwit:0:999999999999",
"-addresstype=legacy",
],
[
+ "-acceptnonstdtxn=1",
"-blockversion=4",
"-rpcserialversion=1",
"-vbparams=segwit:0:999999999999",
"-addresstype=legacy",
],
[
+ "-acceptnonstdtxn=1",
"-blockversion=536870915",
"-vbparams=segwit:0:999999999999",
"-addresstype=legacy",
diff --git a/test/functional/interface_rpc.py b/test/functional/interface_rpc.py
index 49ae0fb1a9..e99fa22646 100755
--- a/test/functional/interface_rpc.py
+++ b/test/functional/interface_rpc.py
@@ -4,6 +4,7 @@
# file COPYING or http://www.opensource.org/licenses/mit-license.php.
"""Tests some generic aspects of the RPC interface."""
+import os
from test_framework.authproxy import JSONRPCException
from test_framework.test_framework import BitcoinTestFramework
from test_framework.util import assert_equal, assert_greater_than_or_equal
@@ -31,6 +32,7 @@ class RPCInterfaceTest(BitcoinTestFramework):
command = info['active_commands'][0]
assert_equal(command['method'], 'getrpcinfo')
assert_greater_than_or_equal(command['duration'], 0)
+ assert_equal(info['logpath'], os.path.join(self.nodes[0].datadir, 'regtest', 'debug.log'))
def test_batch_request(self):
self.log.info("Testing basic JSON-RPC batch request...")
diff --git a/test/functional/mempool_accept.py b/test/functional/mempool_accept.py
index 2bb5d8ab7d..209a222004 100755
--- a/test/functional/mempool_accept.py
+++ b/test/functional/mempool_accept.py
@@ -36,7 +36,6 @@ class MempoolAcceptanceTest(BitcoinTestFramework):
self.num_nodes = 1
self.extra_args = [[
'-txindex',
- '-acceptnonstdtxn=0', # Try to mimic main-net
]] * self.num_nodes
def skip_test_if_missing_module(self):
diff --git a/test/functional/mempool_limit.py b/test/functional/mempool_limit.py
index 351b27e94a..edf2069933 100755
--- a/test/functional/mempool_limit.py
+++ b/test/functional/mempool_limit.py
@@ -13,7 +13,11 @@ class MempoolLimitTest(BitcoinTestFramework):
def set_test_params(self):
self.setup_clean_chain = True
self.num_nodes = 1
- self.extra_args = [["-maxmempool=5", "-spendzeroconfchange=0"]]
+ self.extra_args = [[
+ "-acceptnonstdtxn=1",
+ "-maxmempool=5",
+ "-spendzeroconfchange=0",
+ ]]
def skip_test_if_missing_module(self):
self.skip_if_no_wallet()
diff --git a/test/functional/mining_prioritisetransaction.py b/test/functional/mining_prioritisetransaction.py
index b0a069be81..7e05a8e6c8 100755
--- a/test/functional/mining_prioritisetransaction.py
+++ b/test/functional/mining_prioritisetransaction.py
@@ -14,7 +14,10 @@ class PrioritiseTransactionTest(BitcoinTestFramework):
def set_test_params(self):
self.setup_clean_chain = True
self.num_nodes = 2
- self.extra_args = [["-printpriority=1"], ["-printpriority=1"]]
+ self.extra_args = [[
+ "-printpriority=1",
+ "-acceptnonstdtxn=1",
+ ]] * self.num_nodes
def skip_test_if_missing_module(self):
self.skip_if_no_wallet()
diff --git a/test/functional/p2p_compactblocks.py b/test/functional/p2p_compactblocks.py
index 3ca6bec254..eb3336bd3b 100755
--- a/test/functional/p2p_compactblocks.py
+++ b/test/functional/p2p_compactblocks.py
@@ -10,7 +10,7 @@ Version 2 compact blocks are post-segwit (wtxids)
import random
from test_framework.blocktools import create_block, create_coinbase, add_witness_commitment
-from test_framework.messages import BlockTransactions, BlockTransactionsRequest, calculate_shortid, CBlock, CBlockHeader, CInv, COutPoint, CTransaction, CTxIn, CTxInWitness, CTxOut, FromHex, HeaderAndShortIDs, msg_block, msg_blocktxn, msg_cmpctblock, msg_getblocktxn, msg_getdata, msg_getheaders, msg_headers, msg_inv, msg_sendcmpct, msg_sendheaders, msg_tx, msg_witness_block, msg_witness_blocktxn, MSG_WITNESS_FLAG, NODE_NETWORK, P2PHeaderAndShortIDs, PrefilledTransaction, ser_uint256, ToHex
+from test_framework.messages import BlockTransactions, BlockTransactionsRequest, calculate_shortid, CBlock, CBlockHeader, CInv, COutPoint, CTransaction, CTxIn, CTxInWitness, CTxOut, FromHex, HeaderAndShortIDs, msg_no_witness_block, msg_no_witness_blocktxn, msg_cmpctblock, msg_getblocktxn, msg_getdata, msg_getheaders, msg_headers, msg_inv, msg_sendcmpct, msg_sendheaders, msg_tx, msg_block, msg_blocktxn, MSG_WITNESS_FLAG, NODE_NETWORK, P2PHeaderAndShortIDs, PrefilledTransaction, ser_uint256, ToHex
from test_framework.mininode import mininode_lock, P2PInterface
from test_framework.script import CScript, OP_TRUE, OP_DROP
from test_framework.test_framework import BitcoinTestFramework
@@ -95,6 +95,9 @@ class CompactBlocksTest(BitcoinTestFramework):
def set_test_params(self):
self.setup_clean_chain = True
self.num_nodes = 1
+ self.extra_args = [[
+ "-acceptnonstdtxn=1",
+ ]]
self.utxos = []
def skip_test_if_missing_module(self):
@@ -114,7 +117,7 @@ class CompactBlocksTest(BitcoinTestFramework):
# Create 10 more anyone-can-spend utxo's for testing.
def make_utxos(self):
block = self.build_block_on_tip(self.nodes[0])
- self.segwit_node.send_and_ping(msg_block(block))
+ self.segwit_node.send_and_ping(msg_no_witness_block(block))
assert int(self.nodes[0].getbestblockhash(), 16) == block.sha256
self.nodes[0].generatetoaddress(100, self.nodes[0].getnewaddress(address_type="bech32"))
@@ -130,7 +133,7 @@ class CompactBlocksTest(BitcoinTestFramework):
block2.vtx.append(tx)
block2.hashMerkleRoot = block2.calc_merkle_root()
block2.solve()
- self.segwit_node.send_and_ping(msg_block(block2))
+ self.segwit_node.send_and_ping(msg_no_witness_block(block2))
assert_equal(int(self.nodes[0].getbestblockhash(), 16), block2.sha256)
self.utxos.extend([[tx.sha256, i, out_value] for i in range(10)])
@@ -408,9 +411,9 @@ class CompactBlocksTest(BitcoinTestFramework):
# Send the coinbase, and verify that the tip advances.
if version == 2:
- msg = msg_witness_blocktxn()
- else:
msg = msg_blocktxn()
+ else:
+ msg = msg_no_witness_blocktxn()
msg.block_transactions.blockhash = block.sha256
msg.block_transactions.transactions = [block.vtx[0]]
test_node.send_and_ping(msg)
@@ -463,9 +466,9 @@ class CompactBlocksTest(BitcoinTestFramework):
test_getblocktxn_response(comp_block, test_node, [1, 2, 3, 4, 5])
- msg_bt = msg_blocktxn()
+ msg_bt = msg_no_witness_blocktxn()
if with_witness:
- msg_bt = msg_witness_blocktxn() # serialize with witnesses
+ msg_bt = msg_blocktxn() # serialize with witnesses
msg_bt.block_transactions = BlockTransactions(block.sha256, block.vtx[1:])
test_tip_after_message(node, test_node, msg_bt, block.sha256)
@@ -554,9 +557,9 @@ class CompactBlocksTest(BitcoinTestFramework):
# different peer provide the block further down, so that we're still
# verifying that the block isn't marked bad permanently. This is good
# enough for now.
- msg = msg_blocktxn()
+ msg = msg_no_witness_blocktxn()
if version == 2:
- msg = msg_witness_blocktxn()
+ msg = msg_blocktxn()
msg.block_transactions = BlockTransactions(block.sha256, [block.vtx[5]] + block.vtx[7:])
test_node.send_and_ping(msg)
@@ -571,9 +574,9 @@ class CompactBlocksTest(BitcoinTestFramework):
# Deliver the block
if version == 2:
- test_node.send_and_ping(msg_witness_block(block))
- else:
test_node.send_and_ping(msg_block(block))
+ else:
+ test_node.send_and_ping(msg_no_witness_block(block))
assert_equal(int(node.getbestblockhash(), 16), block.sha256)
def test_getblocktxn_handler(self, test_node):
@@ -785,7 +788,7 @@ class CompactBlocksTest(BitcoinTestFramework):
delivery_peer.send_and_ping(msg_cmpctblock(cmpct_block.to_p2p()))
assert int(node.getbestblockhash(), 16) != block.sha256
- msg = msg_blocktxn()
+ msg = msg_no_witness_blocktxn()
msg.block_transactions.blockhash = block.sha256
msg.block_transactions.transactions = block.vtx[1:]
stalling_peer.send_and_ping(msg)
diff --git a/test/functional/p2p_invalid_tx.py b/test/functional/p2p_invalid_tx.py
index 1b18dd3e58..3cca2d78db 100755
--- a/test/functional/p2p_invalid_tx.py
+++ b/test/functional/p2p_invalid_tx.py
@@ -25,6 +25,9 @@ from data import invalid_txs
class InvalidTxRequestTest(BitcoinTestFramework):
def set_test_params(self):
self.num_nodes = 1
+ self.extra_args = [[
+ "-acceptnonstdtxn=1",
+ ]]
self.setup_clean_chain = True
def bootstrap_p2p(self, *, num_connections=1):
diff --git a/test/functional/p2p_segwit.py b/test/functional/p2p_segwit.py
index df0f9fa365..dca71aec43 100755
--- a/test/functional/p2p_segwit.py
+++ b/test/functional/p2p_segwit.py
@@ -25,12 +25,12 @@ from test_framework.messages import (
MSG_WITNESS_FLAG,
NODE_NETWORK,
NODE_WITNESS,
- msg_block,
+ msg_no_witness_block,
msg_getdata,
msg_headers,
msg_inv,
msg_tx,
- msg_witness_block,
+ msg_block,
msg_witness_tx,
ser_uint256,
ser_vector,
@@ -111,7 +111,7 @@ def get_virtual_size(witness_block):
Virtual size is base + witness/4."""
base_size = len(witness_block.serialize(with_witness=False))
- total_size = len(witness_block.serialize(with_witness=True))
+ total_size = len(witness_block.serialize())
# the "+3" is so we round up
vsize = int((3 * base_size + total_size + 3) / 4)
return vsize
@@ -134,7 +134,7 @@ def test_witness_block(node, p2p, block, accepted, with_witness=True, reason=Non
- use the getbestblockhash rpc to check for acceptance."""
reason = [reason] if reason else []
with node.assert_debug_log(expected_msgs=reason):
- p2p.send_message(msg_witness_block(block) if with_witness else msg_block(block))
+ p2p.send_message(msg_block(block) if with_witness else msg_no_witness_block(block))
p2p.sync_with_ping()
assert_equal(node.getbestblockhash() == block.hash, accepted)
@@ -184,7 +184,11 @@ class SegWitTest(BitcoinTestFramework):
self.setup_clean_chain = True
self.num_nodes = 3
# This test tests SegWit both pre and post-activation, so use the normal BIP9 activation.
- self.extra_args = [["-whitelist=127.0.0.1", "-vbparams=segwit:0:999999999999"], ["-whitelist=127.0.0.1", "-acceptnonstdtxn=0", "-vbparams=segwit:0:999999999999"], ["-whitelist=127.0.0.1", "-vbparams=segwit:0:0"]]
+ self.extra_args = [
+ ["-whitelist=127.0.0.1", "-acceptnonstdtxn=1", "-vbparams=segwit:0:999999999999"],
+ ["-whitelist=127.0.0.1", "-acceptnonstdtxn=0", "-vbparams=segwit:0:999999999999"],
+ ["-whitelist=127.0.0.1", "-acceptnonstdtxn=1", "-vbparams=segwit:0:0"],
+ ]
def skip_test_if_missing_module(self):
self.skip_if_no_wallet()
@@ -298,7 +302,7 @@ class SegWitTest(BitcoinTestFramework):
block = self.build_next_block(version=1)
block.solve()
- self.test_node.send_message(msg_block(block))
+ self.test_node.send_message(msg_no_witness_block(block))
self.test_node.sync_with_ping() # make sure the block was processed
txid = block.vtx[0].sha256
@@ -345,7 +349,7 @@ class SegWitTest(BitcoinTestFramework):
# But it should not be permanently marked bad...
# Resend without witness information.
- self.test_node.send_message(msg_block(block))
+ self.test_node.send_message(msg_no_witness_block(block))
self.test_node.sync_with_ping()
assert_equal(self.nodes[0].getbestblockhash(), block.hash)
@@ -403,7 +407,7 @@ class SegWitTest(BitcoinTestFramework):
block_hash = int(block_hash, 16)
block = self.test_node.request_block(block_hash, 2)
wit_block = self.test_node.request_block(block_hash, 2 | MSG_WITNESS_FLAG)
- assert_equal(block.serialize(True), wit_block.serialize(True))
+ assert_equal(block.serialize(), wit_block.serialize())
assert_equal(block.serialize(), hex_str_to_bytes(rpc_block))
else:
# After activation, witness blocks and non-witness blocks should
@@ -419,15 +423,15 @@ class SegWitTest(BitcoinTestFramework):
rpc_block = self.nodes[0].getblock(block.hash, False)
non_wit_block = self.test_node.request_block(block.sha256, 2)
wit_block = self.test_node.request_block(block.sha256, 2 | MSG_WITNESS_FLAG)
- assert_equal(wit_block.serialize(True), hex_str_to_bytes(rpc_block))
+ assert_equal(wit_block.serialize(), hex_str_to_bytes(rpc_block))
assert_equal(wit_block.serialize(False), non_wit_block.serialize())
- assert_equal(wit_block.serialize(True), block.serialize(True))
+ assert_equal(wit_block.serialize(), block.serialize())
# Test size, vsize, weight
rpc_details = self.nodes[0].getblock(block.hash, True)
- assert_equal(rpc_details["size"], len(block.serialize(True)))
+ assert_equal(rpc_details["size"], len(block.serialize()))
assert_equal(rpc_details["strippedsize"], len(block.serialize(False)))
- weight = 3 * len(block.serialize(False)) + len(block.serialize(True))
+ weight = 3 * len(block.serialize(False)) + len(block.serialize())
assert_equal(rpc_details["weight"], weight)
# Upgraded node should not ask for blocks from unupgraded
@@ -791,7 +795,7 @@ class SegWitTest(BitcoinTestFramework):
block.solve()
# Test the test -- witness serialization should be different
- assert msg_witness_block(block).serialize() != msg_block(block).serialize()
+ assert msg_block(block).serialize() != msg_no_witness_block(block).serialize()
# This empty block should be valid.
test_witness_block(self.nodes[0], self.test_node, block, accepted=True)
@@ -884,13 +888,13 @@ class SegWitTest(BitcoinTestFramework):
# We can't send over the p2p network, because this is too big to relay
# TODO: repeat this test with a block that can be relayed
- self.nodes[0].submitblock(block.serialize(True).hex())
+ self.nodes[0].submitblock(block.serialize().hex())
assert self.nodes[0].getbestblockhash() != block.hash
block.vtx[0].wit.vtxinwit[0].scriptWitness.stack.pop()
assert get_virtual_size(block) < MAX_BLOCK_BASE_SIZE
- self.nodes[0].submitblock(block.serialize(True).hex())
+ self.nodes[0].submitblock(block.serialize().hex())
assert self.nodes[0].getbestblockhash() == block.hash
@@ -969,7 +973,7 @@ class SegWitTest(BitcoinTestFramework):
assert_equal(vsize, MAX_BLOCK_BASE_SIZE + 1)
# Make sure that our test case would exceed the old max-network-message
# limit
- assert len(block.serialize(True)) > 2 * 1024 * 1024
+ assert len(block.serialize()) > 2 * 1024 * 1024
test_witness_block(self.nodes[0], self.test_node, block, accepted=False)
@@ -997,14 +1001,14 @@ class SegWitTest(BitcoinTestFramework):
add_witness_commitment(block, nonce=1)
block.vtx[0].wit = CTxWitness() # drop the nonce
block.solve()
- self.nodes[0].submitblock(block.serialize(True).hex())
+ self.nodes[0].submitblock(block.serialize().hex())
assert self.nodes[0].getbestblockhash() != block.hash
# Now redo commitment with the standard nonce, but let bitcoind fill it in.
add_witness_commitment(block, nonce=0)
block.vtx[0].wit = CTxWitness()
block.solve()
- self.nodes[0].submitblock(block.serialize(True).hex())
+ self.nodes[0].submitblock(block.serialize().hex())
assert_equal(self.nodes[0].getbestblockhash(), block.hash)
# This time, add a tx with non-empty witness, but don't supply
@@ -1019,7 +1023,7 @@ class SegWitTest(BitcoinTestFramework):
block_2.vtx[0].vout.pop()
block_2.vtx[0].wit = CTxWitness()
- self.nodes[0].submitblock(block_2.serialize(True).hex())
+ self.nodes[0].submitblock(block_2.serialize().hex())
# Tip should not advance!
assert self.nodes[0].getbestblockhash() != block_2.hash
diff --git a/test/functional/rpc_createmultisig.py b/test/functional/rpc_createmultisig.py
index 7abcd71bb8..62f3843756 100755
--- a/test/functional/rpc_createmultisig.py
+++ b/test/functional/rpc_createmultisig.py
@@ -7,9 +7,13 @@
from test_framework.test_framework import BitcoinTestFramework
from test_framework.util import (
assert_raises_rpc_error,
+ assert_equal,
)
-import decimal
+from test_framework.key import ECPubKey
+import binascii
+import decimal
+import itertools
class RpcCreateMultiSigTest(BitcoinTestFramework):
def set_test_params(self):
@@ -44,6 +48,30 @@ class RpcCreateMultiSigTest(BitcoinTestFramework):
self.checkbalances()
+ # Test mixed compressed and uncompressed pubkeys
+ self.log.info('Mixed compressed and uncompressed multisigs are not allowed')
+ pk0 = node0.getaddressinfo(node0.getnewaddress())['pubkey']
+ pk1 = node1.getaddressinfo(node1.getnewaddress())['pubkey']
+ pk2 = node2.getaddressinfo(node2.getnewaddress())['pubkey']
+
+ # decompress pk2
+ pk_obj = ECPubKey()
+ pk_obj.set(binascii.unhexlify(pk2))
+ pk_obj.compressed = False
+ pk2 = binascii.hexlify(pk_obj.get_bytes()).decode()
+
+ # Check all permutations of keys because order matters apparently
+ for keys in itertools.permutations([pk0, pk1, pk2]):
+ # Results should be the same as this legacy one
+ legacy_addr = node0.createmultisig(2, keys, 'legacy')['address']
+ assert_equal(legacy_addr, node0.addmultisigaddress(2, keys, '', 'legacy')['address'])
+
+ # Generate addresses with the segwit types. These should all make legacy addresses
+ assert_equal(legacy_addr, node0.createmultisig(2, keys, 'bech32')['address'])
+ assert_equal(legacy_addr, node0.createmultisig(2, keys, 'p2sh-segwit')['address'])
+ assert_equal(legacy_addr, node0.addmultisigaddress(2, keys, '', 'bech32')['address'])
+ assert_equal(legacy_addr, node0.addmultisigaddress(2, keys, '', 'p2sh-segwit')['address'])
+
def check_addmultisigaddress_errors(self):
self.log.info('Check that addmultisigaddress fails when the private keys are missing')
addresses = [self.nodes[1].getnewaddress(address_type='legacy') for _ in range(2)]
@@ -101,6 +129,11 @@ class RpcCreateMultiSigTest(BitcoinTestFramework):
outval = value - decimal.Decimal("0.00001000")
rawtx = node2.createrawtransaction([{"txid": txid, "vout": vout}], [{self.final: outval}])
+ prevtx_err = dict(prevtxs[0])
+ del prevtx_err["redeemScript"]
+
+ assert_raises_rpc_error(-8, "Missing redeemScript/witnessScript", node2.signrawtransactionwithkey, rawtx, self.priv[0:self.nsigs-1], [prevtx_err])
+
rawtx2 = node2.signrawtransactionwithkey(rawtx, self.priv[0:self.nsigs - 1], prevtxs)
rawtx3 = node2.signrawtransactionwithkey(rawtx2["hex"], [self.priv[-1]], prevtxs)
diff --git a/test/functional/rpc_fundrawtransaction.py b/test/functional/rpc_fundrawtransaction.py
index d89fd6461f..cdf636e200 100755
--- a/test/functional/rpc_fundrawtransaction.py
+++ b/test/functional/rpc_fundrawtransaction.py
@@ -662,6 +662,7 @@ class RawTransactionsTest(BitcoinTestFramework):
result = self.nodes[3].fundrawtransaction(rawtx) # uses min_relay_tx_fee (set by settxfee)
result2 = self.nodes[3].fundrawtransaction(rawtx, {"feeRate": 2*min_relay_tx_fee})
result3 = self.nodes[3].fundrawtransaction(rawtx, {"feeRate": 10*min_relay_tx_fee})
+ assert_raises_rpc_error(-4, "Fee exceeds maximum configured by -maxtxfee", self.nodes[3].fundrawtransaction, rawtx, {"feeRate": 1})
result_fee_rate = result['fee'] * 1000 / count_bytes(result['hex'])
assert_fee_amount(result2['fee'], count_bytes(result2['hex']), 2 * result_fee_rate)
assert_fee_amount(result3['fee'], count_bytes(result3['hex']), 10 * result_fee_rate)
diff --git a/test/functional/rpc_psbt.py b/test/functional/rpc_psbt.py
index 8a7ea7aa58..b3d8696208 100755
--- a/test/functional/rpc_psbt.py
+++ b/test/functional/rpc_psbt.py
@@ -9,6 +9,7 @@ from decimal import Decimal
from test_framework.test_framework import BitcoinTestFramework
from test_framework.util import (
assert_equal,
+ assert_greater_than,
assert_raises_rpc_error,
connect_nodes_bi,
disconnect_nodes,
@@ -129,6 +130,15 @@ class PSBTTest(BitcoinTestFramework):
assert_equal(walletprocesspsbt_out['complete'], True)
self.nodes[1].sendrawtransaction(self.nodes[1].finalizepsbt(walletprocesspsbt_out['psbt'])['hex'])
+ # feeRate of 0.1 BTC / KB produces a total fee slightly below -maxtxfee (~0.05280000):
+ res = self.nodes[1].walletcreatefundedpsbt([{"txid":txid,"vout":p2wpkh_pos},{"txid":txid,"vout":p2sh_p2wpkh_pos},{"txid":txid,"vout":p2pkh_pos}], {self.nodes[1].getnewaddress():29.99}, 0, {"feeRate": 0.1})
+ assert_greater_than(res["fee"], 0.05)
+ assert_greater_than(0.06, res["fee"])
+
+ # feeRate of 10 BTC / KB produces a total fee well above -maxtxfee
+ # previously this was silently capped at -maxtxfee
+ assert_raises_rpc_error(-4, "Fee exceeds maximum configured by -maxtxfee", self.nodes[1].walletcreatefundedpsbt, [{"txid":txid,"vout":p2wpkh_pos},{"txid":txid,"vout":p2sh_p2wpkh_pos},{"txid":txid,"vout":p2pkh_pos}], {self.nodes[1].getnewaddress():29.99}, 0, {"feeRate": 10})
+
# partially sign multisig things with node 1
psbtx = self.nodes[1].walletcreatefundedpsbt([{"txid":txid,"vout":p2wsh_pos},{"txid":txid,"vout":p2sh_pos},{"txid":txid,"vout":p2sh_p2wsh_pos}], {self.nodes[1].getnewaddress():29.99})['psbt']
walletprocesspsbt_out = self.nodes[1].walletprocesspsbt(psbtx)
@@ -152,9 +162,10 @@ class PSBTTest(BitcoinTestFramework):
# Make sure that a non-psbt with signatures cannot be converted
# Error could be either "TX decode failed" (segwit inputs causes parsing to fail) or "Inputs must not have scriptSigs and scriptWitnesses"
+ # We must set iswitness=True because the serialized transaction has inputs and is therefore a witness transaction
signedtx = self.nodes[0].signrawtransactionwithwallet(rawtx['hex'])
- assert_raises_rpc_error(-22, "", self.nodes[0].converttopsbt, signedtx['hex'])
- assert_raises_rpc_error(-22, "", self.nodes[0].converttopsbt, signedtx['hex'], False)
+ assert_raises_rpc_error(-22, "", self.nodes[0].converttopsbt, hexstring=signedtx['hex'], iswitness=True)
+ assert_raises_rpc_error(-22, "", self.nodes[0].converttopsbt, hexstring=signedtx['hex'], permitsigdata=False, iswitness=True)
# Unless we allow it to convert and strip signatures
self.nodes[0].converttopsbt(signedtx['hex'], True)
@@ -314,18 +325,32 @@ class PSBTTest(BitcoinTestFramework):
vout3 = find_output(self.nodes[0], txid3, 11)
self.sync_all()
- # Update a PSBT with UTXOs from the node
- # Bech32 inputs should be filled with witness UTXO. Other inputs should not be filled because they are non-witness
+ def test_psbt_input_keys(psbt_input, keys):
+ """Check that the psbt input has only the expected keys."""
+ assert_equal(set(keys), set(psbt_input.keys()))
+
+ # Create a PSBT. None of the inputs are filled initially
psbt = self.nodes[1].createpsbt([{"txid":txid1, "vout":vout1},{"txid":txid2, "vout":vout2},{"txid":txid3, "vout":vout3}], {self.nodes[0].getnewaddress():32.999})
decoded = self.nodes[1].decodepsbt(psbt)
- assert "witness_utxo" not in decoded['inputs'][0] and "non_witness_utxo" not in decoded['inputs'][0]
- assert "witness_utxo" not in decoded['inputs'][1] and "non_witness_utxo" not in decoded['inputs'][1]
- assert "witness_utxo" not in decoded['inputs'][2] and "non_witness_utxo" not in decoded['inputs'][2]
+ test_psbt_input_keys(decoded['inputs'][0], [])
+ test_psbt_input_keys(decoded['inputs'][1], [])
+ test_psbt_input_keys(decoded['inputs'][2], [])
+
+ # Update a PSBT with UTXOs from the node
+ # Bech32 inputs should be filled with witness UTXO. Other inputs should not be filled because they are non-witness
updated = self.nodes[1].utxoupdatepsbt(psbt)
decoded = self.nodes[1].decodepsbt(updated)
- assert "witness_utxo" in decoded['inputs'][0] and "non_witness_utxo" not in decoded['inputs'][0]
- assert "witness_utxo" not in decoded['inputs'][1] and "non_witness_utxo" not in decoded['inputs'][1]
- assert "witness_utxo" not in decoded['inputs'][2] and "non_witness_utxo" not in decoded['inputs'][2]
+ test_psbt_input_keys(decoded['inputs'][0], ['witness_utxo'])
+ test_psbt_input_keys(decoded['inputs'][1], [])
+ test_psbt_input_keys(decoded['inputs'][2], [])
+
+ # Try again, now while providing descriptors, making P2SH-segwit work, and causing bip32_derivs and redeem_script to be filled in
+ descs = [self.nodes[1].getaddressinfo(addr)['desc'] for addr in [addr1,addr2,addr3]]
+ updated = self.nodes[1].utxoupdatepsbt(psbt=psbt, descriptors=descs)
+ decoded = self.nodes[1].decodepsbt(updated)
+ test_psbt_input_keys(decoded['inputs'][0], ['witness_utxo', 'bip32_derivs'])
+ test_psbt_input_keys(decoded['inputs'][1], [])
+ test_psbt_input_keys(decoded['inputs'][2], ['witness_utxo', 'bip32_derivs', 'redeem_script'])
# Two PSBTs with a common input should not be joinable
psbt1 = self.nodes[1].createpsbt([{"txid":txid1, "vout":vout1}], {self.nodes[0].getnewaddress():Decimal('10.999')})
diff --git a/test/functional/rpc_users.py b/test/functional/rpc_users.py
index 102dd22594..8bbb3c04fa 100755
--- a/test/functional/rpc_users.py
+++ b/test/functional/rpc_users.py
@@ -20,6 +20,17 @@ import string
import configparser
import sys
+def call_with_auth(node, user, password):
+ url = urllib.parse.urlparse(node.url)
+ headers = {"Authorization": "Basic " + str_to_b64str('{}:{}'.format(user, password))}
+
+ conn = http.client.HTTPConnection(url.hostname, url.port)
+ conn.connect()
+ conn.request('POST', '/', '{"method": "getbestblockhash"}', headers)
+ resp = conn.getresponse()
+ conn.close()
+ return resp
+
class HTTPBasicsTest(BitcoinTestFramework):
def set_test_params(self):
@@ -28,15 +39,24 @@ class HTTPBasicsTest(BitcoinTestFramework):
def setup_chain(self):
super().setup_chain()
#Append rpcauth to bitcoin.conf before initialization
+ self.rtpassword = "cA773lm788buwYe4g4WT+05pKyNruVKjQ25x3n0DQcM="
rpcauth = "rpcauth=rt:93648e835a54c573682c2eb19f882535$7681e9c5b74bdd85e78166031d2058e1069b3ed7ed967c93fc63abba06f31144"
- rpcauth2 = "rpcauth=rt2:f8607b1a88861fac29dfccf9b52ff9f$ff36a0c23c8c62b4846112e50fa888416e94c17bfd4c42f88fd8f55ec6a3137e"
- rpcuser = "rpcuser=rpcuser💻"
- rpcpassword = "rpcpassword=rpcpassword🔑"
- self.user = ''.join(SystemRandom().choice(string.ascii_letters + string.digits) for _ in range(10))
+ self.rpcuser = "rpcuser💻"
+ self.rpcpassword = "rpcpassword🔑"
+
config = configparser.ConfigParser()
config.read_file(open(self.options.configfile))
gen_rpcauth = config['environment']['RPCAUTH']
+
+ # Generate RPCAUTH with specified password
+ self.rt2password = "8/F3uMDw4KSEbw96U3CA1C4X05dkHDN2BPFjTgZW4KI="
+ p = subprocess.Popen([sys.executable, gen_rpcauth, 'rt2', self.rt2password], stdout=subprocess.PIPE, universal_newlines=True)
+ lines = p.stdout.read().splitlines()
+ rpcauth2 = lines[1]
+
+ # Generate RPCAUTH without specifying password
+ self.user = ''.join(SystemRandom().choice(string.ascii_letters + string.digits) for _ in range(10))
p = subprocess.Popen([sys.executable, gen_rpcauth, self.user], stdout=subprocess.PIPE, universal_newlines=True)
lines = p.stdout.read().splitlines()
rpcauth3 = lines[1]
@@ -47,160 +67,40 @@ class HTTPBasicsTest(BitcoinTestFramework):
f.write(rpcauth2+"\n")
f.write(rpcauth3+"\n")
with open(os.path.join(get_datadir_path(self.options.tmpdir, 1), "bitcoin.conf"), 'a', encoding='utf8') as f:
- f.write(rpcuser+"\n")
- f.write(rpcpassword+"\n")
-
- def run_test(self):
-
- ##################################################
- # Check correctness of the rpcauth config option #
- ##################################################
- url = urllib.parse.urlparse(self.nodes[0].url)
-
- #Old authpair
- authpair = url.username + ':' + url.password
-
- #New authpair generated via share/rpcauth tool
- password = "cA773lm788buwYe4g4WT+05pKyNruVKjQ25x3n0DQcM="
-
- #Second authpair with different username
- password2 = "8/F3uMDw4KSEbw96U3CA1C4X05dkHDN2BPFjTgZW4KI="
- authpairnew = "rt:"+password
+ f.write("rpcuser={}\n".format(self.rpcuser))
+ f.write("rpcpassword={}\n".format(self.rpcpassword))
+ def test_auth(self, node, user, password):
self.log.info('Correct...')
- headers = {"Authorization": "Basic " + str_to_b64str(authpair)}
+ assert_equal(200, call_with_auth(node, user, password).status)
- conn = http.client.HTTPConnection(url.hostname, url.port)
- conn.connect()
- conn.request('POST', '/', '{"method": "getbestblockhash"}', headers)
- resp = conn.getresponse()
- assert_equal(resp.status, 200)
- conn.close()
-
- #Use new authpair to confirm both work
- self.log.info('Correct...')
- headers = {"Authorization": "Basic " + str_to_b64str(authpairnew)}
-
- conn = http.client.HTTPConnection(url.hostname, url.port)
- conn.connect()
- conn.request('POST', '/', '{"method": "getbestblockhash"}', headers)
- resp = conn.getresponse()
- assert_equal(resp.status, 200)
- conn.close()
-
- #Wrong login name with rt's password
self.log.info('Wrong...')
- authpairnew = "rtwrong:"+password
- headers = {"Authorization": "Basic " + str_to_b64str(authpairnew)}
-
- conn = http.client.HTTPConnection(url.hostname, url.port)
- conn.connect()
- conn.request('POST', '/', '{"method": "getbestblockhash"}', headers)
- resp = conn.getresponse()
- assert_equal(resp.status, 401)
- conn.close()
+ assert_equal(401, call_with_auth(node, user, password+'wrong').status)
- #Wrong password for rt
self.log.info('Wrong...')
- authpairnew = "rt:"+password+"wrong"
- headers = {"Authorization": "Basic " + str_to_b64str(authpairnew)}
+ assert_equal(401, call_with_auth(node, user+'wrong', password).status)
- conn = http.client.HTTPConnection(url.hostname, url.port)
- conn.connect()
- conn.request('POST', '/', '{"method": "getbestblockhash"}', headers)
- resp = conn.getresponse()
- assert_equal(resp.status, 401)
- conn.close()
-
- #Correct for rt2
- self.log.info('Correct...')
- authpairnew = "rt2:"+password2
- headers = {"Authorization": "Basic " + str_to_b64str(authpairnew)}
-
- conn = http.client.HTTPConnection(url.hostname, url.port)
- conn.connect()
- conn.request('POST', '/', '{"method": "getbestblockhash"}', headers)
- resp = conn.getresponse()
- assert_equal(resp.status, 200)
- conn.close()
-
- #Wrong password for rt2
self.log.info('Wrong...')
- authpairnew = "rt2:"+password2+"wrong"
- headers = {"Authorization": "Basic " + str_to_b64str(authpairnew)}
-
- conn = http.client.HTTPConnection(url.hostname, url.port)
- conn.connect()
- conn.request('POST', '/', '{"method": "getbestblockhash"}', headers)
- resp = conn.getresponse()
- assert_equal(resp.status, 401)
- conn.close()
+ assert_equal(401, call_with_auth(node, user+'wrong', password+'wrong').status)
- #Correct for randomly generated user
- self.log.info('Correct...')
- authpairnew = self.user+":"+self.password
- headers = {"Authorization": "Basic " + str_to_b64str(authpairnew)}
-
- conn = http.client.HTTPConnection(url.hostname, url.port)
- conn.connect()
- conn.request('POST', '/', '{"method": "getbestblockhash"}', headers)
- resp = conn.getresponse()
- assert_equal(resp.status, 200)
- conn.close()
+ def run_test(self):
- #Wrong password for randomly generated user
- self.log.info('Wrong...')
- authpairnew = self.user+":"+self.password+"Wrong"
- headers = {"Authorization": "Basic " + str_to_b64str(authpairnew)}
+ ##################################################
+ # Check correctness of the rpcauth config option #
+ ##################################################
+ url = urllib.parse.urlparse(self.nodes[0].url)
- conn = http.client.HTTPConnection(url.hostname, url.port)
- conn.connect()
- conn.request('POST', '/', '{"method": "getbestblockhash"}', headers)
- resp = conn.getresponse()
- assert_equal(resp.status, 401)
- conn.close()
+ self.test_auth(self.nodes[0], url.username, url.password)
+ self.test_auth(self.nodes[0], 'rt', self.rtpassword)
+ self.test_auth(self.nodes[0], 'rt2', self.rt2password)
+ self.test_auth(self.nodes[0], self.user, self.password)
###############################################################
# Check correctness of the rpcuser/rpcpassword config options #
###############################################################
url = urllib.parse.urlparse(self.nodes[1].url)
- # rpcuser and rpcpassword authpair
- self.log.info('Correct...')
- rpcuserauthpair = "rpcuser💻:rpcpassword🔑"
-
- headers = {"Authorization": "Basic " + str_to_b64str(rpcuserauthpair)}
-
- conn = http.client.HTTPConnection(url.hostname, url.port)
- conn.connect()
- conn.request('POST', '/', '{"method": "getbestblockhash"}', headers)
- resp = conn.getresponse()
- assert_equal(resp.status, 200)
- conn.close()
-
- #Wrong login name with rpcuser's password
- rpcuserauthpair = "rpcuserwrong:rpcpassword"
- headers = {"Authorization": "Basic " + str_to_b64str(rpcuserauthpair)}
-
- conn = http.client.HTTPConnection(url.hostname, url.port)
- conn.connect()
- conn.request('POST', '/', '{"method": "getbestblockhash"}', headers)
- resp = conn.getresponse()
- assert_equal(resp.status, 401)
- conn.close()
-
- #Wrong password for rpcuser
- self.log.info('Wrong...')
- rpcuserauthpair = "rpcuser:rpcpasswordwrong"
- headers = {"Authorization": "Basic " + str_to_b64str(rpcuserauthpair)}
-
- conn = http.client.HTTPConnection(url.hostname, url.port)
- conn.connect()
- conn.request('POST', '/', '{"method": "getbestblockhash"}', headers)
- resp = conn.getresponse()
- assert_equal(resp.status, 401)
- conn.close()
-
+ self.test_auth(self.nodes[1], self.rpcuser, self.rpcpassword)
if __name__ == '__main__':
HTTPBasicsTest ().main ()
diff --git a/test/functional/test_framework/messages.py b/test/functional/test_framework/messages.py
index 2ab9c37576..89a5a65e64 100755
--- a/test/functional/test_framework/messages.py
+++ b/test/functional/test_framework/messages.py
@@ -1130,7 +1130,7 @@ class msg_block:
self.block.deserialize(f)
def serialize(self):
- return self.block.serialize(with_witness=False)
+ return self.block.serialize()
def __repr__(self):
return "msg_block(block=%s)" % (repr(self.block))
@@ -1152,11 +1152,10 @@ class msg_generic:
return "msg_generic()"
-class msg_witness_block(msg_block):
+class msg_no_witness_block(msg_block):
__slots__ = ()
def serialize(self):
- r = self.block.serialize(with_witness=True)
- return r
+ return self.block.serialize(with_witness=False)
class msg_getaddr:
@@ -1442,17 +1441,15 @@ class msg_blocktxn:
def serialize(self):
r = b""
- r += self.block_transactions.serialize(with_witness=False)
+ r += self.block_transactions.serialize()
return r
def __repr__(self):
return "msg_blocktxn(block_transactions=%s)" % (repr(self.block_transactions))
-class msg_witness_blocktxn(msg_blocktxn):
+class msg_no_witness_blocktxn(msg_blocktxn):
__slots__ = ()
def serialize(self):
- r = b""
- r += self.block_transactions.serialize(with_witness=True)
- return r
+ return self.block_transactions.serialize(with_witness=False)
diff --git a/test/functional/test_framework/util.py b/test/functional/test_framework/util.py
index 26215083fb..efd962ea93 100644
--- a/test/functional/test_framework/util.py
+++ b/test/functional/test_framework/util.py
@@ -19,6 +19,7 @@ import time
from . import coverage
from .authproxy import AuthServiceProxy, JSONRPCException
+from io import BytesIO
logger = logging.getLogger("TestFramework.utils")
@@ -515,14 +516,13 @@ def gen_return_txouts():
for i 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 = "81"
+ txouts = []
+ from .messages import CTxOut
+ txout = CTxOut()
+ txout.nValue = 0
+ txout.scriptPubKey = hex_str_to_bytes(script_pubkey)
for k in range(128):
- # add txout value
- txouts = txouts + "0000000000000000"
- # add length of script_pubkey
- txouts = txouts + "fd0402"
- # add script_pubkey
- txouts = txouts + script_pubkey
+ txouts.append(txout)
return txouts
# Create a spend of each passed-in utxo, splicing in "txouts" to each raw
@@ -530,6 +530,7 @@ def gen_return_txouts():
def create_lots_of_big_transactions(node, txouts, utxos, num, fee):
addr = node.getnewaddress()
txids = []
+ from .messages import CTransaction
for _ in range(num):
t = utxos.pop()
inputs = [{"txid": t["txid"], "vout": t["vout"]}]
@@ -537,9 +538,11 @@ def create_lots_of_big_transactions(node, txouts, utxos, num, fee):
change = t['amount'] - fee
outputs[addr] = satoshi_round(change)
rawtx = node.createrawtransaction(inputs, outputs)
- newtx = rawtx[0:92]
- newtx = newtx + txouts
- newtx = newtx + rawtx[94:]
+ tx = CTransaction()
+ tx.deserialize(BytesIO(hex_str_to_bytes(rawtx)))
+ for txout in txouts:
+ tx.vout.append(txout)
+ newtx = tx.serialize().hex()
signresult = node.signrawtransactionwithwallet(newtx, None, "NONE")
txid = node.sendrawtransaction(signresult["hex"], 0)
txids.append(txid)
diff --git a/test/functional/test_runner.py b/test/functional/test_runner.py
index 80dced733e..71738be1d0 100755
--- a/test/functional/test_runner.py
+++ b/test/functional/test_runner.py
@@ -67,14 +67,14 @@ TEST_EXIT_PASSED = 0
TEST_EXIT_SKIPPED = 77
EXTENDED_SCRIPTS = [
- # These tests are not run by the travis build process.
+ # These tests are not run by default.
# Longest test should go first, to favor running tests in parallel
'feature_pruning.py',
'feature_dbcrash.py',
]
BASE_SCRIPTS = [
- # Scripts that are run by the travis build process.
+ # Scripts that are run by default.
# Longest test should go first, to favor running tests in parallel
'feature_fee_estimation.py',
'wallet_hd.py',
@@ -120,6 +120,7 @@ BASE_SCRIPTS = [
'rpc_misc.py',
'interface_rest.py',
'mempool_spend_coinbase.py',
+ 'wallet_avoidreuse.py',
'mempool_reorg.py',
'mempool_persist.py',
'wallet_multiwallet.py',
@@ -233,6 +234,7 @@ def main():
parser.add_argument('--quiet', '-q', action='store_true', help='only print dots, results summary and failure logs')
parser.add_argument('--tmpdirprefix', '-t', default=tempfile.gettempdir(), help="Root directory for datadirs")
parser.add_argument('--failfast', action='store_true', help='stop execution after the first test failure')
+ parser.add_argument('--filter', help='filter scripts to run by regular expression')
args, unknown_args = parser.parse_known_args()
# args to be passed on always start with two dashes; tests are the remaining unknown args
@@ -268,11 +270,22 @@ def main():
test_list = []
if tests:
# Individual tests have been specified. Run specified tests that exist
- # in the ALL_SCRIPTS list. Accept the name with or without .py extension.
- tests = [test + ".py" if ".py" not in test else test for test in tests]
+ # in the ALL_SCRIPTS list. Accept names with or without a .py extension.
+ # Specified tests can contain wildcards, but in that case the supplied
+ # paths should be coherent, e.g. the same path as that provided to call
+ # test_runner.py. Examples:
+ # `test/functional/test_runner.py test/functional/wallet*`
+ # `test/functional/test_runner.py ./test/functional/wallet*`
+ # `test_runner.py wallet*`
+ # but not:
+ # `test/functional/test_runner.py wallet*`
+ # Multiple wildcards can be passed:
+ # `test_runner.py tool* mempool*`
for test in tests:
- if test in ALL_SCRIPTS:
- test_list.append(test)
+ script = test.split("/")[-1]
+ script = script + ".py" if ".py" not in script else script
+ if script in ALL_SCRIPTS:
+ test_list.append(script)
else:
print("{}WARNING!{} Test '{}' not found in full test list.".format(BOLD[1], BOLD[0], test))
elif args.extended:
@@ -293,6 +306,9 @@ def main():
if not exclude_list:
print("{}WARNING!{} Test '{}' not found in current test list.".format(BOLD[1], BOLD[0], exclude_test))
+ if args.filter:
+ test_list = list(filter(re.compile(args.filter).search, test_list))
+
if not test_list:
print("No valid test scripts specified. Check that your test is in one "
"of the test lists in test_runner.py, or run test_runner.py with no arguments to run all tests")
@@ -494,7 +510,8 @@ class TestHandler:
for job in self.jobs:
(name, start_time, proc, testdir, log_out, log_err) = job
if int(time.time() - start_time) > self.timeout_duration:
- # In travis, timeout individual tests (to stop tests hanging and not providing useful output).
+ # Timeout individual tests if timeout is specified (to stop
+ # tests hanging and not providing useful output).
proc.send_signal(signal.SIGINT)
if proc.poll() is not None:
log_out.seek(0), log_err.seek(0)
@@ -582,7 +599,7 @@ def check_script_list(*, src_dir, fail_on_warn):
if len(missed_tests) != 0:
print("%sWARNING!%s The following scripts are not being run: %s. Check the test lists in test_runner.py." % (BOLD[1], BOLD[0], str(missed_tests)))
if fail_on_warn:
- # On travis this warning is an error to prevent merging incomplete commits into master
+ # On CI this warning is an error to prevent merging incomplete commits into master
sys.exit(1)
diff --git a/test/functional/tool_wallet.py b/test/functional/tool_wallet.py
index fbcf21e729..28a65f7823 100755
--- a/test/functional/tool_wallet.py
+++ b/test/functional/tool_wallet.py
@@ -1,14 +1,20 @@
#!/usr/bin/env python3
-# Copyright (c) 2018 The Bitcoin Core developers
+# Copyright (c) 2018-2019 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 bitcoin-wallet."""
+
+import hashlib
+import os
+import stat
import subprocess
import textwrap
from test_framework.test_framework import BitcoinTestFramework
from test_framework.util import assert_equal
+BUFFER_SIZE = 16 * 1024
+
class ToolWalletTest(BitcoinTestFramework):
def set_test_params(self):
self.num_nodes = 1
@@ -32,23 +38,54 @@ class ToolWalletTest(BitcoinTestFramework):
def assert_tool_output(self, output, *args):
p = self.bitcoin_wallet_process(*args)
stdout, stderr = p.communicate()
- assert_equal(p.poll(), 0)
assert_equal(stderr, '')
assert_equal(stdout, output)
+ assert_equal(p.poll(), 0)
- def run_test(self):
+ def wallet_shasum(self):
+ h = hashlib.sha1()
+ mv = memoryview(bytearray(BUFFER_SIZE))
+ with open(self.wallet_path, 'rb', buffering=0) as f:
+ for n in iter(lambda : f.readinto(mv), 0):
+ h.update(mv[:n])
+ return h.hexdigest()
+ def wallet_timestamp(self):
+ return os.path.getmtime(self.wallet_path)
+
+ def wallet_permissions(self):
+ return oct(os.lstat(self.wallet_path).st_mode)[-3:]
+
+ def log_wallet_timestamp_comparison(self, old, new):
+ result = 'unchanged' if new == old else 'increased!'
+ self.log.debug('Wallet file timestamp {}'.format(result))
+
+ def test_invalid_tool_commands_and_args(self):
+ self.log.info('Testing that various invalid commands raise with specific error messages')
self.assert_raises_tool_error('Invalid command: foo', 'foo')
- # `bitcoin-wallet help` is an error. Use `bitcoin-wallet -help`
+ # `bitcoin-wallet help` raises an error. Use `bitcoin-wallet -help`.
self.assert_raises_tool_error('Invalid command: help', 'help')
self.assert_raises_tool_error('Error: two methods provided (info and create). Only one method should be provided.', 'info', 'create')
self.assert_raises_tool_error('Error parsing command line arguments: Invalid parameter -foo', '-foo')
self.assert_raises_tool_error('Error loading wallet.dat. Is wallet being used by other process?', '-wallet=wallet.dat', 'info')
self.assert_raises_tool_error('Error: no wallet file at nonexistent.dat', '-wallet=nonexistent.dat', 'info')
- # stop the node to close the wallet to call info command
+ def test_tool_wallet_info(self):
+ # Stop the node to close the wallet to call the info command.
self.stop_node(0)
-
+ self.log.info('Calling wallet tool info, testing output')
+ #
+ # TODO: Wallet tool info should work with wallet file permissions set to
+ # read-only without raising:
+ # "Error loading wallet.dat. Is wallet being used by another process?"
+ # The following lines should be uncommented and the tests still succeed:
+ #
+ # self.log.debug('Setting wallet file permissions to 400 (read-only)')
+ # os.chmod(self.wallet_path, stat.S_IRUSR)
+ # assert(self.wallet_permissions() in ['400', '666']) # Sanity check. 666 because Appveyor.
+ # shasum_before = self.wallet_shasum()
+ timestamp_before = self.wallet_timestamp()
+ self.log.debug('Wallet file timestamp before calling info: {}'.format(timestamp_before))
out = textwrap.dedent('''\
Wallet info
===========
@@ -59,12 +96,35 @@ class ToolWalletTest(BitcoinTestFramework):
Address Book: 3
''')
self.assert_tool_output(out, '-wallet=wallet.dat', 'info')
-
- # mutate the wallet to check the info command output changes accordingly
+ timestamp_after = self.wallet_timestamp()
+ self.log.debug('Wallet file timestamp after calling info: {}'.format(timestamp_after))
+ self.log_wallet_timestamp_comparison(timestamp_before, timestamp_after)
+ self.log.debug('Setting wallet file permissions back to 600 (read/write)')
+ os.chmod(self.wallet_path, stat.S_IRUSR | stat.S_IWUSR)
+ assert(self.wallet_permissions() in ['600', '666']) # Sanity check. 666 because Appveyor.
+ #
+ # TODO: Wallet tool info should not write to the wallet file.
+ # The following lines should be uncommented and the tests still succeed:
+ #
+ # assert_equal(timestamp_before, timestamp_after)
+ # shasum_after = self.wallet_shasum()
+ # assert_equal(shasum_before, shasum_after)
+ # self.log.debug('Wallet file shasum unchanged\n')
+
+ def test_tool_wallet_info_after_transaction(self):
+ """
+ Mutate the wallet with a transaction to verify that the info command
+ output changes accordingly.
+ """
self.start_node(0)
+ self.log.info('Generating transaction to mutate wallet')
self.nodes[0].generate(1)
self.stop_node(0)
+ self.log.info('Calling wallet tool info after generating a transaction, testing output')
+ shasum_before = self.wallet_shasum()
+ timestamp_before = self.wallet_timestamp()
+ self.log.debug('Wallet file timestamp before calling info: {}'.format(timestamp_before))
out = textwrap.dedent('''\
Wallet info
===========
@@ -75,7 +135,22 @@ class ToolWalletTest(BitcoinTestFramework):
Address Book: 3
''')
self.assert_tool_output(out, '-wallet=wallet.dat', 'info')
-
+ shasum_after = self.wallet_shasum()
+ timestamp_after = self.wallet_timestamp()
+ self.log.debug('Wallet file timestamp after calling info: {}'.format(timestamp_after))
+ self.log_wallet_timestamp_comparison(timestamp_before, timestamp_after)
+ #
+ # TODO: Wallet tool info should not write to the wallet file.
+ # This assertion should be uncommented and succeed:
+ # assert_equal(timestamp_before, timestamp_after)
+ assert_equal(shasum_before, shasum_after)
+ self.log.debug('Wallet file shasum unchanged\n')
+
+ def test_tool_wallet_create_on_existing_wallet(self):
+ self.log.info('Calling wallet tool create on an existing wallet, testing output')
+ shasum_before = self.wallet_shasum()
+ timestamp_before = self.wallet_timestamp()
+ self.log.debug('Wallet file timestamp before calling create: {}'.format(timestamp_before))
out = textwrap.dedent('''\
Topping up keypool...
Wallet info
@@ -87,15 +162,48 @@ class ToolWalletTest(BitcoinTestFramework):
Address Book: 0
''')
self.assert_tool_output(out, '-wallet=foo', 'create')
-
+ shasum_after = self.wallet_shasum()
+ timestamp_after = self.wallet_timestamp()
+ self.log.debug('Wallet file timestamp after calling create: {}'.format(timestamp_after))
+ self.log_wallet_timestamp_comparison(timestamp_before, timestamp_after)
+ assert_equal(timestamp_before, timestamp_after)
+ assert_equal(shasum_before, shasum_after)
+ self.log.debug('Wallet file shasum unchanged\n')
+
+ def test_getwalletinfo_on_different_wallet(self):
+ self.log.info('Starting node with arg -wallet=foo')
self.start_node(0, ['-wallet=foo'])
+
+ self.log.info('Calling getwalletinfo on a different wallet ("foo"), testing output')
+ shasum_before = self.wallet_shasum()
+ timestamp_before = self.wallet_timestamp()
+ self.log.debug('Wallet file timestamp before calling getwalletinfo: {}'.format(timestamp_before))
out = self.nodes[0].getwalletinfo()
self.stop_node(0)
+ shasum_after = self.wallet_shasum()
+ timestamp_after = self.wallet_timestamp()
+ self.log.debug('Wallet file timestamp after calling getwalletinfo: {}'.format(timestamp_after))
+
assert_equal(0, out['txcount'])
assert_equal(1000, out['keypoolsize'])
assert_equal(1000, out['keypoolsize_hd_internal'])
assert_equal(True, 'hdseedid' in out)
+ self.log_wallet_timestamp_comparison(timestamp_before, timestamp_after)
+ assert_equal(timestamp_before, timestamp_after)
+ assert_equal(shasum_after, shasum_before)
+ self.log.debug('Wallet file shasum unchanged\n')
+
+ def run_test(self):
+ self.wallet_path = os.path.join(self.nodes[0].datadir, 'regtest', 'wallets', 'wallet.dat')
+ self.test_invalid_tool_commands_and_args()
+ # Warning: The following tests are order-dependent.
+ self.test_tool_wallet_info()
+ self.test_tool_wallet_info_after_transaction()
+ self.test_tool_wallet_create_on_existing_wallet()
+ self.test_getwalletinfo_on_different_wallet()
+
+
if __name__ == '__main__':
ToolWalletTest().main()
diff --git a/test/functional/wallet_avoidreuse.py b/test/functional/wallet_avoidreuse.py
new file mode 100755
index 0000000000..58ad835d39
--- /dev/null
+++ b/test/functional/wallet_avoidreuse.py
@@ -0,0 +1,241 @@
+#!/usr/bin/env python3
+# Copyright (c) 2018 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 avoid_reuse and setwalletflag features."""
+
+from test_framework.test_framework import BitcoinTestFramework
+from test_framework.util import (
+ assert_equal,
+ assert_raises_rpc_error,
+ connect_nodes_bi,
+)
+
+# TODO: Copied from wallet_groups.py -- should perhaps move into util.py
+def assert_approx(v, vexp, vspan=0.00001):
+ if v < vexp - vspan:
+ raise AssertionError("%s < [%s..%s]" % (str(v), str(vexp - vspan), str(vexp + vspan)))
+ if v > vexp + vspan:
+ raise AssertionError("%s > [%s..%s]" % (str(v), str(vexp - vspan), str(vexp + vspan)))
+
+def reset_balance(node, discardaddr):
+ '''Throw away all owned coins by the node so it gets a balance of 0.'''
+ balance = node.getbalance(avoid_reuse=False)
+ if balance > 0.5:
+ node.sendtoaddress(address=discardaddr, amount=balance, subtractfeefromamount=True, avoid_reuse=False)
+
+def count_unspent(node):
+ '''Count the unspent outputs for the given node and return various statistics'''
+ r = {
+ "total": {
+ "count": 0,
+ "sum": 0,
+ },
+ "reused": {
+ "count": 0,
+ "sum": 0,
+ },
+ }
+ supports_reused = True
+ for utxo in node.listunspent(minconf=0):
+ r["total"]["count"] += 1
+ r["total"]["sum"] += utxo["amount"]
+ if supports_reused and "reused" in utxo:
+ if utxo["reused"]:
+ r["reused"]["count"] += 1
+ r["reused"]["sum"] += utxo["amount"]
+ else:
+ supports_reused = False
+ r["reused"]["supported"] = supports_reused
+ return r
+
+def assert_unspent(node, total_count=None, total_sum=None, reused_supported=None, reused_count=None, reused_sum=None):
+ '''Make assertions about a node's unspent output statistics'''
+ stats = count_unspent(node)
+ if total_count is not None:
+ assert_equal(stats["total"]["count"], total_count)
+ if total_sum is not None:
+ assert_approx(stats["total"]["sum"], total_sum, 0.001)
+ if reused_supported is not None:
+ assert_equal(stats["reused"]["supported"], reused_supported)
+ if reused_count is not None:
+ assert_equal(stats["reused"]["count"], reused_count)
+ if reused_sum is not None:
+ assert_approx(stats["reused"]["sum"], reused_sum, 0.001)
+
+def assert_balances(node, mine):
+ '''Make assertions about a node's getbalances output'''
+ got = node.getbalances()["mine"]
+ for k,v in mine.items():
+ assert_approx(got[k], v, 0.001)
+
+class AvoidReuseTest(BitcoinTestFramework):
+
+ def set_test_params(self):
+ self.setup_clean_chain = False
+ self.num_nodes = 2
+
+ def skip_test_if_missing_module(self):
+ self.skip_if_no_wallet()
+
+ def run_test(self):
+ '''Set up initial chain and run tests defined below'''
+
+ self.test_persistence()
+ self.test_immutable()
+
+ self.nodes[0].generate(110)
+ self.sync_all()
+ reset_balance(self.nodes[1], self.nodes[0].getnewaddress())
+ self.test_fund_send_fund_senddirty()
+ reset_balance(self.nodes[1], self.nodes[0].getnewaddress())
+ self.test_fund_send_fund_send()
+
+ def test_persistence(self):
+ '''Test that wallet files persist the avoid_reuse flag.'''
+ # Configure node 1 to use avoid_reuse
+ self.nodes[1].setwalletflag('avoid_reuse')
+
+ # Flags should be node1.avoid_reuse=false, node2.avoid_reuse=true
+ assert_equal(self.nodes[0].getwalletinfo()["avoid_reuse"], False)
+ assert_equal(self.nodes[1].getwalletinfo()["avoid_reuse"], True)
+
+ # Stop and restart node 1
+ self.stop_node(1)
+ self.start_node(1)
+ connect_nodes_bi(self.nodes, 0, 1)
+
+ # Flags should still be node1.avoid_reuse=false, node2.avoid_reuse=true
+ assert_equal(self.nodes[0].getwalletinfo()["avoid_reuse"], False)
+ assert_equal(self.nodes[1].getwalletinfo()["avoid_reuse"], True)
+
+ # Attempting to set flag to its current state should throw
+ assert_raises_rpc_error(-8, "Wallet flag is already set to false", self.nodes[0].setwalletflag, 'avoid_reuse', False)
+ assert_raises_rpc_error(-8, "Wallet flag is already set to true", self.nodes[1].setwalletflag, 'avoid_reuse', True)
+
+ def test_immutable(self):
+ '''Test immutable wallet flags'''
+ # Attempt to set the disable_private_keys flag; this should not work
+ assert_raises_rpc_error(-8, "Wallet flag is immutable", self.nodes[1].setwalletflag, 'disable_private_keys')
+
+ tempwallet = ".wallet_avoidreuse.py_test_immutable_wallet.dat"
+
+ # Create a wallet with disable_private_keys set; this should work
+ self.nodes[1].createwallet(tempwallet, True)
+ w = self.nodes[1].get_wallet_rpc(tempwallet)
+
+ # Attempt to unset the disable_private_keys flag; this should not work
+ assert_raises_rpc_error(-8, "Wallet flag is immutable", w.setwalletflag, 'disable_private_keys', False)
+
+ # Unload temp wallet
+ self.nodes[1].unloadwallet(tempwallet)
+
+ def test_fund_send_fund_senddirty(self):
+ '''
+ Test the same as test_fund_send_fund_send, except send the 10 BTC with
+ the avoid_reuse flag set to false. This means the 10 BTC send should succeed,
+ where it fails in test_fund_send_fund_send.
+ '''
+
+ fundaddr = self.nodes[1].getnewaddress()
+ retaddr = self.nodes[0].getnewaddress()
+
+ self.nodes[0].sendtoaddress(fundaddr, 10)
+ self.nodes[0].generate(1)
+ self.sync_all()
+
+ # listunspent should show 1 single, unused 10 btc output
+ assert_unspent(self.nodes[1], total_count=1, total_sum=10, reused_supported=True, reused_count=0)
+ # getbalances should show no used, 10 btc trusted
+ assert_balances(self.nodes[1], mine={"used": 0, "trusted": 10})
+ # node 0 should not show a used entry, as it does not enable avoid_reuse
+ assert("used" not in self.nodes[0].getbalances()["mine"])
+
+ self.nodes[1].sendtoaddress(retaddr, 5)
+ self.nodes[0].generate(1)
+ self.sync_all()
+
+ # listunspent should show 1 single, unused 5 btc output
+ assert_unspent(self.nodes[1], total_count=1, total_sum=5, reused_supported=True, reused_count=0)
+ # getbalances should show no used, 5 btc trusted
+ assert_balances(self.nodes[1], mine={"used": 0, "trusted": 5})
+
+ self.nodes[0].sendtoaddress(fundaddr, 10)
+ self.nodes[0].generate(1)
+ self.sync_all()
+
+ # listunspent should show 2 total outputs (5, 10 btc), one unused (5), one reused (10)
+ assert_unspent(self.nodes[1], total_count=2, total_sum=15, reused_count=1, reused_sum=10)
+ # getbalances should show 10 used, 5 btc trusted
+ assert_balances(self.nodes[1], mine={"used": 10, "trusted": 5})
+
+ self.nodes[1].sendtoaddress(address=retaddr, amount=10, avoid_reuse=False)
+
+ # listunspent should show 1 total outputs (5 btc), unused
+ assert_unspent(self.nodes[1], total_count=1, total_sum=5, reused_count=0)
+ # getbalances should show no used, 5 btc trusted
+ assert_balances(self.nodes[1], mine={"used": 0, "trusted": 5})
+
+ # node 1 should now have about 5 btc left (for both cases)
+ assert_approx(self.nodes[1].getbalance(), 5, 0.001)
+ assert_approx(self.nodes[1].getbalance(avoid_reuse=False), 5, 0.001)
+
+ def test_fund_send_fund_send(self):
+ '''
+ Test the simple case where [1] generates a new address A, then
+ [0] sends 10 BTC to A.
+ [1] spends 5 BTC from A. (leaving roughly 5 BTC useable)
+ [0] sends 10 BTC to A again.
+ [1] tries to spend 10 BTC (fails; dirty).
+ [1] tries to spend 4 BTC (succeeds; change address sufficient)
+ '''
+
+ fundaddr = self.nodes[1].getnewaddress()
+ retaddr = self.nodes[0].getnewaddress()
+
+ self.nodes[0].sendtoaddress(fundaddr, 10)
+ self.nodes[0].generate(1)
+ self.sync_all()
+
+ # listunspent should show 1 single, unused 10 btc output
+ assert_unspent(self.nodes[1], total_count=1, total_sum=10, reused_supported=True, reused_count=0)
+ # getbalances should show no used, 10 btc trusted
+ assert_balances(self.nodes[1], mine={"used": 0, "trusted": 10})
+
+ self.nodes[1].sendtoaddress(retaddr, 5)
+ self.nodes[0].generate(1)
+ self.sync_all()
+
+ # listunspent should show 1 single, unused 5 btc output
+ assert_unspent(self.nodes[1], total_count=1, total_sum=5, reused_supported=True, reused_count=0)
+ # getbalances should show no used, 5 btc trusted
+ assert_balances(self.nodes[1], mine={"used": 0, "trusted": 5})
+
+ self.nodes[0].sendtoaddress(fundaddr, 10)
+ self.nodes[0].generate(1)
+ self.sync_all()
+
+ # listunspent should show 2 total outputs (5, 10 btc), one unused (5), one reused (10)
+ assert_unspent(self.nodes[1], total_count=2, total_sum=15, reused_count=1, reused_sum=10)
+ # getbalances should show 10 used, 5 btc trusted
+ assert_balances(self.nodes[1], mine={"used": 10, "trusted": 5})
+
+ # node 1 should now have a balance of 5 (no dirty) or 15 (including dirty)
+ assert_approx(self.nodes[1].getbalance(), 5, 0.001)
+ assert_approx(self.nodes[1].getbalance(avoid_reuse=False), 15, 0.001)
+
+ assert_raises_rpc_error(-6, "Insufficient funds", self.nodes[1].sendtoaddress, retaddr, 10)
+
+ self.nodes[1].sendtoaddress(retaddr, 4)
+
+ # listunspent should show 2 total outputs (1, 10 btc), one unused (1), one reused (10)
+ assert_unspent(self.nodes[1], total_count=2, total_sum=11, reused_count=1, reused_sum=10)
+ # getbalances should show 10 used, 1 btc trusted
+ assert_balances(self.nodes[1], mine={"used": 10, "trusted": 1})
+
+ # node 1 should now have about 1 btc left (no dirty) and 11 (including dirty)
+ assert_approx(self.nodes[1].getbalance(), 1, 0.001)
+ assert_approx(self.nodes[1].getbalance(avoid_reuse=False), 11, 0.001)
+
+if __name__ == '__main__':
+ AvoidReuseTest().main()
diff --git a/test/functional/wallet_balance.py b/test/functional/wallet_balance.py
index 15f2195e21..137c77a51e 100755
--- a/test/functional/wallet_balance.py
+++ b/test/functional/wallet_balance.py
@@ -203,6 +203,8 @@ class WalletTest(BitcoinTestFramework):
self.log.info('Put txs back into mempool of node 1 (not node 0)')
self.nodes[0].invalidateblock(block_reorg)
self.nodes[1].invalidateblock(block_reorg)
+ self.sync_blocks()
+ self.nodes[0].syncwithvalidationinterfacequeue()
assert_equal(self.nodes[0].getbalance(minconf=0), 0) # wallet txs not in the mempool are untrusted
self.nodes[0].generatetoaddress(1, ADDRESS_WATCHONLY)
assert_equal(self.nodes[0].getbalance(minconf=0), 0) # wallet txs not in the mempool are untrusted
diff --git a/test/functional/wallet_basic.py b/test/functional/wallet_basic.py
index daa834b5b8..34e84fcf55 100755
--- a/test/functional/wallet_basic.py
+++ b/test/functional/wallet_basic.py
@@ -20,6 +20,9 @@ from test_framework.util import (
class WalletTest(BitcoinTestFramework):
def set_test_params(self):
self.num_nodes = 4
+ self.extra_args = [[
+ "-acceptnonstdtxn=1",
+ ]] * self.num_nodes
self.setup_clean_chain = True
def skip_test_if_missing_module(self):
diff --git a/test/functional/wallet_bumpfee.py b/test/functional/wallet_bumpfee.py
index 568b1f28d8..030eb50791 100755
--- a/test/functional/wallet_bumpfee.py
+++ b/test/functional/wallet_bumpfee.py
@@ -80,6 +80,7 @@ class BumpFeeTest(BitcoinTestFramework):
test_bumpfee_metadata(rbf_node, dest_address)
test_locked_wallet_fails(rbf_node, dest_address)
test_change_script_match(rbf_node, dest_address)
+ test_maxtxfee_fails(self, rbf_node, dest_address)
# These tests wipe out a number of utxos that are expected in other tests
test_small_output_with_feerate_succeeds(rbf_node, dest_address)
test_no_more_inputs_fails(rbf_node, dest_address)
@@ -248,6 +249,15 @@ def test_settxfee(rbf_node, dest_address):
rbf_node.settxfee(Decimal("0.00000000")) # unset paytxfee
+def test_maxtxfee_fails(test, rbf_node, dest_address):
+ test.restart_node(1, ['-maxtxfee=0.00003'] + test.extra_args[1])
+ rbf_node.walletpassphrase(WALLET_PASSPHRASE, WALLET_PASSPHRASE_TIMEOUT)
+ rbfid = spend_one_input(rbf_node, dest_address)
+ assert_raises_rpc_error(-4, "Unable to create transaction: Fee exceeds maximum configured by -maxtxfee", rbf_node.bumpfee, rbfid)
+ test.restart_node(1, test.extra_args[1])
+ rbf_node.walletpassphrase(WALLET_PASSPHRASE, WALLET_PASSPHRASE_TIMEOUT)
+
+
def test_rebumping(rbf_node, dest_address):
# check that re-bumping the original tx fails, but bumping the bumper succeeds
rbfid = spend_one_input(rbf_node, dest_address)
@@ -304,7 +314,9 @@ def test_unconfirmed_not_spendable(rbf_node, rbf_node_address):
def test_bumpfee_metadata(rbf_node, dest_address):
- rbfid = rbf_node.sendtoaddress(dest_address, Decimal("0.00100000"), "comment value", "to value")
+ assert(rbf_node.getbalance() < 49)
+ rbf_node.generatetoaddress(101, rbf_node.getnewaddress())
+ rbfid = rbf_node.sendtoaddress(dest_address, 49, "comment value", "to value")
bumped_tx = rbf_node.bumpfee(rbfid)
bumped_wtx = rbf_node.gettransaction(bumped_tx["txid"])
assert_equal(bumped_wtx["comment"], "comment value")
@@ -360,7 +372,7 @@ def submit_block_with_tx(node, tx):
block.hashMerkleRoot = block.calc_merkle_root()
add_witness_commitment(block)
block.solve()
- node.submitblock(block.serialize(True).hex())
+ node.submitblock(block.serialize().hex())
return block
def test_no_more_inputs_fails(rbf_node, dest_address):
diff --git a/test/functional/wallet_create_tx.py b/test/functional/wallet_create_tx.py
index 0b584a0bb2..330de8b0fc 100755
--- a/test/functional/wallet_create_tx.py
+++ b/test/functional/wallet_create_tx.py
@@ -6,6 +6,7 @@
from test_framework.test_framework import BitcoinTestFramework
from test_framework.util import (
assert_equal,
+ assert_raises_rpc_error,
)
from test_framework.blocktools import (
TIME_GENESIS_BLOCK,
@@ -26,6 +27,10 @@ class CreateTxWalletTest(BitcoinTestFramework):
self.nodes[0].generate(200)
self.nodes[0].setmocktime(0)
+ self.test_anti_fee_sniping()
+ self.test_tx_size_too_large()
+
+ def test_anti_fee_sniping(self):
self.log.info('Check that we have some (old) blocks and that anti-fee-sniping is disabled')
assert_equal(self.nodes[0].getblockchaininfo()['blocks'], 200)
txid = self.nodes[0].sendtoaddress(self.nodes[0].getnewaddress(), 1)
@@ -38,6 +43,40 @@ class CreateTxWalletTest(BitcoinTestFramework):
tx = self.nodes[0].decoderawtransaction(self.nodes[0].gettransaction(txid)['hex'])
assert 0 < tx['locktime'] <= 201
+ def test_tx_size_too_large(self):
+ # More than 10kB of outputs, so that we hit -maxtxfee with a high feerate
+ outputs = {self.nodes[0].getnewaddress(address_type='bech32'): 0.000025 for i in range(400)}
+ raw_tx = self.nodes[0].createrawtransaction(inputs=[], outputs=outputs)
+
+ for fee_setting in ['-minrelaytxfee=0.01', '-mintxfee=0.01', '-paytxfee=0.01']:
+ self.log.info('Check maxtxfee in combination with {}'.format(fee_setting))
+ self.restart_node(0, extra_args=[fee_setting])
+ assert_raises_rpc_error(
+ -6,
+ "Fee exceeds maximum configured by -maxtxfee",
+ lambda: self.nodes[0].sendmany(dummy="", amounts=outputs),
+ )
+ assert_raises_rpc_error(
+ -4,
+ "Fee exceeds maximum configured by -maxtxfee",
+ lambda: self.nodes[0].fundrawtransaction(hexstring=raw_tx),
+ )
+
+ self.log.info('Check maxtxfee in combination with settxfee')
+ self.restart_node(0)
+ self.nodes[0].settxfee(0.01)
+ assert_raises_rpc_error(
+ -6,
+ "Fee exceeds maximum configured by -maxtxfee",
+ lambda: self.nodes[0].sendmany(dummy="", amounts=outputs),
+ )
+ assert_raises_rpc_error(
+ -4,
+ "Fee exceeds maximum configured by -maxtxfee",
+ lambda: self.nodes[0].fundrawtransaction(hexstring=raw_tx),
+ )
+ self.nodes[0].settxfee(0)
+
if __name__ == '__main__':
CreateTxWalletTest().main()
diff --git a/test/functional/wallet_encryption.py b/test/functional/wallet_encryption.py
index c514b7e0b4..fbcb4e75ba 100755
--- a/test/functional/wallet_encryption.py
+++ b/test/functional/wallet_encryption.py
@@ -49,7 +49,7 @@ class WalletEncryptionTest(BitcoinTestFramework):
assert_equal(privkey, self.nodes[0].dumpprivkey(address))
# Check that the timeout is right
- time.sleep(2)
+ time.sleep(3)
assert_raises_rpc_error(-13, "Please enter the wallet passphrase with walletpassphrase first", self.nodes[0].dumpprivkey, address)
# Test wrong passphrase
diff --git a/test/functional/wallet_importmulti.py b/test/functional/wallet_importmulti.py
index 7d652a7825..e19c7919a9 100755
--- a/test/functional/wallet_importmulti.py
+++ b/test/functional/wallet_importmulti.py
@@ -571,6 +571,7 @@ class ImportMultiTest(BitcoinTestFramework):
# Test ranged descriptor fails if range is not specified
xpriv = "tprv8ZgxMBicQKsPeuVhWwi6wuMQGfPKi9Li5GtX35jVNknACgqe3CY4g5xgkfDDJcmtF7o1QnxWDRYw4H5P26PXq7sbcUkEqeR4fg3Kxp2tigg"
addresses = ["2N7yv4p8G8yEaPddJxY41kPihnWvs39qCMf", "2MsHxyb2JS3pAySeNUsJ7mNnurtpeenDzLA"] # hdkeypath=m/0'/0'/0' and 1'
+ addresses += ["bcrt1qrd3n235cj2czsfmsuvqqpr3lu6lg0ju7scl8gn", "bcrt1qfqeppuvj0ww98r6qghmdkj70tv8qpchehegrg8"] # wpkh subscripts corresponding to the above addresses
desc = "sh(wpkh(" + xpriv + "/0'/0'/*'" + "))"
self.log.info("Ranged descriptor import should fail without a specified range")
self.test_importmulti({"desc": descsum_create(desc),
@@ -579,17 +580,17 @@ class ImportMultiTest(BitcoinTestFramework):
error_code=-8,
error_message='Descriptor is ranged, please specify the range')
- # Test importing of a ranged descriptor without keys
+ # Test importing of a ranged descriptor with xpriv
self.log.info("Should import the ranged descriptor with specified range as solvable")
self.test_importmulti({"desc": descsum_create(desc),
"timestamp": "now",
"range": 1},
- success=True,
- warnings=["Some private keys are missing, outputs will be considered watchonly. If this is intentional, specify the watchonly flag."])
+ success=True)
for address in addresses:
test_address(self.nodes[1],
- key.p2sh_p2wpkh_addr,
- solvable=True)
+ address,
+ solvable=True,
+ ismine=True)
self.test_importmulti({"desc": descsum_create(desc), "timestamp": "now", "range": -1},
success=False, error_code=-8, error_message='End of range is too high')
@@ -606,6 +607,23 @@ class ImportMultiTest(BitcoinTestFramework):
self.test_importmulti({"desc": descsum_create(desc), "timestamp": "now", "range": [0, 1000001]},
success=False, error_code=-8, error_message='Range is too large')
+ # Test importing a descriptor containing a WIF private key
+ wif_priv = "cTe1f5rdT8A8DFgVWTjyPwACsDPJM9ff4QngFxUixCSvvbg1x6sh"
+ address = "2MuhcG52uHPknxDgmGPsV18jSHFBnnRgjPg"
+ desc = "sh(wpkh(" + wif_priv + "))"
+ self.log.info("Should import a descriptor with a WIF private key as spendable")
+ self.test_importmulti({"desc": descsum_create(desc),
+ "timestamp": "now"},
+ success=True)
+ test_address(self.nodes[1],
+ address,
+ solvable=True,
+ ismine=True)
+
+ # dump the private key to ensure it matches what was imported
+ privkey = self.nodes[1].dumpprivkey(address)
+ assert_equal(privkey, wif_priv)
+
# Test importing of a P2PKH address via descriptor
key = get_key(self.nodes[0])
self.log.info("Should import a p2pkh address from descriptor")
diff --git a/test/functional/wallet_resendwallettransactions.py b/test/functional/wallet_resendwallettransactions.py
index 5810e94938..91d26e9cb3 100755
--- a/test/functional/wallet_resendwallettransactions.py
+++ b/test/functional/wallet_resendwallettransactions.py
@@ -57,8 +57,7 @@ class ResendWalletTransactionsTest(BitcoinTestFramework):
# after the last time we tried to broadcast. Use mocktime and give an extra minute to be sure.
block_time = int(time.time()) + 6 * 60
node.setmocktime(block_time)
- block = create_block(int(node.getbestblockhash(), 16), create_coinbase(node.getblockchaininfo()['blocks']), block_time)
- block.nVersion = 3
+ block = create_block(int(node.getbestblockhash(), 16), create_coinbase(node.getblockcount() + 1), block_time)
block.rehash()
block.solve()
node.submitblock(ToHex(block))
diff --git a/test/fuzz/test_runner.py b/test/fuzz/test_runner.py
index 1869f71753..f19cf924d2 100755
--- a/test/fuzz/test_runner.py
+++ b/test/fuzz/test_runner.py
@@ -106,8 +106,10 @@ def run_once(*, corpus, test_list, build_dir, export_coverage):
os.path.join(corpus, t),
]
logging.debug('Run {} with args {}'.format(t, args))
- output = subprocess.run(args, check=True, stderr=subprocess.PIPE, universal_newlines=True).stderr
+ result = subprocess.run(args, stderr=subprocess.PIPE, universal_newlines=True)
+ output = result.stderr
logging.debug('Output: {}'.format(output))
+ result.check_returncode()
if not export_coverage:
continue
for l in output.splitlines():
diff --git a/test/lint/commit-script-check.sh b/test/lint/commit-script-check.sh
index 4267f9fa0d..5603456e62 100755
--- a/test/lint/commit-script-check.sh
+++ b/test/lint/commit-script-check.sh
@@ -18,12 +18,12 @@ if test "x$1" = "x"; then
fi
RET=0
-PREV_BRANCH=`git name-rev --name-only HEAD`
-PREV_HEAD=`git rev-parse HEAD`
-for commit in `git rev-list --reverse $1`; do
+PREV_BRANCH=$(git name-rev --name-only HEAD)
+PREV_HEAD=$(git rev-parse HEAD)
+for commit in $(git rev-list --reverse $1); do
if git rev-list -n 1 --pretty="%s" $commit | grep -q "^scripted-diff:"; then
git checkout --quiet $commit^ || exit
- SCRIPT="`git rev-list --format=%b -n1 $commit | sed '/^-BEGIN VERIFY SCRIPT-$/,/^-END VERIFY SCRIPT-$/{//!b};d'`"
+ SCRIPT="$(git rev-list --format=%b -n1 $commit | sed '/^-BEGIN VERIFY SCRIPT-$/,/^-END VERIFY SCRIPT-$/{//!b};d')"
if test "x$SCRIPT" = "x"; then
echo "Error: missing script for: $commit"
echo "Failed"
diff --git a/test/lint/extended-lint-all.sh b/test/lint/extended-lint-all.sh
new file mode 100755
index 0000000000..65c51e02f5
--- /dev/null
+++ b/test/lint/extended-lint-all.sh
@@ -0,0 +1,26 @@
+#!/usr/bin/env bash
+#
+# Copyright (c) 2019 The Bitcoin Core developers
+# Distributed under the MIT software license, see the accompanying
+# file COPYING or http://www.opensource.org/licenses/mit-license.php.
+#
+# This script runs all contrib/devtools/extended-lint-*.sh files, and fails if
+# any exit with a non-zero status code.
+
+# This script is intentionally locale dependent by not setting "export LC_ALL=C"
+# in order to allow for the executed lint scripts to opt in or opt out of locale
+# dependence themselves.
+
+set -u
+
+SCRIPTDIR=$(dirname "${BASH_SOURCE[0]}")
+LINTALL=$(basename "${BASH_SOURCE[0]}")
+
+for f in "${SCRIPTDIR}"/extended-lint-*.sh; do
+ if [ "$(basename "$f")" != "$LINTALL" ]; then
+ if ! "$f"; then
+ echo "^---- failure generated from $f"
+ exit 1
+ fi
+ fi
+done
diff --git a/test/lint/extended-lint-cppcheck.sh b/test/lint/extended-lint-cppcheck.sh
new file mode 100755
index 0000000000..47df25ba6b
--- /dev/null
+++ b/test/lint/extended-lint-cppcheck.sh
@@ -0,0 +1,80 @@
+#!/usr/bin/env bash
+#
+# Copyright (c) 2019 The Bitcoin Core developers
+# Distributed under the MIT software license, see the accompanying
+# file COPYING or http://www.opensource.org/licenses/mit-license.php.
+#
+
+export LC_ALL=C
+
+ENABLED_CHECKS=(
+ "Class '.*' has a constructor with 1 argument that is not explicit."
+ "Struct '.*' has a constructor with 1 argument that is not explicit."
+)
+
+IGNORED_WARNINGS=(
+ "src/arith_uint256.h:.* Class 'arith_uint256' has a constructor with 1 argument that is not explicit."
+ "src/arith_uint256.h:.* Class 'base_uint < 256 >' has a constructor with 1 argument that is not explicit."
+ "src/arith_uint256.h:.* Class 'base_uint' has a constructor with 1 argument that is not explicit."
+ "src/coins.h:.* Class 'CCoinsViewBacked' has a constructor with 1 argument that is not explicit."
+ "src/coins.h:.* Class 'CCoinsViewCache' has a constructor with 1 argument that is not explicit."
+ "src/coins.h:.* Class 'CCoinsViewCursor' has a constructor with 1 argument that is not explicit."
+ "src/net.h:.* Class 'CNetMessage' has a constructor with 1 argument that is not explicit."
+ "src/policy/feerate.h:.* Class 'CFeeRate' has a constructor with 1 argument that is not explicit."
+ "src/prevector.h:.* Class 'const_iterator' has a constructor with 1 argument that is not explicit."
+ "src/prevector.h:.* Class 'const_reverse_iterator' has a constructor with 1 argument that is not explicit."
+ "src/prevector.h:.* Class 'iterator' has a constructor with 1 argument that is not explicit."
+ "src/prevector.h:.* Class 'reverse_iterator' has a constructor with 1 argument that is not explicit."
+ "src/primitives/block.h:.* Class 'CBlock' has a constructor with 1 argument that is not explicit."
+ "src/primitives/transaction.h:.* Class 'CTransaction' has a constructor with 1 argument that is not explicit."
+ "src/protocol.h:.* Class 'CMessageHeader' has a constructor with 1 argument that is not explicit."
+ "src/qt/guiutil.h:.* Class 'ItemDelegate' has a constructor with 1 argument that is not explicit."
+ "src/rpc/util.h:.* Struct 'RPCResults' has a constructor with 1 argument that is not explicit."
+ "src/rpc/util.h:.* style: Struct 'UniValueType' has a constructor with 1 argument that is not explicit."
+ "src/script/descriptor.cpp:.* Class 'AddressDescriptor' has a constructor with 1 argument that is not explicit."
+ "src/script/descriptor.cpp:.* Class 'ComboDescriptor' has a constructor with 1 argument that is not explicit."
+ "src/script/descriptor.cpp:.* Class 'ConstPubkeyProvider' has a constructor with 1 argument that is not explicit."
+ "src/script/descriptor.cpp:.* Class 'PKDescriptor' has a constructor with 1 argument that is not explicit."
+ "src/script/descriptor.cpp:.* Class 'PKHDescriptor' has a constructor with 1 argument that is not explicit."
+ "src/script/descriptor.cpp:.* Class 'RawDescriptor' has a constructor with 1 argument that is not explicit."
+ "src/script/descriptor.cpp:.* Class 'SHDescriptor' has a constructor with 1 argument that is not explicit."
+ "src/script/descriptor.cpp:.* Class 'WPKHDescriptor' has a constructor with 1 argument that is not explicit."
+ "src/script/descriptor.cpp:.* Class 'WSHDescriptor' has a constructor with 1 argument that is not explicit."
+ "src/script/script.h:.* Class 'CScript' has a constructor with 1 argument that is not explicit."
+ "src/script/standard.h:.* Class 'CScriptID' has a constructor with 1 argument that is not explicit."
+ "src/support/allocators/secure.h:.* Struct 'secure_allocator < char >' has a constructor with 1 argument that is not explicit."
+ "src/support/allocators/secure.h:.* Struct 'secure_allocator < RNGState >' has a constructor with 1 argument that is not explicit."
+ "src/support/allocators/secure.h:.* Struct 'secure_allocator < unsigned char >' has a constructor with 1 argument that is not explicit."
+ "src/support/allocators/zeroafterfree.h:.* Struct 'zero_after_free_allocator < char >' has a constructor with 1 argument that is not explicit."
+ "src/test/checkqueue_tests.cpp:.* Struct 'FailingCheck' has a constructor with 1 argument that is not explicit."
+ "src/test/checkqueue_tests.cpp:.* Struct 'MemoryCheck' has a constructor with 1 argument that is not explicit."
+ "src/test/checkqueue_tests.cpp:.* Struct 'UniqueCheck' has a constructor with 1 argument that is not explicit."
+ "src/wallet/db.h:.* Class 'BerkeleyEnvironment' has a constructor with 1 argument that is not explicit."
+)
+
+if ! command -v cppcheck > /dev/null; then
+ echo "Skipping cppcheck linting since cppcheck is not installed. Install by running \"apt install cppcheck\""
+ exit 0
+fi
+
+function join_array {
+ local IFS="$1"
+ shift
+ echo "$*"
+}
+
+ENABLED_CHECKS_REGEXP=$(join_array "|" "${ENABLED_CHECKS[@]}")
+IGNORED_WARNINGS_REGEXP=$(join_array "|" "${IGNORED_WARNINGS[@]}")
+WARNINGS=$(git ls-files -- "*.cpp" "*.h" ":(exclude)src/leveldb/" ":(exclude)src/secp256k1/" ":(exclude)src/univalue/" | \
+ xargs cppcheck --enable=all -j "$(getconf _NPROCESSORS_ONLN)" --language=c++ --std=c++11 --template=gcc -D__cplusplus -DCLIENT_VERSION_BUILD -DCLIENT_VERSION_IS_RELEASE -DCLIENT_VERSION_MAJOR -DCLIENT_VERSION_MINOR -DCLIENT_VERSION_REVISION -DCOPYRIGHT_YEAR -DDEBUG -DHAVE_WORKING_BOOST_SLEEP_FOR -I src/ -q 2>&1 | sort -u | \
+ grep -E "${ENABLED_CHECKS_REGEXP}" | \
+ grep -vE "${IGNORED_WARNINGS_REGEXP}")
+if [[ ${WARNINGS} != "" ]]; then
+ echo "${WARNINGS}"
+ echo
+ echo "Advice not applicable in this specific case? Add an exception by updating"
+ echo "IGNORED_WARNINGS in $0"
+ # Uncomment to enforce the developer note policy "By default, declare single-argument constructors `explicit`"
+ # exit 1
+fi
+exit 0
diff --git a/test/lint/lint-circular-dependencies.sh b/test/lint/lint-circular-dependencies.sh
index e1a99abc49..8607fc4371 100755
--- a/test/lint/lint-circular-dependencies.sh
+++ b/test/lint/lint-circular-dependencies.sh
@@ -12,7 +12,6 @@ EXPECTED_CIRCULAR_DEPENDENCIES=(
"chainparamsbase -> util/system -> chainparamsbase"
"index/txindex -> validation -> index/txindex"
"policy/fees -> txmempool -> policy/fees"
- "policy/policy -> policy/settings -> policy/policy"
"qt/addresstablemodel -> qt/walletmodel -> qt/addresstablemodel"
"qt/bantablemodel -> qt/clientmodel -> qt/bantablemodel"
"qt/bitcoingui -> qt/utilitydialog -> qt/bitcoingui"
@@ -25,18 +24,13 @@ EXPECTED_CIRCULAR_DEPENDENCIES=(
"qt/transactiontablemodel -> qt/walletmodel -> qt/transactiontablemodel"
"qt/walletmodel -> qt/walletmodeltransaction -> qt/walletmodel"
"txmempool -> validation -> txmempool"
- "validation -> validationinterface -> validation"
"wallet/coincontrol -> wallet/wallet -> wallet/coincontrol"
"wallet/fees -> wallet/wallet -> wallet/fees"
"wallet/wallet -> wallet/walletdb -> wallet/wallet"
"policy/fees -> txmempool -> validation -> policy/fees"
- "policy/rbf -> txmempool -> validation -> policy/rbf"
- "qt/addressbookpage -> qt/bitcoingui -> qt/walletview -> qt/addressbookpage"
"qt/guiutil -> qt/walletmodel -> qt/optionsmodel -> qt/guiutil"
"txmempool -> validation -> validationinterface -> txmempool"
- "qt/addressbookpage -> qt/bitcoingui -> qt/walletview -> qt/receivecoinsdialog -> qt/addressbookpage"
- "qt/addressbookpage -> qt/bitcoingui -> qt/walletview -> qt/signverifymessagedialog -> qt/addressbookpage"
- "qt/addressbookpage -> qt/bitcoingui -> qt/walletview -> qt/sendcoinsdialog -> qt/sendcoinsentry -> qt/addressbookpage"
+ "wallet/ismine -> wallet/wallet -> wallet/ismine"
)
EXIT_CODE=0
@@ -45,7 +39,7 @@ CIRCULAR_DEPENDENCIES=()
IFS=$'\n'
for CIRC in $(cd src && ../contrib/devtools/circular-dependencies.py {*,*/*,*/*/*}.{h,cpp} | sed -e 's/^Circular dependency: //'); do
- CIRCULAR_DEPENDENCIES+=($CIRC)
+ CIRCULAR_DEPENDENCIES+=( "$CIRC" )
IS_EXPECTED_CIRC=0
for EXPECTED_CIRC in "${EXPECTED_CIRCULAR_DEPENDENCIES[@]}"; do
if [[ "${CIRC}" == "${EXPECTED_CIRC}" ]]; then
diff --git a/test/lint/lint-format-strings.py b/test/lint/lint-format-strings.py
index 224e62f04a..99b0eaa38e 100755
--- a/test/lint/lint-format-strings.py
+++ b/test/lint/lint-format-strings.py
@@ -16,8 +16,7 @@ FALSE_POSITIVES = [
("src/dbwrapper.cpp", "vsnprintf(p, limit - p, format, backup_ap)"),
("src/index/base.cpp", "FatalError(const char* fmt, const Args&... args)"),
("src/netbase.cpp", "LogConnectFailure(bool manual_connection, const char* fmt, const Args&... args)"),
- ("src/util/system.cpp", "strprintf(_(COPYRIGHT_HOLDERS), _(COPYRIGHT_HOLDERS_SUBSTITUTION))"),
- ("src/util/system.cpp", "strprintf(COPYRIGHT_HOLDERS, COPYRIGHT_HOLDERS_SUBSTITUTION)"),
+ ("src/util/system.cpp", "strprintf(_(COPYRIGHT_HOLDERS), COPYRIGHT_HOLDERS_SUBSTITUTION)"),
("src/wallet/wallet.h", "WalletLogPrintf(std::string fmt, Params... parameters)"),
("src/wallet/wallet.h", "LogPrintf((\"%s \" + fmt).c_str(), GetDisplayName(), parameters...)"),
("src/logging.h", "LogPrintf(const char* fmt, const Args&... args)"),
diff --git a/test/lint/lint-format-strings.sh b/test/lint/lint-format-strings.sh
index c994ae3f4d..cb630c78ad 100755
--- a/test/lint/lint-format-strings.sh
+++ b/test/lint/lint-format-strings.sh
@@ -13,6 +13,7 @@ export LC_ALL=C
FUNCTION_NAMES_AND_NUMBER_OF_LEADING_ARGUMENTS=(
"FatalError,0"
"fprintf,1"
+ "tfm::format,1" # Assuming tfm::::format(std::ostream&, ...
"LogConnectFailure,1"
"LogPrint,1"
"LogPrintf,0"
diff --git a/test/lint/lint-locale-dependence.sh b/test/lint/lint-locale-dependence.sh
index 2b6c78c2c8..9a1aa766f7 100755
--- a/test/lint/lint-locale-dependence.sh
+++ b/test/lint/lint-locale-dependence.sh
@@ -8,7 +8,6 @@ KNOWN_VIOLATIONS=(
"src/dbwrapper.cpp:.*vsnprintf"
"src/httprpc.cpp.*trim"
"src/init.cpp:.*atoi"
- "src/init.cpp:.*fprintf"
"src/qt/rpcconsole.cpp:.*atoi"
"src/rest.cpp:.*strtol"
"src/test/dbwrapper_tests.cpp:.*snprintf"
@@ -85,7 +84,7 @@ LOCALE_DEPENDENT_FUNCTIONS=(
mbtowc # LC_CTYPE
mktime
normalize # boost::locale::normalize
-# printf # LC_NUMERIC
+ printf # LC_NUMERIC
putwc
putwchar
scanf # LC_NUMERIC
@@ -189,8 +188,7 @@ GIT_GREP_OUTPUT=$(git grep -E "[^a-zA-Z0-9_\`'\"<>](${REGEXP_LOCALE_DEPENDENT_FU
EXIT_CODE=0
for LOCALE_DEPENDENT_FUNCTION in "${LOCALE_DEPENDENT_FUNCTIONS[@]}"; do
MATCHES=$(grep -E "[^a-zA-Z0-9_\`'\"<>]${LOCALE_DEPENDENT_FUNCTION}(_r|_s)?[^a-zA-Z0-9_\`'\"<>]" <<< "${GIT_GREP_OUTPUT}" | \
- grep -vE "\.(c|cpp|h):\s*(//|\*|/\*|\").*${LOCALE_DEPENDENT_FUNCTION}" | \
- grep -vE 'fprintf\(.*(stdout|stderr)')
+ grep -vE "\.(c|cpp|h):\s*(//|\*|/\*|\").*${LOCALE_DEPENDENT_FUNCTION}")
if [[ ${REGEXP_IGNORE_EXTERNAL_DEPENDENCIES} != "" ]]; then
MATCHES=$(grep -vE "${REGEXP_IGNORE_EXTERNAL_DEPENDENCIES}" <<< "${MATCHES}")
fi
diff --git a/test/lint/lint-logs.sh b/test/lint/lint-logs.sh
index 1afd4cfc1a..632ed7c812 100755
--- a/test/lint/lint-logs.sh
+++ b/test/lint/lint-logs.sh
@@ -19,6 +19,7 @@ UNTERMINATED_LOGS=$(git grep --extended-regexp "LogPrintf?\(" -- "*.cpp" | \
grep -v "LogPrint()" | \
grep -v "LogPrintf()")
if [[ ${UNTERMINATED_LOGS} != "" ]]; then
+ # shellcheck disable=SC2028
echo "All calls to LogPrintf() and LogPrint() should be terminated with \\n"
echo
echo "${UNTERMINATED_LOGS}"
diff --git a/test/lint/lint-shell.sh b/test/lint/lint-shell.sh
index 6f5e6546c5..69fc3cf368 100755
--- a/test/lint/lint-shell.sh
+++ b/test/lint/lint-shell.sh
@@ -23,25 +23,9 @@ fi
# Disabled warnings:
disabled=(
- SC1087 # Use braces when expanding arrays, e.g. ${array[idx]} (or ${var}[.. to quiet).
- SC2001 # See if you can use ${variable//search/replace} instead.
- SC2004 # $/${} is unnecessary on arithmetic variables.
- SC2005 # Useless echo? Instead of 'echo $(cmd)', just use 'cmd'.
- SC2006 # Use $(..) instead of legacy `..`.
- SC2016 # Expressions don't expand in single quotes, use double quotes for that.
- SC2028 # echo won't expand escape sequences. Consider printf.
SC2046 # Quote this to prevent word splitting.
- SC2048 # Use "$@" (with quotes) to prevent whitespace problems.
- SC2066 # Since you double quoted this, it will not word split, and the loop will only run once.
SC2086 # Double quote to prevent globbing and word splitting.
- SC2116 # Useless echo? Instead of 'cmd $(echo foo)', just use 'cmd foo'.
SC2162 # read without -r will mangle backslashes.
- SC2166 # Prefer [ p ] {&&,||} [ q ] as [ p -{a,o} q ] is not well defined.
- SC2181 # Check exit code directly with e.g. 'if mycmd;', not indirectly with $?.
- SC2206 # Quote to prevent word splitting, or split robustly with mapfile or read -a.
- SC2207 # Prefer mapfile or read -a to split command output (or quote to avoid splitting).
- SC2230 # which is non-standard. Use builtin 'command -v' instead.
- SC2236 # Don't force -n instead of ! -z.
)
shellcheck -e "$(IFS=","; echo "${disabled[*]}")" \
$(git ls-files -- "*.sh" | grep -vE 'src/(secp256k1|univalue)/')
diff --git a/test/lint/lint-spelling.ignore-words.txt b/test/lint/lint-spelling.ignore-words.txt
index f0415443db..a25de2435b 100644
--- a/test/lint/lint-spelling.ignore-words.txt
+++ b/test/lint/lint-spelling.ignore-words.txt
@@ -5,3 +5,10 @@ mut
objext
unselect
useable
+wit
+unparseable
+copyable
+cachable
+errorstring
+keyserver
+homogenous
diff --git a/test/sanitizer_suppressions/lsan b/test/sanitizer_suppressions/lsan
index 6f15c0f1d4..90a92a5115 100644
--- a/test/sanitizer_suppressions/lsan
+++ b/test/sanitizer_suppressions/lsan
@@ -4,3 +4,6 @@ leak:libqminimal
leak:libQt5Core
leak:libQt5Gui
leak:libQt5Widgets
+
+# false-positive due to use of secure_allocator<>
+leak:GetRNGState