GossipSub
Gossip-based message propagation
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 KBThe 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.PubSubThe 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.VoteState 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.BlockDirectory 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.RegistrationMarketplace 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 *MarketplaceMsgPre-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...[!WARNING] 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)[!INFO] 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.