pygossmap: store features for nodes and channels

also makes them acessible using bitmask functions
This commit is contained in:
Michael Schmoock 2023-02-23 11:47:22 +01:00 committed by Rusty Russell
parent 3130f4ec27
commit f1b6047d69
2 changed files with 101 additions and 1 deletions

View File

@ -1,6 +1,6 @@
from .lightning import LightningRpc, RpcError, Millisatoshi
from .plugin import Plugin, monkey_patch, RpcException
from .gossmap import Gossmap, GossmapNode, GossmapChannel, GossmapHalfchannel, GossmapNodeId
from .gossmap import Gossmap, GossmapNode, GossmapChannel, GossmapHalfchannel, GossmapNodeId, LnFeatureBits
__version__ = "23.02"
@ -17,4 +17,5 @@ __all__ = [
"GossmapChannel",
"GossmapHalfchannel",
"GossmapNodeId",
"LnFeatureBits",
]

View File

@ -26,6 +26,64 @@ WIRE_GOSSIP_STORE_ENDED = 4105
WIRE_GOSSIP_STORE_CHANNEL_AMOUNT = 4101
class LnFeatureBits(object):
""" feature flags taken from bolts.git/09-features.md
Flags are numbered from the least-significant bit, at bit 0 (i.e. 0x1,
an _even_ bit). They are generally assigned in pairs so that features
can be introduced as optional (_odd_ bits) and later upgraded to be compulsory
(_even_ bits), which will be refused by outdated nodes:
CONTEXT:
* `I`: presented in the `init` message.
* `N`: presented in the `node_announcement` messages
* `C`: presented in the `channel_announcement` message.
* `C-`: presented in the `channel_announcement` message, but always odd (optional).
* `C+`: presented in the `channel_announcement` message, but always even (required).
* `9`: presented in [BOLT 11](11-payment-encoding.md) invoices.
FEATURE_NAME # CONTEXT # PRs
----------------------------------------------------------------- """
OPTION_DATA_LOSS_PROTECT = 0 # IN
INITIAL_ROUTING_SYNC = 2 # I
OPTION_UPFRONT_SHUTDOWN_SCRIPT = 4 # IN
GOSSIP_QUERIES = 6 # IN
VAR_ONION_OPTIN = 8 # IN9
GOSSIP_QUERIES_EX = 10 # IN
OPTION_STATIC_REMOTEKEY = 12 # IN
PAYMENT_SECRET = 14 # IN9
BASIC_MPP = 16 # IN9
OPTION_SUPPORT_LARGE_CHANNEL = 18 # IN
OPTION_ANCHOR_OUTPUTS = 20 # IN
OPTION_ANCHORS_ZERO_FEE_HTLC_TX = 22 # IN
OPTION_SHUTDOWN_ANYSEGWIT = 26 # IN
OPTION_CHANNEL_TYPE = 44 # IN
OPTION_SCID_ALIAS = 46 # IN
OPTION_PAYMENT_METADATA = 48 # 9
OPTION_ZEROCONF = 50 # IN
OPTION_PROPOSED_ROUTE_BLINDING = 24 # IN9 #765 #798
OPTION_PROPOSED_DUAL_FUND = 28 # IN #851 #1009
OPTION_PROPOSED_ALTERNATIVE_FEERATES = 32 # IN #1036
OPTION_PROPOSED_QUIESCE = 34 # IN #869 #868
OPTION_PROPOSED_ONION_MESSAGES = 38 # IN #759
OPTION_PROPOSED_WANT_PEER_BACKUP_STORAGE = 40 # IN #881
OPTION_PROPOSED_PROVIDE_PEER_BACKUP = 42 # IN #881
OPTION_PROPOSED_TRAMPOLINE_ROUTING = 56 # IN9 #836
OPTION_PROPOSED_UPFRONT_FEE = 56 # IN9 #1052
OPTION_PROPOSED_CLOSING_REJECTED = 60 # IN #1016
OPTION_PROPOSED_SPLICE = 62 # IN #863
def _parse_features(featurebytes):
# featurebytes e.g.: [136, 160, 0, 8, 2, 105, 162]
result = 0
for byte in featurebytes:
result <<= 8
result |= byte
return result
class GossipStoreHeader(object):
def __init__(self, buf: bytes, off: int):
self.flags, self.length, self.crc, self.timestamp = struct.unpack('>HHII', buf)
@ -136,6 +194,7 @@ class GossmapChannel(object):
self.node2 = node2
self.satoshis = None
self.half_channels: List[Optional[GossmapHalfchannel]] = [None, None]
self.features = _parse_features(fields['features'])
def _update_channel(self,
direction: int,
@ -164,6 +223,21 @@ class GossmapChannel(object):
def __hash__(self):
return self.scid.__hash__()
def has_feature(self, bit):
return 3 << bit & self.features != 0
def has_feature_compulsory(self, bit):
return 1 << bit & self.features != 0
def has_feature_optional(self, bit):
return 2 << bit & self.features != 0
def has_features(self, *bits):
for bit in bits:
if not self.has_feature(bit):
return False
return True
class GossmapNode(object):
"""A node: fields of node_announcement are in .fields,
@ -201,6 +275,29 @@ class GossmapNode(object):
def __str__(self):
return str(self.node_id)
def has_feature(self, bit):
if not self.announced:
return None
return 3 << bit & self.features != 0
def has_feature_compulsory(self, bit):
if not self.announced:
return None
return 1 << bit & self.features != 0
def has_feature_optional(self, bit):
if not self.announced:
return None
return 2 << bit & self.features != 0
def has_features(self, *bits):
if not self.announced:
return None
for bit in bits:
if not self.has_feature(bit):
return False
return True
def _parse_addresses(self, data: bytes):
""" parse address descriptors defined in bolts 07-routing-gossip.md """
result = []
@ -435,6 +532,8 @@ class Gossmap(object):
node.fields = fields
node.hdr = hdr
# read metadata
node.features = _parse_features(fields['features'])
node.timestamp = fields['timestamp']
node.alias = bytes(fields['alias']).decode('utf-8')
node.rgb = fields['rgb_color']