aboutsummaryrefslogtreecommitdiff
path: root/test
diff options
context:
space:
mode:
Diffstat (limited to 'test')
-rw-r--r--test/functional/README.md10
-rwxr-xr-xtest/functional/rpc_fundrawtransaction.py227
-rwxr-xr-xtest/functional/rpc_invalidateblock.py6
-rwxr-xr-xtest/functional/rpc_rawtransaction.py2
-rw-r--r--test/functional/test-shell.md186
-rw-r--r--test/functional/test_framework/address.py1
-rwxr-xr-xtest/functional/test_framework/mininode.py3
-rwxr-xr-xtest/functional/test_framework/test_framework.py94
-rw-r--r--test/functional/test_framework/test_shell.py75
-rw-r--r--test/functional/test_framework/util.py1
-rwxr-xr-xtest/functional/test_runner.py1
-rwxr-xr-xtest/functional/wallet_balance.py48
-rwxr-xr-xtest/functional/wallet_bumpfee.py12
-rwxr-xr-xtest/functional/wallet_implicitsegwit.py64
-rwxr-xr-xtest/functional/wallet_listsinceblock.py49
-rwxr-xr-xtest/lint/git-subtree-check.sh3
-rwxr-xr-xtest/lint/lint-assertions.sh11
-rw-r--r--test/lint/lint-spelling.ignore-words.txt3
-rwxr-xr-xtest/lint/lint-spelling.sh2
19 files changed, 623 insertions, 175 deletions
diff --git a/test/functional/README.md b/test/functional/README.md
index a9b83076eb..77a9ce9acb 100644
--- a/test/functional/README.md
+++ b/test/functional/README.md
@@ -99,6 +99,16 @@ P2PInterface object and override the callback methods.
Examples tests are [p2p_unrequested_blocks.py](p2p_unrequested_blocks.py),
[p2p_compactblocks.py](p2p_compactblocks.py).
+#### Prototyping tests
+
+The [`TestShell`](test-shell.md) class exposes the BitcoinTestFramework
+functionality to interactive Python3 environments and can be used to prototype
+tests. This may be especially useful in a REPL environment with session logging
+utilities, such as
+[IPython](https://ipython.readthedocs.io/en/stable/interactive/reference.html#session-logging-and-restoring).
+The logs of such interactive sessions can later be adapted into permanent test
+cases.
+
### Test framework modules
The following are useful modules for test developers. They are located in
[test/functional/test_framework/](test_framework).
diff --git a/test/functional/rpc_fundrawtransaction.py b/test/functional/rpc_fundrawtransaction.py
index c956af1cbe..41a9b50ea6 100755
--- a/test/functional/rpc_fundrawtransaction.py
+++ b/test/functional/rpc_fundrawtransaction.py
@@ -41,6 +41,7 @@ class RawTransactionsTest(BitcoinTestFramework):
connect_nodes(self.nodes[0], 3)
def run_test(self):
+ self.log.info("Connect nodes, set fees, generate blocks, and sync")
self.min_relay_tx_fee = self.nodes[0].getnetworkinfo()['relayfee']
# This test is not meant to test fee estimation and we'd like
# to be sure all txs are sent at a consistent desired feerate
@@ -90,7 +91,8 @@ class RawTransactionsTest(BitcoinTestFramework):
self.test_option_subtract_fee_from_outputs()
def test_change_position(self):
- # ensure that setting changePosition in fundraw with an exact match is handled properly
+ """Ensure setting changePosition in fundraw with an exact match is handled properly."""
+ self.log.info("Test fundrawtxn changePosition option")
rawmatch = self.nodes[2].createrawtransaction([], {self.nodes[2].getnewaddress():50})
rawmatch = self.nodes[2].fundrawtransaction(rawmatch, {"changePosition":1, "subtractFeeFromOutputs":[0]})
assert_equal(rawmatch["changepos"], -1)
@@ -115,9 +117,7 @@ class RawTransactionsTest(BitcoinTestFramework):
self.sync_all()
def test_simple(self):
- ###############
- # simple test #
- ###############
+ self.log.info("Test fundrawtxn")
inputs = [ ]
outputs = { self.nodes[0].getnewaddress() : 1.0 }
rawtx = self.nodes[2].createrawtransaction(inputs, outputs)
@@ -127,9 +127,7 @@ class RawTransactionsTest(BitcoinTestFramework):
assert len(dec_tx['vin']) > 0 #test that we have enough inputs
def test_simple_two_coins(self):
- ##############################
- # simple test with two coins #
- ##############################
+ self.log.info("Test fundrawtxn with 2 coins")
inputs = [ ]
outputs = { self.nodes[0].getnewaddress() : 2.2 }
rawtx = self.nodes[2].createrawtransaction(inputs, outputs)
@@ -141,9 +139,8 @@ class RawTransactionsTest(BitcoinTestFramework):
assert_equal(dec_tx['vin'][0]['scriptSig']['hex'], '')
def test_simple_two_outputs(self):
- ################################
- # simple test with two outputs #
- ################################
+ self.log.info("Test fundrawtxn with 2 outputs")
+
inputs = [ ]
outputs = { self.nodes[0].getnewaddress() : 2.6, self.nodes[1].getnewaddress() : 2.5 }
rawtx = self.nodes[2].createrawtransaction(inputs, outputs)
@@ -159,9 +156,7 @@ class RawTransactionsTest(BitcoinTestFramework):
assert_equal(dec_tx['vin'][0]['scriptSig']['hex'], '')
def test_change(self):
- #########################################################################
- # test a fundrawtransaction with a VIN greater than the required amount #
- #########################################################################
+ self.log.info("Test fundrawtxn with a vin > required amount")
utx = get_unspent(self.nodes[2].listunspent(), 5)
inputs = [ {'txid' : utx['txid'], 'vout' : utx['vout']}]
@@ -181,9 +176,7 @@ class RawTransactionsTest(BitcoinTestFramework):
assert_equal(fee + totalOut, utx['amount']) #compare vin total and totalout+fee
def test_no_change(self):
- #####################################################################
- # test a fundrawtransaction with which will not get a change output #
- #####################################################################
+ self.log.info("Test fundrawtxn not having a change output")
utx = get_unspent(self.nodes[2].listunspent(), 5)
inputs = [ {'txid' : utx['txid'], 'vout' : utx['vout']}]
@@ -203,9 +196,7 @@ class RawTransactionsTest(BitcoinTestFramework):
assert_equal(fee + totalOut, utx['amount']) #compare vin total and totalout+fee
def test_invalid_option(self):
- ####################################################
- # test a fundrawtransaction with an invalid option #
- ####################################################
+ self.log.info("Test fundrawtxn with an invalid option")
utx = get_unspent(self.nodes[2].listunspent(), 5)
inputs = [ {'txid' : utx['txid'], 'vout' : utx['vout']} ]
@@ -220,9 +211,7 @@ class RawTransactionsTest(BitcoinTestFramework):
assert_raises_rpc_error(-3, "Unexpected key reserveChangeKey", lambda: self.nodes[2].fundrawtransaction(hexstring=rawtx, options={'reserveChangeKey': True}))
def test_invalid_change_address(self):
- ############################################################
- # test a fundrawtransaction with an invalid change address #
- ############################################################
+ self.log.info("Test fundrawtxn with an invalid change address")
utx = get_unspent(self.nodes[2].listunspent(), 5)
inputs = [ {'txid' : utx['txid'], 'vout' : utx['vout']} ]
@@ -234,9 +223,7 @@ class RawTransactionsTest(BitcoinTestFramework):
assert_raises_rpc_error(-5, "changeAddress must be a valid bitcoin address", self.nodes[2].fundrawtransaction, rawtx, {'changeAddress':'foobar'})
def test_valid_change_address(self):
- ############################################################
- # test a fundrawtransaction with a provided change address #
- ############################################################
+ self.log.info("Test fundrawtxn with a provided change address")
utx = get_unspent(self.nodes[2].listunspent(), 5)
inputs = [ {'txid' : utx['txid'], 'vout' : utx['vout']} ]
@@ -253,9 +240,7 @@ class RawTransactionsTest(BitcoinTestFramework):
assert_equal(change, out['scriptPubKey']['addresses'][0])
def test_change_type(self):
- #########################################################
- # test a fundrawtransaction with a provided change type #
- #########################################################
+ self.log.info("Test fundrawtxn with a provided change type")
utx = get_unspent(self.nodes[2].listunspent(), 5)
inputs = [ {'txid' : utx['txid'], 'vout' : utx['vout']} ]
@@ -268,9 +253,7 @@ class RawTransactionsTest(BitcoinTestFramework):
assert_equal('witness_v0_keyhash', dec_tx['vout'][rawtx['changepos']]['scriptPubKey']['type'])
def test_coin_selection(self):
- #########################################################################
- # test a fundrawtransaction with a VIN smaller than the required amount #
- #########################################################################
+ self.log.info("Test fundrawtxn with a vin < required amount")
utx = get_unspent(self.nodes[2].listunspent(), 1)
inputs = [ {'txid' : utx['txid'], 'vout' : utx['vout']}]
@@ -302,9 +285,7 @@ class RawTransactionsTest(BitcoinTestFramework):
assert_equal(len(dec_tx['vout']), 2)
def test_two_vin(self):
- ###########################################
- # test a fundrawtransaction with two VINs #
- ###########################################
+ self.log.info("Test fundrawtxn with 2 vins")
utx = get_unspent(self.nodes[2].listunspent(), 1)
utx2 = get_unspent(self.nodes[2].listunspent(), 5)
@@ -335,9 +316,7 @@ class RawTransactionsTest(BitcoinTestFramework):
assert_equal(matchingIns, 2) #we now must see two vins identical to vins given as params
def test_two_vin_two_vout(self):
- #########################################################
- # test a fundrawtransaction with two VINs and two vOUTs #
- #########################################################
+ self.log.info("Test fundrawtxn with 2 vins and 2 vouts")
utx = get_unspent(self.nodes[2].listunspent(), 1)
utx2 = get_unspent(self.nodes[2].listunspent(), 5)
@@ -360,52 +339,54 @@ class RawTransactionsTest(BitcoinTestFramework):
assert_equal(len(dec_tx['vout']), 3)
def test_invalid_input(self):
- ##############################################
- # test a fundrawtransaction with invalid vin #
- ##############################################
+ self.log.info("Test fundrawtxn with an invalid vin")
inputs = [ {'txid' : "1c7f966dab21119bac53213a2bc7532bff1fa844c124fd750a7d0b1332440bd1", 'vout' : 0} ] #invalid vin!
outputs = { self.nodes[0].getnewaddress() : 1.0}
rawtx = self.nodes[2].createrawtransaction(inputs, outputs)
assert_raises_rpc_error(-4, "Insufficient funds", self.nodes[2].fundrawtransaction, rawtx)
def test_fee_p2pkh(self):
- ############################################################
- #compare fee of a standard pubkeyhash transaction
+ """Compare fee of a standard pubkeyhash transaction."""
+ self.log.info("Test fundrawtxn p2pkh fee")
inputs = []
outputs = {self.nodes[1].getnewaddress():1.1}
rawtx = self.nodes[0].createrawtransaction(inputs, outputs)
fundedTx = self.nodes[0].fundrawtransaction(rawtx)
- #create same transaction over sendtoaddress
+ # Create same transaction over sendtoaddress.
txId = self.nodes[0].sendtoaddress(self.nodes[1].getnewaddress(), 1.1)
signedFee = self.nodes[0].getrawmempool(True)[txId]['fee']
- #compare fee
+ # Compare fee.
feeDelta = Decimal(fundedTx['fee']) - Decimal(signedFee)
assert feeDelta >= 0 and feeDelta <= self.fee_tolerance
- ############################################################
def test_fee_p2pkh_multi_out(self):
- ############################################################
- #compare fee of a standard pubkeyhash transaction with multiple outputs
+ """Compare fee of a standard pubkeyhash transaction with multiple outputs."""
+ self.log.info("Test fundrawtxn p2pkh fee with multiple outputs")
inputs = []
- outputs = {self.nodes[1].getnewaddress():1.1,self.nodes[1].getnewaddress():1.2,self.nodes[1].getnewaddress():0.1,self.nodes[1].getnewaddress():1.3,self.nodes[1].getnewaddress():0.2,self.nodes[1].getnewaddress():0.3}
+ outputs = {
+ self.nodes[1].getnewaddress():1.1,
+ self.nodes[1].getnewaddress():1.2,
+ self.nodes[1].getnewaddress():0.1,
+ self.nodes[1].getnewaddress():1.3,
+ self.nodes[1].getnewaddress():0.2,
+ self.nodes[1].getnewaddress():0.3,
+ }
rawtx = self.nodes[0].createrawtransaction(inputs, outputs)
fundedTx = self.nodes[0].fundrawtransaction(rawtx)
- #create same transaction over sendtoaddress
+
+ # Create same transaction over sendtoaddress.
txId = self.nodes[0].sendmany("", outputs)
signedFee = self.nodes[0].getrawmempool(True)[txId]['fee']
- #compare fee
+ # Compare fee.
feeDelta = Decimal(fundedTx['fee']) - Decimal(signedFee)
assert feeDelta >= 0 and feeDelta <= self.fee_tolerance
- ############################################################
def test_fee_p2sh(self):
- ############################################################
- #compare fee of a 2of2 multisig p2sh transaction
-
- # create 2of2 addr
+ """Compare fee of a 2-of-2 multisig p2sh transaction."""
+ # Create 2-of-2 addr.
addr1 = self.nodes[1].getnewaddress()
addr2 = self.nodes[1].getnewaddress()
@@ -419,20 +400,19 @@ class RawTransactionsTest(BitcoinTestFramework):
rawtx = self.nodes[0].createrawtransaction(inputs, outputs)
fundedTx = self.nodes[0].fundrawtransaction(rawtx)
- #create same transaction over sendtoaddress
+ # Create same transaction over sendtoaddress.
txId = self.nodes[0].sendtoaddress(mSigObj, 1.1)
signedFee = self.nodes[0].getrawmempool(True)[txId]['fee']
- #compare fee
+ # Compare fee.
feeDelta = Decimal(fundedTx['fee']) - Decimal(signedFee)
assert feeDelta >= 0 and feeDelta <= self.fee_tolerance
- ############################################################
def test_fee_4of5(self):
- ############################################################
- #compare fee of a standard pubkeyhash transaction
+ """Compare fee of a standard pubkeyhash transaction."""
+ self.log.info("Test fundrawtxn fee with 4-of-5 addresses")
- # create 4of5 addr
+ # Create 4-of-5 addr.
addr1 = self.nodes[1].getnewaddress()
addr2 = self.nodes[1].getnewaddress()
addr3 = self.nodes[1].getnewaddress()
@@ -445,37 +425,50 @@ class RawTransactionsTest(BitcoinTestFramework):
addr4Obj = self.nodes[1].getaddressinfo(addr4)
addr5Obj = self.nodes[1].getaddressinfo(addr5)
- mSigObj = self.nodes[1].addmultisigaddress(4, [addr1Obj['pubkey'], addr2Obj['pubkey'], addr3Obj['pubkey'], addr4Obj['pubkey'], addr5Obj['pubkey']])['address']
+ mSigObj = self.nodes[1].addmultisigaddress(
+ 4,
+ [
+ addr1Obj['pubkey'],
+ addr2Obj['pubkey'],
+ addr3Obj['pubkey'],
+ addr4Obj['pubkey'],
+ addr5Obj['pubkey'],
+ ]
+ )['address']
inputs = []
outputs = {mSigObj:1.1}
rawtx = self.nodes[0].createrawtransaction(inputs, outputs)
fundedTx = self.nodes[0].fundrawtransaction(rawtx)
- #create same transaction over sendtoaddress
+ # Create same transaction over sendtoaddress.
txId = self.nodes[0].sendtoaddress(mSigObj, 1.1)
signedFee = self.nodes[0].getrawmempool(True)[txId]['fee']
- #compare fee
+ # Compare fee.
feeDelta = Decimal(fundedTx['fee']) - Decimal(signedFee)
assert feeDelta >= 0 and feeDelta <= self.fee_tolerance
- ############################################################
def test_spend_2of2(self):
- ############################################################
- # spend a 2of2 multisig transaction over fundraw
+ """Spend a 2-of-2 multisig transaction over fundraw."""
+ self.log.info("Test fundrawtxn spending 2-of-2 multisig")
- # create 2of2 addr
+ # Create 2-of-2 addr.
addr1 = self.nodes[2].getnewaddress()
addr2 = self.nodes[2].getnewaddress()
addr1Obj = self.nodes[2].getaddressinfo(addr1)
addr2Obj = self.nodes[2].getaddressinfo(addr2)
- mSigObj = self.nodes[2].addmultisigaddress(2, [addr1Obj['pubkey'], addr2Obj['pubkey']])['address']
+ mSigObj = self.nodes[2].addmultisigaddress(
+ 2,
+ [
+ addr1Obj['pubkey'],
+ addr2Obj['pubkey'],
+ ]
+ )['address']
-
- # send 1.2 BTC to msig addr
+ # Send 1.2 BTC to msig addr.
self.nodes[0].sendtoaddress(mSigObj, 1.2)
self.sync_all()
self.nodes[1].generate(1)
@@ -493,18 +486,18 @@ class RawTransactionsTest(BitcoinTestFramework):
self.nodes[1].generate(1)
self.sync_all()
- # make sure funds are received at node1
+ # Make sure funds are received at node1.
assert_equal(oldBalance+Decimal('1.10000000'), self.nodes[1].getbalance())
def test_locked_wallet(self):
- ############################################################
- # locked wallet test
+ self.log.info("Test fundrawtxn with locked wallet")
+
self.nodes[1].encryptwallet("test")
self.stop_nodes()
self.start_nodes()
# This test is not meant to test fee estimation and we'd like
- # to be sure all txs are sent at a consistent desired feerate
+ # to be sure all txns are sent at a consistent desired feerate.
for node in self.nodes:
node.settxfee(self.min_relay_tx_fee)
@@ -513,11 +506,11 @@ class RawTransactionsTest(BitcoinTestFramework):
connect_nodes(self.nodes[0], 2)
connect_nodes(self.nodes[0], 3)
# Again lock the watchonly UTXO or nodes[0] may spend it, because
- # lockunspent is memory-only and thus lost on restart
+ # lockunspent is memory-only and thus lost on restart.
self.nodes[0].lockunspent(False, [{"txid": self.watchonly_txid, "vout": self.watchonly_vout}])
self.sync_all()
- # drain the keypool
+ # Drain the keypool.
self.nodes[1].getnewaddress()
self.nodes[1].getrawchangeaddress()
inputs = []
@@ -527,7 +520,7 @@ class RawTransactionsTest(BitcoinTestFramework):
# creating the key must be impossible because the wallet is locked
assert_raises_rpc_error(-4, "Keypool ran out, please call keypoolrefill first", self.nodes[1].fundrawtransaction, rawtx)
- #refill the keypool
+ # Refill the keypool.
self.nodes[1].walletpassphrase("test", 100)
self.nodes[1].keypoolrefill(8) #need to refill the keypool to get an internal change address
self.nodes[1].walletlock()
@@ -541,22 +534,21 @@ class RawTransactionsTest(BitcoinTestFramework):
rawtx = self.nodes[1].createrawtransaction(inputs, outputs)
fundedTx = self.nodes[1].fundrawtransaction(rawtx)
- #now we need to unlock
+ # Now we need to unlock.
self.nodes[1].walletpassphrase("test", 600)
signedTx = self.nodes[1].signrawtransactionwithwallet(fundedTx['hex'])
self.nodes[1].sendrawtransaction(signedTx['hex'])
self.nodes[1].generate(1)
self.sync_all()
- # make sure funds are received at node1
+ # Make sure funds are received at node1.
assert_equal(oldBalance+Decimal('51.10000000'), self.nodes[0].getbalance())
def test_many_inputs_fee(self):
- ###############################################
- # multiple (~19) inputs tx test | Compare fee #
- ###############################################
+ """Multiple (~19) inputs tx test | Compare fee."""
+ self.log.info("Test fundrawtxn fee with many inputs")
- #empty node1, send some small coins from node0 to node1
+ # Empty node1, send some small coins from node0 to node1.
self.nodes[1].sendtoaddress(self.nodes[0].getnewaddress(), self.nodes[1].getbalance(), "", "", True)
self.sync_all()
self.nodes[0].generate(1)
@@ -567,26 +559,25 @@ class RawTransactionsTest(BitcoinTestFramework):
self.nodes[0].generate(1)
self.sync_all()
- #fund a tx with ~20 small inputs
+ # Fund a tx with ~20 small inputs.
inputs = []
outputs = {self.nodes[0].getnewaddress():0.15,self.nodes[0].getnewaddress():0.04}
rawtx = self.nodes[1].createrawtransaction(inputs, outputs)
fundedTx = self.nodes[1].fundrawtransaction(rawtx)
- #create same transaction over sendtoaddress
+ # Create same transaction over sendtoaddress.
txId = self.nodes[1].sendmany("", outputs)
signedFee = self.nodes[1].getrawmempool(True)[txId]['fee']
- #compare fee
+ # Compare fee.
feeDelta = Decimal(fundedTx['fee']) - Decimal(signedFee)
assert feeDelta >= 0 and feeDelta <= self.fee_tolerance * 19 #~19 inputs
def test_many_inputs_send(self):
- #############################################
- # multiple (~19) inputs tx test | sign/send #
- #############################################
+ """Multiple (~19) inputs tx test | sign/send."""
+ self.log.info("Test fundrawtxn sign+send with many inputs")
- #again, empty node1, send some small coins from node0 to node1
+ # Again, empty node1, send some small coins from node0 to node1.
self.nodes[1].sendtoaddress(self.nodes[0].getnewaddress(), self.nodes[1].getbalance(), "", "", True)
self.sync_all()
self.nodes[0].generate(1)
@@ -597,7 +588,7 @@ class RawTransactionsTest(BitcoinTestFramework):
self.nodes[0].generate(1)
self.sync_all()
- #fund a tx with ~20 small inputs
+ # Fund a tx with ~20 small inputs.
oldBalance = self.nodes[0].getbalance()
inputs = []
@@ -612,9 +603,7 @@ class RawTransactionsTest(BitcoinTestFramework):
assert_equal(oldBalance+Decimal('50.19000000'), self.nodes[0].getbalance()) #0.19+block reward
def test_op_return(self):
- #####################################################
- # test fundrawtransaction with OP_RETURN and no vin #
- #####################################################
+ self.log.info("Test fundrawtxn with OP_RETURN and no vin")
rawtx = "0100000000010000000000000000066a047465737400000000"
dec_tx = self.nodes[2].decoderawtransaction(rawtx)
@@ -629,9 +618,7 @@ class RawTransactionsTest(BitcoinTestFramework):
assert_equal(len(dec_tx['vout']), 2) # one change output added
def test_watchonly(self):
- ##################################################
- # test a fundrawtransaction using only watchonly #
- ##################################################
+ self.log.info("Test fundrawtxn using only watchonly")
inputs = []
outputs = {self.nodes[2].getnewaddress(): self.watchonly_amount / 2}
@@ -646,15 +633,13 @@ class RawTransactionsTest(BitcoinTestFramework):
assert_greater_than(result["changepos"], -1)
def test_all_watched_funds(self):
- ###############################################################
- # test fundrawtransaction using the entirety of watched funds #
- ###############################################################
+ self.log.info("Test fundrawtxn using entirety of watched funds")
inputs = []
outputs = {self.nodes[2].getnewaddress(): self.watchonly_amount}
rawtx = self.nodes[3].createrawtransaction(inputs, outputs)
- # Backward compatibility test (2nd param is includeWatching)
+ # Backward compatibility test (2nd param is includeWatching).
result = self.nodes[3].fundrawtransaction(rawtx, True)
res_dec = self.nodes[0].decoderawtransaction(result["hex"])
assert_equal(len(res_dec["vin"]), 2)
@@ -673,11 +658,9 @@ class RawTransactionsTest(BitcoinTestFramework):
self.sync_all()
def test_option_feerate(self):
- #######################
- # Test feeRate option #
- #######################
+ self.log.info("Test fundrawtxn feeRate option")
- # Make sure there is exactly one input so coin selection can't skew the result
+ # Make sure there is exactly one input so coin selection can't skew the result.
assert_equal(len(self.nodes[3].listunspent(1)), 1)
inputs = []
@@ -692,9 +675,8 @@ class RawTransactionsTest(BitcoinTestFramework):
assert_fee_amount(result3['fee'], count_bytes(result3['hex']), 10 * result_fee_rate)
def test_address_reuse(self):
- ################################
- # Test no address reuse occurs #
- ################################
+ """Test no address reuse occurs."""
+ self.log.info("Test fundrawtxn does not reuse addresses")
rawtx = self.nodes[3].createrawtransaction(inputs=[], outputs={self.nodes[3].getnewaddress(): 1})
result3 = self.nodes[3].fundrawtransaction(rawtx)
@@ -705,15 +687,13 @@ class RawTransactionsTest(BitcoinTestFramework):
changeaddress += out['scriptPubKey']['addresses'][0]
assert changeaddress != ""
nextaddr = self.nodes[3].getnewaddress()
- # Now the change address key should be removed from the keypool
+ # Now the change address key should be removed from the keypool.
assert changeaddress != nextaddr
def test_option_subtract_fee_from_outputs(self):
- ######################################
- # Test subtractFeeFromOutputs option #
- ######################################
+ self.log.info("Test fundrawtxn subtractFeeFromOutputs option")
- # Make sure there is exactly one input so coin selection can't skew the result
+ # Make sure there is exactly one input so coin selection can't skew the result.
assert_equal(len(self.nodes[3].listunspent(1)), 1)
inputs = []
@@ -744,38 +724,39 @@ class RawTransactionsTest(BitcoinTestFramework):
rawtx = self.nodes[3].createrawtransaction(inputs, outputs)
result = [self.nodes[3].fundrawtransaction(rawtx),
- # split the fee between outputs 0, 2, and 3, but not output 1
+ # Split the fee between outputs 0, 2, and 3, but not output 1.
self.nodes[3].fundrawtransaction(rawtx, {"subtractFeeFromOutputs": [0, 2, 3]})]
dec_tx = [self.nodes[3].decoderawtransaction(result[0]['hex']),
self.nodes[3].decoderawtransaction(result[1]['hex'])]
- # Nested list of non-change output amounts for each transaction
+ # Nested list of non-change output amounts for each transaction.
output = [[out['value'] for i, out in enumerate(d['vout']) if i != r['changepos']]
for d, r in zip(dec_tx, result)]
- # List of differences in output amounts between normal and subtractFee transactions
+ # List of differences in output amounts between normal and subtractFee transactions.
share = [o0 - o1 for o0, o1 in zip(output[0], output[1])]
- # output 1 is the same in both transactions
+ # Output 1 is the same in both transactions.
assert_equal(share[1], 0)
- # the other 3 outputs are smaller as a result of subtractFeeFromOutputs
+ # The other 3 outputs are smaller as a result of subtractFeeFromOutputs.
assert_greater_than(share[0], 0)
assert_greater_than(share[2], 0)
assert_greater_than(share[3], 0)
- # outputs 2 and 3 take the same share of the fee
+ # Outputs 2 and 3 take the same share of the fee.
assert_equal(share[2], share[3])
- # output 0 takes at least as much share of the fee, and no more than 2 satoshis more, than outputs 2 and 3
+ # Output 0 takes at least as much share of the fee, and no more than 2
+ # satoshis more, than outputs 2 and 3.
assert_greater_than_or_equal(share[0], share[2])
assert_greater_than_or_equal(share[2] + Decimal(2e-8), share[0])
- # the fee is the same in both transactions
+ # The fee is the same in both transactions.
assert_equal(result[0]['fee'], result[1]['fee'])
- # the total subtracted from the outputs is equal to the fee
+ # The total subtracted from the outputs is equal to the fee.
assert_equal(share[0] + share[2] + share[3], result[0]['fee'])
if __name__ == '__main__':
diff --git a/test/functional/rpc_invalidateblock.py b/test/functional/rpc_invalidateblock.py
index 595b40f7cb..1fdc134f97 100755
--- a/test/functional/rpc_invalidateblock.py
+++ b/test/functional/rpc_invalidateblock.py
@@ -5,7 +5,7 @@
"""Test the invalidateblock RPC."""
from test_framework.test_framework import BitcoinTestFramework
-from test_framework.address import ADDRESS_BCRT1_UNSPENDABLE
+from test_framework.address import ADDRESS_BCRT1_UNSPENDABLE_DESCRIPTOR
from test_framework.util import (
assert_equal,
connect_nodes,
@@ -62,7 +62,7 @@ class InvalidateTest(BitcoinTestFramework):
wait_until(lambda: self.nodes[1].getblockcount() == 4, timeout=5)
self.log.info("Verify that we reconsider all ancestors as well")
- blocks = self.nodes[1].generatetoaddress(10, ADDRESS_BCRT1_UNSPENDABLE)
+ blocks = self.nodes[1].generatetodescriptor(10, ADDRESS_BCRT1_UNSPENDABLE_DESCRIPTOR)
assert_equal(self.nodes[1].getbestblockhash(), blocks[-1])
# Invalidate the two blocks at the tip
self.nodes[1].invalidateblock(blocks[-1])
@@ -74,7 +74,7 @@ class InvalidateTest(BitcoinTestFramework):
assert_equal(self.nodes[1].getbestblockhash(), blocks[-1])
self.log.info("Verify that we reconsider all descendants")
- blocks = self.nodes[1].generatetoaddress(10, ADDRESS_BCRT1_UNSPENDABLE)
+ blocks = self.nodes[1].generatetodescriptor(10, ADDRESS_BCRT1_UNSPENDABLE_DESCRIPTOR)
assert_equal(self.nodes[1].getbestblockhash(), blocks[-1])
# Invalidate the two blocks at the tip
self.nodes[1].invalidateblock(blocks[-2])
diff --git a/test/functional/rpc_rawtransaction.py b/test/functional/rpc_rawtransaction.py
index 74fea07350..4ee46d5f53 100755
--- a/test/functional/rpc_rawtransaction.py
+++ b/test/functional/rpc_rawtransaction.py
@@ -209,7 +209,7 @@ class RawTransactionsTest(BitcoinTestFramework):
rawtx = self.nodes[2].signrawtransactionwithwallet(rawtx)
# This will raise an exception since there are missing inputs
- assert_raises_rpc_error(-25, "Missing inputs", self.nodes[2].sendrawtransaction, rawtx['hex'])
+ assert_raises_rpc_error(-25, "bad-txns-inputs-missingorspent", self.nodes[2].sendrawtransaction, rawtx['hex'])
#####################################
# getrawtransaction with block hash #
diff --git a/test/functional/test-shell.md b/test/functional/test-shell.md
new file mode 100644
index 0000000000..f6ea9ef682
--- /dev/null
+++ b/test/functional/test-shell.md
@@ -0,0 +1,186 @@
+Test Shell for Interactive Environments
+=========================================
+
+This document describes how to use the `TestShell` submodule in the functional
+test suite.
+
+The `TestShell` submodule extends the `BitcoinTestFramework` functionality to
+external interactive environments for prototyping and educational purposes. Just
+like `BitcoinTestFramework`, the `TestShell` allows the user to:
+
+* Manage regtest bitcoind subprocesses.
+* Access RPC interfaces of the underlying bitcoind instances.
+* Log events to the functional test logging utility.
+
+The `TestShell` can be useful in interactive environments where it is necessary
+to extend the object lifetime of the underlying `BitcoinTestFramework` between
+user inputs. Such environments include the Python3 command line interpreter or
+[Jupyter](https://jupyter.org/) notebooks running a Python3 kernel.
+
+## 1. Requirements
+
+* Python3
+* `bitcoind` built in the same repository as the `TestShell`.
+
+## 2. Importing `TestShell` from the Bitcoin Core repository
+
+We can import the `TestShell` by adding the path of the Bitcoin Core
+`test_framework` module to the beginning of the PATH variable, and then
+importing the `TestShell` class from the `test_shell` sub-package.
+
+```
+>>> import sys
+>>> sys.path.insert(0, "/path/to/bitcoin/test/functional")
+>>> from test_framework.test_shell import TestShell
+```
+
+The following `TestShell` methods manage the lifetime of the underlying bitcoind
+processes and logging utilities.
+
+* `TestShell.setup()`
+* `TestShell.shutdown()`
+
+The `TestShell` inherits all `BitcoinTestFramework` members and methods, such
+as:
+* `TestShell.nodes[index].rpc_method()`
+* `TestShell.log.info("Custom log message")`
+
+The following sections demonstrate how to initialize, run, and shut down a
+`TestShell` object.
+
+## 3. Initializing a `TestShell` object
+
+```
+>>> test = TestShell().setup(num_nodes=2, setup_clean_chain=True)
+20XX-XX-XXTXX:XX:XX.XXXXXXX TestFramework (INFO): Initializing test directory /path/to/bitcoin_func_test_XXXXXXX
+```
+The `TestShell` forwards all functional test parameters of the parent
+`BitcoinTestFramework` object. The full set of argument keywords which can be
+used to initialize the `TestShell` can be found in [section
+#6](#custom-testshell-parameters) of this document.
+
+**Note: Running multiple instances of `TestShell` is not allowed.** Running a
+single process also ensures that logging remains consolidated in the same
+temporary folder. If you need more bitcoind nodes than set by default (1),
+simply increase the `num_nodes` parameter during setup.
+
+```
+>>> test2 = TestShell().setup()
+TestShell is already running!
+```
+
+## 4. Interacting with the `TestShell`
+
+Unlike the `BitcoinTestFramework` class, the `TestShell` keeps the underlying
+Bitcoind subprocesses (nodes) and logging utilities running until the user
+explicitly shuts down the `TestShell` object.
+
+During the time between the `setup` and `shutdown` calls, all `bitcoind` node
+processes and `BitcoinTestFramework` convenience methods can be accessed
+interactively.
+
+**Example: Mining a regtest chain**
+
+By default, the `TestShell` nodes are initialized with a clean chain. This means
+that each node of the `TestShell` is initialized with a block height of 0.
+
+```
+>>> test.nodes[0].getblockchaininfo()["blocks"]
+0
+```
+
+We now let the first node generate 101 regtest blocks, and direct the coinbase
+rewards to a wallet address owned by the mining node.
+
+```
+>>> address = test.nodes[0].getnewaddress()
+>>> test.nodes[0].generatetoaddress(101, address)
+['2b98dd0044aae6f1cca7f88a0acf366a4bfe053c7f7b00da3c0d115f03d67efb', ...
+```
+Since the two nodes are both initialized by default to establish an outbound
+connection to each other during `setup`, the second node's chain will include
+the mined blocks as soon as they propagate.
+
+```
+>>> test.nodes[1].getblockchaininfo()["blocks"]
+101
+```
+The block rewards from the first block are now spendable by the wallet of the
+first node.
+
+```
+>>> test.nodes[0].getbalance()
+Decimal('50.00000000')
+```
+
+We can also log custom events to the logger.
+
+```
+>>> test.nodes[0].log.info("Successfully mined regtest chain!")
+20XX-XX-XXTXX:XX:XX.XXXXXXX TestFramework.node0 (INFO): Successfully mined regtest chain!
+```
+
+**Note: Please also consider the functional test
+[readme](../test/functional/README.md), which provides an overview of the
+test-framework**. Modules such as
+[key.py](../test/functional/test_framework/key.py),
+[script.py](../test/functional/test_framework/script.py) and
+[messages.py](../test/functional/test_framework/messages.py) are particularly
+useful in constructing objects which can be passed to the bitcoind nodes managed
+by a running `TestShell` object.
+
+## 5. Shutting the `TestShell` down
+
+Shutting down the `TestShell` will safely tear down all running bitcoind
+instances and remove all temporary data and logging directories.
+
+```
+>>> test.shutdown()
+20XX-XX-XXTXX:XX:XX.XXXXXXX TestFramework (INFO): Stopping nodes
+20XX-XX-XXTXX:XX:XX.XXXXXXX TestFramework (INFO): Cleaning up /path/to/bitcoin_func_test_XXXXXXX on exit
+20XX-XX-XXTXX:XX:XX.XXXXXXX TestFramework (INFO): Tests successful
+```
+To prevent the logs from being removed after a shutdown, simply set the
+`TestShell.options.nocleanup` member to `True`.
+```
+>>> test.options.nocleanup = True
+>>> test.shutdown()
+20XX-XX-XXTXX:XX:XX.XXXXXXX TestFramework (INFO): Stopping nodes
+20XX-XX-XXTXX:XX:XX.XXXXXXX TestFramework (INFO): Not cleaning up dir /path/to/bitcoin_func_test_XXXXXXX on exit
+20XX-XX-XXTXX:XX:XX.XXXXXXX TestFramework (INFO): Tests successful
+```
+
+The following utility consolidates logs from the bitcoind nodes and the
+underlying `BitcoinTestFramework`:
+
+* `/path/to/bitcoin/test/functional/combine_logs.py
+ '/path/to/bitcoin_func_test_XXXXXXX'`
+
+## 6. Custom `TestShell` parameters
+
+The `TestShell` object initializes with the default settings inherited from the
+`BitcoinTestFramework` class. The user can override these in
+`TestShell.setup(key=value)`.
+
+**Note:** `TestShell.reset()` will reset test parameters to default values and
+can be called after the TestShell is shut down.
+
+| Test parameter key | Default Value | Description |
+|---|---|---|
+| `bind_to_localhost_only` | `True` | Binds bitcoind RPC services to `127.0.0.1` if set to `True`.|
+| `cachedir` | `"/path/to/bitcoin/test/cache"` | Sets the bitcoind datadir directory. |
+| `chain` | `"regtest"` | Sets the chain-type for the underlying test bitcoind processes. |
+| `configfile` | `"/path/to/bitcoin/test/config.ini"` | Sets the location of the test framework config file. |
+| `coveragedir` | `None` | Records bitcoind RPC test coverage into this directory if set. |
+| `loglevel` | `INFO` | Logs events at this level and higher. Can be set to `DEBUG`, `INFO`, `WARNING`, `ERROR` or `CRITICAL`. |
+| `nocleanup` | `False` | Cleans up temporary test directory if set to `True` during `shutdown`. |
+| `noshutdown` | `False` | Does not stop bitcoind instances after `shutdown` if set to `True`. |
+| `num_nodes` | `1` | Sets the number of initialized bitcoind processes. |
+| `perf` | False | Profiles running nodes with `perf` for the duration of the test if set to `True`. |
+| `rpc_timeout` | `60` | Sets the RPC server timeout for the underlying bitcoind processes. |
+| `setup_clean_chain` | `False` | Initializes an empty blockchain by default. A 199-block-long chain is initialized if set to `True`. |
+| `randomseed` | Random Integer | `TestShell.options.randomseed` is a member of `TestShell` which can be accessed during a test to seed a random generator. User can override default with a constant value for reproducible test runs. |
+| `supports_cli` | `False` | Whether the bitcoin-cli utility is compiled and available for the test. |
+| `tmpdir` | `"/var/folders/.../"` | Sets directory for test logs. Will be deleted upon a successful test run unless `nocleanup` is set to `True` |
+| `trace_rpc` | `False` | Logs all RPC calls if set to `True`. |
+| `usecli` | `False` | Uses the bitcoin-cli interface for all bitcoind commands instead of directly calling the RPC server. Requires `supports_cli`. |
diff --git a/test/functional/test_framework/address.py b/test/functional/test_framework/address.py
index 194f2f061b..97585fe054 100644
--- a/test/functional/test_framework/address.py
+++ b/test/functional/test_framework/address.py
@@ -12,6 +12,7 @@ from .util import hex_str_to_bytes
from . import segwit_addr
ADDRESS_BCRT1_UNSPENDABLE = 'bcrt1qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq3xueyj'
+ADDRESS_BCRT1_UNSPENDABLE_DESCRIPTOR = 'addr(bcrt1qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq3xueyj)#juyq9d97'
class AddressType(enum.Enum):
diff --git a/test/functional/test_framework/mininode.py b/test/functional/test_framework/mininode.py
index f95c158a68..a9e669fea9 100755
--- a/test/functional/test_framework/mininode.py
+++ b/test/functional/test_framework/mininode.py
@@ -478,7 +478,8 @@ class NetworkThread(threading.Thread):
wait_until(lambda: not self.network_event_loop.is_running(), timeout=timeout)
self.network_event_loop.close()
self.join(timeout)
-
+ # Safe to remove event loop.
+ NetworkThread.network_event_loop = None
class P2PDataStore(P2PInterface):
"""A P2P data store class.
diff --git a/test/functional/test_framework/test_framework.py b/test/functional/test_framework/test_framework.py
index 780aa5fe03..c56c0d06ff 100755
--- a/test/functional/test_framework/test_framework.py
+++ b/test/functional/test_framework/test_framework.py
@@ -99,12 +99,39 @@ class BitcoinTestFramework(metaclass=BitcoinTestMetaClass):
self.supports_cli = False
self.bind_to_localhost_only = True
self.set_test_params()
-
- assert hasattr(self, "num_nodes"), "Test must set self.num_nodes in set_test_params()"
+ self.parse_args()
def main(self):
"""Main function. This should not be overridden by the subclass test scripts."""
+ assert hasattr(self, "num_nodes"), "Test must set self.num_nodes in set_test_params()"
+
+ try:
+ self.setup()
+ self.run_test()
+ except JSONRPCException:
+ self.log.exception("JSONRPC error")
+ self.success = TestStatus.FAILED
+ except SkipTest as e:
+ self.log.warning("Test Skipped: %s" % e.message)
+ self.success = TestStatus.SKIPPED
+ except AssertionError:
+ self.log.exception("Assertion failed")
+ self.success = TestStatus.FAILED
+ except KeyError:
+ self.log.exception("Key error")
+ self.success = TestStatus.FAILED
+ except Exception:
+ self.log.exception("Unexpected exception caught during testing")
+ self.success = TestStatus.FAILED
+ except KeyboardInterrupt:
+ self.log.warning("Exiting after keyboard interrupt")
+ self.success = TestStatus.FAILED
+ finally:
+ exit_code = self.shutdown()
+ sys.exit(exit_code)
+
+ def parse_args(self):
parser = argparse.ArgumentParser(usage="%(prog)s [options]")
parser.add_argument("--nocleanup", dest="nocleanup", default=False, action="store_true",
help="Leave bitcoinds and test.* datadir on exit or error")
@@ -135,6 +162,9 @@ class BitcoinTestFramework(metaclass=BitcoinTestMetaClass):
self.add_options(parser)
self.options = parser.parse_args()
+ def setup(self):
+ """Call this method to start up the test framework object with options set."""
+
PortSeed.n = self.options.port_seed
check_json_precision()
@@ -181,33 +211,20 @@ class BitcoinTestFramework(metaclass=BitcoinTestMetaClass):
self.network_thread = NetworkThread()
self.network_thread.start()
- success = TestStatus.FAILED
+ if self.options.usecli:
+ if not self.supports_cli:
+ raise SkipTest("--usecli specified but test does not support using CLI")
+ self.skip_if_no_cli()
+ self.skip_test_if_missing_module()
+ self.setup_chain()
+ self.setup_network()
- try:
- if self.options.usecli:
- if not self.supports_cli:
- raise SkipTest("--usecli specified but test does not support using CLI")
- self.skip_if_no_cli()
- self.skip_test_if_missing_module()
- self.setup_chain()
- self.setup_network()
- self.run_test()
- success = TestStatus.PASSED
- except JSONRPCException:
- self.log.exception("JSONRPC error")
- except SkipTest as e:
- self.log.warning("Test Skipped: %s" % e.message)
- success = TestStatus.SKIPPED
- except AssertionError:
- self.log.exception("Assertion failed")
- except KeyError:
- self.log.exception("Key error")
- except Exception:
- self.log.exception("Unexpected exception caught during testing")
- except KeyboardInterrupt:
- self.log.warning("Exiting after keyboard interrupt")
+ self.success = TestStatus.PASSED
- if success == TestStatus.FAILED and self.options.pdbonfailure:
+ def shutdown(self):
+ """Call this method to shut down the test framework object."""
+
+ if self.success == TestStatus.FAILED and self.options.pdbonfailure:
print("Testcase failed. Attaching python debugger. Enter ? for help")
pdb.set_trace()
@@ -225,7 +242,7 @@ class BitcoinTestFramework(metaclass=BitcoinTestMetaClass):
should_clean_up = (
not self.options.nocleanup and
not self.options.noshutdown and
- success != TestStatus.FAILED and
+ self.success != TestStatus.FAILED and
not self.options.perf
)
if should_clean_up:
@@ -238,20 +255,33 @@ class BitcoinTestFramework(metaclass=BitcoinTestMetaClass):
self.log.warning("Not cleaning up dir {}".format(self.options.tmpdir))
cleanup_tree_on_exit = False
- if success == TestStatus.PASSED:
+ if self.success == TestStatus.PASSED:
self.log.info("Tests successful")
exit_code = TEST_EXIT_PASSED
- elif success == TestStatus.SKIPPED:
+ elif self.success == TestStatus.SKIPPED:
self.log.info("Test skipped")
exit_code = TEST_EXIT_SKIPPED
else:
self.log.error("Test failed. Test logging available at %s/test_framework.log", self.options.tmpdir)
self.log.error("Hint: Call {} '{}' to consolidate all logs".format(os.path.normpath(os.path.dirname(os.path.realpath(__file__)) + "/../combine_logs.py"), self.options.tmpdir))
exit_code = TEST_EXIT_FAILED
- logging.shutdown()
+ # Logging.shutdown will not remove stream- and filehandlers, so we must
+ # do it explicitly. Handlers are removed so the next test run can apply
+ # different log handler settings.
+ # See: https://docs.python.org/3/library/logging.html#logging.shutdown
+ for h in list(self.log.handlers):
+ h.flush()
+ h.close()
+ self.log.removeHandler(h)
+ rpc_logger = logging.getLogger("BitcoinRPC")
+ for h in list(rpc_logger.handlers):
+ h.flush()
+ rpc_logger.removeHandler(h)
if cleanup_tree_on_exit:
shutil.rmtree(self.options.tmpdir)
- sys.exit(exit_code)
+
+ self.nodes.clear()
+ return exit_code
# Methods to override in subclass test scripts.
def set_test_params(self):
diff --git a/test/functional/test_framework/test_shell.py b/test/functional/test_framework/test_shell.py
new file mode 100644
index 0000000000..26df128f1f
--- /dev/null
+++ b/test/functional/test_framework/test_shell.py
@@ -0,0 +1,75 @@
+#!/usr/bin/env python3
+# 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.
+
+from test_framework.test_framework import BitcoinTestFramework
+
+class TestShell:
+ """Wrapper Class for BitcoinTestFramework.
+
+ The TestShell class extends the BitcoinTestFramework
+ rpc & daemon process management functionality to external
+ python environments.
+
+ It is a singleton class, which ensures that users only
+ start a single TestShell at a time."""
+
+ class __TestShell(BitcoinTestFramework):
+ def set_test_params(self):
+ pass
+
+ def run_test(self):
+ pass
+
+ def setup(self, **kwargs):
+ if self.running:
+ print("TestShell is already running!")
+ return
+
+ # Num_nodes parameter must be set
+ # by BitcoinTestFramework child class.
+ self.num_nodes = 1
+
+ # User parameters override default values.
+ for key, value in kwargs.items():
+ if hasattr(self, key):
+ setattr(self, key, value)
+ elif hasattr(self.options, key):
+ setattr(self.options, key, value)
+ else:
+ raise KeyError(key + " not a valid parameter key!")
+
+ super().setup()
+ self.running = True
+ return self
+
+ def shutdown(self):
+ if not self.running:
+ print("TestShell is not running!")
+ else:
+ super().shutdown()
+ self.running = False
+
+ def reset(self):
+ if self.running:
+ print("Shutdown TestShell before resetting!")
+ else:
+ self.num_nodes = None
+ super().__init__()
+
+ instance = None
+
+ def __new__(cls):
+ # This implementation enforces singleton pattern, and will return the
+ # previously initialized instance if available
+ if not TestShell.instance:
+ TestShell.instance = TestShell.__TestShell()
+ TestShell.instance.running = False
+ return TestShell.instance
+
+ def __getattr__(self, name):
+ return getattr(self.instance, name)
+
+ def __setattr__(self, name, value):
+ return setattr(self.instance, name, value)
diff --git a/test/functional/test_framework/util.py b/test/functional/test_framework/util.py
index cde99a2219..4d7967273a 100644
--- a/test/functional/test_framework/util.py
+++ b/test/functional/test_framework/util.py
@@ -316,6 +316,7 @@ def initialize_datadir(dirname, n, chain):
f.write("listenonion=0\n")
f.write("printtoconsole=0\n")
f.write("upnp=0\n")
+ f.write("shrinkdebugfile=0\n")
os.makedirs(os.path.join(datadir, 'stderr'), exist_ok=True)
os.makedirs(os.path.join(datadir, 'stdout'), exist_ok=True)
return datadir
diff --git a/test/functional/test_runner.py b/test/functional/test_runner.py
index 2938c54a35..9b4a5d2030 100755
--- a/test/functional/test_runner.py
+++ b/test/functional/test_runner.py
@@ -181,6 +181,7 @@ BASE_SCRIPTS = [
'mining_basic.py',
'wallet_bumpfee.py',
'wallet_bumpfee_totalfee_deprecation.py',
+ 'wallet_implicitsegwit.py',
'rpc_named_arguments.py',
'wallet_listsinceblock.py',
'p2p_leak.py',
diff --git a/test/functional/wallet_balance.py b/test/functional/wallet_balance.py
index c50dcd987a..a5f9a047ed 100755
--- a/test/functional/wallet_balance.py
+++ b/test/functional/wallet_balance.py
@@ -109,13 +109,51 @@ class WalletTest(BitcoinTestFramework):
self.log.info("Test getbalance and getunconfirmedbalance with unconfirmed inputs")
+ # Before `test_balance()`, we have had two nodes with a balance of 50
+ # each and then we:
+ #
+ # 1) Sent 40 from node A to node B with fee 0.01
+ # 2) Sent 60 from node B to node A with fee 0.01
+ #
+ # Then we check the balances:
+ #
+ # 1) As is
+ # 2) With transaction 2 from above with 2x the fee
+ #
+ # Prior to #16766, in this situation, the node would immediately report
+ # a balance of 30 on node B as unconfirmed and trusted.
+ #
+ # After #16766, we show that balance as unconfirmed.
+ #
+ # The balance is indeed "trusted" and "confirmed" insofar as removing
+ # the mempool transactions would return at least that much money. But
+ # the algorithm after #16766 marks it as unconfirmed because the 'taint'
+ # tracking of transaction trust for summing balances doesn't consider
+ # which inputs belong to a user. In this case, the change output in
+ # question could be "destroyed" by replace the 1st transaction above.
+ #
+ # The post #16766 behavior is correct; we shouldn't be treating those
+ # funds as confirmed. If you want to rely on that specific UTXO existing
+ # which has given you that balance, you cannot, as a third party
+ # spending the other input would destroy that unconfirmed.
+ #
+ # For example, if the test transactions were:
+ #
+ # 1) Sent 40 from node A to node B with fee 0.01
+ # 2) Sent 10 from node B to node A with fee 0.01
+ #
+ # Then our node would report a confirmed balance of 40 + 50 - 10 = 80
+ # BTC, which is more than would be available if transaction 1 were
+ # replaced.
+
+
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
+ assert_equal(self.nodes[1].getbalance(), Decimal('0')) # node 1's send had an unsafe input
# 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)
+ assert_equal(self.nodes[1].getbalance(minconf=0), Decimal('0'))
# 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'))
@@ -125,9 +163,9 @@ class WalletTest(BitcoinTestFramework):
assert_equal(self.nodes[0].getbalances()['mine']['untrusted_pending'], Decimal('60'))
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].getbalances()['mine']['untrusted_pending'], Decimal('0'))
- assert_equal(self.nodes[1].getwalletinfo()["unconfirmed_balance"], Decimal('0'))
+ assert_equal(self.nodes[1].getunconfirmedbalance(), Decimal('30') - fee_node_1) # Doesn't include output of node 0's send since it was spent
+ assert_equal(self.nodes[1].getbalances()['mine']['untrusted_pending'], Decimal('30') - fee_node_1)
+ assert_equal(self.nodes[1].getwalletinfo()["unconfirmed_balance"], Decimal('30') - fee_node_1)
test_balances(fee_node_1=Decimal('0.01'))
diff --git a/test/functional/wallet_bumpfee.py b/test/functional/wallet_bumpfee.py
index 0948d47653..95d51adebb 100755
--- a/test/functional/wallet_bumpfee.py
+++ b/test/functional/wallet_bumpfee.py
@@ -38,7 +38,7 @@ class BumpFeeTest(BitcoinTestFramework):
"-walletrbf={}".format(i),
"-mintxfee=0.00002",
"-deprecatedrpc=totalFee",
- "-addresstype=p2sh-segwit", # TODO update constants in test and remove
+ "-addresstype=bech32",
] for i in range(self.num_nodes)]
def skip_test_if_missing_module(self):
@@ -246,10 +246,8 @@ def test_dust_to_fee(rbf_node, dest_address):
# the bumped tx sets fee=49,900, but it converts to 50,000
rbfid = spend_one_input(rbf_node, dest_address)
fulltx = rbf_node.getrawtransaction(rbfid, 1)
- # (32-byte p2sh-pwpkh output size + 148 p2pkh spend estimate) * 10k(discard_rate) / 1000 = 1800
- # P2SH outputs are slightly "over-discarding" due to the IsDust calculation assuming it will
- # be spent as a P2PKH.
- bumped_tx = rbf_node.bumpfee(rbfid, {"totalFee": 50000 - 1800})
+ # (31-vbyte p2wpkh output size + 67-vbyte p2wpkh spend estimate) * 10k(discard_rate) / 1000 = 980
+ bumped_tx = rbf_node.bumpfee(rbfid, {"totalFee": 50000 - 980})
full_bumped_tx = rbf_node.getrawtransaction(bumped_tx["txid"], 1)
assert_equal(bumped_tx["fee"], Decimal("0.00050000"))
assert_equal(len(fulltx["vout"]), 2)
@@ -272,7 +270,9 @@ def test_settxfee(rbf_node, dest_address):
def test_maxtxfee_fails(test, rbf_node, dest_address):
- test.restart_node(1, ['-maxtxfee=0.00003'] + test.extra_args[1])
+ # size of bumped transaction (p2wpkh, 1 input, 2 outputs): 141 vbytes
+ # expected bumping feerate of 20 sats/vbyte => 141*20 sats = 0.00002820 btc
+ test.restart_node(1, ['-maxtxfee=0.000025'] + 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)
diff --git a/test/functional/wallet_implicitsegwit.py b/test/functional/wallet_implicitsegwit.py
new file mode 100755
index 0000000000..379fa6a12f
--- /dev/null
+++ b/test/functional/wallet_implicitsegwit.py
@@ -0,0 +1,64 @@
+#!/usr/bin/env python3
+# 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.
+"""Test the wallet implicit segwit feature."""
+
+import test_framework.address as address
+from test_framework.test_framework import BitcoinTestFramework
+
+# TODO: Might be nice to test p2pk here too
+address_types = ('legacy', 'bech32', 'p2sh-segwit')
+
+def key_to_address(key, address_type):
+ if address_type == 'legacy':
+ return address.key_to_p2pkh(key)
+ elif address_type == 'p2sh-segwit':
+ return address.key_to_p2sh_p2wpkh(key)
+ elif address_type == 'bech32':
+ return address.key_to_p2wpkh(key)
+
+def send_a_to_b(receive_node, send_node):
+ keys = {}
+ for a in address_types:
+ a_address = receive_node.getnewaddress(address_type=a)
+ pubkey = receive_node.getaddressinfo(a_address)['pubkey']
+ keys[a] = pubkey
+ for b in address_types:
+ b_address = key_to_address(pubkey, b)
+ send_node.sendtoaddress(address=b_address, amount=1)
+ return keys
+
+def check_implicit_transactions(implicit_keys, implicit_node):
+ # The implicit segwit node allows conversion all possible ways
+ txs = implicit_node.listtransactions(None, 99999)
+ for a in address_types:
+ pubkey = implicit_keys[a]
+ for b in address_types:
+ b_address = key_to_address(pubkey, b)
+ assert(('receive', b_address) in tuple((tx['category'], tx['address']) for tx in txs))
+
+class ImplicitSegwitTest(BitcoinTestFramework):
+ def set_test_params(self):
+ self.num_nodes = 2
+
+ def skip_test_if_missing_module(self):
+ self.skip_if_no_wallet()
+
+ def run_test(self):
+ self.log.info("Manipulating addresses and sending transactions to all variations")
+ implicit_keys = send_a_to_b(self.nodes[0], self.nodes[1])
+
+ self.sync_all()
+
+ self.log.info("Checking that transactions show up correctly without a restart")
+ check_implicit_transactions(implicit_keys, self.nodes[0])
+
+ self.log.info("Checking that transactions still show up correctly after a restart")
+ self.restart_node(0)
+ self.restart_node(1)
+
+ check_implicit_transactions(implicit_keys, self.nodes[0])
+
+if __name__ == '__main__':
+ ImplicitSegwitTest().main()
diff --git a/test/functional/wallet_listsinceblock.py b/test/functional/wallet_listsinceblock.py
index 4aeb393255..455e89e310 100755
--- a/test/functional/wallet_listsinceblock.py
+++ b/test/functional/wallet_listsinceblock.py
@@ -5,6 +5,7 @@
"""Test the listsincelast RPC."""
from test_framework.test_framework import BitcoinTestFramework
+from test_framework.messages import BIP125_SEQUENCE_NUMBER
from test_framework.util import (
assert_array_result,
assert_equal,
@@ -12,6 +13,7 @@ from test_framework.util import (
connect_nodes,
)
+from decimal import Decimal
class ListSinceBlockTest(BitcoinTestFramework):
def set_test_params(self):
@@ -33,6 +35,7 @@ class ListSinceBlockTest(BitcoinTestFramework):
self.test_reorg()
self.test_double_spend()
self.test_double_send()
+ self.double_spends_filtered()
def test_no_blockhash(self):
txid = self.nodes[2].sendtoaddress(self.nodes[0].getnewaddress(), 1)
@@ -291,5 +294,51 @@ class ListSinceBlockTest(BitcoinTestFramework):
if tx['txid'] == txid1:
assert_equal(tx['confirmations'], 2)
+ def double_spends_filtered(self):
+ '''
+ `listsinceblock` was returning conflicted transactions even if they
+ occurred before the specified cutoff blockhash
+ '''
+ spending_node = self.nodes[2]
+ dest_address = spending_node.getnewaddress()
+
+ tx_input = dict(
+ sequence=BIP125_SEQUENCE_NUMBER, **next(u for u in spending_node.listunspent()))
+ rawtx = spending_node.createrawtransaction(
+ [tx_input], {dest_address: tx_input["amount"] - Decimal("0.00051000"),
+ spending_node.getrawchangeaddress(): Decimal("0.00050000")})
+ signedtx = spending_node.signrawtransactionwithwallet(rawtx)
+ orig_tx_id = spending_node.sendrawtransaction(signedtx["hex"])
+ original_tx = spending_node.gettransaction(orig_tx_id)
+
+ double_tx = spending_node.bumpfee(orig_tx_id)
+
+ # check that both transactions exist
+ block_hash = spending_node.listsinceblock(
+ spending_node.getblockhash(spending_node.getblockcount()))
+ original_found = False
+ double_found = False
+ for tx in block_hash['transactions']:
+ if tx['txid'] == original_tx['txid']:
+ original_found = True
+ if tx['txid'] == double_tx['txid']:
+ double_found = True
+ assert_equal(original_found, True)
+ assert_equal(double_found, True)
+
+ lastblockhash = spending_node.generate(1)[0]
+
+ # check that neither transaction exists
+ block_hash = spending_node.listsinceblock(lastblockhash)
+ original_found = False
+ double_found = False
+ for tx in block_hash['transactions']:
+ if tx['txid'] == original_tx['txid']:
+ original_found = True
+ if tx['txid'] == double_tx['txid']:
+ double_found = True
+ assert_equal(original_found, False)
+ assert_equal(double_found, False)
+
if __name__ == '__main__':
ListSinceBlockTest().main()
diff --git a/test/lint/git-subtree-check.sh b/test/lint/git-subtree-check.sh
index 85e8b841b6..7b5707a17a 100755
--- a/test/lint/git-subtree-check.sh
+++ b/test/lint/git-subtree-check.sh
@@ -4,7 +4,8 @@
# file COPYING or http://www.opensource.org/licenses/mit-license.php.
export LC_ALL=C
-DIR="$1"
+# Strip trailing / from directory path (in case it was added by autocomplete)
+DIR="${1%/}"
COMMIT="$2"
if [ -z "$COMMIT" ]; then
COMMIT=HEAD
diff --git a/test/lint/lint-assertions.sh b/test/lint/lint-assertions.sh
index 5bbcae79eb..a4c6f0a8d4 100755
--- a/test/lint/lint-assertions.sh
+++ b/test/lint/lint-assertions.sh
@@ -20,4 +20,15 @@ if [[ ${OUTPUT} != "" ]]; then
EXIT_CODE=1
fi
+# Macro CHECK_NONFATAL(condition) should be used instead of assert for RPC code, where it
+# is undesirable to crash the whole program. See: src/util/check.h
+# src/rpc/server.cpp is excluded from this check since it's mostly meta-code.
+OUTPUT=$(git grep -nE 'assert *\(.*\);' -- "src/rpc/" "src/wallet/rpc*" ":(exclude)src/rpc/server.cpp")
+if [[ ${OUTPUT} != "" ]]; then
+ echo "CHECK_NONFATAL(condition) should be used instead of assert for RPC code."
+ echo
+ echo "${OUTPUT}"
+ EXIT_CODE=1
+fi
+
exit ${EXIT_CODE}
diff --git a/test/lint/lint-spelling.ignore-words.txt b/test/lint/lint-spelling.ignore-words.txt
index b08837c1d4..576ae94098 100644
--- a/test/lint/lint-spelling.ignore-words.txt
+++ b/test/lint/lint-spelling.ignore-words.txt
@@ -1,9 +1,7 @@
-cas
hights
mor
mut
objext
-unselect
useable
wit
unparseable
@@ -13,3 +11,4 @@ errorstring
keyserver
homogenous
setban
+hist
diff --git a/test/lint/lint-spelling.sh b/test/lint/lint-spelling.sh
index e70b73e1cc..251a94f7ff 100755
--- a/test/lint/lint-spelling.sh
+++ b/test/lint/lint-spelling.sh
@@ -15,6 +15,6 @@ if ! command -v codespell > /dev/null; then
fi
IGNORE_WORDS_FILE=test/lint/lint-spelling.ignore-words.txt
-if ! codespell --check-filenames --disable-colors --quiet-level=7 --ignore-words=${IGNORE_WORDS_FILE} $(git ls-files -- ":(exclude)build-aux/m4/" ":(exclude)contrib/seeds/*.txt" ":(exclude)depends/" ":(exclude)doc/release-notes/" ":(exclude)src/leveldb/" ":(exclude)src/qt/locale/" ":(exclude)src/secp256k1/" ":(exclude)src/univalue/"); then
+if ! codespell --check-filenames --disable-colors --quiet-level=7 --ignore-words=${IGNORE_WORDS_FILE} $(git ls-files -- ":(exclude)build-aux/m4/" ":(exclude)contrib/seeds/*.txt" ":(exclude)depends/" ":(exclude)doc/release-notes/" ":(exclude)src/leveldb/" ":(exclude)src/qt/locale/" ":(exclude)src/qt/*.qrc" ":(exclude)src/secp256k1/" ":(exclude)src/univalue/"); then
echo "^ Warning: codespell identified likely spelling errors. Any false positives? Add them to the list of ignored words in ${IGNORE_WORDS_FILE}"
fi