Multi-Blockchain Coinstacks

Overview

The **Multi-Blockchain Coinstacks** module provides modular implementations to support a wide variety of blockchain networks within the ShapeShift Unchained platform. Each blockchain is encapsulated as a distinct "coinstack" comprising several specialized services—primarily the **daemon node(s)**, **indexer service**, and **API servers**—deployed and managed in a Kubernetes environment. This modular design enables scalable, maintainable, and extensible support for diverse blockchains such as Bitcoin, Ethereum, Solana, Thorchain, and their layer 2 or variant networks.

This module addresses the complexity of running heterogeneous blockchain infrastructures by standardizing deployment, configuration, and service orchestration patterns per blockchain. It enables seamless interaction with distinct blockchain protocols via uniform infrastructure and API abstractions.


Core Concepts and Purpose


How the Multi-Blockchain Coinstacks Module Works

1. Modular Pulumi Deployment Scripts

Each coinstack has a dedicated Pulumi deployment script located under [node/coinstacks/{coin}/pulumi/index.ts](/projects/291/68798) or for Go-based stacks under `go/coinstacks/{coin}/pulumi/index.ts`. These scripts:

For example, the Litecoin coinstack defines two core services, daemon and indexer, with specific ports and probes:

const coinServiceArgs = config.statefulService?.services?.map((service): CoinServiceArgs => {
  switch (service.name) {
    case 'daemon':
      return {
        ...service,
        ports: { 'daemon-rpc': { port: 8332 } },
        readinessProbe: { periodSeconds: 30, failureThreshold: 10 },
      }
    case 'indexer':
      return {
        ...service,
        ...defaultBlockbookServiceArgs,
        configMapData: { 'indexer-config.json': readFileSync('../indexer/config.json').toString() },
      }
    default:
      throw new Error(`no support for coin service: ${service.name}`)
  }
})

This pattern is repeated with variations per blockchain, reflecting differences in port assignments, environment variables, volume mounts, and probe configurations.

2. Service Types and Characteristics

3. Deployment Workflow


Interaction with Other System Components


Important Concepts and Design Patterns


Code Structure and Examples

import { readFileSync } from 'fs'
import { deployCoinstack } from '../../../../pulumi/src/coinstack'
import { Outputs, CoinServiceArgs, getConfig } from '../../../../pulumi/src'
import { defaultBlockbookServiceArgs } from '../../../packages/blockbook/src/constants'

export = async (): Promise<Outputs> => {
  const appName = 'unchained'
  const coinstack = 'litecoin'
  const sampleEnv = readFileSync('../sample.env')
  const { kubeconfig, config, namespace } = await getConfig()

  const coinServiceArgs = config.statefulService?.services?.map((service): CoinServiceArgs => {
    switch (service.name) {
      case 'daemon':
        return {
          ...service,
          ports: { 'daemon-rpc': { port: 8332 } },
          readinessProbe: { periodSeconds: 30, failureThreshold: 10 },
        }
      case 'indexer':
        return {
          ...service,
          ...defaultBlockbookServiceArgs,
          configMapData: { 'indexer-config.json': readFileSync('../indexer/config.json').toString() },
        }
      default:
        throw new Error(`no support for coin service: ${service.name}`)
    }
  })

  return deployCoinstack({
    appName,
    coinServiceArgs,
    coinstack,
    coinstackType: 'node',
    config,
    kubeconfig,
    namespace,
    sampleEnv,
  })
}
case 'daemon':
  return {
    ...service,
    ports: {
      'daemon-rpc': { port: 8545 },
      'daemon-ws': { port: 8546, pathPrefix: '/websocket', stripPathPrefix: true },
      'daemon-auth': { port: 8551, ingressRoute: false },
      'daemon-beacon': { port: 3500, ingressRoute: false },
    },
    configMapData: {
      'jwt.hex': readFileSync('../daemon/jwt.hex').toString(),
      'evm.sh': readFileSync('../../../scripts/evm.sh').toString(),
    },
    volumeMounts: [
      { name: 'config-map', mountPath: '/jwt.hex', subPath: 'jwt.hex' },
      { name: 'config-map', mountPath: '/evm.sh', subPath: 'evm.sh' },
    ],
    env: {
      NETWORK: config.network,
    },
    startupProbe: { periodSeconds: 30, failureThreshold: 60, timeoutSeconds: 10 },
    livenessProbe: { periodSeconds: 30, failureThreshold: 5, timeoutSeconds: 10 },
    readinessProbe: { periodSeconds: 30, failureThreshold: 10, timeoutSeconds: 10 },
  }

Mermaid Diagram: Flowchart of Multi-Blockchain Coinstack Deployment Workflow

flowchart TD
  A[Start Deployment] --> B[Load Environment & Config Files]
  B --> C[Parse Stateful Services from Config]
  C --> D[Map Each Service to Deployment Args]
  D --> E{Service Type?}
  E -->|daemon| F[Configure Daemon Node: Ports, Env, Probes, Volumes]
  E -->|indexer| G[Configure Indexer: ConfigMap, Ports, Probes]
  E -->|other| H[Configure Additional Services (e.g., Beacon, Heimdall)]
  F --> I[Aggregate All Service Args]
  G --> I
  H --> I
  I --> J[Call deployCoinstack() with Args]
  J --> K[Provision Kubernetes Resources (StatefulSets, Services, ConfigMaps)]
  K --> L[Kubernetes Starts Pods and Runs Probes]
  L --> M[Daemon Nodes Synchronize Blockchain]
  L --> N[Indexers Build Blockchain Data Cache]
  M & N --> O[Services Ready for API Layer Interaction]
  O --> P[End Deployment]

This documentation explains the modular architecture of the Multi-Blockchain Coinstacks module, its deployment workflow using Pulumi, and how it organizes blockchain node daemons, indexers, and supporting services into manageable Kubernetes deployments for multi-chain support.