History of lightning_network
The changes to the source code of this entry in the repository, newest first, each with the differences it made to the entry's files. They are also listed on GitLab.
[entries] Add the date when each entry was added, and remove icon_hash beff7c9
The dates are those of the commits that first added the entries' files (2020-08-11 for the entries of the initial commit); renamed entries keep the date of their first name. Changed: none
diff --git a/entries/lightning_network/__init__.py b/entries/lightning_network/__init__.py
index 1ba84bd..1de6520 100644
--- a/entries/lightning_network/__init__.py
+++ b/entries/lightning_network/__init__.py
@@ -49,7 +49,7 @@ citation = [('V. Valko and J. Marx Gómez, "Geolocated Lightning Network topolog
'https://doi.org/10.1038/s41597-025-06413-7'),
('C. Decker, "Lightning Network Research - Topology Datasets." Zenodo (2020)',
'https://doi.org/10.5281/zenodo.4088530')]
-icon_hash = None
+date_added = '2026-10-31'
upstream_license = ('CC BY 4.0', 'https://creativecommons.org/licenses/by/4.0/')
# (Harvard Dataverse, version 1.1 of the dataset; the file identifier is
[entries] Prefix the properties in the descriptions with (Node), (Edge) or (Graph) bc3dbbd
The lists of properties at the end of the descriptions marked edge properties with "Edge" but left node properties unmarked, or grouped them in "Nodes: `a`, ...; `b`, ..." items, so the kind of a property was often unclear. Every item now starts with (Node), (Edge) or (Graph), followed by the property names, a colon and the explanation, one item per property or group of properties; qualifications (kinds of nodes, networks) follow the names in parentheses. Properties that were only named got a short explanation, and a few items that named the wrong kind of property were corrected.
diff --git a/entries/lightning_network/__init__.py b/entries/lightning_network/__init__.py
index 47f32ae..1ba84bd 100644
--- a/entries/lightning_network/__init__.py
+++ b/entries/lightning_network/__init__.py
@@ -28,20 +28,20 @@ description = r"""Snapshots of the Lightning Network, the network of payment cha
The networks have the following properties:
-- `pubkey`: the public key of the node (its identifier in the network).
-- `alias`: the alias announced by the node.
-- `rgb_color`: the color announced by the node.
-- `features`: the feature bits announced by the node (hexadecimal).
-- `addresses`: the network addresses announced by the node (IPv4, IPv6 or Tor), or empty.
-- `timestamp`: the time of the node's last announcement, in Unix time (0 or -1 if none).
-- `city`, `region`, `country`, `postal`, `timezone`, `org`: the geolocation of the node's IP address (with IPinfo), or empty for the nodes that were not geolocated (e.g. Tor nodes).
-- `latitude`, `longitude`: the coordinates of the geolocation (NaN if not geolocated).
-- Edge `scid`: the short channel identifier (block height, transaction index and output index of the funding transaction), with the direction bit of the half-channel (e.g. `641702x1022x1/0`).
-- Edge `timestamp`: the time of the last update of the half-channel, in Unix time.
-- Edge `features`: the feature bits of the channel (hexadecimal).
-- Edge `fee_base_msat`, `fee_proportional_millionths`: the base fee (in millisatoshis) and the proportional fee (in millionths) to forward payments.
-- Edge `htlc_minimum_msat`, `htlc_maximum_msat`: the minimum and maximum amounts of a forwarded payment, in millisatoshis.
-- Edge `cltv_expiry_delta`: the number of blocks of the time lock delta of the half-channel."""
+- (Node) `pubkey`: the public key of the node (its identifier in the network).
+- (Node) `alias`: the alias announced by the node.
+- (Node) `rgb_color`: the color announced by the node.
+- (Node) `features`: the feature bits announced by the node (hexadecimal).
+- (Node) `addresses`: the network addresses announced by the node (IPv4, IPv6 or Tor), or empty.
+- (Node) `timestamp`: the time of the node's last announcement, in Unix time (0 or -1 if none).
+- (Node) `city`, `region`, `country`, `postal`, `timezone`, `org`: the geolocation of the node's IP address (with IPinfo), or empty for the nodes that were not geolocated (e.g. Tor nodes).
+- (Node) `latitude`, `longitude`: the coordinates of the geolocation (NaN if not geolocated).
+- (Edge) `scid`: the short channel identifier (block height, transaction index and output index of the funding transaction), with the direction bit of the half-channel (e.g. `641702x1022x1/0`).
+- (Edge) `timestamp`: the time of the last update of the half-channel, in Unix time.
+- (Edge) `features`: the feature bits of the channel (hexadecimal).
+- (Edge) `fee_base_msat`, `fee_proportional_millionths`: the base fee (in millisatoshis) and the proportional fee (in millionths) to forward payments.
+- (Edge) `htlc_minimum_msat`, `htlc_maximum_msat`: the minimum and maximum amounts of a forwarded payment, in millisatoshis.
+- (Edge) `cltv_expiry_delta`: the number of blocks of the time lock delta of the half-channel."""
tags = ['Economic', 'Cryptocurrency', 'Technological', 'Timestamps', 'Metadata']
url = ['https://doi.org/10.7910/DVN/2OAVO6', 'https://dataverse.harvard.edu/dataset.xhtml?persistentId=doi:10.7910/DVN/2OAVO6',
'https://github.com/lnresearch/topology']
[entries] New entry: lightning_network 484f067
diff --git a/entries/lightning_network/__init__.py b/entries/lightning_network/__init__.py
new file mode 100644
index 0000000..47f32ae
--- /dev/null
+++ b/entries/lightning_network/__init__.py
@@ -0,0 +1,147 @@
+#! /usr/bin/env python
+# -*- coding: utf-8 -*-
+#
+# Copyright (C) 2026 Tiago de Paula Peixoto <tiago@skewed.de>
+#
+# This program is free software: you can redistribute it and/or modify it under
+# the terms of the GNU Affero General Public License as published by the Free
+# Software Foundation, either version 3 of the License, or (at your option) any
+# later version.
+#
+# This program is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
+# details.
+#
+# You should have received a copy of the GNU Affero General Public License along
+# with this program. If not, see <http://www.gnu.org/licenses/>.
+
+
+from .. import *
+import process_entry
+
+# The snapshots: the last one of each year in the dataset
+SNAPSHOTS = ["20191118", "20201230", "20211010", "20220605", "20230716"]
+
+title = "Bitcoin Lightning Network (2019-2023)"
+description = r"""Snapshots of the Lightning Network, the network of payment channels built on top of the Bitcoin blockchain, from the geolocated topology snapshots of V. Valko and J. Marx Gómez, who reconstructed them from the gossip messages collected by C. Decker (the lnresearch topology datasets) and geolocated the nodes with the IP addresses they announce. Nodes are Lightning nodes (identified by their public keys), and each payment channel between two nodes gives two directed edges, one for each direction of the channel ("half-channel"), with the fees and limits announced for that direction; the edges point to the node given as their `destination` in the dataset. The dataset has 336 snapshots from January 2019 to July 2023; the networks here are the last snapshot of each year (`2019_11_18`, `2020_12_30`, `2021_10_10`, `2022_06_05` and `2023_07_16`). The capacities of the channels (which are not part of the gossip messages) are not given.
+
+The networks have the following properties:
+
+- `pubkey`: the public key of the node (its identifier in the network).
+- `alias`: the alias announced by the node.
+- `rgb_color`: the color announced by the node.
+- `features`: the feature bits announced by the node (hexadecimal).
+- `addresses`: the network addresses announced by the node (IPv4, IPv6 or Tor), or empty.
+- `timestamp`: the time of the node's last announcement, in Unix time (0 or -1 if none).
+- `city`, `region`, `country`, `postal`, `timezone`, `org`: the geolocation of the node's IP address (with IPinfo), or empty for the nodes that were not geolocated (e.g. Tor nodes).
+- `latitude`, `longitude`: the coordinates of the geolocation (NaN if not geolocated).
+- Edge `scid`: the short channel identifier (block height, transaction index and output index of the funding transaction), with the direction bit of the half-channel (e.g. `641702x1022x1/0`).
+- Edge `timestamp`: the time of the last update of the half-channel, in Unix time.
+- Edge `features`: the feature bits of the channel (hexadecimal).
+- Edge `fee_base_msat`, `fee_proportional_millionths`: the base fee (in millisatoshis) and the proportional fee (in millionths) to forward payments.
+- Edge `htlc_minimum_msat`, `htlc_maximum_msat`: the minimum and maximum amounts of a forwarded payment, in millisatoshis.
+- Edge `cltv_expiry_delta`: the number of blocks of the time lock delta of the half-channel."""
+tags = ['Economic', 'Cryptocurrency', 'Technological', 'Timestamps', 'Metadata']
+url = ['https://doi.org/10.7910/DVN/2OAVO6', 'https://dataverse.harvard.edu/dataset.xhtml?persistentId=doi:10.7910/DVN/2OAVO6',
+ 'https://github.com/lnresearch/topology']
+citation = [('V. Valko and J. Marx Gómez, "Geolocated Lightning Network topology snapshots: A dataset covering 2019-2023." Scientific Data 12, 1939 (2025)',
+ 'https://doi.org/10.1038/s41597-025-06413-7'),
+ ('C. Decker, "Lightning Network Research - Topology Datasets." Zenodo (2020)',
+ 'https://doi.org/10.5281/zenodo.4088530')]
+icon_hash = None
+upstream_license = ('CC BY 4.0', 'https://creativecommons.org/licenses/by/4.0/')
+
+# (Harvard Dataverse, version 1.1 of the dataset; the file identifier is
+# that of this version of the file)
+URLS = {"snapshots.geo.zip": "https://dataverse.harvard.edu/api/access/datafile/12510549"}
+upstream_prefix = None
+files = [(f"snapshots.geo.zip:{s}.gml.geo", f"{s[:4]}_{s[4:6]}_{s[6:]}", None)
+ for s in SNAPSHOTS]
+
+def fetch_upstream(force=False):
+ # (a 562 MB file, streamed to the cache)
+ name = __name__.split(".")[-1]
+ for fname, url in URLS.items():
+ if not force and upstream_file_exists(name, fname):
+ continue
+ if process_entry._check_health:
+ with open_url(url) as r:
+ r.read(1)
+ continue
+ with open_url(url) as r:
+ with open_upstream_file(name, fname, "wb") as out:
+ copy_download(r, out, label=fname)
+
+GEO = ["city", "region", "country", "postal", "timezone", "org"]
+EINT = ["fee_base_msat", "fee_proportional_millionths", "htlc_minimum_msat",
+ "htlc_maximum_msat", "cltv_expiry_delta", "timestamp"]
+
+@cache_network()
+@coerce_props()
+@annotate()
+def parse(alts=None):
+ name = __name__.split(".")[-1]
+ for fname, alt, fmt in files:
+ if alts is not None and alt not in alts:
+ continue
+ with open_upstream_file(name, fname, "rb") as f:
+ u = load_graph(f, fmt="gml")
+ # the edges point to the node given as "destination", which is the
+ # "source" of the edges of the GML file (which has no direction)
+ dest = u.ep.destination
+ label = u.vp.label
+ for e in u.edges():
+ # (in undirected graphs, the source of an edge is as in the file
+ # only up to orientation; check both ends)
+ assert dest[e] in (label[e.source()], label[e.target()])
+ u.set_directed(True)
+ src = u.get_edges([u.edge_index])
+ g = Graph(directed=True)
+ g.add_vertex(u.num_vertices())
+ # orient each edge towards its destination, in the order of the file
+ order = numpy.argsort(src[:, 2])
+ src = src[order]
+ lab = numpy.array(list(label), dtype=object)
+ dst = numpy.array([dest[e] for e in sorted(u.edges(), key=lambda e: u.edge_index[e])],
+ dtype=object)
+ to_t = dst == lab[src[:, 1]]
+ s_ = numpy.where(to_t, src[:, 0], src[:, 1])
+ t_ = numpy.where(to_t, src[:, 1], src[:, 0])
+ g.add_edge_list(numpy.column_stack([s_, t_]))
+ g.vp.pubkey = g.new_vp("string", vals=list(label))
+ for k in ["alias", "rgb_color", "features", "addresses"]:
+ g.vp[k] = (g.new_vp("string", vals=list(u.vp[k])) if k in u.vp
+ else g.new_vp("string"))
+ g.vp.timestamp = g.new_vp("int64_t", val=-1)
+ if "timestamp" in u.vp:
+ x = u.vp.timestamp.fa
+ g.vp.timestamp.fa = numpy.where(numpy.isnan(x), -1, x).astype("int64")
+ for k in GEO:
+ g.vp[k] = g.new_vp("string")
+ g.vp.latitude = g.new_vp("double", val=numpy.nan)
+ g.vp.longitude = g.new_vp("double", val=numpy.nan)
+ if "geojson" in u.vp:
+ for v in u.vertices():
+ d = u.vp.geojson[v]
+ if not isinstance(d, dict):
+ continue
+ for k in GEO:
+ g.vp[k][v] = str(d.get(k, ""))
+ try:
+ lat, lon = map(float, str(d.get("loc", "")).split(","))
+ g.vp.latitude[v] = lat
+ g.vp.longitude[v] = lon
+ except ValueError:
+ pass
+ # (the edges of g are those of u, in the order of their indices)
+ ue = sorted(u.edges(), key=lambda e: u.edge_index[e])
+ g.ep.scid = g.new_ep("string", vals=[u.ep.scid[e] for e in ue])
+ g.ep.features = g.new_ep("string", vals=[u.ep.features[e] for e in ue])
+ for k in EINT:
+ uk = "htlc_minimim_msat" if k == "htlc_minimum_msat" else k
+ p = g.ep[k] = g.new_ep("int64_t", val=-1)
+ if uk in u.ep:
+ x = u.ep[uk].fa
+ p.fa = numpy.where(numpy.isnan(x), -1, x).astype("int64")
+ yield alt, g