teranode-bridge

module
v0.9.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Sep 10, 2026 License: Apache-2.0

README

teranode-bridge

CI CodeQL Release Go Reference License

Part of the BSV Layered Multicast open-source project — see the main repository for the full architecture, design docs, and BRC specifications.

A landing-tier bridge for pushed delivery into an unmodified Teranode cluster.

teranode-bridge terminates the per-class object delivery lanes on a machine in front of a Teranode cluster, hands each object class to the cluster service that owns it, and submits cluster-produced subtrees and blocks back onto the object plane — while the Teranode cluster itself stays vanilla.

   object plane ══push══▶  teranode-bridge                Teranode LAN
                           ├── tx lane      → propagation      (cluster ingest)
                           ├── subtree lane → cache + announce ─────┐
                           ├── block lane   → cache + announce ─────┤
                           ├── retrieval plane   ◀────── asset pull ┘
                           └── reverse: blockchain Subscribe → BRC-143/144 → up

Why an announce shim

Teranode learns about subtrees and blocks by announcement plus pull. The bridge already has the bytes — they were pushed to it — so it stores them, announces itself as the source, and serves the resulting pull from the same LAN. That buys a fully pushed wide-area path with no fork of Teranode, and the cluster still fetches and validates exactly as it would from a peer.

Architecture › Why an announce shim covers the contract in full, including why writing into Teranode's own stores was rejected.

Planes

Plane Role Scales with
Ingest lane termination, frame parse, cache write, announce produce inbound object bandwidth (stateless; round-robin delivery may spray objects across any number of bridges)
Cache content-addressed, TTL'd store of pushed bytes — a cache, not a store of record delivery rate × validation lag (seconds)
Retrieval serves the cluster's asset-style pulls from the cache pull concurrency (stateless replicas behind a VIP)
Reverse blockchain notifications → BRC-143/144 → upward submit one submitter per class

At small scale all four run in one process (-mode all, the default). Splitting them is deployment topology, not a different design.

Documentation

Requirements

  • Go 1.26 or later
  • A reachable Teranode cluster (propagation HTTP, Kafka, asset HTTP, blockchain gRPC) — or -mode sink, which needs none of them
  • Network reachability from the cluster back to the bridge's retrieval plane
  • For make ci / make ci-*: Docker (Dagger provisions its engine through it)
  • For make proto: buf plus protoc-gen-go and protoc-gen-go-grpc on PATH

Build

make build          # static binary at ./teranode-bridge
make test           # go test -race ./...
make lint           # golangci-lint
make ci             # full containerised pipeline (see below)
make help           # list targets

Or without the Makefile:

go build ./cmd/teranode-bridge

Run

# Delivery only: terminate the lanes, submit transactions, announce and serve
# subtrees and blocks.
./teranode-bridge \
  -retrieval-listen '[2001:db8:3f::1]:9145' \
  -advertise        'http://[2001:db8:3f::1]:9145' \
  -propagation      'http://192.0.2.10:20833' \
  -kafka            '192.0.2.10:19092'

# Both directions: also publish what this cluster produces back onto the
# object plane.
./teranode-bridge \
  -retrieval-listen '[2001:db8:3f::1]:9145' \
  -advertise        'http://[2001:db8:3f::1]:9145' \
  -propagation      'http://192.0.2.10:20833' \
  -kafka            '192.0.2.10:19092' \
  -blockchain       '192.0.2.10:20087' \
  -local-asset      'http://192.0.2.10:20090/api/v1' \
  -edge-ingress     '2001:db8:1::1'

# Sink: receive, parse, verify and count with no cluster targets at all.
./teranode-bridge -mode sink -stats-every 10s

See docs/configuration.md for the full flag reference.

Default ports

Port Direction Carries
8725 in transaction lane (BRC-30 extended format only; standard-format transactions are refused)
9143 in subtree lane (BRC-143 push frames)
9144 in block lane (BRC-144 push frames)
9145 in retrieval plane — the cluster's pulls
9146 in /metrics, /health*, /healthz, /readyz, /loglevel, /debug/pprof
9143 out BRC-143 subtree submits to the object-plane ingress
9144 out BRC-144 block submits to the object-plane ingress

Port numbers track the payload: 8725 is the object plane's transaction class number, and 9143/9144 carry bare BRC-143/BRC-144 objects — the same numbers inbound (delivery) and outbound (upward submits), because a bridge only ever handles bare, unframed objects. The multicast-framed subtree/block lanes (8726/8727) are not bridge ports, and every listener stays clear of a stock cluster's own service ports. See Configuration › Lane numbers.

Observability

Metrics, health, tracing and profiling are shaped to match Teranode's own, so a bridge reads like a cluster service rather than a foreign appliance: series are teranode_bridge_* on the same Namespace/Subsystem grid, histograms use Teranode's bucket sets verbatim, and /health, /health/readiness and /health/liveness answer with the same JSON dependency report and the same ?timeout= override.

Everything is on -metrics-addr (default [::]:9146). Structured log/slog output carries the same numbers in a stats block every -stats-every (60 s).

The alert that matters is teranode_bridge_echo_mismatch_total (log line ECHO MISMATCH): non-zero means the object plane returned different bytes than were published — a data-integrity fault, not a delivery hiccup. Two more are specific to what a landing bridge can get wrong: teranode_bridge_announce_to_first_pull_seconds is the only measurement of whether the announce-shim trick is working at all (nothing on either side of the bridge measures it), and sum(teranode_bridge_submitter_active) must equal exactly 1 per cluster per class — 0 means nothing is published upward, 2 means double publish.

Series that were btb_* are dual-emitted under both names while -metrics-legacy-prefix is true (the default), so existing dashboards survive the cutover.

Container image

The Dockerfile produces a gcr.io/distroless/static:nonroot image with a single static binary at /usr/local/bin/teranode-bridge. No in-image ENV defaults are set — the bridge is configured entirely by flags, so pass them as the container command or Helm args.

docker build --build-arg VERSION=0.7.1 -t teranode-bridge:0.7.1 .
docker run --rm teranode-bridge:0.7.1 -mode sink

Published images are gated behind a manual image-publish workflow run (ghcr.io/lightwebinc/teranode-bridge:<tag>); there is no automatic push.

CI

make ci runs the whole pipeline in containers via Daggertidylintvulnunitbuildimage — so a local run and a GitHub Actions run execute the same steps. Each stage is also available on its own (make ci-unit, make ci-lint, …), and make ci-shell drops you into the builder container.

CI resolves shard-common from a sibling checkout and applies a local replace, picking the branch matching the current one when it exists and main otherwise — so a shared-library change can be validated here before it is tagged. The image build deliberately does not use that replace: a published image always resolves shard-common from the module proxy at its committed version.

Helm chart

A Kubernetes Helm chart is published from a dedicated chart repository:

config.advertise, config.propagation and config.kafka are effectively required (the chart warns and the bridge exits without them) unless config.mode=sink. See the chart README for the three-service shape and the submitter-role scaling rules.

Layout

.
├── cmd/teranode-bridge/     # entrypoint: flags, wiring, per-class handlers
├── internal/
│   ├── lanes/               # per-class TCP listeners over bare object streams
│   ├── submit/              # propagation HTTP submit + upward object submit
│   ├── txpipe/              # batching transaction submit pipeline (POST /txs)
│   ├── announce/            # Kafka {hash, URL} producer + wire codec
│   ├── cache/               # hash-keyed LRU with TTL and byte ceiling
│   ├── registry/            # TTL'd seen-set with direction
│   ├── retrieval/           # the asset-API subset the cluster pulls from
│   ├── tnasset/             # the mirror: pulls objects back out of the cluster
│   ├── reverse/             # blockchain Subscribe → origin filter → publish up
│   ├── encode/              # BRC-143 / BRC-144 push-frame builders
│   ├── tnwire/              # BRC-144 ⇄ Teranode block serialization
│   ├── metrics/             # Prometheus collector over Stats()
│   └── hashid/              # internal ⇄ display byte order, in one place
├── proto/blockchain_api/    # minimal wire-compatible Subscribe subset
├── ci/                      # Dagger CI driver
├── hack/tnbench/            # throughput bench rig (mock propagation + feeder)
├── docs/                    # architecture + configuration
├── Dockerfile
├── Makefile
└── .github/workflows/{ci,codeql,image-publish,release}.yml

Dependencies

The bridge deliberately does not link Teranode's own module. The two contracts it needs — a three-field announcement message and a one-method notification stream — are reproduced from their wire definitions instead, so a small bridge does not pull in a full node's dependency tree.

License

Apache 2.0. See LICENSE and NOTICE.

Directories

Path Synopsis
Package announce publishes subtree and block announcements into the Teranode cluster's Kafka, pointing them at this bridge's retrieval plane.
Package announce publishes subtree and block announcements into the Teranode cluster's Kafka, pointing them at this bridge's retrieval plane.
Package cache holds pushed object bytes between delivery and the moment the Teranode cluster pulls and validates them.
Package cache holds pushed object bytes between delivery and the moment the Teranode cluster pulls and validates them.
cmd
teranode-bridge command
Command teranode-bridge is the landing-tier bridge for pushed delivery into a Teranode cluster.
Command teranode-bridge is the landing-tier bridge for pushed delivery into a Teranode cluster.
Package encode builds the push frames the bridge sends back up the tunnel.
Package encode builds the push frames the bridge sends back up the tunnel.
hack
tnbench command
Command tnbench drives the teranode-bridge tx lane at saturation.
Command tnbench drives the teranode-bridge tx lane at saturation.
Package hashid handles the one byte-order trap that runs through this whole bridge.
Package hashid handles the one byte-order trap that runs through this whole bridge.
internal
health
Package health implements the bridge's dependency-aggregating health surface, shaped to match Teranode's own (teranode/util/health + daemon/daemon.go).
Package health implements the bridge's dependency-aggregating health surface, shaped to match Teranode's own (teranode/util/health + daemon/daemon.go).
metrics
Package metrics exposes the bridge's counters on a Prometheus endpoint, alongside the health, profiling and runtime-log-level routes every service in the stack serves.
Package metrics exposes the bridge's counters on a Prometheus endpoint, alongside the health, profiling and runtime-log-level routes every service in the stack serves.
obs
Package obs owns the bridge's latency, size and freshness instrumentation — everything the scrape-time collector in internal/metrics cannot express.
Package obs owns the bridge's latency, size and freshness instrumentation — everything the scrape-time collector in internal/metrics cannot express.
submit
Package submit hands delivered transactions to the Teranode cluster.
Package submit hands delivered transactions to the Teranode cluster.
tnasset
Package tnasset reads objects back out of the local Teranode cluster so the bridge can publish what the cluster produced.
Package tnasset reads objects back out of the local Teranode cluster so the bridge can publish what the cluster produced.
tracing
Package tracing initialises OpenTelemetry tracing for the bridge, matching Teranode's tracing setup (teranode/util/tracing) so a trace crosses the bridge instead of ending at it.
Package tracing initialises OpenTelemetry tracing for the bridge, matching Teranode's tracing setup (teranode/util/tracing) so a trace crosses the bridge instead of ending at it.
txpipe
Package txpipe decouples the tx lane's read loop from cluster submission and turns per-transaction round trips into batched ones.
Package txpipe decouples the tx lane's read loop from cluster submission and turns per-transaction round trips into batched ones.
Package lanes terminates the per-class object delivery lanes.
Package lanes terminates the per-class object delivery lanes.
proto
Package registry remembers which object hashes crossed the bridge, and in which direction.
Package registry remembers which object hashes crossed the bridge, and in which direction.
Package retrieval serves the pulls a Teranode cluster makes after the bridge announces an object — the half of the announce shim that makes a pushed object look, to the cluster, exactly like one fetched from a peer's Asset service.
Package retrieval serves the pulls a Teranode cluster makes after the bridge announces an object — the half of the announce shim that makes a pushed object look, to the cluster, exactly like one fetched from a peer's Asset service.
Package reverse carries this cluster's own subtrees and blocks back into the fabric.
Package reverse carries this cluster's own subtrees and blocks back into the fabric.
Package tnwire converts between Teranode's block serialization and the BRC-144 push frame.
Package tnwire converts between Teranode's block serialization and the BRC-144 push frame.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL