Skip to content

GossipSub

XE uses GossipSub for broadcasting messages to all peers in the network. Each category of data has its own topic, and each topic has a dedicated gossip type with Publish() and Subscribe() methods.

Topics

Topic Constant Data Type Channel Buffer
xe/blocks BlockTopic BlockMsg (wraps core.Block) 256
xe/votes VoteTopic VoteMsg (wraps core.Vote) 256
xe/marketplace MarketplaceTopic MarketplaceMsg 256
xe/statechain StateChainTopic StateChainMsg (wraps statechain.Block) 16
xe/directory DirectoryTopic directory.Registration 256
xe/certificates CertificateTopic perf.Certificate 64

Message Size Limit

const MaxGossipMessageSize = 262144 // 256 KB

The 256 KB limit accommodates state chain blocks which can carry large key-value data. Block lattice messages are much smaller but share the same PubSub instance.

Gossip Types

All gossip types share a common PubSub instance created with NewPubSub():

func NewPubSub(ctx context.Context, h host.Host) (*pubsub.PubSub, error)

Each gossip type joins its respective topic and provides Publish() and Subscribe() methods.

Block Gossip (Gossip)

Broadcasts and receives block lattice blocks.

type Gossip struct { /* ... */ }

func NewGossip(ctx context.Context, h host.Host, ps ...*pubsub.PubSub) (*Gossip, error)
func (g *Gossip) Publish(ctx context.Context, b *core.Block) error
func (g *Gossip) Subscribe(ctx context.Context) <-chan *core.Block
func (g *Gossip) PubSub() *pubsub.PubSub

The PubSub() accessor returns the underlying PubSub instance for sharing with other gossip types.

Vote Gossip (VoteGossip)

Broadcasts and receives conflict resolution votes.

type VoteGossip struct { /* ... */ }

func NewVoteGossip(ps *pubsub.PubSub) (*VoteGossip, error)
func (vg *VoteGossip) Publish(ctx context.Context, v *core.Vote) error
func (vg *VoteGossip) Subscribe(ctx context.Context) <-chan *core.Vote

State Chain Gossip (StateChainGossip)

Broadcasts and receives state chain blocks (governance operations).

type StateChainGossip struct { /* ... */ }

func NewStateChainGossip(ps *pubsub.PubSub) (*StateChainGossip, error)
func (sg *StateChainGossip) Publish(ctx context.Context, b *statechain.Block) error
func (sg *StateChainGossip) Subscribe(ctx context.Context) <-chan *statechain.Block

Directory Gossip (DirectoryGossip)

Broadcasts and receives account directory registrations (human-readable name mappings).

type DirectoryGossip struct { /* ... */ }

func NewDirectoryGossip(ps *pubsub.PubSub) (*DirectoryGossip, error)
func (dg *DirectoryGossip) Publish(ctx context.Context, reg *directory.Registration) error
func (dg *DirectoryGossip) Subscribe(ctx context.Context) <-chan *directory.Registration

Marketplace Gossip (MarketplaceGossip)

Broadcasts and receives marketplace messages (resource advertisements, requests, and offers).

type MarketplaceGossip struct { /* ... */ }

func NewMarketplaceGossip(ps *pubsub.PubSub) (*MarketplaceGossip, error)
func (mg *MarketplaceGossip) Publish(ctx context.Context, msg *MarketplaceMsg) error
func (mg *MarketplaceGossip) Subscribe(ctx context.Context) <-chan *MarketplaceMsg

Pre-Validation

Messages are validated before being passed to consumers. This rejects malformed data before expensive cryptographic verification.

Block Validation

// Hash and Account are 64 hex chars (32 bytes), Signature is 128 hex chars (64-byte ed25519)
if len(b.Hash) != 64 || len(b.Signature) != 128 || len(b.Account) != 64 {
    continue // drop
}
Field Expected Length Encoding
Hash 64 chars Hex (32 bytes)
Signature 128 chars Hex (64 bytes)
Account 64 chars Hex (32 bytes)

Vote Validation

// RepPubKey and BlockHash are 64 hex chars, Signature is 64 raw bytes (ed25519)
if len(v.RepPubKey) != 64 || len(v.BlockHash) != 64 || len(v.Signature) != 64 {
    continue // drop
}

State Chain Block Validation

// Hash must be 64 hex chars, at least one signature required
if len(scm.Block.Hash) != 64 || len(scm.Block.Signatures) == 0 {
    continue // drop
}

Directory Validation

// Account and Signature must be non-empty
if reg.Account == "" || reg.Signature == "" {
    continue // drop
}

Marketplace Validation

// Type field must be non-empty
if mm.Type == "" {
    continue // drop
}

Message Flow

Publisher Node                          Subscriber Node
─────────────                          ───────────────
     │                                       │
     │  Publish(ctx, block)                  │
     │     │                                 │
     │     ▼                                 │
     │  JSON marshal                         │
     │     │                                 │
     │     ▼                                 │
     │  topic.Publish() ──── GossipSub ────▶ sub.Next()
     │                                       │
     │                                       ▼
     │                                  JSON decode
     │                                       │
     │                                       ▼
     │                                  Pre-validate
     │                                       │
     │                                  ┌────┴────┐
     │                                  │ Valid?  │
     │                                  └────┬────┘
     │                                   yes │ no
     │                                       │  └─▶ drop
     │                                       ▼
     │                                  ch <- block
     │                                  (or drop if full)

Backpressure

Each Subscribe method creates a buffered channel. If the consumer is slow and the channel fills up, incoming messages are dropped with a log warning:

gossip: block channel full, dropping block a1b2c3d4e5f6...

Dropped Messages

Dropped gossip messages are recovered by the sync protocol, which runs every 10 seconds to catch up on any missed blocks. Vote and marketplace messages are not sync-recoverable and rely on re-transmission by peers.

JSON Encoding

All gossip messages use JSON encoding with encoding/json. Block and vote decoders use DisallowUnknownFields() for strict parsing. State chain and marketplace decoders use standard json.Unmarshal.

Shared PubSub Instance

All gossip types share a single PubSub instance. The typical initialization order is:

// 1. Create shared PubSub
ps, err := xenet.NewPubSub(ctx, host)

// 2. Create block gossip (can also create PubSub internally)
blockGossip, err := xenet.NewGossip(ctx, host, ps)

// 3. Create other gossip types sharing the same PubSub
voteGossip, err := xenet.NewVoteGossip(ps)
stateChainGossip, err := xenet.NewStateChainGossip(ps)
directoryGossip, err := xenet.NewDirectoryGossip(ps)
marketplaceGossip, err := xenet.NewMarketplaceGossip(ps)

Why Share PubSub?

A single PubSub instance maintains one set of peer connections and mesh overlays. Sharing it across topics avoids duplicate connection overhead and ensures consistent peer scoring.