Repository: bitcoin/bitcoin
File: contrib/asmap/asmap-tool.py (Python)

Code before:
        ipv4_changed = 0
        ipv6_changed = 0
        for prefix, old_asn, new_asn in state1.diff(state2):
            if args.ignore_unassigned and old_asn == 0:
                continue
            net = asmap.prefix_to_net(prefix)
            if isinstance(net, ipaddress.IPv4Network):
                ipv4_changed += 1 << (32 - net.prefixlen)
            elif isinstance(net, ipaddress.IPv6Network):
                ipv6_changed += 1 << (128 - net.prefixlen)
            if new_asn == 0:
                print(f"# {net} was AS{old_asn}")
            elif old_asn == 0:
                print(f"{net} AS{new_asn} # was unassigned")
            else:
                print(f"{net} AS{new_asn} # was AS{old_asn}")
        ipv4_change_str = "" if ipv4_changed == 0 else f" (2^{math.log2(ipv4_changed):.2f})"
        ipv6_change_str = "" if ipv6_changed == 0 else f" (2^{math.log2(ipv6_changed):.2f})"

        print(
            f"# {ipv4_changed}{ipv4_change_str} IPv4 addresses changed; "
            f"{ipv6_changed}{ipv6_change_str} IPv6 addresses changed"
        )
    elif args.subcommand == "diff_addrs":
        state1 = load_file(args.infile1)
        state2 = load_file(args.infile2)
        address_info = json.load(args.addrs_file)
        addrs = {a["address"] for a in address_info if a["network"] in ["ipv4", "ipv6"]}
        reassignments = defaultdict(list)
        for addr in addrs:
            net = ipaddress.ip_network(addr)
            prefix = asmap.net_to_prefix(net)
            old_asn = state1.lookup(prefix)
            new_asn = state2.lookup(prefix)
            if new_asn != old_asn:
                reassignments[(old_asn, new_asn)].append(addr)
        reassignments = sorted(reassignments.items(), key=lambda item: len(item[1]), reverse=True)
        num_reassignment_type = defaultdict(int)
        for (old_asn, new_asn), reassigned_addrs in reassignments:
            num_reassigned = len(reassigned_addrs)
            num_reassignment_type[(bool(old_asn), bool(new_asn))] += num_reassigned
            old_asn_str = f"AS{old_asn}" if old_asn else "unassigned"
            new_asn_str = f"AS{new_asn}" if new_asn else "unassigned"
            opt = ": " + ", ".join(reassigned_addrs) if args.show_addresses else ""
            print(f"{num_reassigned} address(es) reassigned from {old_asn_str} to {new_asn_str}{opt}")
        num_reassignments = sum(len(addrs) for _, addrs in reassignments)
        share = num_reassignments / len(addrs) if len(addrs) > 0 else 0
        print(f"Summary: {num_reassignments:,} ({share:.2%}) of {len(addrs):,} addresses were reassigned "
              f"(migrations={num_reassignment_type[True, True]}, assignments={num_reassignment_type[False, True]}, "
              f"unassignments={num_reassignment_type[True, False]})")
    else:

Review comment (performance):
suggestion: Would be nice to output how many entries there are in total, something like
```
Total entries - {args.infile1.name}: {len(state1.to_entries(overlapping=False))}, {args.infile2.name}: {len(state2.to_entries(overlapping=False))}
->
Total entries - 1762865730.dat: 706503, 1762878275.dat: 698844
```
But adding that makes it run 7 seconds slower on top of the ~29 seconds I got otherwise. Maybe there's some other way to get an approximate value.

Code after:
        ipv4_changed = 0
        ipv4_entries_changed = 0
        ipv6_changed = 0
        ipv6_entries_changed = 0
        for prefix, old_asn, new_asn in state1.diff(state2):
            if args.ignore_unassigned and old_asn == 0:
                continue
            net = asmap.prefix_to_net(prefix)
            if isinstance(net, ipaddress.IPv4Network):
                ipv4_changed += net.num_addresses
                ipv4_entries_changed += 1
            elif isinstance(net, ipaddress.IPv6Network):
                ipv6_changed += net.num_addresses
                ipv6_entries_changed += 1
            if new_asn == 0:
                print(f"# {net} was AS{old_asn}")
            elif old_asn == 0:
                print(f"{net} AS{new_asn} # was unassigned")
            else:
                print(f"{net} AS{new_asn} # was AS{old_asn}")
        ipv4_change_str = "" if ipv4_changed == 0 else f" (2^{math.log2(ipv4_changed):.2f})"
        ipv6_change_str = "" if ipv6_changed == 0 else f" (2^{math.log2(ipv6_changed):.2f})"

        print(
f"""# Summary
IPv4: {ipv4_entries_changed} entries with {ipv4_changed}{ipv4_change_str} addresses changed
IPv6: {ipv6_entries_changed} entries with {ipv6_changed}{ipv6_change_str} addresses changed"""
        )
    elif args.subcommand == "diff_addrs":
        state1 = load_file(args.infile1)
        state2 = load_file(args.infile2)
        address_info = json.load(args.addrs_file)
        addrs = {a["address"] for a in address_info if a["network"] in ["ipv4", "ipv6"]}
        reassignments = defaultdict(list)
        for addr in addrs:
            net = ipaddress.ip_network(addr)
            prefix = asmap.net_to_prefix(net)
            old_asn = state1.lookup(prefix)
            new_asn = state2.lookup(prefix)
            if new_asn != old_asn:
                reassignments[(old_asn, new_asn)].append(addr)
        reassignments = sorted(reassignments.items(), key=lambda item: len(item[1]), reverse=True)
        num_reassignment_type = defaultdict(int)
        for (old_asn, new_asn), reassigned_addrs in reassignments:
            num_reassigned = len(reassigned_addrs)
            num_reassignment_type[(bool(old_asn), bool(new_asn))] += num_reassigned
            old_asn_str = f"AS{old_asn}" if old_asn else "unassigned"
            new_asn_str = f"AS{new_asn}" if new_asn else "unassigned"
            opt = ": " + ", ".join(reassigned_addrs) if args.show_addresses else ""
            print(f"{num_reassigned} address(es) reassigned from {old_asn_str} to {new_asn_str}{opt}")
        num_reassignments = sum(len(addrs) for _, addrs in reassignments)