Menu
BOLT-bolt-07 Final

BOLT-07: Gossip Protocol

Lightning Network gossip protocol specification. Channel announcements, node announcements, and network topology discovery.

Type Basis of Lightning Technology
Number bolt-07
Status Final
Authors Lightning Network Developers
Original https://github.com/lightning/bolts/blob/master/07-routing-gossip.md
Requires

BOLT-07: Gossip Protocol

BOLT-07 defines how nodes share information about the network topology. This gossip propagates channel and node information, enabling pathfinding.

Specification Summary

AspectValue
StatusFinal
LayerNetwork
PurposeTopology discovery
DependenciesBOLT-01

Message Types

TypeNamePurpose
256channel_announcementNew channel
257node_announcementNode metadata
258channel_updateChannel parameters
261query_channel_rangeRequest channels
262reply_channel_rangeChannel list
263query_short_channel_idsRequest details
264reply_short_channel_ids_endEnd of response

Channel Announcement

Announces a new public channel to the network.

Message Format (type 256)

┌─────────────────────────────────────────────┐
│ node_signature_1 (64 bytes)                 │
│ node_signature_2 (64 bytes)                 │
│ bitcoin_signature_1 (64 bytes)              │
│ bitcoin_signature_2 (64 bytes)              │
├─────────────────────────────────────────────┤
│ features (variable)                         │
│ chain_hash (32 bytes)                       │
│ short_channel_id (8 bytes)                  │
│ node_id_1 (33 bytes)                        │
│ node_id_2 (33 bytes)                        │
│ bitcoin_key_1 (33 bytes)                    │
│ bitcoin_key_2 (33 bytes)                    │
└─────────────────────────────────────────────┘

Short Channel ID

Compact channel identifier:

┌────────────────────────────────────────────┐
│ block_height (3 bytes) │ tx_index (3 bytes)│ output_index (2 bytes) │
└────────────────────────────────────────────┘
def encode_short_channel_id(block: int, tx: int, output: int) -> int:
    return (block << 40) | (tx << 16) | output

def decode_short_channel_id(scid: int) -> tuple:
    block = (scid >> 40) & 0xFFFFFF
    tx = (scid >> 16) & 0xFFFFFF
    output = scid & 0xFFFF
    return (block, tx, output)

# Format: 123x456x0
def format_scid(scid: int) -> str:
    block, tx, output = decode_short_channel_id(scid)
    return f"{block}x{tx}x{output}"

Verification

Nodes must verify:

  1. Funding transaction exists at short_channel_id
  2. Output is 2-of-2 multisig of bitcoin_key_1 and bitcoin_key_2
  3. All signatures are valid
  4. node_id_1 < node_id_2 (lexicographically)

Node Announcement

Broadcasts node metadata to the network.

Message Format (type 257)

┌─────────────────────────────────────────────┐
│ signature (64 bytes)                        │
├─────────────────────────────────────────────┤
│ features (variable)                         │
│ timestamp (4 bytes)                         │
│ node_id (33 bytes)                          │
│ rgb_color (3 bytes)                         │
│ alias (32 bytes)                            │
│ addresses (variable)                        │
└─────────────────────────────────────────────┘

Address Types

TypeDescription
1IPv4 (4 bytes + 2-byte port)
2IPv6 (16 bytes + 2-byte port)
3Tor v2 (deprecated)
4Tor v3 (35 bytes)
5DNS hostname

RGB Color

Node color for visualization:

rgb = bytes.fromhex("3399ff")  # Blue
# Displayed in explorers as node color

Alias

32-byte UTF-8 alias (padded with zeros):

alias = "MyNode".encode('utf-8').ljust(32, b'\x00')

Channel Update

Updates channel routing parameters.

Message Format (type 258)

┌─────────────────────────────────────────────┐
│ signature (64 bytes)                        │
├─────────────────────────────────────────────┤
│ chain_hash (32 bytes)                       │
│ short_channel_id (8 bytes)                  │
│ timestamp (4 bytes)                         │
│ message_flags (1 byte)                      │
│ channel_flags (1 byte)                      │
│ cltv_expiry_delta (2 bytes)                 │
│ htlc_minimum_msat (8 bytes)                 │
│ fee_base_msat (4 bytes)                     │
│ fee_proportional_millionths (4 bytes)       │
│ htlc_maximum_msat (8 bytes, if flag set)    │
└─────────────────────────────────────────────┘

Channel Flags

BitNameMeaning
0direction0 = node_1→node_2, 1 = node_2→node_1
1disabledChannel is disabled

Message Flags

BitNameMeaning
0htlc_maximumhtlc_maximum_msat field present

Fee Calculation

From channel_update fields:

def calculate_fee(amount_msat: int, update: ChannelUpdate) -> int:
    """Calculate routing fee for forwarding."""
    return update.fee_base_msat + (amount_msat * update.fee_proportional_millionths // 1_000_000)

Gossip Queries

query_channel_range (type 261)

Request channels in a block range:

┌─────────────────────────────────────────────┐
│ chain_hash (32 bytes)                       │
│ first_blocknum (4 bytes)                    │
│ number_of_blocks (4 bytes)                  │
│ [tlvs]                                      │
└─────────────────────────────────────────────┘

reply_channel_range (type 262)

Response with channel list:

┌─────────────────────────────────────────────┐
│ chain_hash (32 bytes)                       │
│ first_blocknum (4 bytes)                    │
│ number_of_blocks (4 bytes)                  │
│ sync_complete (1 byte)                      │
│ len (2 bytes)                               │
│ encoded_short_ids (len bytes)               │
│ [tlvs]                                      │
└─────────────────────────────────────────────┘

query_short_channel_ids (type 263)

Request specific channel details:

┌─────────────────────────────────────────────┐
│ chain_hash (32 bytes)                       │
│ len (2 bytes)                               │
│ encoded_short_ids (len bytes)               │
│ [tlvs]                                      │
└─────────────────────────────────────────────┘

Timestamp Rules

Update Priority

Newer timestamps replace older:

def should_replace(existing: int, new: int) -> bool:
    # Newer timestamp wins
    if new > existing:
        return True
    # Same timestamp, keep existing
    return False

Rate Limiting

Nodes should rate-limit:

  • node_announcement: ~1 per 30 seconds
  • channel_update: ~1 per 60 seconds per channel

Agent Implementation

Tracking Network State

class NetworkGraph:
    def __init__(self):
        self.channels = {}  # scid -> ChannelAnnouncement
        self.nodes = {}     # pubkey -> NodeAnnouncement
        self.updates = {}   # (scid, direction) -> ChannelUpdate

    def process_channel_announcement(self, msg: bytes):
        ann = parse_channel_announcement(msg)
        if verify_channel_announcement(ann):
            self.channels[ann.short_channel_id] = ann

    def process_node_announcement(self, msg: bytes):
        ann = parse_node_announcement(msg)
        if ann.timestamp > self.nodes.get(ann.node_id, 0):
            self.nodes[ann.node_id] = ann

    def process_channel_update(self, msg: bytes):
        update = parse_channel_update(msg)
        key = (update.short_channel_id, update.direction)
        if update.timestamp > self.updates.get(key, {}).get('timestamp', 0):
            self.updates[key] = update

    def get_channel_fee(self, scid: int, direction: int) -> int:
        update = self.updates.get((scid, direction))
        if not update:
            return None
        return update.fee_base_msat, update.fee_proportional_millionths

Gossip Synchronization

async def sync_gossip(peer, graph):
    """Synchronize gossip with peer."""
    # Get our current highest block
    our_height = graph.get_highest_block()

    # Query new channels
    await peer.send(query_channel_range(
        first_blocknum=our_height - 1000,  # Some overlap
        number_of_blocks=10000
    ))

    # Process replies
    while True:
        msg = await peer.receive()
        if msg.type == 262:  # reply_channel_range
            for scid in msg.short_channel_ids:
                if scid not in graph.channels:
                    # Request full announcement
                    await peer.send(query_short_channel_ids([scid]))

            if msg.sync_complete:
                break

Machine-Readable Summary

{
  "bolt": "07",
  "title": "Gossip Protocol",
  "status": "final",
  "messages": [
    {"type": 256, "name": "channel_announcement"},
    {"type": 257, "name": "node_announcement"},
    {"type": 258, "name": "channel_update"},
    {"type": 261, "name": "query_channel_range"},
    {"type": 262, "name": "reply_channel_range"},
    {"type": 263, "name": "query_short_channel_ids"}
  ],
  "key_concepts": [
    "short-channel-id",
    "network-topology",
    "fee-announcements"
  ]
}