diff options
Diffstat (limited to 'test')
-rwxr-xr-x | test/functional/p2p_tx_privacy.py | 78 | ||||
-rwxr-xr-x | test/functional/rpc_fundrawtransaction.py | 45 | ||||
-rwxr-xr-x | test/functional/test_runner.py | 1 | ||||
-rwxr-xr-x | test/functional/wallet_migration.py | 10 | ||||
-rwxr-xr-x | test/functional/wallet_taproot.py | 153 | ||||
-rwxr-xr-x | test/lint/lint-git-commit-check.py | 2 | ||||
-rwxr-xr-x | test/lint/lint-whitespace.py | 2 |
7 files changed, 231 insertions, 60 deletions
diff --git a/test/functional/p2p_tx_privacy.py b/test/functional/p2p_tx_privacy.py new file mode 100755 index 0000000000..b885ccdf5d --- /dev/null +++ b/test/functional/p2p_tx_privacy.py @@ -0,0 +1,78 @@ +#!/usr/bin/env python3 +# Copyright (c) 2022 The Bitcoin Core developers +# Distributed under the MIT software license, see the accompanying +# file COPYING or http://www.opensource.org/licenses/mit-license.php. +""" +Test that transaction announcements are only queued for peers that have +successfully completed the version handshake. + +Topology: + + tx_originator ----> node[0] <---- spy + +We test that a transaction sent by tx_originator is only relayed to spy +if it was received after spy's version handshake completed. + +1. Fully connect tx_originator +2. Connect spy (no version handshake) +3. tx_originator sends tx1 +4. spy completes the version handshake +5. tx_originator sends tx2 +6. We check that only tx2 is announced on the spy interface +""" +from test_framework.messages import ( + msg_wtxidrelay, + msg_verack, + msg_tx, + CInv, + MSG_WTX, +) +from test_framework.p2p import ( + P2PInterface, +) +from test_framework.test_framework import BitcoinTestFramework +from test_framework.wallet import MiniWallet + +class P2PTxSpy(P2PInterface): + def __init__(self): + super().__init__() + self.all_invs = [] + + def on_version(self, message): + self.send_message(msg_wtxidrelay()) + + def on_inv(self, message): + self.all_invs += message.inv + + def wait_for_inv_match(self, expected_inv): + self.wait_until(lambda: len(self.all_invs) == 1 and self.all_invs[0] == expected_inv) + +class TxPrivacyTest(BitcoinTestFramework): + def set_test_params(self): + self.num_nodes = 1 + + def run_test(self): + self.wallet = MiniWallet(self.nodes[0]) + self.wallet.rescan_utxos() + + tx_originator = self.nodes[0].add_p2p_connection(P2PInterface()) + spy = self.nodes[0].add_p2p_connection(P2PTxSpy(), wait_for_verack=False) + spy.wait_for_verack() + + # tx_originator sends tx1 + tx1 = self.wallet.create_self_transfer()["tx"] + tx_originator.send_and_ping(msg_tx(tx1)) + + # Spy sends the verack + spy.send_and_ping(msg_verack()) + + # tx_originator sends tx2 + tx2 = self.wallet.create_self_transfer()["tx"] + tx_originator.send_and_ping(msg_tx(tx2)) + + # Spy should only get an inv for the second transaction as the first + # one was received pre-verack with the spy + spy.wait_for_inv_match(CInv(MSG_WTX, tx2.calc_sha256(True))) + +if __name__ == '__main__': + TxPrivacyTest().main() diff --git a/test/functional/rpc_fundrawtransaction.py b/test/functional/rpc_fundrawtransaction.py index a963bb5e2d..9a3a356097 100755 --- a/test/functional/rpc_fundrawtransaction.py +++ b/test/functional/rpc_fundrawtransaction.py @@ -107,6 +107,7 @@ class RawTransactionsTest(BitcoinTestFramework): self.generate(self.nodes[0], 121) self.test_add_inputs_default_value() + self.test_preset_inputs_selection() self.test_weight_calculation() self.test_change_position() self.test_simple() @@ -1189,6 +1190,50 @@ class RawTransactionsTest(BitcoinTestFramework): self.nodes[2].unloadwallet("test_preset_inputs") + def test_preset_inputs_selection(self): + self.log.info('Test wallet preset inputs are not double-counted or reused in coin selection') + + # Create and fund the wallet with 4 UTXO of 5 BTC each (20 BTC total) + self.nodes[2].createwallet("test_preset_inputs_selection") + wallet = self.nodes[2].get_wallet_rpc("test_preset_inputs_selection") + outputs = {} + for _ in range(4): + outputs[wallet.getnewaddress(address_type="bech32")] = 5 + self.nodes[0].sendmany("", outputs) + self.generate(self.nodes[0], 1) + + # Select the preset inputs + coins = wallet.listunspent() + preset_inputs = [coins[0], coins[1], coins[2]] + + # Now let's create the tx creation options + options = { + "inputs": preset_inputs, + "add_inputs": True, # automatically add coins from the wallet to fulfill the target + "subtract_fee_from_outputs": [0], # deduct fee from first output + "add_to_wallet": False + } + + # Attempt to send 29 BTC from a wallet that only has 20 BTC. The wallet should exclude + # the preset inputs from the pool of available coins, realize that there is not enough + # money to fund the 29 BTC payment, and fail with "Insufficient funds". + # + # Even with SFFO, the wallet can only afford to send 20 BTC. + # If the wallet does not properly exclude preset inputs from the pool of available coins + # prior to coin selection, it may create a transaction that does not fund the full payment + # amount or, through SFFO, incorrectly reduce the recipient's amount by the difference + # between the original target and the wrongly counted inputs (in this case 9 BTC) + # so that the recipient's amount is no longer equal to the user's selected target of 29 BTC. + + # First case, use 'subtract_fee_from_outputs = true' + assert_raises_rpc_error(-4, "Insufficient funds", wallet.send, outputs=[{wallet.getnewaddress(address_type="bech32"): 29}], options=options) + + # Second case, don't use 'subtract_fee_from_outputs' + del options["subtract_fee_from_outputs"] + assert_raises_rpc_error(-4, "Insufficient funds", wallet.send, outputs=[{wallet.getnewaddress(address_type="bech32"): 29}], options=options) + + self.nodes[2].unloadwallet("test_preset_inputs_selection") + def test_weight_calculation(self): self.log.info("Test weight calculation with external inputs") diff --git a/test/functional/test_runner.py b/test/functional/test_runner.py index d78c1c634f..caa4af957a 100755 --- a/test/functional/test_runner.py +++ b/test/functional/test_runner.py @@ -316,6 +316,7 @@ BASE_SCRIPTS = [ 'rpc_deriveaddresses.py', 'rpc_deriveaddresses.py --usecli', 'p2p_ping.py', + 'p2p_tx_privacy.py', 'rpc_scantxoutset.py', 'feature_txindex_compatibility.py', 'feature_unsupported_utxo_db.py', diff --git a/test/functional/wallet_migration.py b/test/functional/wallet_migration.py index 3c1cb6ac32..4f060f9960 100755 --- a/test/functional/wallet_migration.py +++ b/test/functional/wallet_migration.py @@ -393,6 +393,15 @@ class WalletMigrationTest(BitcoinTestFramework): assert_equal(bals, wallet.getbalances()) + def test_encrypted(self): + self.log.info("Test migration of an encrypted wallet") + wallet = self.create_legacy_wallet("encrypted") + + wallet.encryptwallet("pass") + + assert_raises_rpc_error(-15, "Error: migratewallet on encrypted wallets is currently unsupported.", wallet.migratewallet) + # TODO: Fix migratewallet so that we can actually migrate encrypted wallets + def run_test(self): self.generate(self.nodes[0], 101) @@ -402,6 +411,7 @@ class WalletMigrationTest(BitcoinTestFramework): self.test_other_watchonly() self.test_no_privkeys() self.test_pk_coinbases() + self.test_encrypted() if __name__ == '__main__': WalletMigrationTest().main() diff --git a/test/functional/wallet_taproot.py b/test/functional/wallet_taproot.py index 3c630ba433..c2acafb373 100755 --- a/test/functional/wallet_taproot.py +++ b/test/functional/wallet_taproot.py @@ -5,6 +5,7 @@ """Test generation and spending of P2TR addresses.""" import random +import uuid from decimal import Decimal from test_framework.address import output_key_to_p2tr @@ -229,17 +230,28 @@ class WalletTaprootTest(BitcoinTestFramework): def do_test_addr(self, comment, pattern, privmap, treefn, keys): self.log.info("Testing %s address derivation" % comment) + + # Create wallets + wallet_uuid = uuid.uuid4().hex + self.nodes[0].createwallet(wallet_name=f"privs_tr_enabled_{wallet_uuid}", descriptors=True, blank=True) + self.nodes[0].createwallet(wallet_name=f"pubs_tr_enabled_{wallet_uuid}", descriptors=True, blank=True, disable_private_keys=True) + self.nodes[0].createwallet(wallet_name=f"addr_gen_{wallet_uuid}", descriptors=True, disable_private_keys=True, blank=True) + privs_tr_enabled = self.nodes[0].get_wallet_rpc(f"privs_tr_enabled_{wallet_uuid}") + pubs_tr_enabled = self.nodes[0].get_wallet_rpc(f"pubs_tr_enabled_{wallet_uuid}") + addr_gen = self.nodes[0].get_wallet_rpc(f"addr_gen_{wallet_uuid}") + desc = self.make_desc(pattern, privmap, keys, False) desc_pub = self.make_desc(pattern, privmap, keys, True) assert_equal(self.nodes[0].getdescriptorinfo(desc)['descriptor'], desc_pub) - result = self.addr_gen.importdescriptors([{"desc": desc_pub, "active": True, "timestamp": "now"}]) + result = addr_gen.importdescriptors([{"desc": desc_pub, "active": True, "timestamp": "now"}]) assert(result[0]['success']) + address_type = "bech32m" if "tr" in pattern else "bech32" for i in range(4): - addr_g = self.addr_gen.getnewaddress(address_type='bech32m') + addr_g = addr_gen.getnewaddress(address_type=address_type) if treefn is not None: addr_r = self.make_addr(treefn, keys, i) assert_equal(addr_g, addr_r) - desc_a = self.addr_gen.getaddressinfo(addr_g)['desc'] + desc_a = addr_gen.getaddressinfo(addr_g)['desc'] if desc.startswith("tr("): assert desc_a.startswith("tr(") rederive = self.nodes[1].deriveaddresses(desc_a) @@ -247,25 +259,37 @@ class WalletTaprootTest(BitcoinTestFramework): assert_equal(rederive[0], addr_g) # tr descriptors can be imported - result = self.privs_tr_enabled.importdescriptors([{"desc": desc, "timestamp": "now"}]) + result = privs_tr_enabled.importdescriptors([{"desc": desc, "timestamp": "now"}]) assert(result[0]["success"]) - result = self.pubs_tr_enabled.importdescriptors([{"desc": desc_pub, "timestamp": "now"}]) + result = pubs_tr_enabled.importdescriptors([{"desc": desc_pub, "timestamp": "now"}]) assert(result[0]["success"]) + # Cleanup + privs_tr_enabled.unloadwallet() + pubs_tr_enabled.unloadwallet() + addr_gen.unloadwallet() + def do_test_sendtoaddress(self, comment, pattern, privmap, treefn, keys_pay, keys_change): self.log.info("Testing %s through sendtoaddress" % comment) + + # Create wallets + wallet_uuid = uuid.uuid4().hex + self.nodes[0].createwallet(wallet_name=f"rpc_online_{wallet_uuid}", descriptors=True, blank=True) + rpc_online = self.nodes[0].get_wallet_rpc(f"rpc_online_{wallet_uuid}") + desc_pay = self.make_desc(pattern, privmap, keys_pay) desc_change = self.make_desc(pattern, privmap, keys_change) desc_pay_pub = self.make_desc(pattern, privmap, keys_pay, True) desc_change_pub = self.make_desc(pattern, privmap, keys_change, True) assert_equal(self.nodes[0].getdescriptorinfo(desc_pay)['descriptor'], desc_pay_pub) assert_equal(self.nodes[0].getdescriptorinfo(desc_change)['descriptor'], desc_change_pub) - result = self.rpc_online.importdescriptors([{"desc": desc_pay, "active": True, "timestamp": "now"}]) + result = rpc_online.importdescriptors([{"desc": desc_pay, "active": True, "timestamp": "now"}]) assert(result[0]['success']) - result = self.rpc_online.importdescriptors([{"desc": desc_change, "active": True, "timestamp": "now", "internal": True}]) + result = rpc_online.importdescriptors([{"desc": desc_change, "active": True, "timestamp": "now", "internal": True}]) assert(result[0]['success']) + address_type = "bech32m" if "tr" in pattern else "bech32" for i in range(4): - addr_g = self.rpc_online.getnewaddress(address_type='bech32m') + addr_g = rpc_online.getnewaddress(address_type=address_type) if treefn is not None: addr_r = self.make_addr(treefn, keys_pay, i) assert_equal(addr_g, addr_r) @@ -273,31 +297,51 @@ class WalletTaprootTest(BitcoinTestFramework): to_amnt = random.randrange(1000000, boring_balance) self.boring.sendtoaddress(address=addr_g, amount=Decimal(to_amnt) / 100000000, subtractfeefromamount=True) self.generatetoaddress(self.nodes[0], 1, self.boring.getnewaddress(), sync_fun=self.no_op) - test_balance = int(self.rpc_online.getbalance() * 100000000) + test_balance = int(rpc_online.getbalance() * 100000000) ret_amnt = random.randrange(100000, test_balance) # Increase fee_rate to compensate for the wallet's inability to estimate fees for script path spends. - res = self.rpc_online.sendtoaddress(address=self.boring.getnewaddress(), amount=Decimal(ret_amnt) / 100000000, subtractfeefromamount=True, fee_rate=200) + res = rpc_online.sendtoaddress(address=self.boring.getnewaddress(), amount=Decimal(ret_amnt) / 100000000, subtractfeefromamount=True, fee_rate=200) self.generatetoaddress(self.nodes[0], 1, self.boring.getnewaddress(), sync_fun=self.no_op) - assert(self.rpc_online.gettransaction(res)["confirmations"] > 0) + assert(rpc_online.gettransaction(res)["confirmations"] > 0) + + # Cleanup + txid = rpc_online.sendall(recipients=[self.boring.getnewaddress()])["txid"] + self.generatetoaddress(self.nodes[0], 1, self.boring.getnewaddress(), sync_fun=self.no_op) + assert(rpc_online.gettransaction(txid)["confirmations"] > 0) + rpc_online.unloadwallet() def do_test_psbt(self, comment, pattern, privmap, treefn, keys_pay, keys_change): self.log.info("Testing %s through PSBT" % comment) + + # Create wallets + wallet_uuid = uuid.uuid4().hex + self.nodes[0].createwallet(wallet_name=f"psbt_online_{wallet_uuid}", descriptors=True, disable_private_keys=True, blank=True) + self.nodes[1].createwallet(wallet_name=f"psbt_offline_{wallet_uuid}", descriptors=True, blank=True) + self.nodes[1].createwallet(f"key_only_wallet_{wallet_uuid}", descriptors=True, blank=True) + psbt_online = self.nodes[0].get_wallet_rpc(f"psbt_online_{wallet_uuid}") + psbt_offline = self.nodes[1].get_wallet_rpc(f"psbt_offline_{wallet_uuid}") + key_only_wallet = self.nodes[1].get_wallet_rpc(f"key_only_wallet_{wallet_uuid}") + desc_pay = self.make_desc(pattern, privmap, keys_pay, False) desc_change = self.make_desc(pattern, privmap, keys_change, False) desc_pay_pub = self.make_desc(pattern, privmap, keys_pay, True) desc_change_pub = self.make_desc(pattern, privmap, keys_change, True) assert_equal(self.nodes[0].getdescriptorinfo(desc_pay)['descriptor'], desc_pay_pub) assert_equal(self.nodes[0].getdescriptorinfo(desc_change)['descriptor'], desc_change_pub) - result = self.psbt_online.importdescriptors([{"desc": desc_pay_pub, "active": True, "timestamp": "now"}]) + result = psbt_online.importdescriptors([{"desc": desc_pay_pub, "active": True, "timestamp": "now"}]) assert(result[0]['success']) - result = self.psbt_online.importdescriptors([{"desc": desc_change_pub, "active": True, "timestamp": "now", "internal": True}]) + result = psbt_online.importdescriptors([{"desc": desc_change_pub, "active": True, "timestamp": "now", "internal": True}]) assert(result[0]['success']) - result = self.psbt_offline.importdescriptors([{"desc": desc_pay, "active": True, "timestamp": "now"}]) + result = psbt_offline.importdescriptors([{"desc": desc_pay, "active": True, "timestamp": "now"}]) assert(result[0]['success']) - result = self.psbt_offline.importdescriptors([{"desc": desc_change, "active": True, "timestamp": "now", "internal": True}]) + result = psbt_offline.importdescriptors([{"desc": desc_change, "active": True, "timestamp": "now", "internal": True}]) assert(result[0]['success']) + for key in keys_pay + keys_change: + result = key_only_wallet.importdescriptors([{"desc": descsum_create(f"wpkh({key['xprv']}/*)"), "timestamp":"now"}]) + assert(result[0]["success"]) + address_type = "bech32m" if "tr" in pattern else "bech32" for i in range(4): - addr_g = self.psbt_online.getnewaddress(address_type='bech32m') + addr_g = psbt_online.getnewaddress(address_type=address_type) if treefn is not None: addr_r = self.make_addr(treefn, keys_pay, i) assert_equal(addr_g, addr_r) @@ -305,28 +349,43 @@ class WalletTaprootTest(BitcoinTestFramework): to_amnt = random.randrange(1000000, boring_balance) self.boring.sendtoaddress(address=addr_g, amount=Decimal(to_amnt) / 100000000, subtractfeefromamount=True) self.generatetoaddress(self.nodes[0], 1, self.boring.getnewaddress(), sync_fun=self.no_op) - test_balance = int(self.psbt_online.getbalance() * 100000000) + test_balance = int(psbt_online.getbalance() * 100000000) ret_amnt = random.randrange(100000, test_balance) # Increase fee_rate to compensate for the wallet's inability to estimate fees for script path spends. - psbt = self.psbt_online.walletcreatefundedpsbt([], [{self.boring.getnewaddress(): Decimal(ret_amnt) / 100000000}], None, {"subtractFeeFromOutputs":[0], "fee_rate": 200, "change_type": "bech32m"})['psbt'] - res = self.psbt_offline.walletprocesspsbt(psbt=psbt, finalize=False) - - decoded = self.psbt_offline.decodepsbt(res["psbt"]) - if pattern.startswith("tr("): - for psbtin in decoded["inputs"]: - assert "non_witness_utxo" not in psbtin - assert "witness_utxo" in psbtin - assert "taproot_internal_key" in psbtin - assert "taproot_bip32_derivs" in psbtin - assert "taproot_key_path_sig" in psbtin or "taproot_script_path_sigs" in psbtin - if "taproot_script_path_sigs" in psbtin: - assert "taproot_merkle_root" in psbtin - assert "taproot_scripts" in psbtin - - rawtx = self.nodes[0].finalizepsbt(res['psbt'])['hex'] + psbt = psbt_online.walletcreatefundedpsbt([], [{self.boring.getnewaddress(): Decimal(ret_amnt) / 100000000}], None, {"subtractFeeFromOutputs":[0], "fee_rate": 200, "change_type": address_type})['psbt'] + res = psbt_offline.walletprocesspsbt(psbt=psbt, finalize=False) + for wallet in [psbt_offline, key_only_wallet]: + res = wallet.walletprocesspsbt(psbt=psbt, finalize=False) + + decoded = wallet.decodepsbt(res["psbt"]) + if pattern.startswith("tr("): + for psbtin in decoded["inputs"]: + assert "non_witness_utxo" not in psbtin + assert "witness_utxo" in psbtin + assert "taproot_internal_key" in psbtin + assert "taproot_bip32_derivs" in psbtin + assert "taproot_key_path_sig" in psbtin or "taproot_script_path_sigs" in psbtin + if "taproot_script_path_sigs" in psbtin: + assert "taproot_merkle_root" in psbtin + assert "taproot_scripts" in psbtin + + rawtx = self.nodes[0].finalizepsbt(res['psbt'])['hex'] + res = self.nodes[0].testmempoolaccept([rawtx]) + assert res[0]["allowed"] + txid = self.nodes[0].sendrawtransaction(rawtx) self.generatetoaddress(self.nodes[0], 1, self.boring.getnewaddress(), sync_fun=self.no_op) - assert(self.psbt_online.gettransaction(txid)['confirmations'] > 0) + assert(psbt_online.gettransaction(txid)['confirmations'] > 0) + + # Cleanup + psbt = psbt_online.sendall(recipients=[self.boring.getnewaddress()], options={"psbt": True})["psbt"] + res = psbt_offline.walletprocesspsbt(psbt=psbt, finalize=False) + rawtx = self.nodes[0].finalizepsbt(res['psbt'])['hex'] + txid = self.nodes[0].sendrawtransaction(rawtx) + self.generatetoaddress(self.nodes[0], 1, self.boring.getnewaddress(), sync_fun=self.no_op) + assert(psbt_online.gettransaction(txid)['confirmations'] > 0) + psbt_online.unloadwallet() + psbt_offline.unloadwallet() def do_test(self, comment, pattern, privmap, treefn): nkeys = len(privmap) @@ -336,21 +395,8 @@ class WalletTaprootTest(BitcoinTestFramework): self.do_test_psbt(comment, pattern, privmap, treefn, keys[2*nkeys:3*nkeys], keys[3*nkeys:4*nkeys]) def run_test(self): - self.log.info("Creating wallets...") - self.nodes[0].createwallet(wallet_name="privs_tr_enabled", descriptors=True, blank=True) - self.privs_tr_enabled = self.nodes[0].get_wallet_rpc("privs_tr_enabled") - self.nodes[0].createwallet(wallet_name="pubs_tr_enabled", descriptors=True, blank=True, disable_private_keys=True) - self.pubs_tr_enabled = self.nodes[0].get_wallet_rpc("pubs_tr_enabled") self.nodes[0].createwallet(wallet_name="boring") - self.nodes[0].createwallet(wallet_name="addr_gen", descriptors=True, disable_private_keys=True, blank=True) - self.nodes[0].createwallet(wallet_name="rpc_online", descriptors=True, blank=True) - self.nodes[0].createwallet(wallet_name="psbt_online", descriptors=True, disable_private_keys=True, blank=True) - self.nodes[1].createwallet(wallet_name="psbt_offline", descriptors=True, blank=True) self.boring = self.nodes[0].get_wallet_rpc("boring") - self.addr_gen = self.nodes[0].get_wallet_rpc("addr_gen") - self.rpc_online = self.nodes[0].get_wallet_rpc("rpc_online") - self.psbt_online = self.nodes[0].get_wallet_rpc("psbt_online") - self.psbt_offline = self.nodes[1].get_wallet_rpc("psbt_offline") self.log.info("Mining blocks...") gen_addr = self.boring.getnewaddress() @@ -460,18 +506,5 @@ class WalletTaprootTest(BitcoinTestFramework): lambda k1: key(k1) ) - self.log.info("Sending everything back...") - - txid = self.rpc_online.sendall(recipients=[self.boring.getnewaddress()])["txid"] - self.generatetoaddress(self.nodes[0], 1, self.boring.getnewaddress(), sync_fun=self.no_op) - assert(self.rpc_online.gettransaction(txid)["confirmations"] > 0) - - psbt = self.psbt_online.sendall(recipients=[self.boring.getnewaddress()], options={"psbt": True})["psbt"] - res = self.psbt_offline.walletprocesspsbt(psbt=psbt, finalize=False) - rawtx = self.nodes[0].finalizepsbt(res['psbt'])['hex'] - txid = self.nodes[0].sendrawtransaction(rawtx) - self.generatetoaddress(self.nodes[0], 1, self.boring.getnewaddress(), sync_fun=self.no_op) - assert(self.psbt_online.gettransaction(txid)['confirmations'] > 0) - if __name__ == '__main__': WalletTaprootTest().main() diff --git a/test/lint/lint-git-commit-check.py b/test/lint/lint-git-commit-check.py index a1d03370e8..049104398a 100755 --- a/test/lint/lint-git-commit-check.py +++ b/test/lint/lint-git-commit-check.py @@ -46,6 +46,8 @@ def main(): commit_range = merge_base + "..HEAD" else: commit_range = os.getenv("COMMIT_RANGE") + if commit_range == "SKIP_EMPTY_NOT_A_PR": + sys.exit(0) commit_hashes = check_output(["git", "log", commit_range, "--format=%H"], universal_newlines=True, encoding="utf8").splitlines() diff --git a/test/lint/lint-whitespace.py b/test/lint/lint-whitespace.py index 3fb5b80013..72b7ebc394 100755 --- a/test/lint/lint-whitespace.py +++ b/test/lint/lint-whitespace.py @@ -97,6 +97,8 @@ def main(): commit_range = merge_base + "..HEAD" else: commit_range = os.getenv("COMMIT_RANGE") + if commit_range == "SKIP_EMPTY_NOT_A_PR": + sys.exit(0) whitespace_selection = [] tab_selection = [] |