lint: drop most remaining default Ruff rule ignores #35721

pull willcl-ark wants to merge 2 commits into bitcoin:master from willcl-ark:drop-ruff-ignores changing 11 files +58 −33
  1. willcl-ark commented at 11:16 AM on July 14, 2026: member

    Follows up on #34547 by dropping (and fixing) most remaining default Ruff rule ignores:

    • E712 avoids noisy == True/False comparisons and makes boolean intent clearer.
    • E731 named def helpers are easier to debug, trace, type-check, and extend than assigned lambdas.

    Dropping the rules means these issues cannot be re-introduced.

    E501 remains as this has 8909 callsites to fix, and IMO it's not clear that having an arbitrary-line length improves anything about the codebase, or for developers.

    E741 remains based on review feedback that it's unnecessary to enforce.

  2. lint: remove E712 Ruff ignore
    Use boolean negation in predicates so Ruff can enforce E712 without
    retaining an exception for false comparisons.
    24191aca1c
  3. DrahtBot added the label Tests on Jul 14, 2026
  4. DrahtBot commented at 11:17 AM on July 14, 2026: contributor

    <!--e57a25ab6845829454e8d69fc972939a-->

    The following sections might be updated with supplementary metadata relevant to reviewers and maintainers.

    <!--006a51241073e994b41acfe9ec718e94-->

    Code Coverage & Benchmarks

    For details see: https://corecheck.dev/bitcoin/bitcoin/pulls/35721.

    <!--021abf342d371248e50ceaed478a90ca-->

    Reviews

    See the guideline and AI policy for information on the review process.

    Type Reviewers
    Stale ACK stickies-v

    If your review is incorrectly listed, please copy-paste <code>&lt;!--meta-tag:bot-skip--&gt;</code> into the comment that the bot should ignore.

    <!--5faf32d7da4f0f540f40219e4f7537a3-->

  5. in contrib/testgen/gen_key_io_test_vectors.py:153 in 425624a572
     149 | @@ -150,7 +150,7 @@ def gen_valid_vectors():
     150 |      glist = [gen_valid_base58_vector, gen_valid_bech32_vector]
     151 |      tlist = [templates, bech32_templates]
     152 |      while True:
     153 | -        for template, valid_vector_generator in [(t, g) for g, l in zip(glist, tlist) for t in l]:
     154 | +        for template, valid_vector_generator in [(t, g) for g, template_list in zip(glist, tlist) for t in template_list]:
    


    maflcko commented at 12:03 PM on July 14, 2026:

    I don't understand this change, nor the rule. It should be pretty clear that l means list in this context. If single-char vars would be problematic, why is t and g and i fine?

    Also, this increases the line length, so with the next use of black (or whatever), this will have to be touched again to re-flow?

    According to the docs, the rule is about confusing l with 1 or I, but if this was the case, it seems like a bug in the software that displays the code to the reader?


    willcl-ark commented at 12:21 PM on July 14, 2026:

    The rule is disallowing potentitally-confusing single-letter variable names (not all single-char var names). Much like Base58 aims to do for us in other places, although in this case only targetting l I and O.

    This could be determined to be a software bug, but doesn't seem like a particularly big deal to me to adhere to?

    I could alternatively substitute with a different single-letter variable name (or we could keep ignoring the rule)?


    maflcko commented at 12:28 PM on July 14, 2026:

    I think base58 can also be used outside of the context of source-code. That is, it is likely that base58 ends up without code-formatting. Though, it should be rare that source code ends up without being formatted as source code. Also, we'd ignore the rule in C++ code anyway:

    src/leveldb/util/testharness.h:  Tester(const char* f, int l) : ok_(true), fname_(f), line_(l) {}
    src/secp256k1/src/tests.c:    int l;
    src/txgraph.cpp:    for (int l = level; l >= 0; --l) {
    

    I'd say to keep ignoring the rule and keep this as-is for now.


    willcl-ark commented at 12:33 PM on July 14, 2026:

    Dropped E741 from this changeset

  6. willcl-ark force-pushed on Jul 14, 2026
  7. in test/functional/feature_taproot.py:1225 in 6bf611a22a outdated
    1220 | @@ -1215,7 +1221,9 @@ def predict_sigops_ratio(n, dummy_size):
    1221 |  
    1222 |      # == Test case for https://github.com/bitcoin/bitcoin/issues/24765 ==
    1223 |  
    1224 | -    zero_fn = lambda h: bytes([0 for _ in range(32)])
    1225 | +    def zero_fn(h):
    1226 | +        return bytes([0 for _ in range(32)])
    


    stickies-v commented at 12:54 PM on July 14, 2026:

    nit: inlining is more clear

    <details> <summary>git diff on 6bf611a22a</summary>

    diff --git a/test/functional/feature_taproot.py b/test/functional/feature_taproot.py
    index 312c68c6ae..eab526c1ad 100755
    --- a/test/functional/feature_taproot.py
    +++ b/test/functional/feature_taproot.py
    @@ -1220,11 +1220,7 @@ def spenders_taproot_active():
             add_spender(spenders, "alwaysvalid/notsuccessx", tap=tap, leaf="op_success", inputs=[], standard=False, failure={"leaf": "normal"}) # err_msg differs based on opcode
     
         # == Test case for [#24765](/bitcoin-bitcoin/24765/) ==
    -
    -    def zero_fn(h):
    -        return bytes([0 for _ in range(32)])
    -
    -    tap = taproot_construct(pubs[0], [("leaf", CScript([pubs[1], OP_CHECKSIG, pubs[1], OP_CHECKSIGADD, OP_2, OP_EQUAL])), zero_fn])
    +    tap = taproot_construct(pubs[0], [("leaf", CScript([pubs[1], OP_CHECKSIG, pubs[1], OP_CHECKSIGADD, OP_2, OP_EQUAL])), [0] * 32])
         add_spender(spenders, "case24765", tap=tap, leaf="leaf", inputs=[getter("sign"), getter("sign")], key=secs[1], no_fail=True)
     
         # == Legacy tests ==
    
    

    </details>


    stickies-v commented at 2:16 PM on July 14, 2026:

    Sorry, bad suggestion, taproot_construct expects a callable of course.

    [0] * 32 would still be cleaner than [0 for _ in range(32)])


    willcl-ark commented at 9:51 AM on July 15, 2026:

    Took this suggestion in 2c8d9710a56caaec176f79f63342b3e49195e23b also

  8. in test/functional/feature_reindex_readonly.py:38 in 6bf611a22a outdated
      34 | @@ -35,13 +35,15 @@ def reindex_readonly(self):
      35 |          filename = self.nodes[0].chain_path / "blocks" / "blk00000.dat"
      36 |          filename.chmod(stat.S_IREAD)
      37 |  
      38 | -        undo_immutable = lambda: None
      39 | +        undo_immutable = None
    


    stickies-v commented at 1:19 PM on July 14, 2026:

    I think this leads to behaviour change on L81, bool(undo_immutable) now changed from True to False.


    willcl-ark commented at 2:40 PM on July 14, 2026:

    nice catch, I believe you are correct.

  9. stickies-v commented at 1:20 PM on July 14, 2026: contributor

    I don't feel very strongly about either of these rules but agree they are a slight improvement, and less exceptions is preferable.

  10. willcl-ark force-pushed on Jul 14, 2026
  11. in test/functional/feature_reindex_readonly.py:45 in e864d2e1b4
      42 |          try:
      43 |              subprocess.run(['chattr'], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
      44 |              try:
      45 |                  subprocess.run(['chattr', '+i', filename], capture_output=True, check=True)
      46 | -                undo_immutable = lambda: subprocess.check_call(['chattr', '-i', filename])
      47 | +                def undo_immutable():
    


    stickies-v commented at 8:25 AM on July 15, 2026:

    I'm not sure this new approach is more readable than the previous one, even if it is more PEP8 compliant. Having one None assignment and then two defs on undo_immutable in an already quite complex control flow is not ideal.

    One alternative approach is to have an undo_cmd which can be [], ['chattr', '-i', filename], or ['chflags', 'nouchg', filename]?

    More generally, I think this function requires a make_immutable contextmanager to be more idiomatic, but that's a bigger overhaul:

    <details> <summary>git diff on e864d2e1b4</summary>

    diff --git a/test/functional/feature_reindex_readonly.py b/test/functional/feature_reindex_readonly.py
    index 3a16e4786e..c0ee89adb4 100755
    --- a/test/functional/feature_reindex_readonly.py
    +++ b/test/functional/feature_reindex_readonly.py
    @@ -6,6 +6,7 @@
     - Start a node, generate blocks, then restart with -reindex after setting blk files to read-only
     """
     
    +import contextlib
     import os
     import stat
     import subprocess
    @@ -19,32 +20,18 @@ class BlockstoreReindexTest(BitcoinTestFramework):
             self.num_nodes = 1
             self.extra_args = [["-fastprune"]]
     
    -    def reindex_readonly(self):
    -        self.log.debug("Generate block big enough to start second block file")
    -        fastprune_blockfile_size = 0x10000
    -        opreturn = "6a"
    -        nulldata = fastprune_blockfile_size * "ff"
    -        self.generateblock(self.nodes[0], output=f"raw({opreturn}{nulldata})", transactions=[])
    -        block_count = self.nodes[0].getblockcount()
    -        self.stop_node(0)
    -
    -        assert (self.nodes[0].chain_path / "blocks" / "blk00000.dat").exists()
    -        assert (self.nodes[0].chain_path / "blocks" / "blk00001.dat").exists()
    -
    -        self.log.debug("Make the first block file read-only")
    -        filename = self.nodes[0].chain_path / "blocks" / "blk00000.dat"
    -        filename.chmod(stat.S_IREAD)
    -
    -        undo_immutable = None
    -        should_reindex = True
    + [@contextlib](/bitcoin-bitcoin/contributor/contextlib/).contextmanager
    +    def make_immutable(self, filename):
    +        """Try to make filename immutable, undo on exit. Yields whether it succeeded."""
    +        undo_cmd = None
    +        made_immutable = False
             # Linux
             try:
                 subprocess.run(['chattr'], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
                 try:
                     subprocess.run(['chattr', '+i', filename], capture_output=True, check=True)
    -                def undo_immutable():
    -                    subprocess.check_call(['chattr', '-i', filename])
    -
    +                undo_cmd = ['chattr', '-i', filename]
    +                made_immutable = True
                     self.log.info("Made file immutable with chattr")
                 except subprocess.CalledProcessError as e:
                     self.log.warning(str(e))
    @@ -53,19 +40,16 @@ class BlockstoreReindexTest(BitcoinTestFramework):
                     if e.stderr:
                         self.log.warning(f"stderr: {e.stderr}")
                     if os.getuid() == 0:
    -                    self.log.warning("Return early on Linux under root, because chattr failed.")
                         self.log.warning("This should only happen due to missing capabilities in a container.")
                         self.log.warning("Make sure to --cap-add LINUX_IMMUTABLE if you want to run this test.")
    -                    should_reindex = False
             except Exception:
                 # macOS, and *BSD
                 try:
                     subprocess.run(['chflags'], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
                     try:
                         subprocess.run(['chflags', 'uchg', filename], capture_output=True, check=True)
    -                    def undo_immutable():
    -                        subprocess.check_call(['chflags', 'nouchg', filename])
    -
    +                    undo_cmd = ['chflags', 'nouchg', filename]
    +                    made_immutable = True
                         self.log.info("Made file immutable with chflags")
                     except subprocess.CalledProcessError as e:
                         self.log.warning(str(e))
    @@ -73,19 +57,39 @@ class BlockstoreReindexTest(BitcoinTestFramework):
                             self.log.warning(f"stdout: {e.stdout}")
                         if e.stderr:
                             self.log.warning(f"stderr: {e.stderr}")
    -                    if os.getuid() == 0:
    -                        self.log.warning("Return early on BSD under root, because chflags failed.")
    -                        should_reindex = False
                 except Exception:
                     pass
     
    -        if should_reindex:
    -            self.log.debug("Attempt to restart and reindex the node with the unwritable block file")
    -            with self.nodes[0].assert_debug_log(["Reindexing finished"], timeout=60):
    -                self.start_node(0, extra_args=['-reindex', '-fastprune'])
    -            assert_equal(block_count, self.nodes[0].getblockcount())
    -            if undo_immutable is not None:
    -                undo_immutable()
    +        try:
    +            yield made_immutable
    +        finally:
    +            if undo_cmd is not None:
    +                subprocess.check_call(undo_cmd)
    +
    +    def reindex_readonly(self):
    +        self.log.debug("Generate block big enough to start second block file")
    +        fastprune_blockfile_size = 0x10000
    +        opreturn = "6a"
    +        nulldata = fastprune_blockfile_size * "ff"
    +        self.generateblock(self.nodes[0], output=f"raw({opreturn}{nulldata})", transactions=[])
    +        block_count = self.nodes[0].getblockcount()
    +        self.stop_node(0)
    +
    +        assert (self.nodes[0].chain_path / "blocks" / "blk00000.dat").exists()
    +        assert (self.nodes[0].chain_path / "blocks" / "blk00001.dat").exists()
    +
    +        self.log.debug("Make the first block file read-only")
    +        filename = self.nodes[0].chain_path / "blocks" / "blk00000.dat"
    +        filename.chmod(stat.S_IREAD)
    +
    +        with self.make_immutable(filename) as made_immutable:
    +            if not made_immutable and os.getuid() == 0:
    +                self.log.warning("Skipping reindex test on root without immutable flag support")
    +            else:
    +                self.log.debug("Attempt to restart and reindex the node with the unwritable block file")
    +                with self.nodes[0].assert_debug_log(["Reindexing finished"], timeout=60):
    +                    self.start_node(0, extra_args=['-reindex', '-fastprune'])
    +                assert_equal(block_count, self.nodes[0].getblockcount())
     
             filename.chmod(0o777)
     
    
    

    </details>


    willcl-ark commented at 9:36 AM on July 15, 2026:

    Yeah I agree, it's a bit messy, and worse than before.

    What do you think about this version: https://github.com/willcl-ark/bitcoin/commit/2c8d9710a56caaec176f79f63342b3e49195e23b


    stickies-v commented at 9:48 AM on July 15, 2026:

    Yeah, that'd be the smallest scoped change that I think is an improvement.


    willcl-ark commented at 9:51 AM on July 15, 2026:

    OK pushed in 2c8d9710a56caaec176f79f63342b3e49195e23b

  12. stickies-v approved
  13. stickies-v commented at 8:27 AM on July 15, 2026: contributor

    ACK e864d2e1b4cfd103a14551ec9c789ab583ef3496

  14. lint: remove E731 Ruff ignore
    Replace assigned lambdas with local functions so Ruff can enforce E731.
    For platform-specific immutable file cleanup, store the command as data
    instead of creating conditional callbacks.
    2c8d9710a5
  15. willcl-ark force-pushed on Jul 15, 2026
  16. in test/functional/feature_pruning.py:353 in 24191aca1c
     349 | @@ -350,14 +350,14 @@ def test_wallet_rescan(self):
     350 |          self.log.info("Stop and start pruning node to trigger wallet rescan")
     351 |          self.restart_node(2, extra_args=["-prune=550"])
     352 |  
     353 | -        self.wait_until(lambda: self.nodes[2].getwalletinfo()["scanning"] == False)
     354 | +        self.wait_until(lambda: not self.nodes[2].getwalletinfo()["scanning"])
    


    stickies-v commented at 12:04 PM on July 15, 2026:

    This actually seems like a regression. According to the RPC docs, scaninfo is either a dict or False:

      "scanning" : {                          (json object) current scanning details, or false if no scan is in progress
        "duration" : n,                       (numeric) elapsed seconds since scan start
        "progress" : n                        (numeric) scanning progress percentage [0.0, 1.0]
      },
    

    With this change, an empty dict (which is currently undefined) is evaluated differently with == False vs with not. So I think the correct change here is to check for is False instead of == False, as also suggested by ruff:

    If you intend to check if a value is the boolean literal True or False, consider using is or is not to check for identity instead.


github-metadata-mirror

This is a metadata mirror of the GitHub repository bitcoin/bitcoin. This site is not affiliated with GitHub. Content is generated from a GitHub metadata backup.
generated: 2026-07-16 07:51 UTC

This site is hosted by @0xB10C
More mirrored repositories can be found on mirror.b10c.me