Skip to content

Latest commit

 

History

History
809 lines (644 loc) · 20.7 KB

File metadata and controls

809 lines (644 loc) · 20.7 KB

SpiffyNode API Documentation

Complete API reference for the SpiffyNode BitcoinSV P2P client library.

Table of Contents


Core Components

initializeMessages()

Must be called before using the library. Registers all Bitcoin protocol message types.

import 'package:spiffynode/spiffy_node.dart';

void main() {
  initializeMessages(); // Required initialization
  // ... rest of your code
}

BitcoinNetwork

Supported Bitcoin networks with their magic numbers:

enum BitcoinNetwork {
  mainnet(0xe8f3e1e3),   // Production Bitcoin network
  testnet(0xf4f3e5f4),   // Test network 
  regtest(0xfabfb5da),   // Regression testing 
  simnet(0x12141c16);    // Simulation network
}

Usage:

const network = BitcoinNetwork.regtest; // For local testing
const network = BitcoinNetwork.mainnet; // For production

Configuration

PeerConfig

Configuration for individual peer connections:

class PeerConfig {
  final Duration handshakeTimeout;     // Handshake timeout (default: 30s)
  final Duration pingInterval;         // Ping frequency (default: 2m)
  final Duration pongTimeout;          // Pong response timeout (default: 30s)
  final int maxUnhealthyScore;         // Max unhealthy score before disconnect (default: 10)
  final int protocolVersion;           // Bitcoin protocol version (default: 70016)
  final String userAgent;              // Client identification (default: '/spiffynode:0.1.0/')
  final bool enablePingPong;           // Enable health monitoring (default: true)
  final int startHeight;               // Chain tip for SPV nodes (default: 0)
}

Examples:

// Default configuration (sync from genesis)
const defaultConfig = PeerConfig();

// SPV node configuration
const spvConfig = PeerConfig(
  startHeight: 50000,              // Current chain tip
  userAgent: '/my-spv-wallet:1.0/',
  handshakeTimeout: Duration(seconds: 60),
  enablePingPong: true,
);

// Mining pool configuration
const poolConfig = PeerConfig(
  protocolVersion: 70016,
  userAgent: '/mining-pool:2.1/',
  pingInterval: Duration(seconds: 30),  // More frequent health checks
  maxUnhealthyScore: 5,                 // Lower tolerance
);

PeerManagerConfig

Configuration for the peer manager:

class PeerManagerConfig {
  final int minPeers;                    // Minimum connected peers (default: 3)
  final int maxPeers;                    // Maximum connected peers (default: 8)
  final Duration peerConnectTimeout;     // Connection timeout (default: 30s)
  final Duration announcementCooldown;   // Rate limiting (default: 100ms)
  final int maxAnnouncementBatch;        // Batch size for announcements (default: 3)
  final bool enableHealthMonitoring;     // Health monitoring (default: true)
  final Duration healthCheckInterval;    // Health check frequency (default: 2m)
  final PeerConfig defaultPeerConfig;    // Default config for new peers
}

Peer Management

Peer

Individual peer connection:

class Peer {
  // Constructor
  Peer({
    required String address,           // IP address or hostname
    required int port,                 // Port number
    required BitcoinNetwork network,   // Bitcoin network
    PeerHandlerI? handler,             // Message handler (optional)
    PeerConfig? config,                // Configuration (optional)
    Logger? logger,                    // Logger instance (optional)
  });

  // Connection management
  Future<void> connect();              // Connect to peer
  Future<void> shutdown();             // Disconnect and cleanup
  
  // Properties
  BitcoinNetwork get network;          // Network this peer is on
  bool get connected;                  // Connection status
  bool get isHealthy;                  // Health status
  PeerState get state;                 // Current state
  bool get handshakeComplete;          // Handshake status
  MsgVersion? get remoteVersion;       // Peer's version info
  
  // Statistics
  DateTime? get connectedAt;           // Connection timestamp
  int get messagesSent;                // Messages sent count
  int get messagesReceived;            // Messages received count
  int get pingsSent;                   // Pings sent count
  int get pongsReceived;               // Pongs received count
  int get unhealthyScore;              // Current health score
  
  // Event streams
  Stream<PeerState> get stateStream;   // State change events
  Stream<void> get isUnhealthyStream;  // Health degradation events
}

Peer States:

enum PeerState {
  disconnected,  // Not connected
  connecting,    // Establishing TCP connection
  connected,     // TCP connected, handshake pending
  handshaking,   // Performing Bitcoin handshake
  ready,         // Ready for Bitcoin protocol messages
  unhealthy,     // Peer is misbehaving
  disconnecting, // Gracefully disconnecting
  error,         // Error state
}

PeerManager

Manages multiple peer connections:

class PeerManager {
  // Constructor
  PeerManager({
    required BitcoinNetwork network,
    PeerHandlerI? handler,
    PeerManagerConfig? config,
    Logger? logger,
    ChainTipTracker? chainTipTracker,
  });

  // Peer management
  Future<Peer> addPeerByAddress(String address, int port, {PeerConfig? peerConfig});
  Future<void> addPeer(Peer peer);
  Future<void> removePeer(String peerId);
  Future<void> addPeersByHostname(String hostname, {int port = 8333, PeerConfig? peerConfig});
  
  // Properties
  BitcoinNetwork get network;          // Network
  int get peerCount;                   // Connected peers count
  int get healthyPeerCount;            // Healthy peers count
  bool get isShutdown;                 // Shutdown status
  List<PeerI> getPeers();              // All peers
  List<PeerI> getHealthyPeers();       // Healthy peers only
  
  // Event streams
  Stream<Peer> get peerAdded;          // New peer connected
  Stream<Peer> get peerRemoved;        // Peer disconnected
  Stream<WireMessage> get messageStream; // All received messages
  
  // Chain management (SPV)
  ChainTipTracker get chainTipTracker; // SPV chain tracker
  
  // Cleanup
  Future<void> shutdown();             // Shutdown all connections
}

Message Handling

PeerHandlerI

Interface for handling Bitcoin protocol messages:

abstract class PeerHandlerI {
  // Transaction handling
  Future<List<Uint8List>> handleTransactionsGet(List<InvVect> msgs, PeerI peer);
  Future<void> handleTransactionSent(WireMessage msg, PeerI peer);
  Future<void> handleTransactionAnnouncement(InvVect msg, PeerI peer);
  Future<void> handleTransactionRejection(WireMessage rejMsg, PeerI peer);
  Future<void> handleTransaction(WireMessage msg, PeerI peer);

  // Block handling  
  Future<List<Uint8List>> handleBlocksGet(List<InvVect> msgs, PeerI peer);
  Future<void> handleBlockSent(WireMessage msg, PeerI peer);
  Future<void> handleBlockAnnouncement(InvVect msg, PeerI peer);
  Future<void> handleBlock(WireMessage msg, PeerI peer);

  // Protocol messages
  Future<void> handleVersion(WireMessage msg, PeerI peer);
  Future<void> handleAddresses(WireMessage msg, PeerI peer);
}

Example Implementation:

class MyWalletHandler implements PeerHandlerI {
  @override
  Future<void> handleTransaction(WireMessage msg, PeerI peer) async {
    final tx = msg as MsgTx;
    print('Received transaction: ${tx.txHash}');
    
    // Process transaction for your wallet
    await processTransaction(tx);
  }

  @override
  Future<void> handleBlock(WireMessage msg, PeerI peer) async {
    final block = msg as MsgBlock;
    print('Received block: ${block.header.hashString}');
    
    // Process block headers for SPV
    await updateChainTip(block.header);
  }

  @override
  Future<void> handleVersion(WireMessage msg, PeerI peer) async {
    final version = msg as MsgVersion;
    print('Peer version: ${version.protocolVersion}, height: ${version.startHeight}');
  }

  // ... implement other methods
}

Built-in Messages

Common Bitcoin protocol messages:

// Version handshake
MsgVersion(
  protocolVersion: 70016,
  services: ServiceFlags.none,
  startHeight: 1000,                    // Your chain tip
  userAgent: '/my-app:1.0/',
  relay: false,                         // SPV nodes typically don't relay
);

// Block requests
MsgGetHeaders(
  locatorHashes: [lastKnownBlockHash],
  hashStop: Hash.zero,                  // Get all available
);

// Transaction
MsgTx(/* transaction data */);

// Inventory announcements
MsgInv([
  InvVect(InvType.msgTx, transactionHash),
  InvVect(InvType.msgBlock, blockHash),
]);

// Address announcements
MsgAddr([
  NetworkAddress.ipv4('192.168.1.100', 8333),
]);

Network Protocols

Service Flags

Bitcoin node service capabilities:

class ServiceFlags {
  static const int none = 0x00;              // No services
  static const int nodeNetwork = 0x01;       // Full node
  static const int nodeGetUtxo = 0x02;       // UTXO queries
  static const int nodeBloom = 0x04;          // Bloom filters
  static const int nodeWitness = 0x08;        // Witness data
  static const int nodeNetworkLimited = 0x0400; // Limited node
}

Network Address

IPv4/IPv6 network addresses:

// IPv4 address
final addr = NetworkAddress.ipv4('127.0.0.1', 8333);

// Manual construction
final addr = NetworkAddress(
  services: ServiceFlags.nodeNetwork,
  ip: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0xFF, 0xFF, 127, 0, 0, 1], // IPv4-mapped
  port: 8333,
  timestamp: DateTime.now().millisecondsSinceEpoch ~/ 1000,
);

// Properties
bool get isIPv4;        // IPv4 address check
bool get isLocalhost;   // Localhost check
bool get isZero;        // Invalid/zero address
String get ipString;    // Human-readable IP

Message Priority

Message queue priority levels:

enum MessagePriority {
  critical,  // Protocol messages (version, verack, ping, pong)
  high,      // Block announcements, urgent data
  normal,    // Regular transactions, data requests
  low,       // Bulk data, background sync
}

SPV Features

Chain Tip Tracking

SPV chain synchronization:

class ChainTipTracker {
  // Register/unregister peers
  void registerPeer(String peerId, int reportedHeight);
  void unregisterPeer(String peerId);
  
  // Chain state
  int get bestHeight;              // Highest reported block
  String get bestPeerId;           // Peer with best chain
  Map<String, int> get peerHeights; // All peer heights
  
  // Health monitoring
  bool isPeerBehind(String peerId, {int threshold = 144}); // ~24 hours
  List<String> getStalePeers({int maxAge = Duration.secondsPerDay});
}

SPV Configuration Patterns

Efficient SPV Sync:

// Get your current chain tip from local storage
final myChainTip = await getStoredChainTip(); // e.g., 50000

// Configure peers to sync only new blocks
const spvConfig = PeerConfig(
  startHeight: myChainTip,             // Don't sync from genesis
  userAgent: '/my-spv-wallet:1.0/',
  services: ServiceFlags.none,         // We're not a full node
  enablePingPong: true,                // Monitor connection health
);

// Connect to multiple peers for redundancy
final peers = [
  'seed1.bitcoin.sv:8333',
  'seed2.bitcoin.sv:8333', 
  'seed3.bitcoin.sv:8333',
];

for (final peerAddr in peers) {
  final [host, port] = peerAddr.split(':');
  await peerManager.addPeerByAddress(
    host, 
    int.parse(port),
    peerConfig: spvConfig,
  );
}

Headers-First Sync:

class SPVHandler implements PeerHandlerI {
  @override
  Future<void> handleVersion(WireMessage msg, PeerI peer) async {
    // After handshake, request block headers
    final getHeaders = MsgGetHeaders(
      locatorHashes: await getBlockLocators(), // Your known blocks
      hashStop: Hash.zero,                     // Get all available
    );
    
    await peer.writeMessage(getHeaders);
  }
  
  @override
  Future<void> handleHeaders(WireMessage msg, PeerI peer) async {
    final headers = msg as MsgHeaders;
    
    // Process headers sequentially
    for (final header in headers.headers) {
      await validateAndStoreHeader(header);
    }
    
    // Request more if we got the maximum batch
    if (headers.headers.length == 2000) {
      await requestMoreHeaders(peer);
    }
  }
}

Error Handling

Exception Types

// Network exceptions
ConnectionException         // Connection failed
ConnectionTimeoutException  // Handshake/operation timeout
ConnectionLostException     // Connection dropped unexpectedly

// Protocol exceptions
ProtocolException          // Protocol violation
MessageException           // Invalid message format
WireException             // Wire protocol error

// Validation exceptions
ValidationException        // Data validation failed
ChecksumException         // Message checksum mismatch

Error Handling Patterns

Connection Resilience:

Future<void> connectWithRetry(Peer peer, {int maxRetries = 3}) async {
  for (int attempt = 1; attempt <= maxRetries; attempt++) {
    try {
      await peer.connect();
      print('Connected successfully!');
      return;
    } on ConnectionTimeoutException {
      print('Attempt $attempt: Connection timeout');
      if (attempt == maxRetries) rethrow;
      await Future.delayed(Duration(seconds: attempt * 2)); // Exponential backoff
    } on ConnectionException catch (e) {
      print('Attempt $attempt: Connection failed: $e');
      if (attempt == maxRetries) rethrow;
      await Future.delayed(Duration(seconds: attempt * 2));
    }
  }
}

Message Handler Error Recovery:

class RobustHandler implements PeerHandlerI {
  @override
  Future<void> handleTransaction(WireMessage msg, PeerI peer) async {
    try {
      final tx = msg as MsgTx;
      await processTransaction(tx);
    } on ValidationException catch (e) {
      logger.warning('Invalid transaction from ${peer.address}: $e');
      // Don't disconnect peer for invalid transactions
    } on Exception catch (e) {
      logger.severe('Error processing transaction: $e');
      // Consider disconnecting peer for serious errors
      await peer.shutdown();
    }
  }
}

Advanced Usage

Custom Message Types

Extending the protocol with custom messages:

class MyCustomMessage extends RegisterableMessage {
  String customData;
  
  MyCustomMessage({required this.customData});
  
  @override
  String get command => 'mycustom';
  
  @override
  int get maxPayloadLength => 1000;
  
  @override
  Uint8List encode(int protocolVersion, MessageEncoding encoding) {
    return Uint8List.fromList(utf8.encode(customData));
  }
  
  @override
  void decode(Uint8List data, int protocolVersion, MessageEncoding encoding) {
    customData = utf8.decode(data);
  }
  
  static void registerWithFactory() {
    RegisterableMessage.register<MyCustomMessage>(
      'mycustom',
      (data, version, encoding) {
        final msg = MyCustomMessage(customData: '');
        msg.decode(data, version, encoding);
        return msg;
      },
      () => MyCustomMessage(customData: ''),
    );
  }
}

Performance Monitoring

class PerformanceMonitor implements PeerHandlerI {
  final Map<String, int> _messageCount = {};
  final Map<String, Duration> _processingTime = {};
  
  @override
  Future<void> handleTransaction(WireMessage msg, PeerI peer) async {
    final stopwatch = Stopwatch()..start();
    
    try {
      // Your transaction processing
      await processTransaction(msg as MsgTx);
    } finally {
      stopwatch.stop();
      
      _messageCount['tx'] = (_messageCount['tx'] ?? 0) + 1;
      _processingTime['tx'] = stopwatch.elapsed;
      
      // Log performance metrics
      if (_messageCount['tx']! % 100 == 0) {
        print('Processed ${_messageCount['tx']} transactions, '
              'avg time: ${_processingTime['tx']!.inMilliseconds}ms');
      }
    }
  }
}

Examples

Simple Bitcoin Monitor

import 'package:spiffynode/spiffy_node.dart';

class BitcoinMonitor implements PeerHandlerI {
  @override
  Future<void> handleBlockAnnouncement(InvVect msg, PeerI peer) async {
    print('📦 New block announced: ${msg.hash}');
  }
  
  @override
  Future<void> handleTransactionAnnouncement(InvVect msg, PeerI peer) async {
    print('💰 New transaction: ${msg.hash}');
  }
  
  // ... implement other required methods
}

Future<void> main() async {
  initializeMessages();
  
  final monitor = BitcoinMonitor();
  final peerManager = PeerManager(
    network: BitcoinNetwork.mainnet,
    handler: monitor,
  );
  
  // Connect to Bitcoin network
  await peerManager.addPeerByAddress('seed.bitcoin.sv', 8333);
  
  print('🚀 Bitcoin monitor started!');
  
  // Keep running
  await Future.delayed(Duration(hours: 24));
  await peerManager.shutdown();
}

SPV Wallet

class SPVWallet implements PeerHandlerI {
  final Set<String> _watchedAddresses;
  int _currentHeight = 0;
  
  SPVWallet(this._watchedAddresses);
  
  @override
  Future<void> handleTransaction(WireMessage msg, PeerI peer) async {
    final tx = msg as MsgTx;
    
    // Check if transaction affects our addresses
    for (final output in tx.outputs) {
      final address = extractAddress(output.script);
      if (_watchedAddresses.contains(address)) {
        print('💰 Received ${output.value} satoshis to $address');
        await notifyUser(address, output.value);
      }
    }
  }
  
  @override
  Future<void> handleHeaders(WireMessage msg, PeerI peer) async {
    final headers = msg as MsgHeaders;
    
    for (final header in headers.headers) {
      _currentHeight++;
      await storeHeader(header, _currentHeight);
    }
    
    print('📊 Synced to block $_currentHeight');
  }
}

Future<void> main() async {
  initializeMessages();
  
  // Your Bitcoin addresses to watch
  final addresses = {
    '1A1zP1eP5QGefi2DMPTfTL5SLmv7DivfNa', // Genesis address
    '1BvBMSEYstWetqTFn5Au4m4GFg7xJaNVN2', // Your address
  };
  
  final wallet = SPVWallet(addresses);
  
  // Get current chain tip from storage
  final storedHeight = await getStoredChainTip() ?? 0;
  
  final peer = Peer(
    address: 'bitcoin-node.example.com',
    port: 8333,
    network: BitcoinNetwork.mainnet,
    handler: wallet,
    config: PeerConfig(
      startHeight: storedHeight,         // Resume from last sync
      userAgent: '/my-spv-wallet:1.0/',
      enablePingPong: true,
    ),
  );
  
  await peer.connect();
  print('📱 SPV wallet connected and syncing...');
  
  // Keep wallet running
  await Future.delayed(Duration.infinity);
}

Mining Pool Connector

class MiningPoolConnector implements PeerHandlerI {
  final String poolAddress;
  
  MiningPoolConnector(this.poolAddress);
  
  @override
  Future<void> handleBlockAnnouncement(InvVect msg, PeerI peer) async {
    // Request full block for mining template
    final getBlock = MsgGetData([msg]);
    await peer.writeMessage(getBlock);
  }
  
  @override
  Future<void> handleBlock(WireMessage msg, PeerI peer) async {
    final block = msg as MsgBlock;
    
    // Forward to mining pool
    await forwardToPool(block);
    
    // Update mining template
    await updateMiningTemplate(block.header);
  }
  
  Future<void> submitBlock(MsgBlock block) async {
    // Submit solved block to network
    final peers = await getPeers();
    for (final peer in peers) {
      await peer.writeMessage(block);
    }
  }
}

Best Practices

1. Always Initialize Messages

void main() {
  initializeMessages(); // First thing in main()
  // ... rest of application
}

2. Use Appropriate Configuration

// For SPV wallets
const spvConfig = PeerConfig(
  startHeight: currentChainTip,
  services: ServiceFlags.none,
  relay: false,
);

// For full nodes
const fullNodeConfig = PeerConfig(
  services: ServiceFlags.nodeNetwork,
  relay: true,
);

3. Handle Connection Failures

try {
  await peer.connect();
} on ConnectionTimeoutException {
  // Try different peer
} on ConnectionException catch (e) {
  // Log error and retry
}

4. Monitor Peer Health

peer.stateStream.listen((state) {
  if (state == PeerState.unhealthy) {
    // Replace unhealthy peer
    await replacePeer(peer);
  }
});

5. Implement Proper Error Handling

class SafeHandler implements PeerHandlerI {
  @override
  Future<void> handleTransaction(WireMessage msg, PeerI peer) async {
    try {
      await processTransaction(msg as MsgTx);
    } catch (e) {
      logger.warning('Transaction processing failed: $e');
      // Don't crash the application
    }
  }
}

For more examples and advanced usage patterns, see the examples directory.