Skip to content

ltcsuite/mwebsync

Repository files navigation

MwebSync

Litecoin MWEB Light Client implementing LIP-0006 syncing

The Problem

MWEB sync requires:

  1. Block headers (for sampling and verification)
  2. P2P connections to Litecoin nodes (to download MWEB data)

Different Litecoin backends have headers but may not have P2P support.

The Solution

MwebSync handles MWEB P2P internally using neutrino/query. Backends only need to provide:

  1. Access to block headers
  2. Access to P2P peers (MwebSync does the actual MWEB queries)

Key Features

  • Backend-Agnostic: Works with any block header source (neutrino, Electrum, full node RPC, etc.)
  • Minimal Interface Surface: Only 3 core interfaces to implement

Installation

go get github.com/ltcsuite/mwebsync

Quick Start

package main

import (
    "context"
    "log"

    "github.com/ltcsuite/ltcd/chaincfg"
    "github.com/ltcsuite/mwebsync"
)

func main() {
    // Implement the required interfaces for your backend
    headerProvider := NewYourHeaderProvider()  // Provides block headers
    peerQuerier := NewYourPeerQuerier()        // Handles P2P queries
    syncNotifier := NewYourSyncNotifier()      // Signals when headers are synced
    coinDB := NewYourCoinDatabase()            // Stores MWEB UTXOs

    // Create the syncer
    syncer, err := mwebsync.New(&mwebsync.Config{
        HeaderProvider: headerProvider,
        PeerQuerier:    peerQuerier,
        SyncNotifier:   syncNotifier,
        CoinDatabase:   coinDB,
        ChainParams:    &chaincfg.MainNetParams,
        OnUtxosAdded: func(leafset *mweb.Leafset, utxos []*wire.MwebNetUtxo) {
            log.Printf("Received %d new MWEB UTXOs", len(utxos))
            // Process UTXOs for your wallet
        },
    })
    if err != nil {
        log.Fatal(err)
    }

    // Start syncing
    if err := syncer.Start(); err != nil {
        log.Fatal(err)
    }

    // Wait for sync to complete
    <-context.Background().Done()

    // Gracefully stop
    syncer.Stop()
}

Architecture

MwebSync requires 3 simple interfaces from external backends:

1. BlockHeaderProvider

Supplies block headers (however your backend gets them).

type BlockHeaderProvider interface {
    ChainTip() (*wire.BlockHeader, uint32, error)
    FetchHeaderByHeight(height uint32) (*wire.BlockHeader, error)
}

2. PeerProvider

Provides access to connected Litecoin P2P peers. MwebSync handles all MWEB queries internally using neutrino/query.

type PeerProvider interface {
    // Gives access to connected peers
    ConnectedPeers() (<-chan query.Peer, func(), error)

    // Bans misbehaving peers
    BanPeer(peerAddr string, reason banman.Reason) error

    // Queries all peers (for tip verification)
    QueryAllPeers(queryMsg wire.Message,
        checkResponse func(sp query.Peer, resp wire.Message,
            quit chan<- struct{}, peerQuit chan<- struct{}))
}

Different backends:

  • Neutrino: Wraps ChainService to reuse existing peer connections
  • Electrum: Creates P2P connection manager (Electrum servers don't support MWEB)

3. SyncStateNotifier

Signals when block headers are synced. MWEB sync waits for block headers to be ready before starting.

type SyncStateNotifier interface {
    WaitForHeadersSync(ctx context.Context) error
    GetHeaderTip() uint32
}

4. CoinDatabase

Stores MWEB coins and related data. This interface is identical to github.com/ltcsuite/neutrino/mwebdb.CoinDatabase and can use the same implementation.

type CoinDatabase interface {
    GetRollbackHeight() (uint32, error)
    PutRollbackHeight(uint32) error
    ClearRollbackHeight(uint32) error
    GetLeavesAtHeight() (map[uint32]uint64, error)
    PutLeavesAtHeight(map[uint32]uint64) error
    RollbackLeavesAtHeight(uint32) error
    GetLeafset() (*mweb.Leafset, error)
    PutLeafsetAndPurge(*mweb.Leafset, []uint64) error
    PutCoins([]*wire.MwebNetUtxo) error
    FetchCoin(*chainhash.Hash) (*wire.MwebOutput, error)
    FetchLeaves([]uint64) ([]*wire.MwebNetUtxo, error)
    PurgeCoins() error
}

MWEB Sync Flow

The sync process follows these phases:

  1. Wait Phase: Wait for block headers to sync
  2. Rollback Phase: Handle chain reorganizations
  3. Header Sampling: Build height→leaf-count index (stratified sampling)
  4. Tip Verification: Get and verify current MWEB state (header + leafset)
  5. UTXO Differential Sync: Download only changed UTXOs
  6. Finalization: Update database and notify listeners
  7. Idle Phase: Wait for new blocks

Stratified Sampling

MwebSync uses a two-pass sampling strategy to build an efficient height→leaf-count index:

  • Coarse pass: Every 100th block from MWEB activation to tip
  • Fine pass: Every block in the last 4000 blocks

This index is used for "UTXO dating" - estimating when a UTXO was created based on its leaf index.

Differential Sync

Instead of downloading all UTXOs on every sync, MwebSync compares the old and new leafsets (bitmaps) to identify:

  • Added leaves: New UTXOs to download
  • Removed leaves: Spent UTXOs to purge

This minimizes bandwidth and processing requirements.

Integration

For Neutrino

Neutrino can wrap its existing components:

type NeutrinoPeerProvider struct {
    chainService *neutrino.ChainService
}

func (n *NeutrinoPeerProvider) ConnectedPeers() (<-chan query.Peer, func(), error) {
    // Return channel from chainService.Peers()
}

func (n *NeutrinoPeerProvider) BanPeer(addr string, reason banman.Reason) error {
    // Use chainService's BanPeer
}

func (n *NeutrinoPeerProvider) QueryAllPeers(...) {
    // Use chainService's queryAllPeers function
}

For Electrum

Electrum needs to create a P2P connection manager since Electrum servers don't support MWEB:

type ElectrumPeerProvider struct {
    peerManager *ltcp2p.PeerManager  // Simple P2P manager
}

func (e *ElectrumPeerProvider) ConnectedPeers() (<-chan query.Peer, func(), error) {
    // Return peers from P2P manager
}

Security

  • Header Verification: mweb.VerifyHeader() ensures MWEB headers are valid
  • Leafset Verification: mweb.VerifyLeafset() ensures leafsets match headers
  • UTXO Verification: mweb.VerifyUtxos() ensures UTXOs are valid against the MMR root
  • Peer Banning: Malicious peers are banned automatically

FAQ

Q: Do I need to implement CoinDatabase myself?

No, you can reuse the existing implementation from github.com/ltcsuite/neutrino/mwebdb.

Q: How does this handle chain reorganizations?

MwebSync detects rollbacks automatically and rewinds MWEB state accordingly:

  • Rollbacks > 10 blocks: Purge all MWEB data and resync
  • Rollbacks ≤ 10 blocks: Roll back to the rollback height

Q: What about mempool transactions?

MwebSync focuses on confirmed transactions. Mempool handling is left to the wallet implementation via the OnUtxosAdded callback.

Links

About

Litecoin MWEB Light Client implementing LIP-0006 syncing

Resources

Stars

Watchers

Forks

Packages

 
 
 

Contributors

Languages