aboutsummaryrefslogtreecommitdiff
path: root/test
diff options
context:
space:
mode:
Diffstat (limited to 'test')
-rw-r--r--test/config.ini.in1
-rwxr-xr-xtest/functional/combine_logs.py54
-rw-r--r--test/functional/data/invalid_txs.py6
-rwxr-xr-xtest/functional/feature_assumevalid.py8
-rwxr-xr-xtest/functional/feature_block.py26
-rwxr-xr-xtest/functional/feature_filelock.py2
-rwxr-xr-xtest/functional/feature_pruning.py13
-rwxr-xr-xtest/functional/interface_bitcoin_cli.py2
-rwxr-xr-xtest/functional/mempool_persist.py17
-rwxr-xr-xtest/functional/p2p_invalid_messages.py14
-rwxr-xr-xtest/functional/p2p_segwit.py62
-rwxr-xr-xtest/functional/rpc_createmultisig.py46
-rwxr-xr-xtest/functional/rpc_getblockfilter.py59
-rwxr-xr-xtest/functional/rpc_psbt.py7
-rwxr-xr-xtest/functional/rpc_rawtransaction.py4
-rw-r--r--test/functional/test_framework/key.py574
-rwxr-xr-xtest/functional/test_framework/mininode.py2
-rwxr-xr-xtest/functional/test_framework/test_framework.py15
-rwxr-xr-xtest/functional/test_runner.py15
-rwxr-xr-xtest/functional/wallet_balance.py110
-rwxr-xr-xtest/functional/wallet_bumpfee.py72
-rwxr-xr-xtest/functional/wallet_import_rescan.py6
-rwxr-xr-xtest/lint/check-doc.py34
-rwxr-xr-xtest/lint/lint-circular-dependencies.sh2
-rwxr-xr-xtest/lint/lint-python-dead-code.sh2
25 files changed, 786 insertions, 367 deletions
diff --git a/test/config.ini.in b/test/config.ini.in
index 6b7ef70659..060c553da2 100644
--- a/test/config.ini.in
+++ b/test/config.ini.in
@@ -6,6 +6,7 @@
# test/functional/test_runner.py and test/util/bitcoin-util-test.py
[environment]
+PACKAGE_NAME=@PACKAGE_NAME@
SRCDIR=@abs_top_srcdir@
BUILDDIR=@abs_top_builddir@
EXEEXT=@EXEEXT@
diff --git a/test/functional/combine_logs.py b/test/functional/combine_logs.py
index 5bb3b5c094..45ecaabe14 100755
--- a/test/functional/combine_logs.py
+++ b/test/functional/combine_logs.py
@@ -11,6 +11,7 @@ from collections import defaultdict, namedtuple
import heapq
import itertools
import os
+import pathlib
import re
import sys
import tempfile
@@ -51,9 +52,23 @@ def main():
if not args.testdir:
print("Opening latest test directory: {}".format(testdir), file=sys.stderr)
+ colors = defaultdict(lambda: '')
+ if args.color:
+ colors["test"] = "\033[0;36m" # CYAN
+ colors["node0"] = "\033[0;34m" # BLUE
+ colors["node1"] = "\033[0;32m" # GREEN
+ colors["node2"] = "\033[0;31m" # RED
+ colors["node3"] = "\033[0;33m" # YELLOW
+ colors["reset"] = "\033[0m" # Reset font color
+
log_events = read_logs(testdir)
- print_logs(log_events, color=args.color, html=args.html)
+ if args.html:
+ print_logs_html(log_events)
+ else:
+ print_logs_plain(log_events, colors)
+ print_node_warnings(testdir, colors)
+
def read_logs(tmp_dir):
"""Reads log files.
@@ -71,6 +86,26 @@ def read_logs(tmp_dir):
return heapq.merge(*[get_log_events(source, f) for source, f in files])
+def print_node_warnings(tmp_dir, colors):
+ """Print nodes' errors and warnings"""
+
+ warnings = []
+ for stream in ['stdout', 'stderr']:
+ for i in itertools.count():
+ folder = "{}/node{}/{}".format(tmp_dir, i, stream)
+ if not os.path.isdir(folder):
+ break
+ for (_, _, fns) in os.walk(folder):
+ for fn in fns:
+ warning = pathlib.Path('{}/{}'.format(folder, fn)).read_text().strip()
+ if warning:
+ warnings.append(("node{} {}".format(i, stream), warning))
+
+ print()
+ for w in warnings:
+ print("{} {} {} {}".format(colors[w[0].split()[0]], w[0], w[1], colors["reset"]))
+
+
def find_latest_test_dir():
"""Returns the latest tmpfile test directory prefix."""
tmpdir = tempfile.gettempdir()
@@ -127,18 +162,9 @@ def get_log_events(source, logfile):
except FileNotFoundError:
print("File %s could not be opened. Continuing without it." % logfile, file=sys.stderr)
-def print_logs(log_events, color=False, html=False):
- """Renders the iterator of log events into text or html."""
- if not html:
- colors = defaultdict(lambda: '')
- if color:
- colors["test"] = "\033[0;36m" # CYAN
- colors["node0"] = "\033[0;34m" # BLUE
- colors["node1"] = "\033[0;32m" # GREEN
- colors["node2"] = "\033[0;31m" # RED
- colors["node3"] = "\033[0;33m" # YELLOW
- colors["reset"] = "\033[0m" # Reset font color
+def print_logs_plain(log_events, colors):
+ """Renders the iterator of log events into text."""
for event in log_events:
lines = event.event.splitlines()
print("{0} {1: <5} {2} {3}".format(colors[event.source.rstrip()], event.source, lines[0], colors["reset"]))
@@ -146,7 +172,9 @@ def print_logs(log_events, color=False, html=False):
for line in lines[1:]:
print("{0}{1}{2}".format(colors[event.source.rstrip()], line, colors["reset"]))
- else:
+
+def print_logs_html(log_events):
+ """Renders the iterator of log events into html."""
try:
import jinja2
except ImportError:
diff --git a/test/functional/data/invalid_txs.py b/test/functional/data/invalid_txs.py
index f0991a284b..454eb583f7 100644
--- a/test/functional/data/invalid_txs.py
+++ b/test/functional/data/invalid_txs.py
@@ -71,9 +71,13 @@ class InputMissing(BadTxTemplate):
reject_reason = "bad-txns-vin-empty"
expect_disconnect = True
+ # We use a blank transaction here to make sure
+ # it is interpreted as a non-witness transaction.
+ # Otherwise the transaction will fail the
+ # "surpufluous witness" check during deserialization
+ # rather than the input count check.
def get_tx(self):
tx = CTransaction()
- tx.vout.append(CTxOut(0, sc.CScript([sc.OP_TRUE] * 100)))
tx.calc_sha256()
return tx
diff --git a/test/functional/feature_assumevalid.py b/test/functional/feature_assumevalid.py
index e7e4f84ad9..b7814bf33e 100755
--- a/test/functional/feature_assumevalid.py
+++ b/test/functional/feature_assumevalid.py
@@ -32,7 +32,7 @@ Start three nodes:
import time
from test_framework.blocktools import (create_block, create_coinbase)
-from test_framework.key import CECKey
+from test_framework.key import ECKey
from test_framework.messages import (
CBlockHeader,
COutPoint,
@@ -104,9 +104,9 @@ class AssumeValidTest(BitcoinTestFramework):
self.blocks = []
# Get a pubkey for the coinbase TXO
- coinbase_key = CECKey()
- coinbase_key.set_secretbytes(b"horsebattery")
- coinbase_pubkey = coinbase_key.get_pubkey()
+ coinbase_key = ECKey()
+ coinbase_key.generate()
+ coinbase_pubkey = coinbase_key.get_pubkey().get_bytes()
# Create the first block with a coinbase output to our key
height = 1
diff --git a/test/functional/feature_block.py b/test/functional/feature_block.py
index 9a9424b82b..3ad83cd2b3 100755
--- a/test/functional/feature_block.py
+++ b/test/functional/feature_block.py
@@ -14,7 +14,7 @@ from test_framework.blocktools import (
get_legacy_sigopcount_block,
MAX_BLOCK_SIGOPS,
)
-from test_framework.key import CECKey
+from test_framework.key import ECKey
from test_framework.messages import (
CBlock,
COIN,
@@ -86,9 +86,9 @@ class FullBlockTest(BitcoinTestFramework):
self.bootstrap_p2p() # Add one p2p connection to the node
self.block_heights = {}
- self.coinbase_key = CECKey()
- self.coinbase_key.set_secretbytes(b"horsebattery")
- self.coinbase_pubkey = self.coinbase_key.get_pubkey()
+ self.coinbase_key = ECKey()
+ self.coinbase_key.generate()
+ self.coinbase_pubkey = self.coinbase_key.get_pubkey().get_bytes()
self.tip = None
self.blocks = {}
self.genesis_hash = int(self.nodes[0].getbestblockhash(), 16)
@@ -146,20 +146,6 @@ class FullBlockTest(BitcoinTestFramework):
badtx = template.get_tx()
if TxTemplate != invalid_txs.InputMissing:
self.sign_tx(badtx, attempt_spend_tx)
- else:
- # Segwit is active in regtest at this point, so to deserialize a
- # transaction without any inputs correctly, we set the outputs
- # to an empty list. This is a hack, as the serialization of an
- # empty list of outputs is deserialized as flags==0 and thus
- # deserialization of the outputs is skipped.
- # A policy check requires "loose" txs to be of a minimum size,
- # so vtx is not set to be empty in the TxTemplate class and we
- # only apply the workaround where txs are not "loose", i.e. in
- # blocks.
- #
- # The workaround has the purpose that both sides calculate
- # the same tx hash in the merkle tree
- badtx.vout = []
badtx.rehash()
badblock = self.update_block(blockname, [badtx])
self.send_blocks(
@@ -528,7 +514,7 @@ class FullBlockTest(BitcoinTestFramework):
tx.vin.append(CTxIn(COutPoint(b39.vtx[i].sha256, 0), b''))
# Note: must pass the redeem_script (not p2sh_script) to the signature hash function
(sighash, err) = SignatureHash(redeem_script, tx, 1, SIGHASH_ALL)
- sig = self.coinbase_key.sign(sighash) + bytes(bytearray([SIGHASH_ALL]))
+ sig = self.coinbase_key.sign_ecdsa(sighash) + bytes(bytearray([SIGHASH_ALL]))
scriptSig = CScript([sig, redeem_script])
tx.vin[1].scriptSig = scriptSig
@@ -1284,7 +1270,7 @@ class FullBlockTest(BitcoinTestFramework):
tx.vin[0].scriptSig = CScript()
return
(sighash, err) = SignatureHash(spend_tx.vout[0].scriptPubKey, tx, 0, SIGHASH_ALL)
- tx.vin[0].scriptSig = CScript([self.coinbase_key.sign(sighash) + bytes(bytearray([SIGHASH_ALL]))])
+ tx.vin[0].scriptSig = CScript([self.coinbase_key.sign_ecdsa(sighash) + bytes(bytearray([SIGHASH_ALL]))])
def create_and_sign_transaction(self, spend_tx, value, script=CScript([OP_TRUE])):
tx = self.create_tx(spend_tx, 0, value, script)
diff --git a/test/functional/feature_filelock.py b/test/functional/feature_filelock.py
index 9fb0d35a68..ba116e41f5 100755
--- a/test/functional/feature_filelock.py
+++ b/test/functional/feature_filelock.py
@@ -23,7 +23,7 @@ class FilelockTest(BitcoinTestFramework):
self.log.info("Using datadir {}".format(datadir))
self.log.info("Check that we can't start a second bitcoind instance using the same datadir")
- expected_msg = "Error: Cannot obtain a lock on data directory {}. Bitcoin Core is probably already running.".format(datadir)
+ expected_msg = "Error: Cannot obtain a lock on data directory {0}. {1} is probably already running.".format(datadir, self.config['environment']['PACKAGE_NAME'])
self.nodes[1].assert_start_raises_init_error(extra_args=['-datadir={}'.format(self.nodes[0].datadir), '-noserver'], expected_msg=expected_msg)
if self.is_wallet_compiled():
diff --git a/test/functional/feature_pruning.py b/test/functional/feature_pruning.py
index 8fb7c49640..e2b3b2d544 100755
--- a/test/functional/feature_pruning.py
+++ b/test/functional/feature_pruning.py
@@ -35,16 +35,17 @@ def mine_large_blocks(node, n):
# followed by 950k of OP_NOP. This would be non-standard in a non-coinbase
# transaction but is consensus valid.
+ # Set the nTime if this is the first time this function has been called.
+ # A static variable ensures that time is monotonicly increasing and is therefore
+ # different for each block created => blockhash is unique.
+ if "nTimes" not in mine_large_blocks.__dict__:
+ mine_large_blocks.nTime = 0
+
# Get the block parameters for the first block
big_script = CScript([OP_RETURN] + [OP_NOP] * 950000)
best_block = node.getblock(node.getbestblockhash())
height = int(best_block["height"]) + 1
- try:
- # Static variable ensures that time is monotonicly increasing and is therefore
- # different for each block created => blockhash is unique.
- mine_large_blocks.nTime = min(mine_large_blocks.nTime, int(best_block["time"])) + 1
- except AttributeError:
- mine_large_blocks.nTime = int(best_block["time"]) + 1
+ mine_large_blocks.nTime = max(mine_large_blocks.nTime, int(best_block["time"])) + 1
previousblockhash = int(best_block["hash"], 16)
for _ in range(n):
diff --git a/test/functional/interface_bitcoin_cli.py b/test/functional/interface_bitcoin_cli.py
index 15b7ba9ae1..7bb7044cc0 100755
--- a/test/functional/interface_bitcoin_cli.py
+++ b/test/functional/interface_bitcoin_cli.py
@@ -16,7 +16,7 @@ class TestBitcoinCli(BitcoinTestFramework):
"""Main test logic"""
cli_response = self.nodes[0].cli("-version").send_cli()
- assert "Bitcoin Core RPC client version" in cli_response
+ assert "{} RPC client version".format(self.config['environment']['PACKAGE_NAME']) in cli_response
self.log.info("Compare responses from getwalletinfo RPC and `bitcoin-cli getwalletinfo`")
if self.is_wallet_compiled():
diff --git a/test/functional/mempool_persist.py b/test/functional/mempool_persist.py
index d74d4eaaf1..bb0169ee52 100755
--- a/test/functional/mempool_persist.py
+++ b/test/functional/mempool_persist.py
@@ -37,7 +37,6 @@ Test is as follows:
"""
from decimal import Decimal
import os
-import time
from test_framework.test_framework import BitcoinTestFramework
from test_framework.util import assert_equal, assert_raises_rpc_error, wait_until
@@ -83,9 +82,10 @@ class MempoolPersistTest(BitcoinTestFramework):
self.start_node(1, extra_args=["-persistmempool=0"])
self.start_node(0)
self.start_node(2)
- # Give bitcoind a second to reload the mempool
- wait_until(lambda: len(self.nodes[0].getrawmempool()) == 5, timeout=1)
- wait_until(lambda: len(self.nodes[2].getrawmempool()) == 5, timeout=1)
+ wait_until(lambda: self.nodes[0].getmempoolinfo()["loaded"], timeout=1)
+ wait_until(lambda: self.nodes[2].getmempoolinfo()["loaded"], timeout=1)
+ assert_equal(len(self.nodes[0].getrawmempool()), 5)
+ assert_equal(len(self.nodes[2].getrawmempool()), 5)
# The others have loaded their mempool. If node_1 loaded anything, we'd probably notice by now:
assert_equal(len(self.nodes[1].getrawmempool()), 0)
@@ -100,14 +100,14 @@ class MempoolPersistTest(BitcoinTestFramework):
self.log.debug("Stop-start node0 with -persistmempool=0. Verify that it doesn't load its mempool.dat file.")
self.stop_nodes()
self.start_node(0, extra_args=["-persistmempool=0"])
- # Give bitcoind a second to reload the mempool
- time.sleep(1)
+ wait_until(lambda: self.nodes[0].getmempoolinfo()["loaded"])
assert_equal(len(self.nodes[0].getrawmempool()), 0)
self.log.debug("Stop-start node0. Verify that it has the transactions in its mempool.")
self.stop_nodes()
self.start_node(0)
- wait_until(lambda: len(self.nodes[0].getrawmempool()) == 5)
+ wait_until(lambda: self.nodes[0].getmempoolinfo()["loaded"])
+ assert_equal(len(self.nodes[0].getrawmempool()), 5)
mempooldat0 = os.path.join(self.nodes[0].datadir, 'regtest', 'mempool.dat')
mempooldat1 = os.path.join(self.nodes[1].datadir, 'regtest', 'mempool.dat')
@@ -120,7 +120,8 @@ class MempoolPersistTest(BitcoinTestFramework):
os.rename(mempooldat0, mempooldat1)
self.stop_nodes()
self.start_node(1, extra_args=[])
- wait_until(lambda: len(self.nodes[1].getrawmempool()) == 5)
+ wait_until(lambda: self.nodes[1].getmempoolinfo()["loaded"])
+ assert_equal(len(self.nodes[1].getrawmempool()), 5)
self.log.debug("Prevent bitcoind from writing mempool.dat to disk. Verify that `savemempool` fails")
# to test the exception we are creating a tmp folder called mempool.dat.new
diff --git a/test/functional/p2p_invalid_messages.py b/test/functional/p2p_invalid_messages.py
index 700fdf6e04..481d697e63 100755
--- a/test/functional/p2p_invalid_messages.py
+++ b/test/functional/p2p_invalid_messages.py
@@ -3,11 +3,12 @@
# Distributed under the MIT software license, see the accompanying
# file COPYING or http://www.opensource.org/licenses/mit-license.php.
"""Test node responses to invalid network messages."""
+import asyncio
import os
import struct
from test_framework import messages
-from test_framework.mininode import P2PDataStore
+from test_framework.mininode import P2PDataStore, NetworkThread
from test_framework.test_framework import BitcoinTestFramework
@@ -143,8 +144,15 @@ class InvalidMessagesTest(BitcoinTestFramework):
def test_magic_bytes(self):
conn = self.nodes[0].add_p2p_connection(P2PDataStore())
- conn._on_data = lambda: None # Need to ignore all incoming messages from now, since they come with "invalid" magic bytes
- conn.magic_bytes = b'\x00\x11\x22\x32'
+
+ def swap_magic_bytes():
+ conn._on_data = lambda: None # Need to ignore all incoming messages from now, since they come with "invalid" magic bytes
+ conn.magic_bytes = b'\x00\x11\x22\x32'
+
+ # Call .result() to block until the atomic swap is complete, otherwise
+ # we might run into races later on
+ asyncio.run_coroutine_threadsafe(asyncio.coroutine(swap_magic_bytes)(), NetworkThread.network_event_loop).result()
+
with self.nodes[0].assert_debug_log(['PROCESSMESSAGE: INVALID MESSAGESTART ping']):
conn.send_message(messages.msg_ping(nonce=0xff))
conn.wait_for_disconnect(timeout=1)
diff --git a/test/functional/p2p_segwit.py b/test/functional/p2p_segwit.py
index a901e11536..000c30646a 100755
--- a/test/functional/p2p_segwit.py
+++ b/test/functional/p2p_segwit.py
@@ -9,7 +9,7 @@ import struct
import time
from test_framework.blocktools import create_block, create_coinbase, add_witness_commitment, get_witness_script, WITNESS_COMMITMENT_HEADER
-from test_framework.key import CECKey, CPubKey
+from test_framework.key import ECKey
from test_framework.messages import (
BIP125_SEQUENCE_NUMBER,
CBlock,
@@ -36,6 +36,7 @@ from test_framework.messages import (
ser_vector,
sha256,
uint256_from_str,
+ FromHex,
)
from test_framework.mininode import (
P2PInterface,
@@ -77,6 +78,7 @@ from test_framework.util import (
disconnect_nodes,
get_bip9_status,
hex_str_to_bytes,
+ assert_raises_rpc_error,
)
# The versionbit bit used to signal activation of SegWit
@@ -100,7 +102,7 @@ def get_p2pkh_script(pubkeyhash):
def sign_p2pk_witness_input(script, tx_to, in_idx, hashtype, value, key):
"""Add signature for a P2PK witness program."""
tx_hash = SegwitVersion1SignatureHash(script, tx_to, in_idx, hashtype, value)
- signature = key.sign(tx_hash) + chr(hashtype).encode('latin-1')
+ signature = key.sign_ecdsa(tx_hash) + chr(hashtype).encode('latin-1')
tx_to.wit.vtxinwit[in_idx].scriptWitness.stack = [signature, script]
tx_to.rehash()
@@ -269,6 +271,7 @@ class SegWitTest(BitcoinTestFramework):
self.test_non_standard_witness()
self.test_upgrade_after_activation()
self.test_witness_sigops()
+ self.test_superfluous_witness()
# Individual tests
@@ -1357,7 +1360,8 @@ class SegWitTest(BitcoinTestFramework):
def test_segwit_versions(self):
"""Test validity of future segwit version transactions.
- Future segwit version transactions are non-standard, but valid in blocks.
+ Future segwit versions are non-standard to spend, but valid in blocks.
+ Sending to future segwit versions is always allowed.
Can run this before and after segwit activation."""
NUM_SEGWIT_VERSIONS = 17 # will test OP_0, OP1, ..., OP_16
@@ -1397,7 +1401,7 @@ class SegWitTest(BitcoinTestFramework):
assert len(self.nodes[0].getrawmempool()) == 0
# Finally, verify that version 0 -> version 1 transactions
- # are non-standard
+ # are standard
script_pubkey = CScript([CScriptOp(OP_1), witness_hash])
tx2 = CTransaction()
tx2.vin = [CTxIn(COutPoint(tx.sha256, 0), b"")]
@@ -1405,10 +1409,9 @@ class SegWitTest(BitcoinTestFramework):
tx2.wit.vtxinwit.append(CTxInWitness())
tx2.wit.vtxinwit[0].scriptWitness.stack = [witness_program]
tx2.rehash()
- # Gets accepted to test_node, because standardness of outputs isn't
- # checked with fRequireStandard
+ # Gets accepted to both policy-enforcing nodes and others.
test_transaction_acceptance(self.nodes[0], self.test_node, tx2, with_witness=True, accepted=True)
- test_transaction_acceptance(self.nodes[1], self.std_node, tx2, with_witness=True, accepted=False)
+ test_transaction_acceptance(self.nodes[1], self.std_node, tx2, with_witness=True, accepted=True)
temp_utxo.pop() # last entry in temp_utxo was the output we just spent
temp_utxo.append(UTXO(tx2.sha256, 0, tx2.vout[0].nValue))
@@ -1479,10 +1482,9 @@ class SegWitTest(BitcoinTestFramework):
# Segwit transactions using uncompressed pubkeys are not accepted
# under default policy, but should still pass consensus.
- key = CECKey()
- key.set_secretbytes(b"9")
- key.set_compressed(False)
- pubkey = CPubKey(key.get_pubkey())
+ key = ECKey()
+ key.generate(False)
+ pubkey = key.get_pubkey().get_bytes()
assert_equal(len(pubkey), 65) # This should be an uncompressed pubkey
utxo = self.utxo.pop(0)
@@ -1512,7 +1514,7 @@ class SegWitTest(BitcoinTestFramework):
tx2.vout.append(CTxOut(tx.vout[0].nValue - 1000, script_wsh))
script = get_p2pkh_script(pubkeyhash)
sig_hash = SegwitVersion1SignatureHash(script, tx2, 0, SIGHASH_ALL, tx.vout[0].nValue)
- signature = key.sign(sig_hash) + b'\x01' # 0x1 is SIGHASH_ALL
+ signature = key.sign_ecdsa(sig_hash) + b'\x01' # 0x1 is SIGHASH_ALL
tx2.wit.vtxinwit.append(CTxInWitness())
tx2.wit.vtxinwit[0].scriptWitness.stack = [signature, pubkey]
tx2.rehash()
@@ -1566,7 +1568,7 @@ class SegWitTest(BitcoinTestFramework):
tx5.vin.append(CTxIn(COutPoint(tx4.sha256, 0), b""))
tx5.vout.append(CTxOut(tx4.vout[0].nValue - 1000, CScript([OP_TRUE])))
(sig_hash, err) = SignatureHash(script_pubkey, tx5, 0, SIGHASH_ALL)
- signature = key.sign(sig_hash) + b'\x01' # 0x1 is SIGHASH_ALL
+ signature = key.sign_ecdsa(sig_hash) + b'\x01' # 0x1 is SIGHASH_ALL
tx5.vin[0].scriptSig = CScript([signature, pubkey])
tx5.rehash()
# Should pass policy and consensus.
@@ -1579,9 +1581,9 @@ class SegWitTest(BitcoinTestFramework):
@subtest
def test_signature_version_1(self):
- key = CECKey()
- key.set_secretbytes(b"9")
- pubkey = CPubKey(key.get_pubkey())
+ key = ECKey()
+ key.generate()
+ pubkey = key.get_pubkey().get_bytes()
witness_program = CScript([pubkey, CScriptOp(OP_CHECKSIG)])
witness_hash = sha256(witness_program)
@@ -1716,7 +1718,7 @@ class SegWitTest(BitcoinTestFramework):
script = get_p2pkh_script(pubkeyhash)
sig_hash = SegwitVersion1SignatureHash(script, tx2, 0, SIGHASH_ALL, tx.vout[0].nValue)
- signature = key.sign(sig_hash) + b'\x01' # 0x1 is SIGHASH_ALL
+ signature = key.sign_ecdsa(sig_hash) + b'\x01' # 0x1 is SIGHASH_ALL
# Check that we can't have a scriptSig
tx2.vin[0].scriptSig = CScript([signature, pubkey])
@@ -2035,5 +2037,31 @@ class SegWitTest(BitcoinTestFramework):
# TODO: test p2sh sigop counting
+ def test_superfluous_witness(self):
+ # Serialization of tx that puts witness flag to 1 always
+ def serialize_with_bogus_witness(tx):
+ flags = 1
+ r = b""
+ r += struct.pack("<i", tx.nVersion)
+ if flags:
+ dummy = []
+ r += ser_vector(dummy)
+ r += struct.pack("<B", flags)
+ r += ser_vector(tx.vin)
+ r += ser_vector(tx.vout)
+ if flags & 1:
+ if (len(tx.wit.vtxinwit) != len(tx.vin)):
+ # vtxinwit must have the same length as vin
+ tx.wit.vtxinwit = tx.wit.vtxinwit[:len(tx.vin)]
+ for i in range(len(tx.wit.vtxinwit), len(tx.vin)):
+ tx.wit.vtxinwit.append(CTxInWitness())
+ r += tx.wit.serialize()
+ r += struct.pack("<I", tx.nLockTime)
+ return r
+
+ raw = self.nodes[0].createrawtransaction([{"txid":"00"*32, "vout":0}], {self.nodes[0].getnewaddress():1})
+ tx = FromHex(CTransaction(), raw)
+ assert_raises_rpc_error(-22, "TX decode failed", self.nodes[0].decoderawtransaction, serialize_with_bogus_witness(tx).hex())
+
if __name__ == '__main__':
SegWitTest().main()
diff --git a/test/functional/rpc_createmultisig.py b/test/functional/rpc_createmultisig.py
index 17dbf59a8c..7abcd71bb8 100755
--- a/test/functional/rpc_createmultisig.py
+++ b/test/functional/rpc_createmultisig.py
@@ -1,12 +1,16 @@
#!/usr/bin/env python3
-# Copyright (c) 2015-2018 The Bitcoin Core developers
+# Copyright (c) 2015-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 transaction signing using the signrawtransaction* RPCs."""
+"""Test multisig RPCs"""
from test_framework.test_framework import BitcoinTestFramework
+from test_framework.util import (
+ assert_raises_rpc_error,
+)
import decimal
+
class RpcCreateMultiSigTest(BitcoinTestFramework):
def set_test_params(self):
self.setup_clean_chain = True
@@ -17,29 +21,40 @@ class RpcCreateMultiSigTest(BitcoinTestFramework):
def get_keys(self):
node0, node1, node2 = self.nodes
- self.add = [node1.getnewaddress() for _ in range(self.nkeys)]
- self.pub = [node1.getaddressinfo(a)["pubkey"] for a in self.add]
- self.priv = [node1.dumpprivkey(a) for a in self.add]
+ add = [node1.getnewaddress() for _ in range(self.nkeys)]
+ self.pub = [node1.getaddressinfo(a)["pubkey"] for a in add]
+ self.priv = [node1.dumpprivkey(a) for a in add]
self.final = node2.getnewaddress()
def run_test(self):
- node0,node1,node2 = self.nodes
+ node0, node1, node2 = self.nodes
- # 50 BTC each, rest will be 25 BTC each
+ self.check_addmultisigaddress_errors()
+
+ self.log.info('Generating blocks ...')
node0.generate(149)
self.sync_all()
self.moved = 0
- for self.nkeys in [3,5]:
- for self.nsigs in [2,3]:
+ for self.nkeys in [3, 5]:
+ for self.nsigs in [2, 3]:
for self.output_type in ["bech32", "p2sh-segwit", "legacy"]:
self.get_keys()
self.do_multisig()
self.checkbalances()
+ 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)]
+ assert_raises_rpc_error(-5, 'no full public key for address', lambda: self.nodes[0].addmultisigaddress(nrequired=1, keys=addresses))
+ for a in addresses:
+ # Importing all addresses should not change the result
+ self.nodes[0].importaddress(a)
+ assert_raises_rpc_error(-5, 'no full public key for address', lambda: self.nodes[0].addmultisigaddress(nrequired=1, keys=addresses))
+
def checkbalances(self):
- node0,node1,node2 = self.nodes
+ node0, node1, node2 = self.nodes
node0.generate(100)
self.sync_all()
@@ -49,13 +64,13 @@ class RpcCreateMultiSigTest(BitcoinTestFramework):
height = node0.getblockchaininfo()["blocks"]
assert 150 < height < 350
- total = 149*50 + (height-149-100)*25
+ total = 149 * 50 + (height - 149 - 100) * 25
assert bal1 == 0
assert bal2 == self.moved
- assert bal0+bal1+bal2 == total
+ assert bal0 + bal1 + bal2 == total
def do_multisig(self):
- node0,node1,node2 = self.nodes
+ node0, node1, node2 = self.nodes
msig = node2.createmultisig(self.nsigs, self.pub, self.output_type)
madd = msig["address"]
@@ -74,7 +89,7 @@ class RpcCreateMultiSigTest(BitcoinTestFramework):
txid = node0.sendtoaddress(madd, 40)
tx = node0.getrawtransaction(txid, True)
- vout = [v["n"] for v in tx["vout"] if madd in v["scriptPubKey"].get("addresses",[])]
+ vout = [v["n"] for v in tx["vout"] if madd in v["scriptPubKey"].get("addresses", [])]
assert len(vout) == 1
vout = vout[0]
scriptPubKey = tx["vout"][vout]["scriptPubKey"]["hex"]
@@ -86,7 +101,7 @@ class RpcCreateMultiSigTest(BitcoinTestFramework):
outval = value - decimal.Decimal("0.00001000")
rawtx = node2.createrawtransaction([{"txid": txid, "vout": vout}], [{self.final: outval}])
- rawtx2 = node2.signrawtransactionwithkey(rawtx, self.priv[0:self.nsigs-1], prevtxs)
+ rawtx2 = node2.signrawtransactionwithkey(rawtx, self.priv[0:self.nsigs - 1], prevtxs)
rawtx3 = node2.signrawtransactionwithkey(rawtx2["hex"], [self.priv[-1]], prevtxs)
self.moved += outval
@@ -97,5 +112,6 @@ class RpcCreateMultiSigTest(BitcoinTestFramework):
txinfo = node0.getrawtransaction(tx, True, blk)
self.log.info("n/m=%d/%d %s size=%d vsize=%d weight=%d" % (self.nsigs, self.nkeys, self.output_type, txinfo["size"], txinfo["vsize"], txinfo["weight"]))
+
if __name__ == '__main__':
RpcCreateMultiSigTest().main()
diff --git a/test/functional/rpc_getblockfilter.py b/test/functional/rpc_getblockfilter.py
new file mode 100755
index 0000000000..bd93b6f7a4
--- /dev/null
+++ b/test/functional/rpc_getblockfilter.py
@@ -0,0 +1,59 @@
+#!/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 getblockfilter RPC."""
+
+from test_framework.test_framework import BitcoinTestFramework
+from test_framework.util import (
+ assert_equal, assert_is_hex_string, assert_raises_rpc_error,
+ connect_nodes, disconnect_nodes, sync_blocks
+ )
+
+FILTER_TYPES = ["basic"]
+
+class GetBlockFilterTest(BitcoinTestFramework):
+ def set_test_params(self):
+ self.setup_clean_chain = True
+ self.num_nodes = 2
+ self.extra_args = [["-blockfilterindex"], []]
+
+ def run_test(self):
+ # Create two chains by disconnecting nodes 0 & 1, mining, then reconnecting
+ disconnect_nodes(self.nodes[0], 1)
+
+ self.nodes[0].generate(3)
+ self.nodes[1].generate(4)
+
+ assert_equal(self.nodes[0].getblockcount(), 3)
+ chain0_hashes = [self.nodes[0].getblockhash(block_height) for block_height in range(4)]
+
+ # Reorg node 0 to a new chain
+ connect_nodes(self.nodes[0], 1)
+ sync_blocks(self.nodes)
+
+ assert_equal(self.nodes[0].getblockcount(), 4)
+ chain1_hashes = [self.nodes[0].getblockhash(block_height) for block_height in range(4)]
+
+ # Test getblockfilter returns a filter for all blocks and filter types on active chain
+ for block_hash in chain1_hashes:
+ for filter_type in FILTER_TYPES:
+ result = self.nodes[0].getblockfilter(block_hash, filter_type)
+ assert_is_hex_string(result['filter'])
+
+ # Test getblockfilter returns a filter for all blocks and filter types on stale chain
+ for block_hash in chain0_hashes:
+ for filter_type in FILTER_TYPES:
+ result = self.nodes[0].getblockfilter(block_hash, filter_type)
+ assert_is_hex_string(result['filter'])
+
+ # Test getblockfilter with unknown block
+ bad_block_hash = "0123456789abcdef" * 4
+ assert_raises_rpc_error(-5, "Block not found", self.nodes[0].getblockfilter, bad_block_hash, "basic")
+
+ # Test getblockfilter with undefined filter type
+ genesis_hash = self.nodes[0].getblockhash(0)
+ assert_raises_rpc_error(-5, "Unknown filtertype", self.nodes[0].getblockfilter, genesis_hash, "unknown")
+
+if __name__ == '__main__':
+ GetBlockFilterTest().main()
diff --git a/test/functional/rpc_psbt.py b/test/functional/rpc_psbt.py
index fb68f79bbd..8a7ea7aa58 100755
--- a/test/functional/rpc_psbt.py
+++ b/test/functional/rpc_psbt.py
@@ -150,10 +150,11 @@ class PSBTTest(BitcoinTestFramework):
new_psbt = self.nodes[0].converttopsbt(rawtx['hex'])
self.nodes[0].decodepsbt(new_psbt)
- # Make sure that a psbt with signatures cannot be converted
+ # 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"
signedtx = self.nodes[0].signrawtransactionwithwallet(rawtx['hex'])
- assert_raises_rpc_error(-22, "TX decode failed", self.nodes[0].converttopsbt, signedtx['hex'])
- assert_raises_rpc_error(-22, "TX decode failed", self.nodes[0].converttopsbt, signedtx['hex'], False)
+ assert_raises_rpc_error(-22, "", self.nodes[0].converttopsbt, signedtx['hex'])
+ assert_raises_rpc_error(-22, "", self.nodes[0].converttopsbt, signedtx['hex'], False)
# Unless we allow it to convert and strip signatures
self.nodes[0].converttopsbt(signedtx['hex'], True)
diff --git a/test/functional/rpc_rawtransaction.py b/test/functional/rpc_rawtransaction.py
index 8c82b0ae4d..6e71817dc3 100755
--- a/test/functional/rpc_rawtransaction.py
+++ b/test/functional/rpc_rawtransaction.py
@@ -446,9 +446,9 @@ class RawTransactionsTest(BitcoinTestFramework):
# and sendrawtransaction should throw
assert_raises_rpc_error(-26, "absurdly-high-fee", self.nodes[2].sendrawtransaction, rawTxSigned['hex'], 0.00001000)
# And below calls should both succeed
- testres = self.nodes[2].testmempoolaccept(rawtxs=[rawTxSigned['hex']], maxfeerate=0.00007000)[0]
+ testres = self.nodes[2].testmempoolaccept(rawtxs=[rawTxSigned['hex']], maxfeerate='0.00007000')[0]
assert_equal(testres['allowed'], True)
- self.nodes[2].sendrawtransaction(hexstring=rawTxSigned['hex'], maxfeerate=0.00007000)
+ self.nodes[2].sendrawtransaction(hexstring=rawTxSigned['hex'], maxfeerate='0.00007000')
if __name__ == '__main__':
diff --git a/test/functional/test_framework/key.py b/test/functional/test_framework/key.py
index 1b3e510dc4..912c0ca978 100644
--- a/test/functional/test_framework/key.py
+++ b/test/functional/test_framework/key.py
@@ -1,226 +1,386 @@
-# Copyright (c) 2011 Sam Rushing
-"""ECC secp256k1 OpenSSL wrapper.
+# Copyright (c) 2019 Pieter Wuille
+# Distributed under the MIT software license, see the accompanying
+# file COPYING or http://www.opensource.org/licenses/mit-license.php.
+"""Test-only secp256k1 elliptic curve implementation
-WARNING: This module does not mlock() secrets; your private keys may end up on
-disk in swap! Use with caution!
+WARNING: This code is slow, uses bad randomness, does not properly protect
+keys, and is trivially vulnerable to side channel attacks. Do not use for
+anything but tests."""
+import random
-This file is modified from python-bitcoinlib.
-"""
-
-import ctypes
-import ctypes.util
-import hashlib
-
-ssl = ctypes.cdll.LoadLibrary(ctypes.util.find_library ('ssl') or 'libeay32')
-
-ssl.BN_new.restype = ctypes.c_void_p
-ssl.BN_new.argtypes = []
-
-ssl.BN_bin2bn.restype = ctypes.c_void_p
-ssl.BN_bin2bn.argtypes = [ctypes.c_char_p, ctypes.c_int, ctypes.c_void_p]
-
-ssl.BN_CTX_free.restype = None
-ssl.BN_CTX_free.argtypes = [ctypes.c_void_p]
-
-ssl.BN_CTX_new.restype = ctypes.c_void_p
-ssl.BN_CTX_new.argtypes = []
-
-ssl.ECDH_compute_key.restype = ctypes.c_int
-ssl.ECDH_compute_key.argtypes = [ctypes.c_void_p, ctypes.c_int, ctypes.c_void_p, ctypes.c_void_p]
-
-ssl.ECDSA_sign.restype = ctypes.c_int
-ssl.ECDSA_sign.argtypes = [ctypes.c_int, ctypes.c_void_p, ctypes.c_int, ctypes.c_void_p, ctypes.c_void_p, ctypes.c_void_p]
-
-ssl.ECDSA_verify.restype = ctypes.c_int
-ssl.ECDSA_verify.argtypes = [ctypes.c_int, ctypes.c_void_p, ctypes.c_int, ctypes.c_void_p, ctypes.c_int, ctypes.c_void_p]
-
-ssl.EC_KEY_free.restype = None
-ssl.EC_KEY_free.argtypes = [ctypes.c_void_p]
-
-ssl.EC_KEY_new_by_curve_name.restype = ctypes.c_void_p
-ssl.EC_KEY_new_by_curve_name.argtypes = [ctypes.c_int]
-
-ssl.EC_KEY_get0_group.restype = ctypes.c_void_p
-ssl.EC_KEY_get0_group.argtypes = [ctypes.c_void_p]
-
-ssl.EC_KEY_get0_public_key.restype = ctypes.c_void_p
-ssl.EC_KEY_get0_public_key.argtypes = [ctypes.c_void_p]
-
-ssl.EC_KEY_set_private_key.restype = ctypes.c_int
-ssl.EC_KEY_set_private_key.argtypes = [ctypes.c_void_p, ctypes.c_void_p]
-
-ssl.EC_KEY_set_conv_form.restype = None
-ssl.EC_KEY_set_conv_form.argtypes = [ctypes.c_void_p, ctypes.c_int]
-
-ssl.EC_KEY_set_public_key.restype = ctypes.c_int
-ssl.EC_KEY_set_public_key.argtypes = [ctypes.c_void_p, ctypes.c_void_p]
-
-ssl.i2o_ECPublicKey.restype = ctypes.c_void_p
-ssl.i2o_ECPublicKey.argtypes = [ctypes.c_void_p, ctypes.c_void_p]
-
-ssl.EC_POINT_new.restype = ctypes.c_void_p
-ssl.EC_POINT_new.argtypes = [ctypes.c_void_p]
-
-ssl.EC_POINT_free.restype = None
-ssl.EC_POINT_free.argtypes = [ctypes.c_void_p]
-
-ssl.EC_POINT_mul.restype = ctypes.c_int
-ssl.EC_POINT_mul.argtypes = [ctypes.c_void_p, ctypes.c_void_p, ctypes.c_void_p, ctypes.c_void_p, ctypes.c_void_p, ctypes.c_void_p]
-
-# this specifies the curve used with ECDSA.
-NID_secp256k1 = 714 # from openssl/obj_mac.h
+def modinv(a, n):
+ """Compute the modular inverse of a modulo n
+ See https://en.wikipedia.org/wiki/Extended_Euclidean_algorithm#Modular_integers.
+ """
+ t1, t2 = 0, 1
+ r1, r2 = n, a
+ while r2 != 0:
+ q = r1 // r2
+ t1, t2 = t2, t1 - q * t2
+ r1, r2 = r2, r1 - q * r2
+ if r1 > 1:
+ return None
+ if t1 < 0:
+ t1 += n
+ return t1
+
+def jacobi_symbol(n, k):
+ """Compute the Jacobi symbol of n modulo k
+
+ See http://en.wikipedia.org/wiki/Jacobi_symbol
+
+ For our application k is always prime, so this is the same as the Legendre symbol."""
+ assert k > 0 and k & 1, "jacobi symbol is only defined for positive odd k"
+ n %= k
+ t = 0
+ while n != 0:
+ while n & 1 == 0:
+ n >>= 1
+ r = k & 7
+ t ^= (r == 3 or r == 5)
+ n, k = k, n
+ t ^= (n & k & 3 == 3)
+ n = n % k
+ if k == 1:
+ return -1 if t else 1
+ return 0
+
+def modsqrt(a, p):
+ """Compute the square root of a modulo p when p % 4 = 3.
+
+ The Tonelli-Shanks algorithm can be used. See https://en.wikipedia.org/wiki/Tonelli-Shanks_algorithm
+
+ Limiting this function to only work for p % 4 = 3 means we don't need to
+ iterate through the loop. The highest n such that p - 1 = 2^n Q with Q odd
+ is n = 1. Therefore Q = (p-1)/2 and sqrt = a^((Q+1)/2) = a^((p+1)/4)
+
+ secp256k1's is defined over field of size 2**256 - 2**32 - 977, which is 3 mod 4.
+ """
+ if p % 4 != 3:
+ raise NotImplementedError("modsqrt only implemented for p % 4 = 3")
+ sqrt = pow(a, (p + 1)//4, p)
+ if pow(sqrt, 2, p) == a % p:
+ return sqrt
+ return None
+
+class EllipticCurve:
+ def __init__(self, p, a, b):
+ """Initialize elliptic curve y^2 = x^3 + a*x + b over GF(p)."""
+ self.p = p
+ self.a = a % p
+ self.b = b % p
+
+ def affine(self, p1):
+ """Convert a Jacobian point tuple p1 to affine form, or None if at infinity.
+
+ An affine point is represented as the Jacobian (x, y, 1)"""
+ x1, y1, z1 = p1
+ if z1 == 0:
+ return None
+ inv = modinv(z1, self.p)
+ inv_2 = (inv**2) % self.p
+ inv_3 = (inv_2 * inv) % self.p
+ return ((inv_2 * x1) % self.p, (inv_3 * y1) % self.p, 1)
+
+ def negate(self, p1):
+ """Negate a Jacobian point tuple p1."""
+ x1, y1, z1 = p1
+ return (x1, (self.p - y1) % self.p, z1)
+
+ def on_curve(self, p1):
+ """Determine whether a Jacobian tuple p is on the curve (and not infinity)"""
+ x1, y1, z1 = p1
+ z2 = pow(z1, 2, self.p)
+ z4 = pow(z2, 2, self.p)
+ return z1 != 0 and (pow(x1, 3, self.p) + self.a * x1 * z4 + self.b * z2 * z4 - pow(y1, 2, self.p)) % self.p == 0
+
+ def is_x_coord(self, x):
+ """Test whether x is a valid X coordinate on the curve."""
+ x_3 = pow(x, 3, self.p)
+ return jacobi_symbol(x_3 + self.a * x + self.b, self.p) != -1
+
+ def lift_x(self, x):
+ """Given an X coordinate on the curve, return a corresponding affine point."""
+ x_3 = pow(x, 3, self.p)
+ v = x_3 + self.a * x + self.b
+ y = modsqrt(v, self.p)
+ if y is None:
+ return None
+ return (x, y, 1)
+
+ def double(self, p1):
+ """Double a Jacobian tuple p1
+
+ See https://en.wikibooks.org/wiki/Cryptography/Prime_Curve/Jacobian_Coordinates - Point Doubling"""
+ x1, y1, z1 = p1
+ if z1 == 0:
+ return (0, 1, 0)
+ y1_2 = (y1**2) % self.p
+ y1_4 = (y1_2**2) % self.p
+ x1_2 = (x1**2) % self.p
+ s = (4*x1*y1_2) % self.p
+ m = 3*x1_2
+ if self.a:
+ m += self.a * pow(z1, 4, self.p)
+ m = m % self.p
+ x2 = (m**2 - 2*s) % self.p
+ y2 = (m*(s - x2) - 8*y1_4) % self.p
+ z2 = (2*y1*z1) % self.p
+ return (x2, y2, z2)
+
+ def add_mixed(self, p1, p2):
+ """Add a Jacobian tuple p1 and an affine tuple p2
+
+ See https://en.wikibooks.org/wiki/Cryptography/Prime_Curve/Jacobian_Coordinates - Point Addition (with affine point)"""
+ x1, y1, z1 = p1
+ x2, y2, z2 = p2
+ assert(z2 == 1)
+ # Adding to the point at infinity is a no-op
+ if z1 == 0:
+ return p2
+ z1_2 = (z1**2) % self.p
+ z1_3 = (z1_2 * z1) % self.p
+ u2 = (x2 * z1_2) % self.p
+ s2 = (y2 * z1_3) % self.p
+ if x1 == u2:
+ if (y1 != s2):
+ # p1 and p2 are inverses. Return the point at infinity.
+ return (0, 1, 0)
+ # p1 == p2. The formulas below fail when the two points are equal.
+ return self.double(p1)
+ h = u2 - x1
+ r = s2 - y1
+ h_2 = (h**2) % self.p
+ h_3 = (h_2 * h) % self.p
+ u1_h_2 = (x1 * h_2) % self.p
+ x3 = (r**2 - h_3 - 2*u1_h_2) % self.p
+ y3 = (r*(u1_h_2 - x3) - y1*h_3) % self.p
+ z3 = (h*z1) % self.p
+ return (x3, y3, z3)
+
+ def add(self, p1, p2):
+ """Add two Jacobian tuples p1 and p2
+
+ See https://en.wikibooks.org/wiki/Cryptography/Prime_Curve/Jacobian_Coordinates - Point Addition"""
+ x1, y1, z1 = p1
+ x2, y2, z2 = p2
+ # Adding the point at infinity is a no-op
+ if z1 == 0:
+ return p2
+ if z2 == 0:
+ return p1
+ # Adding an Affine to a Jacobian is more efficient since we save field multiplications and squarings when z = 1
+ if z1 == 1:
+ return self.add_mixed(p2, p1)
+ if z2 == 1:
+ return self.add_mixed(p1, p2)
+ z1_2 = (z1**2) % self.p
+ z1_3 = (z1_2 * z1) % self.p
+ z2_2 = (z2**2) % self.p
+ z2_3 = (z2_2 * z2) % self.p
+ u1 = (x1 * z2_2) % self.p
+ u2 = (x2 * z1_2) % self.p
+ s1 = (y1 * z2_3) % self.p
+ s2 = (y2 * z1_3) % self.p
+ if u1 == u2:
+ if (s1 != s2):
+ # p1 and p2 are inverses. Return the point at infinity.
+ return (0, 1, 0)
+ # p1 == p2. The formulas below fail when the two points are equal.
+ return self.double(p1)
+ h = u2 - u1
+ r = s2 - s1
+ h_2 = (h**2) % self.p
+ h_3 = (h_2 * h) % self.p
+ u1_h_2 = (u1 * h_2) % self.p
+ x3 = (r**2 - h_3 - 2*u1_h_2) % self.p
+ y3 = (r*(u1_h_2 - x3) - s1*h_3) % self.p
+ z3 = (h*z1*z2) % self.p
+ return (x3, y3, z3)
+
+ def mul(self, ps):
+ """Compute a (multi) point multiplication
+
+ ps is a list of (Jacobian tuple, scalar) pairs.
+ """
+ r = (0, 1, 0)
+ for i in range(255, -1, -1):
+ r = self.double(r)
+ for (p, n) in ps:
+ if ((n >> i) & 1):
+ r = self.add(r, p)
+ return r
+
+SECP256K1 = EllipticCurve(2**256 - 2**32 - 977, 0, 7)
+SECP256K1_G = (0x79BE667EF9DCBBAC55A06295CE870B07029BFCDB2DCE28D959F2815B16F81798, 0x483ADA7726A3C4655DA4FBFC0E1108A8FD17B448A68554199C47D08FFB10D4B8, 1)
SECP256K1_ORDER = 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141
SECP256K1_ORDER_HALF = SECP256K1_ORDER // 2
-# Thx to Sam Devlin for the ctypes magic 64-bit fix.
-def _check_result(val, func, args):
- if val == 0:
- raise ValueError
- else:
- return ctypes.c_void_p (val)
-
-ssl.EC_KEY_new_by_curve_name.restype = ctypes.c_void_p
-ssl.EC_KEY_new_by_curve_name.errcheck = _check_result
-
-class CECKey():
- """Wrapper around OpenSSL's EC_KEY"""
-
- POINT_CONVERSION_COMPRESSED = 2
- POINT_CONVERSION_UNCOMPRESSED = 4
+class ECPubKey():
+ """A secp256k1 public key"""
def __init__(self):
- self.k = ssl.EC_KEY_new_by_curve_name(NID_secp256k1)
-
- def __del__(self):
- if ssl:
- ssl.EC_KEY_free(self.k)
- self.k = None
-
- def set_secretbytes(self, secret):
- priv_key = ssl.BN_bin2bn(secret, 32, ssl.BN_new())
- group = ssl.EC_KEY_get0_group(self.k)
- pub_key = ssl.EC_POINT_new(group)
- ctx = ssl.BN_CTX_new()
- if not ssl.EC_POINT_mul(group, pub_key, priv_key, None, None, ctx):
- raise ValueError("Could not derive public key from the supplied secret.")
- ssl.EC_POINT_mul(group, pub_key, priv_key, None, None, ctx)
- ssl.EC_KEY_set_private_key(self.k, priv_key)
- ssl.EC_KEY_set_public_key(self.k, pub_key)
- ssl.EC_POINT_free(pub_key)
- ssl.BN_CTX_free(ctx)
- return self.k
-
- def set_privkey(self, key):
- self.mb = ctypes.create_string_buffer(key)
- return ssl.d2i_ECPrivateKey(ctypes.byref(self.k), ctypes.byref(ctypes.pointer(self.mb)), len(key))
-
- def set_pubkey(self, key):
- self.mb = ctypes.create_string_buffer(key)
- return ssl.o2i_ECPublicKey(ctypes.byref(self.k), ctypes.byref(ctypes.pointer(self.mb)), len(key))
-
- def get_privkey(self):
- size = ssl.i2d_ECPrivateKey(self.k, 0)
- mb_pri = ctypes.create_string_buffer(size)
- ssl.i2d_ECPrivateKey(self.k, ctypes.byref(ctypes.pointer(mb_pri)))
- return mb_pri.raw
-
- def get_pubkey(self):
- size = ssl.i2o_ECPublicKey(self.k, 0)
- mb = ctypes.create_string_buffer(size)
- ssl.i2o_ECPublicKey(self.k, ctypes.byref(ctypes.pointer(mb)))
- return mb.raw
-
- def get_raw_ecdh_key(self, other_pubkey):
- ecdh_keybuffer = ctypes.create_string_buffer(32)
- r = ssl.ECDH_compute_key(ctypes.pointer(ecdh_keybuffer), 32,
- ssl.EC_KEY_get0_public_key(other_pubkey.k),
- self.k, 0)
- if r != 32:
- raise Exception('CKey.get_ecdh_key(): ECDH_compute_key() failed')
- return ecdh_keybuffer.raw
-
- def get_ecdh_key(self, other_pubkey, kdf=lambda k: hashlib.sha256(k).digest()):
- # FIXME: be warned it's not clear what the kdf should be as a default
- r = self.get_raw_ecdh_key(other_pubkey)
- return kdf(r)
-
- def sign(self, hash, low_s = True):
- # FIXME: need unit tests for below cases
- if not isinstance(hash, bytes):
- raise TypeError('Hash must be bytes instance; got %r' % hash.__class__)
- if len(hash) != 32:
- raise ValueError('Hash must be exactly 32 bytes long')
-
- sig_size0 = ctypes.c_uint32()
- sig_size0.value = ssl.ECDSA_size(self.k)
- mb_sig = ctypes.create_string_buffer(sig_size0.value)
- result = ssl.ECDSA_sign(0, hash, len(hash), mb_sig, ctypes.byref(sig_size0), self.k)
- assert 1 == result
- assert mb_sig.raw[0] == 0x30
- assert mb_sig.raw[1] == sig_size0.value - 2
- total_size = mb_sig.raw[1]
- assert mb_sig.raw[2] == 2
- r_size = mb_sig.raw[3]
- assert mb_sig.raw[4 + r_size] == 2
- s_size = mb_sig.raw[5 + r_size]
- s_value = int.from_bytes(mb_sig.raw[6+r_size:6+r_size+s_size], byteorder='big')
- if (not low_s) or s_value <= SECP256K1_ORDER_HALF:
- return mb_sig.raw[:sig_size0.value]
- else:
- low_s_value = SECP256K1_ORDER - s_value
- low_s_bytes = (low_s_value).to_bytes(33, byteorder='big')
- while len(low_s_bytes) > 1 and low_s_bytes[0] == 0 and low_s_bytes[1] < 0x80:
- low_s_bytes = low_s_bytes[1:]
- new_s_size = len(low_s_bytes)
- new_total_size_byte = (total_size + new_s_size - s_size).to_bytes(1,byteorder='big')
- new_s_size_byte = (new_s_size).to_bytes(1,byteorder='big')
- return b'\x30' + new_total_size_byte + mb_sig.raw[2:5+r_size] + new_s_size_byte + low_s_bytes
-
- def verify(self, hash, sig):
- """Verify a DER signature"""
- return ssl.ECDSA_verify(0, hash, len(hash), sig, len(sig), self.k) == 1
-
- def set_compressed(self, compressed):
- if compressed:
- form = self.POINT_CONVERSION_COMPRESSED
+ """Construct an uninitialized public key"""
+ self.valid = False
+
+ def set(self, data):
+ """Construct a public key from a serialization in compressed or uncompressed format"""
+ if (len(data) == 65 and data[0] == 0x04):
+ p = (int.from_bytes(data[1:33], 'big'), int.from_bytes(data[33:65], 'big'), 1)
+ self.valid = SECP256K1.on_curve(p)
+ if self.valid:
+ self.p = p
+ self.compressed = False
+ elif (len(data) == 33 and (data[0] == 0x02 or data[0] == 0x03)):
+ x = int.from_bytes(data[1:33], 'big')
+ if SECP256K1.is_x_coord(x):
+ p = SECP256K1.lift_x(x)
+ # if the oddness of the y co-ord isn't correct, find the other
+ # valid y
+ if (p[1] & 1) != (data[0] & 1):
+ p = SECP256K1.negate(p)
+ self.p = p
+ self.valid = True
+ self.compressed = True
+ else:
+ self.valid = False
else:
- form = self.POINT_CONVERSION_UNCOMPRESSED
- ssl.EC_KEY_set_conv_form(self.k, form)
-
+ self.valid = False
-class CPubKey(bytes):
- """An encapsulated public key
-
- Attributes:
+ @property
+ def is_compressed(self):
+ return self.compressed
- is_valid - Corresponds to CPubKey.IsValid()
- is_fullyvalid - Corresponds to CPubKey.IsFullyValid()
- is_compressed - Corresponds to CPubKey.IsCompressed()
- """
+ @property
+ def is_valid(self):
+ return self.valid
+
+ def get_bytes(self):
+ assert(self.valid)
+ p = SECP256K1.affine(self.p)
+ if p is None:
+ return None
+ if self.compressed:
+ return bytes([0x02 + (p[1] & 1)]) + p[0].to_bytes(32, 'big')
+ else:
+ return bytes([0x04]) + p[0].to_bytes(32, 'big') + p[1].to_bytes(32, 'big')
+
+ def verify_ecdsa(self, sig, msg, low_s=True):
+ """Verify a strictly DER-encoded ECDSA signature against this pubkey.
+
+ See https://en.wikipedia.org/wiki/Elliptic_Curve_Digital_Signature_Algorithm for the
+ ECDSA verifier algorithm"""
+ assert(self.valid)
+
+ # Extract r and s from the DER formatted signature. Return false for
+ # any DER encoding errors.
+ if (sig[1] + 2 != len(sig)):
+ return False
+ if (len(sig) < 4):
+ return False
+ if (sig[0] != 0x30):
+ return False
+ if (sig[2] != 0x02):
+ return False
+ rlen = sig[3]
+ if (len(sig) < 6 + rlen):
+ return False
+ if rlen < 1 or rlen > 33:
+ return False
+ if sig[4] >= 0x80:
+ return False
+ if (rlen > 1 and (sig[4] == 0) and not (sig[5] & 0x80)):
+ return False
+ r = int.from_bytes(sig[4:4+rlen], 'big')
+ if (sig[4+rlen] != 0x02):
+ return False
+ slen = sig[5+rlen]
+ if slen < 1 or slen > 33:
+ return False
+ if (len(sig) != 6 + rlen + slen):
+ return False
+ if sig[6+rlen] >= 0x80:
+ return False
+ if (slen > 1 and (sig[6+rlen] == 0) and not (sig[7+rlen] & 0x80)):
+ return False
+ s = int.from_bytes(sig[6+rlen:6+rlen+slen], 'big')
+
+ # Verify that r and s are within the group order
+ if r < 1 or s < 1 or r >= SECP256K1_ORDER or s >= SECP256K1_ORDER:
+ return False
+ if low_s and s >= SECP256K1_ORDER_HALF:
+ return False
+ z = int.from_bytes(msg, 'big')
+
+ # Run verifier algorithm on r, s
+ w = modinv(s, SECP256K1_ORDER)
+ u1 = z*w % SECP256K1_ORDER
+ u2 = r*w % SECP256K1_ORDER
+ R = SECP256K1.affine(SECP256K1.mul([(SECP256K1_G, u1), (self.p, u2)]))
+ if R is None or R[0] != r:
+ return False
+ return True
+
+class ECKey():
+ """A secp256k1 private key"""
- def __new__(cls, buf, _cec_key=None):
- self = super(CPubKey, cls).__new__(cls, buf)
- if _cec_key is None:
- _cec_key = CECKey()
- self._cec_key = _cec_key
- self.is_fullyvalid = _cec_key.set_pubkey(self) != 0
- return self
+ def __init__(self):
+ self.valid = False
+
+ def set(self, secret, compressed):
+ """Construct a private key object with given 32-byte secret and compressed flag."""
+ assert(len(secret) == 32)
+ secret = int.from_bytes(secret, 'big')
+ self.valid = (secret > 0 and secret < SECP256K1_ORDER)
+ if self.valid:
+ self.secret = secret
+ self.compressed = compressed
+
+ def generate(self, compressed=True):
+ """Generate a random private key (compressed or uncompressed)."""
+ self.set(random.randrange(1, SECP256K1_ORDER).to_bytes(32, 'big'), compressed)
+
+ def get_bytes(self):
+ """Retrieve the 32-byte representation of this key."""
+ assert(self.valid)
+ return self.secret.to_bytes(32, 'big')
@property
def is_valid(self):
- return len(self) > 0
+ return self.valid
@property
def is_compressed(self):
- return len(self) == 33
-
- def verify(self, hash, sig):
- return self._cec_key.verify(hash, sig)
-
- def __str__(self):
- return repr(self)
-
- def __repr__(self):
- return '%s(%s)' % (self.__class__.__name__, super(CPubKey, self).__repr__())
+ return self.compressed
+ def get_pubkey(self):
+ """Compute an ECPubKey object for this secret key."""
+ assert(self.valid)
+ ret = ECPubKey()
+ p = SECP256K1.mul([(SECP256K1_G, self.secret)])
+ ret.p = p
+ ret.valid = True
+ ret.compressed = self.compressed
+ return ret
+
+ def sign_ecdsa(self, msg, low_s=True):
+ """Construct a DER-encoded ECDSA signature with this key.
+
+ See https://en.wikipedia.org/wiki/Elliptic_Curve_Digital_Signature_Algorithm for the
+ ECDSA signer algorithm."""
+ assert(self.valid)
+ z = int.from_bytes(msg, 'big')
+ # Note: no RFC6979, but a simple random nonce (some tests rely on distinct transactions for the same operation)
+ k = random.randrange(1, SECP256K1_ORDER)
+ R = SECP256K1.affine(SECP256K1.mul([(SECP256K1_G, k)]))
+ r = R[0] % SECP256K1_ORDER
+ s = (modinv(k, SECP256K1_ORDER) * (z + self.secret * r)) % SECP256K1_ORDER
+ if low_s and s > SECP256K1_ORDER_HALF:
+ s = SECP256K1_ORDER - s
+ # Represent in DER format. The byte representations of r and s have
+ # length rounded up (255 bits becomes 32 bytes and 256 bits becomes 33
+ # bytes).
+ rb = r.to_bytes((r.bit_length() + 8) // 8, 'big')
+ sb = s.to_bytes((s.bit_length() + 8) // 8, 'big')
+ return b'\x30' + bytes([4 + len(rb) + len(sb), 2, len(rb)]) + rb + bytes([2, len(sb)]) + sb
diff --git a/test/functional/test_framework/mininode.py b/test/functional/test_framework/mininode.py
index 7a063ac526..11ea968257 100755
--- a/test/functional/test_framework/mininode.py
+++ b/test/functional/test_framework/mininode.py
@@ -531,7 +531,7 @@ class P2PDataStore(P2PInterface):
for b in blocks:
self.send_message(msg_block(block=b))
else:
- self.send_message(msg_headers([CBlockHeader(blocks[-1])]))
+ self.send_message(msg_headers([CBlockHeader(block) for block in blocks]))
wait_until(lambda: blocks[-1].sha256 in self.getdata_requests, timeout=timeout, lock=mininode_lock)
if expect_disconnect:
diff --git a/test/functional/test_framework/test_framework.py b/test/functional/test_framework/test_framework.py
index 4aeff24d12..555d55d97f 100755
--- a/test/functional/test_framework/test_framework.py
+++ b/test/functional/test_framework/test_framework.py
@@ -556,21 +556,12 @@ class BitcoinTestFramework(metaclass=BitcoinTestMetaClass):
def is_cli_compiled(self):
"""Checks whether bitcoin-cli was compiled."""
- config = configparser.ConfigParser()
- config.read_file(open(self.options.configfile))
-
- return config["components"].getboolean("ENABLE_CLI")
+ return self.config["components"].getboolean("ENABLE_CLI")
def is_wallet_compiled(self):
"""Checks whether the wallet module was compiled."""
- config = configparser.ConfigParser()
- config.read_file(open(self.options.configfile))
-
- return config["components"].getboolean("ENABLE_WALLET")
+ return self.config["components"].getboolean("ENABLE_WALLET")
def is_zmq_compiled(self):
"""Checks whether the zmq module was compiled."""
- config = configparser.ConfigParser()
- config.read_file(open(self.options.configfile))
-
- return config["components"].getboolean("ENABLE_ZMQ")
+ return self.config["components"].getboolean("ENABLE_ZMQ")
diff --git a/test/functional/test_runner.py b/test/functional/test_runner.py
index 61c5c9aefb..ec5d6f1715 100755
--- a/test/functional/test_runner.py
+++ b/test/functional/test_runner.py
@@ -66,6 +66,13 @@ if os.name != 'nt' or sys.getwindowsversion() >= (10, 0, 14393):
TEST_EXIT_PASSED = 0
TEST_EXIT_SKIPPED = 77
+EXTENDED_SCRIPTS = [
+ # These tests are not run by the travis build process.
+ # 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.
# Longest test should go first, to favor running tests in parallel
@@ -144,6 +151,7 @@ BASE_SCRIPTS = [
'wallet_txn_doublespend.py',
'wallet_txn_clone.py --mineblock',
'feature_notifications.py',
+ 'rpc_getblockfilter.py',
'rpc_invalidateblock.py',
'feature_rbf.py',
'mempool_packages.py',
@@ -195,13 +203,6 @@ BASE_SCRIPTS = [
# Put them in a random line within the section that fits their approximate run-time
]
-EXTENDED_SCRIPTS = [
- # These tests are not run by the travis build process.
- # Longest test should go first, to favor running tests in parallel
- 'feature_pruning.py',
- 'feature_dbcrash.py',
-]
-
# Place EXTENDED_SCRIPTS first since it has the 3 longest running tests
ALL_SCRIPTS = EXTENDED_SCRIPTS + BASE_SCRIPTS
diff --git a/test/functional/wallet_balance.py b/test/functional/wallet_balance.py
index e2a20beec5..c02d7422b9 100755
--- a/test/functional/wallet_balance.py
+++ b/test/functional/wallet_balance.py
@@ -4,20 +4,23 @@
# file COPYING or http://www.opensource.org/licenses/mit-license.php.
"""Test the wallet balance RPC methods."""
from decimal import Decimal
+import struct
+from test_framework.address import ADDRESS_BCRT1_UNSPENDABLE as ADDRESS_WATCHONLY
from test_framework.test_framework import BitcoinTestFramework
from test_framework.util import (
assert_equal,
assert_raises_rpc_error,
+ connect_nodes_bi,
+ sync_blocks,
)
-RANDOM_COINBASE_ADDRESS = 'mneYUmWYsuk7kySiURxCi3AGxrAqZxLgPZ'
def create_transactions(node, address, amt, fees):
# Create and sign raw transactions from node to address for amt.
# Creates a transaction for each fee and returns an array
# of the raw transactions.
- utxos = node.listunspent(0)
+ utxos = [u for u in node.listunspent(0) if u['spendable']]
# Create transactions
inputs = []
@@ -25,7 +28,7 @@ def create_transactions(node, address, amt, fees):
for utxo in utxos:
inputs.append({"txid": utxo["txid"], "vout": utxo["vout"]})
ins_total += utxo['amount']
- if ins_total > amt:
+ if ins_total + max(fees) > amt:
break
txs = []
@@ -33,6 +36,7 @@ def create_transactions(node, address, amt, fees):
outputs = {address: amt, node.getrawchangeaddress(): ins_total - amt - fee}
raw_tx = node.createrawtransaction(inputs, outputs, 0, True)
raw_tx = node.signrawtransactionwithwallet(raw_tx)
+ assert_equal(raw_tx['complete'], True)
txs.append(raw_tx)
return txs
@@ -41,21 +45,26 @@ class WalletTest(BitcoinTestFramework):
def set_test_params(self):
self.num_nodes = 2
self.setup_clean_chain = True
+ self.extra_args = [
+ ['-limitdescendantcount=3'], # Limit mempool descendants as a hack to have wallet txs rejected from the mempool
+ [],
+ ]
def skip_test_if_missing_module(self):
self.skip_if_no_wallet()
def run_test(self):
+ self.nodes[0].importaddress(ADDRESS_WATCHONLY)
# Check that nodes don't own any UTXOs
assert_equal(len(self.nodes[0].listunspent()), 0)
assert_equal(len(self.nodes[1].listunspent()), 0)
- self.log.info("Mining one block for each node")
+ self.log.info("Mining blocks ...")
self.nodes[0].generate(1)
self.sync_all()
self.nodes[1].generate(1)
- self.nodes[1].generatetoaddress(100, RANDOM_COINBASE_ADDRESS)
+ self.nodes[1].generatetoaddress(101, ADDRESS_WATCHONLY)
self.sync_all()
assert_equal(self.nodes[0].getbalance(), 50)
@@ -64,8 +73,10 @@ class WalletTest(BitcoinTestFramework):
self.log.info("Test getbalance with different arguments")
assert_equal(self.nodes[0].getbalance("*"), 50)
assert_equal(self.nodes[0].getbalance("*", 1), 50)
- assert_equal(self.nodes[0].getbalance("*", 1, True), 50)
+ assert_equal(self.nodes[0].getbalance("*", 1, True), 100)
assert_equal(self.nodes[0].getbalance(minconf=1), 50)
+ assert_equal(self.nodes[0].getbalance(minconf=0, include_watchonly=True), 100)
+ assert_equal(self.nodes[1].getbalance(minconf=0, include_watchonly=True), 50)
# Send 40 BTC from 0 to 1 and 60 BTC from 1 to 0.
txs = create_transactions(self.nodes[0], self.nodes[1].getnewaddress(), 40, [Decimal('0.01')])
@@ -83,32 +94,34 @@ class WalletTest(BitcoinTestFramework):
self.log.info("Test getbalance and getunconfirmedbalance with unconfirmed inputs")
- # getbalance without any arguments includes unconfirmed transactions, but not untrusted transactions
- assert_equal(self.nodes[0].getbalance(), Decimal('9.99')) # change from node 0's send
- assert_equal(self.nodes[1].getbalance(), Decimal('29.99')) # change from node 1's send
- # Same with minconf=0
- assert_equal(self.nodes[0].getbalance(minconf=0), Decimal('9.99'))
- assert_equal(self.nodes[1].getbalance(minconf=0), Decimal('29.99'))
- # getbalance with a minconf incorrectly excludes coins that have been spent more recently than the minconf blocks ago
- # TODO: fix getbalance tracking of coin spentness depth
- assert_equal(self.nodes[0].getbalance(minconf=1), Decimal('0'))
- assert_equal(self.nodes[1].getbalance(minconf=1), Decimal('0'))
- # getunconfirmedbalance
- assert_equal(self.nodes[0].getunconfirmedbalance(), Decimal('60')) # output of node 1's spend
- assert_equal(self.nodes[1].getunconfirmedbalance(), Decimal('0')) # Doesn't include output of node 0's send since it was spent
+ def test_balances(*, fee_node_1=0):
+ # getbalance without any arguments includes unconfirmed transactions, but not untrusted transactions
+ assert_equal(self.nodes[0].getbalance(), Decimal('9.99')) # change from node 0's send
+ assert_equal(self.nodes[1].getbalance(), Decimal('30') - fee_node_1) # change from node 1's send
+ # Same with minconf=0
+ assert_equal(self.nodes[0].getbalance(minconf=0), Decimal('9.99'))
+ assert_equal(self.nodes[1].getbalance(minconf=0), Decimal('30') - fee_node_1)
+ # getbalance with a minconf incorrectly excludes coins that have been spent more recently than the minconf blocks ago
+ # TODO: fix getbalance tracking of coin spentness depth
+ assert_equal(self.nodes[0].getbalance(minconf=1), Decimal('0'))
+ assert_equal(self.nodes[1].getbalance(minconf=1), Decimal('0'))
+ # getunconfirmedbalance
+ assert_equal(self.nodes[0].getunconfirmedbalance(), Decimal('60')) # output of node 1's spend
+ assert_equal(self.nodes[0].getwalletinfo()["unconfirmed_balance"], Decimal('60'))
+ assert_equal(self.nodes[1].getunconfirmedbalance(), Decimal('0')) # Doesn't include output of node 0's send since it was spent
+ assert_equal(self.nodes[1].getwalletinfo()["unconfirmed_balance"], Decimal('0'))
+
+ test_balances(fee_node_1=Decimal('0.01'))
# Node 1 bumps the transaction fee and resends
self.nodes[1].sendrawtransaction(txs[1]['hex'])
+ self.nodes[0].sendrawtransaction(txs[1]['hex']) # sending on both nodes is faster than waiting for propagation
self.sync_all()
self.log.info("Test getbalance and getunconfirmedbalance with conflicted unconfirmed inputs")
+ test_balances(fee_node_1=Decimal('0.02'))
- assert_equal(self.nodes[0].getwalletinfo()["unconfirmed_balance"], Decimal('60')) # output of node 1's send
- assert_equal(self.nodes[0].getunconfirmedbalance(), Decimal('60'))
- assert_equal(self.nodes[1].getwalletinfo()["unconfirmed_balance"], Decimal('0')) # Doesn't include output of node 0's send since it was spent
- assert_equal(self.nodes[1].getunconfirmedbalance(), Decimal('0'))
-
- self.nodes[1].generatetoaddress(1, RANDOM_COINBASE_ADDRESS)
+ self.nodes[1].generatetoaddress(1, ADDRESS_WATCHONLY)
self.sync_all()
# balances are correct after the transactions are confirmed
@@ -118,7 +131,7 @@ class WalletTest(BitcoinTestFramework):
# Send total balance away from node 1
txs = create_transactions(self.nodes[1], self.nodes[0].getnewaddress(), Decimal('29.97'), [Decimal('0.01')])
self.nodes[1].sendrawtransaction(txs[0]['hex'])
- self.nodes[1].generatetoaddress(2, RANDOM_COINBASE_ADDRESS)
+ self.nodes[1].generatetoaddress(2, ADDRESS_WATCHONLY)
self.sync_all()
# getbalance with a minconf incorrectly excludes coins that have been spent more recently than the minconf blocks ago
@@ -140,6 +153,51 @@ class WalletTest(BitcoinTestFramework):
after = self.nodes[1].getunconfirmedbalance()
assert_equal(before + Decimal('0.1'), after)
+ # Create 3 more wallet txs, where the last is not accepted to the
+ # mempool because it is the third descendant of the tx above
+ for _ in range(3):
+ # Set amount high enough such that all coins are spent by each tx
+ txid = self.nodes[0].sendtoaddress(self.nodes[0].getnewaddress(), 99)
+
+ self.log.info('Check that wallet txs not in the mempool are untrusted')
+ assert txid not in self.nodes[0].getrawmempool()
+ assert_equal(self.nodes[0].gettransaction(txid)['trusted'], False)
+ assert_equal(self.nodes[0].getbalance(minconf=0), 0)
+
+ self.log.info("Test replacement and reorg of non-mempool tx")
+ tx_orig = self.nodes[0].gettransaction(txid)['hex']
+ # Increase fee by 1 coin
+ tx_replace = tx_orig.replace(
+ struct.pack("<q", 99 * 10**8).hex(),
+ struct.pack("<q", 98 * 10**8).hex(),
+ )
+ tx_replace = self.nodes[0].signrawtransactionwithwallet(tx_replace)['hex']
+ # Total balance is given by the sum of outputs of the tx
+ total_amount = sum([o['value'] for o in self.nodes[0].decoderawtransaction(tx_replace)['vout']])
+ self.sync_all()
+ self.nodes[1].sendrawtransaction(hexstring=tx_replace, maxfeerate=0)
+
+ # Now confirm tx_replace
+ block_reorg = self.nodes[1].generatetoaddress(1, ADDRESS_WATCHONLY)[0]
+ self.sync_all()
+ assert_equal(self.nodes[0].getbalance(minconf=0), total_amount)
+
+ 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)
+ 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
+
+ # Now confirm tx_orig
+ self.restart_node(1, ['-persistmempool=0'])
+ connect_nodes_bi(self.nodes, 0, 1)
+ sync_blocks(self.nodes)
+ self.nodes[1].sendrawtransaction(tx_orig)
+ self.nodes[1].generatetoaddress(1, ADDRESS_WATCHONLY)
+ self.sync_all()
+ assert_equal(self.nodes[0].getbalance(minconf=0), total_amount + 1) # The reorg recovered our fee of 1 coin
+
if __name__ == '__main__':
WalletTest().main()
diff --git a/test/functional/wallet_bumpfee.py b/test/functional/wallet_bumpfee.py
index 0c6ccbef0f..568b1f28d8 100755
--- a/test/functional/wallet_bumpfee.py
+++ b/test/functional/wallet_bumpfee.py
@@ -79,6 +79,10 @@ class BumpFeeTest(BitcoinTestFramework):
test_unconfirmed_not_spendable(rbf_node, rbf_node_address)
test_bumpfee_metadata(rbf_node, dest_address)
test_locked_wallet_fails(rbf_node, dest_address)
+ test_change_script_match(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)
self.log.info("Success")
@@ -179,6 +183,40 @@ def test_small_output_fails(rbf_node, dest_address):
rbfid = spend_one_input(rbf_node, dest_address)
assert_raises_rpc_error(-4, "Change output is too small", rbf_node.bumpfee, rbfid, {"totalFee": 50001})
+def test_small_output_with_feerate_succeeds(rbf_node, dest_address):
+
+ # Make sure additional inputs exist
+ rbf_node.generatetoaddress(101, rbf_node.getnewaddress())
+ rbfid = spend_one_input(rbf_node, dest_address)
+ original_input_list = rbf_node.getrawtransaction(rbfid, 1)["vin"]
+ assert_equal(len(original_input_list), 1)
+ original_txin = original_input_list[0]
+ # Keep bumping until we out-spend change output
+ tx_fee = 0
+ while tx_fee < Decimal("0.0005"):
+ new_input_list = rbf_node.getrawtransaction(rbfid, 1)["vin"]
+ new_item = list(new_input_list)[0]
+ assert_equal(len(original_input_list), 1)
+ assert_equal(original_txin["txid"], new_item["txid"])
+ assert_equal(original_txin["vout"], new_item["vout"])
+ rbfid_new_details = rbf_node.bumpfee(rbfid)
+ rbfid_new = rbfid_new_details["txid"]
+ raw_pool = rbf_node.getrawmempool()
+ assert rbfid not in raw_pool
+ assert rbfid_new in raw_pool
+ rbfid = rbfid_new
+ tx_fee = rbfid_new_details["origfee"]
+
+ # input(s) have been added
+ final_input_list = rbf_node.getrawtransaction(rbfid, 1)["vin"]
+ assert_greater_than(len(final_input_list), 1)
+ # Original input is in final set
+ assert [txin for txin in final_input_list
+ if txin["txid"] == original_txin["txid"]
+ and txin["vout"] == original_txin["vout"]]
+
+ rbf_node.generatetoaddress(1, rbf_node.getnewaddress())
+ assert_equal(rbf_node.gettransaction(rbfid)["confirmations"], 1)
def test_dust_to_fee(rbf_node, dest_address):
# check that if output is reduced to dust, it will be converted to fee
@@ -278,19 +316,37 @@ def test_locked_wallet_fails(rbf_node, dest_address):
rbf_node.walletlock()
assert_raises_rpc_error(-13, "Please enter the wallet passphrase with walletpassphrase first.",
rbf_node.bumpfee, rbfid)
+ rbf_node.walletpassphrase(WALLET_PASSPHRASE, WALLET_PASSPHRASE_TIMEOUT)
+
+def test_change_script_match(rbf_node, dest_address):
+ """Test that the same change addresses is used for the replacement transaction when possible."""
+ def get_change_address(tx):
+ tx_details = rbf_node.getrawtransaction(tx, 1)
+ txout_addresses = [txout['scriptPubKey']['addresses'][0] for txout in tx_details["vout"]]
+ return [address for address in txout_addresses if rbf_node.getaddressinfo(address)["ischange"]]
+ # Check that there is only one change output
+ rbfid = spend_one_input(rbf_node, dest_address)
+ change_addresses = get_change_address(rbfid)
+ assert_equal(len(change_addresses), 1)
+
+ # Now find that address in each subsequent tx, and no other change
+ bumped_total_tx = rbf_node.bumpfee(rbfid, {"totalFee": 2000})
+ assert_equal(change_addresses, get_change_address(bumped_total_tx['txid']))
+ bumped_rate_tx = rbf_node.bumpfee(bumped_total_tx["txid"])
+ assert_equal(change_addresses, get_change_address(bumped_rate_tx['txid']))
-def spend_one_input(node, dest_address):
+def spend_one_input(node, dest_address, change_size=Decimal("0.00049000")):
tx_input = dict(
sequence=BIP125_SEQUENCE_NUMBER, **next(u for u in node.listunspent() if u["amount"] == Decimal("0.00100000")))
- rawtx = node.createrawtransaction(
- [tx_input], {dest_address: Decimal("0.00050000"),
- node.getrawchangeaddress(): Decimal("0.00049000")})
+ destinations = {dest_address: Decimal("0.00050000")}
+ if change_size > 0:
+ destinations[node.getrawchangeaddress()] = change_size
+ rawtx = node.createrawtransaction([tx_input], destinations)
signedtx = node.signrawtransactionwithwallet(rawtx)
txid = node.sendrawtransaction(signedtx["hex"])
return txid
-
def submit_block_with_tx(node, tx):
ctx = CTransaction()
ctx.deserialize(io.BytesIO(hex_str_to_bytes(tx)))
@@ -307,6 +363,12 @@ def submit_block_with_tx(node, tx):
node.submitblock(block.serialize(True).hex())
return block
+def test_no_more_inputs_fails(rbf_node, dest_address):
+ # feerate rbf requires confirmed outputs when change output doesn't exist or is insufficient
+ rbf_node.generatetoaddress(1, dest_address)
+ # spend all funds, no change output
+ rbfid = rbf_node.sendtoaddress(rbf_node.getnewaddress(), rbf_node.getbalance(), "", "", True)
+ assert_raises_rpc_error(-4, "Unable to create transaction: Insufficient funds", rbf_node.bumpfee, rbfid)
if __name__ == "__main__":
BumpFeeTest().main()
diff --git a/test/functional/wallet_import_rescan.py b/test/functional/wallet_import_rescan.py
index 72df714d80..9de30d0374 100755
--- a/test/functional/wallet_import_rescan.py
+++ b/test/functional/wallet_import_rescan.py
@@ -166,11 +166,12 @@ class ImportRescanTest(BitcoinTestFramework):
timestamp = self.nodes[0].getblockheader(self.nodes[0].getbestblockhash())["time"]
set_node_times(self.nodes, timestamp + TIMESTAMP_WINDOW + 1)
self.nodes[0].generate(1)
- self.sync_blocks()
+ self.sync_all()
# For each variation of wallet key import, invoke the import RPC and
# check the results from getbalance and listtransactions.
for variant in IMPORT_VARIANTS:
+ self.log.info('Run import for variant {}'.format(variant))
variant.expect_disabled = variant.rescan == Rescan.yes and variant.prune and variant.call == Call.single
expect_rescan = variant.rescan == Rescan.yes and not variant.expect_disabled
variant.node = self.nodes[2 + IMPORT_NODES.index(ImportNode(variant.prune, expect_rescan))]
@@ -192,10 +193,11 @@ class ImportRescanTest(BitcoinTestFramework):
# Generate a block containing the new transactions.
self.nodes[0].generate(1)
assert_equal(self.nodes[0].getrawmempool(), [])
- self.sync_blocks()
+ self.sync_all()
# Check the latest results from getbalance and listtransactions.
for variant in IMPORT_VARIANTS:
+ self.log.info('Run check for variant {}'.format(variant))
if not variant.expect_disabled:
variant.expected_balance += variant.sent_amount
variant.expected_txs += 1
diff --git a/test/lint/check-doc.py b/test/lint/check-doc.py
index 3b05d5055c..1d6122a13d 100755
--- a/test/lint/check-doc.py
+++ b/test/lint/check-doc.py
@@ -12,26 +12,23 @@ Author: @MarcoFalke
from subprocess import check_output
import re
-import sys
FOLDER_GREP = 'src'
FOLDER_TEST = 'src/test/'
REGEX_ARG = '(?:ForceSet|SoftSet|Get|Is)(?:Bool)?Args?(?:Set)?\("(-[^"]+)"'
REGEX_DOC = 'AddArg\("(-[^"=]+?)(?:=|")'
-CMD_ROOT_DIR = '`git rev-parse --show-toplevel`/{}'.format(FOLDER_GREP)
+CMD_ROOT_DIR = '$(git rev-parse --show-toplevel)/{}'.format(FOLDER_GREP)
CMD_GREP_ARGS = r"git grep --perl-regexp '{}' -- {} ':(exclude){}'".format(REGEX_ARG, CMD_ROOT_DIR, FOLDER_TEST)
+CMD_GREP_WALLET_ARGS = r"git grep --function-context 'void WalletInit::AddWalletOptions' -- {} | grep AddArg".format(CMD_ROOT_DIR)
+CMD_GREP_WALLET_HIDDEN_ARGS = r"git grep --function-context 'void DummyWalletInit::AddWalletOptions' -- {}".format(CMD_ROOT_DIR)
CMD_GREP_DOCS = r"git grep --perl-regexp '{}' {}".format(REGEX_DOC, CMD_ROOT_DIR)
# list unsupported, deprecated and duplicate args as they need no documentation
SET_DOC_OPTIONAL = set(['-h', '-help', '-dbcrashratio', '-forcecompactdb'])
-def main():
- if sys.version_info >= (3, 6):
- used = check_output(CMD_GREP_ARGS, shell=True, universal_newlines=True, encoding='utf8')
- docd = check_output(CMD_GREP_DOCS, shell=True, universal_newlines=True, encoding='utf8')
- else:
- used = check_output(CMD_GREP_ARGS, shell=True).decode('utf8').strip()
- docd = check_output(CMD_GREP_DOCS, shell=True).decode('utf8').strip()
+def lint_missing_argument_documentation():
+ used = check_output(CMD_GREP_ARGS, shell=True).decode('utf8').strip()
+ docd = check_output(CMD_GREP_DOCS, shell=True).decode('utf8').strip()
args_used = set(re.findall(re.compile(REGEX_ARG), used))
args_docd = set(re.findall(re.compile(REGEX_DOC), docd)).union(SET_DOC_OPTIONAL)
@@ -45,7 +42,24 @@ def main():
print("Args unknown : {}".format(len(args_unknown)))
print(args_unknown)
- sys.exit(len(args_need_doc))
+ assert 0 == len(args_need_doc), "Please document the following arguments: {}".format(args_need_doc)
+
+
+def lint_missing_hidden_wallet_args():
+ wallet_args = check_output(CMD_GREP_WALLET_ARGS, shell=True).decode('utf8').strip()
+ wallet_hidden_args = check_output(CMD_GREP_WALLET_HIDDEN_ARGS, shell=True).decode('utf8').strip()
+
+ wallet_args = set(re.findall(re.compile(REGEX_DOC), wallet_args))
+ wallet_hidden_args = set(re.findall(re.compile(r' "([^"=]+)'), wallet_hidden_args))
+
+ hidden_missing = wallet_args.difference(wallet_hidden_args)
+ if hidden_missing:
+ assert 0, "Please add {} to the hidden args in DummyWalletInit::AddWalletOptions".format(hidden_missing)
+
+
+def main():
+ lint_missing_argument_documentation()
+ lint_missing_hidden_wallet_args()
if __name__ == "__main__":
diff --git a/test/lint/lint-circular-dependencies.sh b/test/lint/lint-circular-dependencies.sh
index 87b451dbbd..e1a99abc49 100755
--- a/test/lint/lint-circular-dependencies.sh
+++ b/test/lint/lint-circular-dependencies.sh
@@ -10,7 +10,6 @@ export LC_ALL=C
EXPECTED_CIRCULAR_DEPENDENCIES=(
"chainparamsbase -> util/system -> chainparamsbase"
- "checkpoints -> validation -> checkpoints"
"index/txindex -> validation -> index/txindex"
"policy/fees -> txmempool -> policy/fees"
"policy/policy -> policy/settings -> policy/policy"
@@ -37,7 +36,6 @@ EXPECTED_CIRCULAR_DEPENDENCIES=(
"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/guiutil -> qt/walletmodel -> qt/optionsmodel -> qt/intro -> qt/guiutil"
"qt/addressbookpage -> qt/bitcoingui -> qt/walletview -> qt/sendcoinsdialog -> qt/sendcoinsentry -> qt/addressbookpage"
)
diff --git a/test/lint/lint-python-dead-code.sh b/test/lint/lint-python-dead-code.sh
index 863caa9d5c..588ba428d7 100755
--- a/test/lint/lint-python-dead-code.sh
+++ b/test/lint/lint-python-dead-code.sh
@@ -15,5 +15,5 @@ fi
vulture \
--min-confidence 60 \
- --ignore-names "argtypes,connection_lost,connection_made,converter,data_received,daemon,errcheck,get_ecdh_key,get_privkey,is_compressed,is_fullyvalid,msg_generic,on_*,optionxform,restype,set_privkey,profile_with_perf" \
+ --ignore-names "argtypes,connection_lost,connection_made,converter,data_received,daemon,errcheck,is_compressed,is_valid,verify_ecdsa,msg_generic,on_*,optionxform,restype,profile_with_perf" \
$(git ls-files -- "*.py" ":(exclude)contrib/" ":(exclude)test/functional/data/invalid_txs.py")