lotus-sdk
Version:
Central repository for several classes of tools for integrating with, and building for, the Lotusia ecosystem
719 lines (532 loc) • 19.7 kB
Markdown
**Status**: Production Ready ✅
**Version**: 1.0.0
**Updated**: November 28, 2025
---
A **reusable, protocol-agnostic DHT advertising and discovery system** for the Lotus SDK P2P layer. This module provides the foundation for peer discovery, capability advertising, and resource lookup across any P2P protocol.
**Built for Extension**: The discovery layer is designed to be extended by protocol-specific implementations (e.g., MuSig2, SwapSig) while providing common functionality out of the box.
**Use Cases:**
- Signer discovery for MuSig2 multi-signature sessions
- Participant discovery for CoinJoin rounds
- Service advertisement and lookup
- Peer capability matching
---
## Architecture
```
┌─────────────────────────────────────────────────────────────────────────────┐
│ Protocol Extensions │
│ ┌─────────────────────┐ ┌─────────────────────┐ ┌─────────────────────┐ │
│ │ MuSig2Discovery │ │ SwapSigDiscovery │ │ CustomDiscovery │ │
│ │ (musig2/) │ │ (swapsig/) │ │ (your-protocol/) │ │
│ └──────────┬──────────┘ └──────────┬──────────┘ └──────────┬──────────┘ │
└─────────────┼────────────────────────┼────────────────────────┼─────────────┘
│ │ │
└────────────────────────┼────────────────────────┘
▼
┌─────────────────────────────────────────────────────────────────────────────┐
│ Core Discovery Layer │
│ ┌─────────────────────┐ ┌─────────────────────┐ ┌─────────────────────┐ │
│ │ DHTAdvertiser │ │ DHTDiscoverer │ │ SecurityValidator │ │
│ │ │ │ │ │ │ │
│ │ • advertise() │ │ • discover() │ │ • validateAd() │ │
│ │ • withdraw() │ │ • subscribe() │ │ • verifySignature() │ │
│ │ • refresh() │ │ • cache management │ │ • reputation │ │
│ └─────────────────────┘ └─────────────────────┘ └─────────────────────┘ │
└─────────────────────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────────────────┐
│ P2PCoordinator (Base) │
│ Kad-DHT │ GossipSub │ Direct P2P │
└─────────────────────────────────────────────────────────────────────────────┘
```
---
## Key Features
✅ **Protocol-Agnostic Design**
- Generic interfaces for any discovery use case
- Extensible criteria and advertisement types
- Custom metadata support
✅ **DHT-Based Discovery**
- Decentralized peer discovery via Kad-DHT
- Automatic advertisement refresh
- Configurable TTL and expiration
✅ **Security Layer**
- Schnorr signature verification
- Replay attack prevention
- Peer reputation tracking
- Rate limiting
✅ **Event-Driven Subscriptions (GossipSub)**
- Real-time discovery via GossipSub pub/sub
- Instant callback notifications when new advertisements are published
- No polling - true event-driven architecture
- Optional initial DHT fetch for existing advertisements
✅ **Caching**
- Local cache for discovered advertisements
- Cache hit/miss statistics
- Configurable cache size and TTL
---
## Quick Start
### Installation
```typescript
import {
DHTAdvertiser,
DHTDiscoverer,
DiscoverySecurityValidator,
createDiscoverySystem,
} from 'lotus-sdk/lib/p2p/discovery'
```
```typescript
import { P2PCoordinator } from 'lotus-sdk/lib/p2p'
import { createDiscoverySystem } from 'lotus-sdk/lib/p2p/discovery'
// Create P2P coordinator
const coordinator = new P2PCoordinator({
listen: ['/ip4/0.0.0.0/tcp/0'],
enableDHT: true,
})
await coordinator.start()
// Create discovery system
const discovery = createDiscoverySystem(coordinator, {
defaultTTL: 30 * 60 * 1000, // 30 minutes
enableAutoRefresh: true,
refreshInterval: 20 * 60 * 1000, // 20 minutes
})
// Start discovery
await discovery.start()
// Advertise capabilities
const adId = await discovery.advertiser.advertise({
protocol: 'my-protocol',
peerId: coordinator.peerId,
capabilities: ['signing', 'relay'],
metadata: { version: '1.0.0' },
})
// Discover peers
const peers = await discovery.discoverer.discover({
protocol: 'my-protocol',
capabilities: ['signing'],
})
console.log(`Found ${peers.length} peers with signing capability`)
// Cleanup
await discovery.stop()
await coordinator.stop()
```
---
```
lib/p2p/discovery/
├── index.ts
├── types.ts
├── dht-advertiser.ts
├── dht-discoverer.ts
├── security.ts
└── README.md
```
---
Handles advertising peer capabilities to the DHT.
```typescript
import { DHTAdvertiser } from 'lotus-sdk/lib/p2p/discovery'
const advertiser = new DHTAdvertiser(coordinator, securityValidator, options)
// Start the advertiser
await advertiser.start()
// Advertise capabilities
const adId = await advertiser.advertise({
protocol: 'musig2',
peerId: coordinator.peerId,
capabilities: ['signer'],
metadata: {
publicKey: myPublicKey.toString(),
transactionTypes: ['spend', 'coinjoin'],
},
})
// Refresh advertisement (reset TTL)
await advertiser.refresh(adId)
// Withdraw advertisement
await advertiser.withdraw(adId)
// Check active advertisements
const activeAds = advertiser.getActiveAdvertisements()
console.log('Active advertisements:', activeAds)
// Stop advertiser
await advertiser.stop()
```
Handles discovering peers using a hybrid DHT + GossipSub approach:
- **DHT**: For one-time queries and persistent storage lookup
- **GossipSub**: For real-time event-driven subscriptions
```typescript
import { DHTDiscoverer } from 'lotus-sdk/lib/p2p/discovery'
const discoverer = new DHTDiscoverer(coordinator)
// Start the discoverer
await discoverer.start()
// One-time discovery (queries DHT)
const peers = await discoverer.discover({
protocol: 'musig2',
capabilities: ['signer'],
minReputation: 50,
})
// Subscribe for real-time updates (uses GossipSub)
// Callback is invoked immediately when new advertisements are published
const subscription = await discoverer.subscribe(
{
protocol: 'musig2',
capabilities: ['signer'],
},
advertisement => {
// Called instantly when a new signer advertises
console.log('New signer discovered:', advertisement.peerInfo.peerId)
},
{
fetchExisting: true, // Also fetch existing from DHT
deduplicate: true, // Don't notify for same ad twice
},
)
// Unsubscribe when done
await discoverer.unsubscribe(subscription.id)
// Cache statistics
const stats = discoverer.getCacheStats()
console.log('Cache hit rate:', stats.hitRate)
// Stop discoverer
await discoverer.stop()
```
Validates advertisements and manages peer reputation.
```typescript
import { DiscoverySecurityValidator } from 'lotus-sdk/lib/p2p/discovery'
const validator = new DiscoverySecurityValidator(options)
// Validate an advertisement
const result = await validator.validateAdvertisement(advertisement)
if (result.valid) {
console.log('Valid advertisement, security score:', result.securityScore)
} else {
console.log('Invalid advertisement:', result.reason)
}
// Check peer reputation
const reputation = validator.getPeerReputation(peerId)
console.log('Peer reputation:', reputation.score)
// Update reputation (after successful interaction)
validator.updateReputation(peerId, 10) // +10 points
// Check rate limiting
const canAdvertise = validator.checkRateLimit(peerId, 'advertise')
```
---
```typescript
interface DiscoveryOptions {
/** Default TTL for advertisements (ms) */
defaultTTL?: number // Default: 30 minutes
/** Number of DHT replicas */
replicaCount?: number // Default: 3
/** Enable auto-refresh of advertisements */
enableAutoRefresh?: boolean // Default: true
/** Refresh interval (ms) */
refreshInterval?: number // Default: 20 minutes
/** Enable local caching */
enableCache?: boolean // Default: true
/** Maximum cache size */
maxCacheSize?: number // Default: 1000
/** Cache TTL (ms) */
cacheTTL?: number // Default: 5 minutes
/** Enable rate limiting */
enableRateLimiting?: boolean // Default: true
/** Maximum advertisements per peer */
maxAdsPerPeer?: number // Default: 10
/** Minimum interval between advertisements (ms) */
minAdvertiseInterval?: number // Default: 30 seconds
}
```
```typescript
interface SecurityPolicy {
/** Require signature verification */
requireSignature?: boolean // Default: true
/** Minimum reputation score to accept */
minReputation?: number // Default: 0
/** Maximum timestamp skew (ms) */
maxTimestampSkew?: number // Default: 5 minutes
/** Enable replay attack prevention */
enableReplayPrevention?: boolean // Default: true
/** Rate limit: max advertisements per minute */
maxAdvertisementsPerMinute?: number // Default: 10
/** Rate limit: max discoveries per minute */
maxDiscoveriesPerMinute?: number // Default: 60
/** Custom validators */
customValidators?: CustomValidator[]
}
```
---
Criteria for discovering peers:
```typescript
interface DiscoveryCriteria {
/** Protocol identifier (e.g., 'musig2', 'swapsig') */
protocol: string
/** Required capabilities */
capabilities?: string[]
/** Minimum reputation score */
minReputation?: number
/** Geographic location filter */
location?: {
latitude: number
longitude: number
radiusKm: number
}
/** Custom criteria (protocol-specific) */
custom?: Record<string, unknown>
}
```
Advertisement published to DHT:
```typescript
interface DiscoveryAdvertisement {
/** Unique advertisement ID */
id: string
/** Protocol identifier */
protocol: string
/** Advertiser's peer ID */
peerId: string
/** Advertiser's multiaddrs */
multiaddrs?: string[]
/** Advertised capabilities */
capabilities: string[]
/** Reputation score */
reputation?: number
/** Geographic location */
location?: { latitude: number; longitude: number }
/** Custom metadata */
metadata?: Record<string, unknown>
/** Schnorr signature */
signature?: string
/** Creation timestamp */
timestamp: number
/** Expiration timestamp */
expiresAt: number
}
```
Result of advertisement validation:
```typescript
interface SecurityValidationResult {
/** Whether the advertisement is valid */
valid: boolean
/** Reason for rejection (if invalid) */
reason?: string
/** Security score (0-100) */
securityScore: number
/** Validation details */
details?: {
signatureValid?: boolean
timestampValid?: boolean
reputationValid?: boolean
rateLimitPassed?: boolean
}
}
```
---
Creates a complete discovery system with advertiser, discoverer, and security validator:
```typescript
import { createDiscoverySystem } from 'lotus-sdk/lib/p2p/discovery'
const discovery = createDiscoverySystem(coordinator, {
defaultTTL: 30 * 60 * 1000,
enableAutoRefresh: true,
})
await discovery.start()
// Access components
discovery.advertiser // DHTAdvertiser
discovery.discoverer // DHTDiscoverer
discovery.securityValidator // DiscoverySecurityValidator
await discovery.stop()
```
Create individual components:
```typescript
import {
createAdvertiser,
createDiscoverer,
createSecurityValidator,
} from 'lotus-sdk/lib/p2p/discovery'
const validator = createSecurityValidator(securityPolicy)
const advertiser = createAdvertiser(coordinator, validator, options)
const discoverer = createDiscoverer(coordinator, validator, options)
```
---
The discovery layer is designed to be extended. See `musig2/discovery-extension.ts` for a complete example.
```typescript
import {
DHTAdvertiser,
DHTDiscoverer,
DiscoveryCriteria,
DiscoveryAdvertisement,
} from 'lotus-sdk/lib/p2p/discovery'
// Define protocol-specific criteria
interface MyProtocolCriteria extends DiscoveryCriteria {
protocol: 'my-protocol'
myCustomField?: string
}
// Define protocol-specific advertisement
interface MyProtocolAdvertisement extends DiscoveryAdvertisement {
protocol: 'my-protocol'
myCustomData?: unknown
}
// Create extension class
class MyProtocolDiscovery {
private advertiser: DHTAdvertiser
private discoverer: DHTDiscoverer
constructor(coordinator: P2PCoordinator) {
const validator = new DiscoverySecurityValidator()
this.advertiser = new DHTAdvertiser(coordinator, validator)
this.discoverer = new DHTDiscoverer(coordinator, validator)
}
async advertiseService(data: MyServiceData): Promise<string> {
return this.advertiser.advertise({
protocol: 'my-protocol',
peerId: this.coordinator.peerId,
capabilities: ['my-service'],
metadata: data,
})
}
async discoverServices(
criteria: MyProtocolCriteria,
): Promise<MyProtocolAdvertisement[]> {
const ads = await this.discoverer.discover(criteria)
return ads as MyProtocolAdvertisement[]
}
}
```
---
All advertisements are signed using Schnorr signatures:
```typescript
// Advertisements are automatically signed when created
const adId = await advertiser.advertise({
protocol: 'musig2',
peerId: coordinator.peerId,
// ... signature is added automatically
})
// Signatures are verified during discovery
const peers = await discoverer.discover({ protocol: 'musig2' })
// Only valid, signed advertisements are returned
```
The security validator tracks seen advertisements to prevent replay attacks:
```typescript
const validator = new DiscoverySecurityValidator({
enableReplayPrevention: true,
})
// First time: valid
const result1 = await validator.validateAdvertisement(ad)
// result1.valid === true
// Replay attempt: rejected
const result2 = await validator.validateAdvertisement(ad)
// result2.valid === false
// result2.reason === 'Replay attack detected'
```
Peer reputation is tracked and used for filtering:
```typescript
// Discover only reputable peers
const peers = await discoverer.discover({
protocol: 'musig2',
minReputation: 70,
})
// Update reputation after interaction
validator.updateReputation(peerId, 10) // Success: +10
validator.updateReputation(peerId, -20) // Failure: -20
```
Rate limiting prevents spam and DoS attacks:
```typescript
const validator = new DiscoverySecurityValidator({
maxAdvertisementsPerMinute: 10,
maxDiscoveriesPerMinute: 60,
})
// Check before advertising
if (validator.checkRateLimit(peerId, 'advertise')) {
await advertiser.advertise(ad)
} else {
console.log('Rate limited, try again later')
}
```
---
```typescript
class DHTAdvertiser implements IDiscoveryAdvertiser {
constructor(
coordinator: P2PCoordinator,
securityValidator: DiscoverySecurityValidator,
options?: DiscoveryOptions,
)
// Lifecycle
async start(): Promise<void>
async stop(): Promise<void>
// Advertising
async advertise(
advertisement: Partial<DiscoveryAdvertisement>,
): Promise<string>
async withdraw(advertisementId: string): Promise<void>
async refresh(advertisementId: string): Promise<void>
// Status
getActiveAdvertisements(): string[]
isAdvertisementActive(advertisementId: string): boolean
}
```
### DHTDiscoverer
```typescript
class DHTDiscoverer implements IDiscoveryDiscoverer {
constructor(
coordinator: P2PCoordinator,
securityValidator: DiscoverySecurityValidator,
options?: DiscoveryOptions,
)
// Lifecycle
async start(): Promise<void>
async stop(): Promise<void>
// Discovery
async discover(criteria: DiscoveryCriteria): Promise<DiscoveryAdvertisement[]>
// Subscriptions
async subscribe(
criteria: DiscoveryCriteria,
callback: (advertisements: DiscoveryAdvertisement[]) => void,
options?: SubscriptionOptions,
): Promise<string>
async unsubscribe(subscriptionId: string): Promise<void>
getActiveSubscriptions(): string[]
// Cache
clearCache(protocol?: string): void
getCacheStats(): CacheStats
}
```
### DiscoverySecurityValidator
```typescript
class DiscoverySecurityValidator {
constructor(policy?: SecurityPolicy)
// Validation
async validateAdvertisement(
advertisement: DiscoveryAdvertisement,
): Promise<SecurityValidationResult>
// Reputation
getPeerReputation(peerId: string): ReputationData
updateReputation(peerId: string, delta: number): void
// Rate Limiting
checkRateLimit(peerId: string, action: 'advertise' | 'discover'): boolean
// Statistics
getSecurityStats(): SecurityStats
}
```
---
## Related Documentation
- [P2P Core README](../README.md) - Core P2P infrastructure
- [P2P Core HOWTO](../HOWTO.md) - Developer guide for P2P
- [MuSig2 Discovery](../musig2/DISCOVERY_README.md) - MuSig2-specific discovery
- [DHT Discovery Implementation Status](../../docs/DHT_DISCOVERY_IMPLEMENTATION_STATUS.md) - Implementation status
---
**Built for the Lotus Ecosystem** 🌸