If your review is incorrectly listed, please react with 👎 to this comment and the bot will ignore it on the next update.
Conflicts
Reviewers, this pull request conflicts with the following ones:
#32155 (miner: timelock the coinbase to the mined block’s height by darosior)
#31492 (Execute Discover() when bind=0.0.0.0 or :: is set by andremralves)
#29415 (Broadcast own transactions only via short-lived Tor or I2P connections by vasild)
If you consider this pull request important, please also help to review the conflicting pull requests. Ideally, start with the one that should be merged first.
DrahtBot added the label
Tests
on Feb 28, 2024
axizqayum686 changes_requested
axizqayum686
commented at 6:47 am on February 28, 2024:
none
.
Empact
commented at 7:03 am on February 28, 2024:
contributor
Would be nice to structure this as a scripted diff: one commit where you add the function and requires, another where you convert the assert !=s.
That would ensure programmatically that you hit all of the cases.
maflcko requested review from theStack
on Feb 28, 2024
kevkevinpal force-pushed
on Mar 2, 2024
kevkevinpal force-pushed
on Mar 2, 2024
DrahtBot added the label
CI failed
on Mar 2, 2024
kevkevinpal force-pushed
on Mar 2, 2024
kevkevinpal
commented at 4:15 pm on March 2, 2024:
contributor
added scripted diff in dc087f1 with the following script
git grep -l "assert.*!=" ./test/functional
I will have to amend the commit as I see more spots I need to modify with the assert_not_equal util
kevkevinpal force-pushed
on Mar 2, 2024
kevkevinpal force-pushed
on Mar 2, 2024
kevkevinpal force-pushed
on Mar 2, 2024
kevkevinpal force-pushed
on Mar 2, 2024
kevkevinpal force-pushed
on Mar 2, 2024
kevkevinpal force-pushed
on Mar 2, 2024
theStack
commented at 8:35 pm on March 2, 2024:
contributor
Concept ACK, thanks for picking this up.
kevkevinpal force-pushed
on Mar 2, 2024
kevkevinpal force-pushed
on Mar 2, 2024
kevkevinpal force-pushed
on Mar 2, 2024
kevkevinpal force-pushed
on Mar 2, 2024
kevkevinpal force-pushed
on Mar 2, 2024
brunoerg
commented at 2:48 pm on March 4, 2024:
contributor
There are some places you are using assert_not_equal but not importing it and other ones you’re importing it twice. Check CI.
0Running Unit Tests for Test Framework Modules
1Traceback (most recent call last):
2File"/ci_container_base/ci/scratch/build/bitcoin-x86_64-pc-linux-gnu/test/functional/test_runner.py", line 906, in<module> 3 main()
4File"/ci_container_base/ci/scratch/build/bitcoin-x86_64-pc-linux-gnu/test/functional/test_runner.py", line 539, in main
5 run_tests(
6File"/ci_container_base/ci/scratch/build/bitcoin-x86_64-pc-linux-gnu/test/functional/test_runner.py", line 580, in run_tests
7 test_framework_tests.addTest(unittest.TestLoader().loadTestsFromName("test_framework.{}".format(module)))
8^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 9File"/usr/lib/python3.12/unittest/loader.py", line 137, in loadTestsFromName
10 module = __import__(module_name)
11^^^^^^^^^^^^^^^^^^^^^^^12File"/ci_container_base/test/functional/test_framework/address.py", line 14, in<module>13 from .script import (
14File"/ci_container_base/test/functional/test_framework/script.py", line 14, in<module>15 from .key import TaggedHash, tweak_add_pubkey, compute_xonly_pubkey
16File"/ci_container_base/test/functional/test_framework/key.py", line 16, in<module>17 from test_framework.crypto import secp256k1
18File"/ci_container_base/test/functional/test_framework/crypto/secp256k1.py", line 316, in<module>19 G = GE.lift_x(0x79BE667EF9DCBBAC55A06295CE870B07029BFCDB2DCE28D959F2815B16F81798)
20^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^21File"/ci_container_base/test/functional/test_framework/crypto/secp256k1.py", line 257, in lift_x
22 y = (FE(x)**3+7).sqrt()
23^^^^^24File"/ci_container_base/test/functional/test_framework/crypto/secp256k1.py", line 41, in __init__
25 assert_not_equal(den, 0)
26^^^^^^^^^^^^^^^^27NameError: name 'assert_not_equal' is not defined
in
test/functional/p2p_blockfilters.py:24
in
ad1e879351outdated
21@@ -21,6 +22,7 @@
22 from test_framework.p2p import P2PInterface
23 from test_framework.test_framework import BitcoinTestFramework
24 from test_framework.util import (
25+ assert_not_equal,
"node 0 chain did not reorganize"
This seems to be the 3rd argument to the assert_not_equal() call, i.e. *args. Will this piece be kicked in then? any(thing1 == arg for arg in args)
I don’t get why would we need to search for main_block_hash in the above error string.
I’m unable to run make successfully because of the following error on my system. I’ve noticed this in another PR as well and it is due to lack of a commit that was merged in later.
Asked couple questions.
assert_equal is helpful because when a == 3 fails, it can be helpful to know what the value of a actually is without having to add debug code and rerun. If a != 3 fails, though, you already know that a == 3, so this doesn’t seem very helpful.
There are cases where you’re comparing a != b and may not be sure which ones getting the wrong value where this could perhaps be helpful, but those seem pretty rare.
Concept -0 for me.
hodlinator
commented at 2:10 pm on February 24, 2025:
Agree this is a valid argument against the PR. It is very seldom useful to print the values when they are equal.
I still prefer assert_not_equal() for:
Symmetry with assert_equal().
Distinguishing itself from assert which is skipped when running Python with -O.
maflcko
commented at 3:11 pm on February 24, 2025:
Distinguishing itself from assert which is skipped when running Python with -O.
It is not possible to run the functional tests with -O (they’d fail on current master), and it is unclear what reason there could be to do so in the first place. So i think using the flag as an argument for a code change seems weak.
If there is a reason for -O, it should be mentioned, all tests should be fixed, and it should be enforced with something like https://docs.astral.sh/ruff/rules/assert/.
If there is no reason for -O, it should be ignored or disallowed.
There are cases where you’re comparing a != b and may not be sure which ones getting the wrong value where this could perhaps be helpful, but those seem pretty rare
I counted (by eyes, could be +/- a few):
16 occurrences where one of the arguments is a constant, e.g. assert_not_equal(peer.nServices & NODE_BLOOM, 0), and
59 occurrences where both arguments are variables, e.g. assert_not_equal(child_one_wtxid, child_two_wtxid)
in
test/functional/test_framework/util.py:78
in
f9ba6fd46doutdated
55@@ -56,6 +56,10 @@ def assert_equal(thing1, thing2, *args):
56 if thing1 != thing2 or any(thing1 != arg for arg in args):
57 raise AssertionError("not(%s)" % " == ".join(str(arg) for arg in (thing1, thing2) + args))
5859+def assert_not_equal(thing1, thing2, *args):
60+ if thing1 == thing2 or any(thing1 == arg for arg in args):
61+ raise AssertionError("%s" % " == ".join(str(arg) for arg in (thing1, thing2) + args))
This error message doesn’t make sense when *args is non-empty: if you say assert_not_equal(a, 1, 2) and a is 1 or 2, it will report 1 == 1 == 2 or 2 == 1 == 2.
I’ve updated this to instead only take three arguments and the last one being an optional message since I did not see anywhere we were already asserting != in a chain.
This should clear up the message and allow the one location we do use a message to be used
kevkevinpal
commented at 0:29 am on May 22, 2024:
contributor
@kevkevinpal Did you use a tool to generate the verification script?
No I did not I just manually used git grep and sed to create the script
kevkevinpal force-pushed
on May 22, 2024
DrahtBot added the label
CI failed
on May 22, 2024
DrahtBot
commented at 0:38 am on May 22, 2024:
contributor
🚧 At least one of the CI tasks failed. Make sure to run all tests locally, according to the
documentation.
Possibly this is due to a silent merge conflict (the changes in this pull request being
incompatible with the current code in the target branch). If so, make sure to rebase on the latest
commit of the target branch.
Leave a comment here, if you need help tracking down a confusing failure.
DrahtBot removed the label
Needs rebase
on May 22, 2024
kevkevinpal force-pushed
on May 22, 2024
DrahtBot removed the label
CI failed
on May 22, 2024
DrahtBot added the label
Needs rebase
on Jul 2, 2024
glozow
commented at 11:59 am on August 7, 2024:
member
Are you still working on this?
kevkevinpal force-pushed
on Aug 7, 2024
kevkevinpal
commented at 2:42 pm on August 7, 2024:
contributor
Are you still working on this?
Yup just haven’t had much activity on it but rebased to 1d72266
DrahtBot removed the label
Needs rebase
on Aug 7, 2024
kevkevinpal requested review from axizqayum686
on Aug 7, 2024
kevkevinpal requested review from rkrux
on Aug 7, 2024
kevkevinpal requested review from brunoerg
on Aug 7, 2024
kevkevinpal requested review from ajtowns
on Aug 7, 2024
achow101 removed review request from rkrux
on Oct 15, 2024
achow101 removed review request from BrandonOdiwuor
on Oct 15, 2024
achow101 removed review request from axizqayum686
on Oct 15, 2024
achow101 requested review from vasild
on Oct 15, 2024
DrahtBot added the label
CI failed
on Oct 24, 2024
DrahtBot removed the label
CI failed
on Oct 25, 2024
vasild approved
vasild
commented at 10:43 am on November 5, 2024:
contributor
Almost ACK1d722660a6, modulo squash of the two commits.
This is a nice addition.
It is better to squash the two commits into one because IMO it does not make sense to first add the import in one commit and in another commit to use it. That way it becomes difficult to review whether an unnecessary import was added to some file. Also some python linter that tests each commit could be upset about the unused imports in the first commit.
I found it easier to read the diff itself instead of the “scripted diff” code (the line is 700+ chars long!). Further, I tweaked the diff to get the left and right sides align, so instead of:
Nit: The 3rd argument can be called error_message.
rkrux
commented at 7:47 am on November 11, 2024:
contributor
Concept ACK1d722660a65522539872c09ae8a3ba8c9ca55b77
The util function looks quite different from the last time I reviewed, simpler and more readable now!
Couple points:
The PR needs a rebase so that it works with the new build system, unable to build and test it in local atm with the latest build commands. Though the PR doesn’t have any conflicts with master, it would be nice to have this PR rebased if not much overhead for the author.
I agree with @vasild’s comment that the first commit should not be importing the helper function only to be used in the next commit. While reviewing the first commit, it immediately struck to me it is being imported but not used, which looked odd. However, I now wonder would squashing the 2 commits still work with the scripted diff as per the comment in the script checker?
DrahtBot added the label
CI failed
on Nov 12, 2024
kevkevinpal force-pushed
on Nov 12, 2024
kevkevinpal force-pushed
on Nov 12, 2024
kevkevinpal force-pushed
on Nov 12, 2024
kevkevinpal force-pushed
on Nov 13, 2024
kevkevinpal force-pushed
on Nov 13, 2024
kevkevinpal
commented at 2:43 am on November 13, 2024:
contributor
yes @rkrux you are right if I want to use a scripted diff then all the changes must be done using the script
I can remove the scripted diff and just squash them into a single diff since right now the script is getting very large already if that is preferred by others
DrahtBot removed the label
CI failed
on Nov 13, 2024
DrahtBot added the label
Needs rebase
on Nov 19, 2024
fanquake marked this as a draft
on Feb 20, 2025
fanquake
commented at 5:29 pm on February 20, 2025:
member
Almost ACKdcf9a45, modulo squash the two commits into one and rebase.
Just rebased and squashed the commits, I removed the scripted-diff because it was getting more difficult to review the diff than the actual code. But now all the changes are in d9cb032
if you do grep -nr assert\ .*!= ./test/functional/ there should only be one result
0 raise AssertionError(f"Both values are {thing1}{f', {error_message}' if error_message else ''}")
hodlinator
commented at 10:14 pm on March 27, 2025:
Would be more eager to ACK if code introduced by the PR followed bitcoin/test/functional/README.md as noted above. If you don’t want to change the phrasing towards what’s in the nit-part that’s fine.
kevkevinpal
commented at 11:15 pm on March 27, 2025:
02025-03-27T23:14:10.629000Z TestFramework (ERROR): Assertion failed
1Traceback (most recent call last):
2 File "/mnt/shared_drive/DEVDIR/bitcoin/test/functional/test_framework/test_framework.py", line 182, in main
3 self.run_test()
4 File "/mnt/shared_drive/DEVDIR/bitcoin/./build_dir/test/functional/p2p_blockfilters.py", line 98, in run_test
5 assert_not_equal(stale_block_hash, stale_block_hash, "node 0 chain did not reorganize")
6 File "/mnt/shared_drive/DEVDIR/bitcoin/test/functional/test_framework/util.py", line 81, in assert_not_equal
7 raise AssertionError(f"Both values are {thing1}, {f'{error_message}' if error_message else ''}")
8AssertionError: Both values are 05ba865bb920e2ff5763411a9dd3c225ace8ff4393469bb25acffd56df1bfb6e, node 0 chain did not reorganize
hodlinator
commented at 9:43 am on March 28, 2025:
DrahtBot added the label
Needs rebase
on Mar 13, 2025
vasild approved
vasild
commented at 1:44 pm on March 19, 2025:
contributor
ACKd9cb032ec1aa7000fc3f5b9fa48405f269879497
DrahtBot requested review from hodlinator
on Mar 19, 2025
ryanofsky approved
ryanofsky
commented at 6:22 pm on March 27, 2025:
contributor
Code review ACKd9cb032ec1aa7000fc3f5b9fa48405f269879497. Seems like a nice change, though needs to be rebased. I think this change is good because it makes tests more readable, prints more information on failures, avoids misusing the assert keyword, and makes the test framework api more consistent internally and consistent with other frameworks.
kevkevinpal force-pushed
on Mar 27, 2025
kevkevinpal
commented at 8:40 pm on March 27, 2025:
contributor
what is the point of the error_message, given that it is unused and highly fragile:
It is not a named arg, nor type-safe, so someone writing assert_not_equal("a", "b", "c") or assert_not_equal(1, 2, 3) will be wrong and confusing at best.
It is not used anywhere else in this file for another assert
hodlinator
commented at 9:42 am on March 28, 2025:
It would be good if all assert_ functions supported error messages explaining why the condition should never fail, or what could be wrong if it has failed. (Built-in assert supports it too).
To avoid mixing the error message up with a value to be checked, one could do:
0def assert_not_equal(thing1, thing2, *, message=None):
1 if thing1 == thing2:
2 raise AssertionError(f"Both values are {thing1}{f', {message}' if message else ''}")
* enforces named parameters from that point.
It would be good to shorten the name for callers.
“, " is only printed if there is a message.
Feels more correct to use None as default value.
The traceback in this case does print the source code line, so a comment on the same line could be sufficient. It depends on how the error is presented. Example failure:
02025-03-28T09:58:37.993000Z TestFramework (ERROR): Assertion failed
1Traceback (most recent call last):
2 File "/home/hodlinator/bitcoin/test/functional/test_framework/test_framework.py", line 182, in main
3 self.run_test()
4 File "/home/hodlinator/bitcoin/build/test/functional/p2p_blockfilters.py", line 98, in run_test
5 assert_not_equal(main_block_hash, stale_block_hash, message="node 0 chain did not reorganize")
6 File "/home/hodlinator/bitcoin/test/functional/test_framework/util.py", line 81, in assert_not_equal
7 raise AssertionError(f"Both values are {thing1}{f', {message}' if message else ''}")
8AssertionError: Both values are 1b0ec65a9941d57790524ff90cc1cb93d2f8e70680c1c7486d9edf934d96a041, node 0 chain did not reorganize
assert_not_equal(main_block_hash, stale_block_hash, message=“node 0 chain did not reorganize”)
I’d say it is already self-explanatory to see assert_not_equal(main_block_hash, stale_block_hash) and the message “AssertionError: Both values are (the same)”. It should be clear that the chains are the same, when they should be different.
Even more so, given that the test logs before the assert: self.log.info("Reorg node 0 to a new chain.").
Unique and descriptive error messages make sense when it is the only piece of information given (for example in the context of an init error during startup of the program). Given that no other asserts in this file give the option, it would be best to defer this to a future where it is actually widely needed. However, given the extensive context in tests and test failures, I doubt it will ever be widely needed. Until then, it is just bike-shedding and overhead that has to be maintained and reviewed.
hodlinator
commented at 10:59 am on March 28, 2025:
“AssertionError: Both values are (the same)”
Don’t know if you mean literally "Both values are (the same)" or f"Both values are {thing1}". Find the latter clearly more useful; providing clues of cause of the failure, making it easier to reproduce.
What it comes down to with custom error messages is being able to directly communicate with whoever is troubleshooting a failure. If one doesn’t even need to go to the logs to see what happened before, I consider it a small win. But agree it is bikeshedding-prone, so I would be okay with removing it.
Don’t know if you mean literally "Both values are (the same)" or f"Both values are {thing1}". Find the latter clearly more useful; providing clues of cause of the failure, making it easier to reproduce.
Agree. Sorry for being unclear, I meant f"Both values are {thing1}" when I said “(the same)”.
I do like error_message parameter idea, fwiw. I could see these making tests more readable if they started being used for more important or more confusing checks. Tests are often written with some important checks directly related to the thing being tested, and other less important checks to see if other things are consistent. Having a place to indicate what it actually means when a check fails could be helpful for more important checks to distinguish them.
yancyribbens
commented at 6:46 pm on March 28, 2025:
The traceback in this case does print the source code line, so a comment on the same line could be sufficient. It depends on how the error is presented. Example failure:
This is an annoying draw back that the stack trace won’t contain the line in the test that’s in error, but instead the line in the assert_not_equal helper. In Rust, one can decorate the helper function with the track_caller macro. I don’t see anything that is similar for C++ sadly.
hodlinator
commented at 9:33 pm on March 28, 2025:
No strong opinion, but I think we shouldn’t spend too much time on the error message explaining the context because the traceback could contain too little context, or on the traceback containing too much (redundant) context by default. A full log of a test failure could be thousands of lines, so one additional line, even if could be redundant in a strict sense should be fine. Also, looking at the historic failures (in Python) at https://github.com/bitcoin/bitcoin/issues?q=is%3Aissue%20%20label%3A%22CI%20failed%22%20, it was never(?) an issue to figure out what failed where. The problem usually is to figure out why something failed, and how to reproduce and fix it.
I don’t mind having an error message being logged and I do agree with the following points:
It is not a named arg, nor type-safe, so someone writing assert_not_equal(“a”, “b”, “c”) or assert_not_equal(1, 2, 3) will be wrong and confusing at best.
Unique and descriptive error messages make sense when it is the only piece of information given (for example in the context of an init error during startup of the program).
As I see from the usages of assert_not_equal, there is only 1 case where the error message is passed out of ~77 total usages. I believe a named argument for the error message could help in avoiding unintended usages of this utility function.
hodlinator approved
hodlinator
commented at 10:09 am on March 28, 2025:
contributor
ACKbe71af3cc0b0bcb7d917cc6f2e5fda119f1b1bd6
This change adds the missing opposite of assert_equal() and decreases usage of built-in assert for performing always-on checks during tests.
DrahtBot requested review from vasild
on Mar 28, 2025
DrahtBot requested review from ryanofsky
on Mar 28, 2025
janb84
commented at 12:38 pm on March 28, 2025:
contributor
Requesting changes mainly because an additional comma is printed in case of an assertion error when the error message is not passed, which can be confusing for the reader later.
02025-03-31T07:28:44.958000Z TestFramework (INFO): Test fundrawtxn with locked wallet and hardened derivation
12025-03-31T07:28:46.455000Z TestFramework (ERROR): Assertion failed
2Traceback (most recent call last):
3 File "/Users/rkrux/projects/bitcoin/test/functional/test_framework/test_framework.py", line 182, in main
4 self.run_test()
5 ~~~~~~~~~~~~~^^
6 File "/Users/rkrux/projects/bitcoin/./build/test/functional/wallet_fundrawtransaction.py", line 141, in run_test
7 self.test_locked_wallet()
8 ~~~~~~~~~~~~~~~~~~~~~~~^^
9 File "/Users/rkrux/projects/bitcoin/./build/test/functional/wallet_fundrawtransaction.py", line 665, in test_locked_wallet
10 assert_not_equal(fundedTx["changepos"], 1)
11 ~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^
12 File "/Users/rkrux/projects/bitcoin/test/functional/test_framework/util.py", line 81, in assert_not_equal
13 raise AssertionError(f"Both values are {thing1}, {f'{error_message}' if error_message else ''}")
14AssertionError: Both values are 1,
152025-03-31T07:28:46.512000Z TestFramework (INFO): Not stopping nodes as test failed. The dangling processes will be cleaned up later.
ryanofsky removed review request from BrandonOdiwuor
on Mar 31, 2025
ryanofsky removed review request from ryanofsky
on Mar 31, 2025
ryanofsky removed review request from janb84
on Mar 31, 2025
kevkevinpal force-pushed
on Mar 31, 2025
kevkevinpal force-pushed
on Mar 31, 2025
kevkevinpal
commented at 9:57 pm on March 31, 2025:
contributor
Added named variable for error_message and also made the assert able to take more than two variables to compare to each other. The error will now say
Two or more values equal to <value>: <error_message> or
Two or more values equal to <value>
if no error_message provided
in
test/functional/test_framework/util.py:87
in
f3d4b6d962outdated
82+ for i, val1 in enumerate(things):
83+ for val2 in things[i + 1:]:
84+ if val1 == val2:
85+ raise AssertionError(
86+ f"Two or more values equal to {val1}{f': {error_message}' if error_message else ''}"
87+ )
0assert_not_equal(stale_block_hash, stale_block_hash, "node 0 chain did not reorganize")
Output was:
02025-04-01T08:15:47.847000Z TestFramework (ERROR): Assertion failed
1Traceback (most recent call last):
2 File "/home/hodlinator/b2/test/functional/test_framework/test_framework.py", line 182, in main
3 self.run_test()
4 File "/home/hodlinator/b2/build/test/functional/p2p_blockfilters.py", line 66, in run_test
5 assert_not_equal(stale_block_hash, stale_block_hash, "node 0 chain did not reorganize")
6 File "/home/hodlinator/b2/test/functional/test_framework/util.py", line 85, in assert_not_equal
7 raise AssertionError(
8AssertionError: Two or more values equal to 775ea4a5e5005376a49f2da6d20ce0449fbd627d766c8fc3108d22db0d41e116
This indicates that the message wasn’t recognized as such. Naming the parameter has the desired effect:
0assert_not_equal(stale_block_hash, stale_block_hash, error_message="node 0 chain did not reorganize")
A possible step forward would be to update callers to name the parameter. As I said before, I think the name should be shortened.
(Phantom space)
0def assert_not_equal(*things, error_message=""):
kevkevinpal
commented at 12:43 pm on April 1, 2025:
I went back to using two params and made error_message a named parameter that cannot be used as a positional argument
test: create assert_not_equal util and add to where imports are needed
In the functional tests there are lots of cases where we assert != which
this new util will replace, we also are adding the imports and the new assertion
7bb83f6718
kevkevinpal force-pushed
on Apr 1, 2025
hodlinator approved
hodlinator
commented at 2:29 pm on April 1, 2025:
contributor
re-ACK7bb83f6718110cb3a29e72522fdc5515db21c7d0
Remaining reservations from #29500 (review) (sorry for broken record):
Current push still uses falsyerror_message="" with the if error_message, instead of clear error_message=None
error_message= at call sites is quite verbose.
DrahtBot requested review from janb84
on Apr 1, 2025
DrahtBot requested review from BrandonOdiwuor
on Apr 1, 2025
DrahtBot requested review from rkrux
on Apr 1, 2025
DrahtBot requested review from ryanofsky
on Apr 1, 2025
ryanofsky approved
ryanofsky
commented at 3:58 pm on April 1, 2025:
contributor
Code review ACK7bb83f6718110cb3a29e72522fdc5515db21c7d0. Only change since last review is fixing error message formatting and passing it as a keyword argument
janb84
commented at 4:51 pm on April 1, 2025:
contributor
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: 2025-06-23 18:13 UTC
This site is hosted by @0xB10C More mirrored repositories can be found on mirror.b10c.me