Skip to content

Circuit breaker plugin #688

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
180 changes: 180 additions & 0 deletions docs/circuit-breaker.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,180 @@
# Circuit Breaker for Warnet

## Overview

Circuit Breaker is a Lightning Network firewall that protects LND nodes from being flooded with HTLCs. When integrated with Warnet, Circuit Breaker runs as a sidecar container alongside your LND nodes.

Circuit Breaker is to Lightning what firewalls are to the internet - it allows nodes to protect themselves by setting maximum limits on in-flight HTLCs on a per-peer basis and applying rate limits to forwarded HTLCs.

* **Repository**: https://github.com/lightningequipment/circuitbreaker
* **Full Documentation**: See the main repository for detailed information about Circuit Breaker's features, operating modes, and configuration options

## Usage in Warnet

### Basic Configuration

To enable Circuit Breaker for an LND node in your `network.yaml` file, add the `circuitbreaker` section under the `lnd` configuration. When enabled, Circuit Breaker will automatically start as a sidecar container and connect to your LND node:

```yaml
nodes:
- name: tank-0003
addnode:
- tank-0000
ln:
lnd: true
lnd:
config: |
bitcoin.timelockdelta=33
channels:
- id:
block: 300
index: 1
target: tank-0004-ln
capacity: 100000
push_amt: 50000
circuitbreaker:
enabled: true # This enables Circuit Breaker for this node
httpPort: 9235 # Can override default port per-node (optional)
```

### Configuration Options

- `enabled`: Set to `true` to enable Circuit Breaker for the node
- `httpPort`: Override the default HTTP port (9235) for the web UI (optional)

### Complete Example

Here's a complete `network.yaml` example with Circuit Breaker enabled on one node:

```yaml
nodes:
- name: tank-0000
addnode:
- tank-0001
ln:
lnd: true

- name: tank-0001
addnode:
- tank-0002
ln:
lnd: true

- name: tank-0002
addnode:
- tank-0000
ln:
lnd: true

- name: tank-0003
addnode:
- tank-0000
ln:
lnd: true
lnd:
config: |
bitcoin.timelockdelta=33
channels:
- id:
block: 300
index: 1
target: tank-0004-ln
capacity: 100000
push_amt: 50000
circuitbreaker:
enabled: true
httpPort: 9235

- name: tank-0004
addnode:
- tank-0000
ln:
lnd: true
lnd:
channels:
- id:
block: 300
index: 2
target: tank-0005-ln
capacity: 50000
push_amt: 25000

- name: tank-0005
addnode:
- tank-0000
ln:
lnd: true
```

## Accessing Circuit Breaker

Circuit Breaker provides both a web-based interface and REST API endpoints for configuration and monitoring.

### Web UI Access

To access the web interface:

1. **Port Forward to the Circuit Breaker service**:
```bash
kubectl port-forward pod/<node-name>-ln <local-port>:<httpPort>
```

For example, if your node is named `tank-0003` and using the default port:
```bash
kubectl port-forward pod/tank-0003-ln 9235:9235
```

2. **Open your browser** and navigate to:
```
http://localhost:9235
```

3. **Configure your firewall rules** through the web interface:
- Set per-peer HTLC limits
- Configure rate limiting parameters
- Choose operating modes
- Monitor HTLC statistics

### API Access

You can also interact with Circuit Breaker programmatically using kubectl commands to access the REST API:

**Get node information:**
```bash
kubectl exec <node-name>-ln -c circuitbreaker -- wget -qO - 127.0.0.1:<httpPort>/api/info
```

**Get current limits:**
```bash
kubectl exec <node-name>-ln -c circuitbreaker -- wget -qO - 127.0.0.1:<httpPort>/api/limits
```

For example, with node `tank-0003-ln`:
```bash
kubectl exec tank-0003-ln -c circuitbreaker -- wget -qO - 127.0.0.1:9235/api/info
kubectl exec tank-0003-ln -c circuitbreaker -- wget -qO - 127.0.0.1:9235/api/limits
```

## Architecture

Circuit Breaker runs as a sidecar container alongside your LND node in Warnet:
- **LND Container**: Runs your Lightning node
- **Circuit Breaker Container**: Connects to LND via RPC and provides firewall functionality
- **Shared Volume**: Allows Circuit Breaker to access LND's TLS certificates and macaroons
- **Web Interface**: Accessible via port forwarding for configuration

## Requirements

- **LND Version**: 0.15.4-beta or above
- **Warnet**: Compatible with standard Warnet LND deployments

## Support

For issues and questions:
- Circuit Breaker Repository: https://github.com/lightningequipment/circuitbreaker
- Warnet Documentation: Refer to the Warnet installation guides [install.md](install.md)
- LND Documentation: https://docs.lightning.engineering/

---

*Circuit Breaker integration for Warnet enables sophisticated HTLC management and protection for Lightning Network nodes in test environments.*
21 changes: 21 additions & 0 deletions resources/charts/bitcoincore/charts/lnd/templates/pod.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -58,16 +58,37 @@ spec:
- mountPath: /root/.lnd/tls.cert
name: config
subPath: tls.cert
- name: shared-volume
mountPath: /root/.lnd/
{{- with .Values.extraContainers }}
{{- toYaml . | nindent 4 }}
{{- end }}
{{- if .Values.circuitbreaker.enabled }}
- name: circuitbreaker
image: {{ .Values.circuitbreaker.image | quote }}
imagePullPolicy: IfNotPresent
args:
- "--network={{ .Values.global.chain }}"
- "--rpcserver=localhost:{{ .Values.RPCPort }}"
- "--tlscertpath=/tls.cert"
- "--macaroonpath=/root/.lnd/data/chain/bitcoin/{{ .Values.global.chain }}/admin.macaroon"
- "--httplisten=0.0.0.0:{{ .Values.circuitbreaker.httpPort }}"
volumeMounts:
- name: shared-volume
mountPath: /root/.lnd/
- name: config
mountPath: /tls.cert
subPath: tls.cert
{{- end }}
Comment on lines +66 to +82
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Was there an issue including circuitbreaker as extraContainers container?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, I was not able to access the value of the port and network variables as the extraContainers is being parsed by a YAML parser before Helm gets to process the templates.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hm the whole point of those extraContainers was for the cb plugin ... :-/

Copy link
Contributor Author

@Camillarhi Camillarhi May 1, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, I understand, but I could not access a dynamic network or port while using it. The current approach also gives the option of enabling circuitbreaker if needed.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@pinheadmz I assume it is acceptable to define network(chain) in network.yaml or node-defaults.yaml based on user desired scenario. if so then extraContainers should be possible...

volumes:
{{- with .Values.volumes }}
{{- toYaml . | nindent 4 }}
{{- end }}
- configMap:
name: {{ include "lnd.fullname" . }}
name: config
- name: shared-volume
emptyDir: {}
{{- with .Values.nodeSelector }}
nodeSelector:
{{- toYaml . | nindent 4 }}
Expand Down
5 changes: 5 additions & 0 deletions resources/charts/bitcoincore/charts/lnd/values.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -132,3 +132,8 @@ config: ""
defaultConfig: ""

channels: []

circuitbreaker:
enabled: false # Default to disabled
image: carlakirkcohen/circuitbreaker:attackathon-test
httpPort: 9235
4 changes: 4 additions & 0 deletions test/data/ln/network.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,10 @@ nodes:
target: tank-0004-ln
capacity: 100000
push_amt: 50000
circuitbreaker:
enabled: true
httpPort: 9235

- name: tank-0004
addnode:
- tank-0000
Expand Down
4 changes: 4 additions & 0 deletions test/data/network_with_plugins/network.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,9 @@ nodes:
target: tank-0004-ln
capacity: 100000
push_amt: 50000
circuitbreaker:
enabled: true # This enables circuitbreaker for this node
httpPort: 9235 # Can override defaults per-node

- name: tank-0004
addnode:
Expand Down Expand Up @@ -85,3 +88,4 @@ plugins: # Each plugin section has a number of hooks available (preDeploy, post
entrypoint: "../plugins/hello"
helloTo: "postNetwork!"
podName: "hello-post-network"

25 changes: 25 additions & 0 deletions test/ln_basic_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import json
import os
import subprocess
from pathlib import Path
from time import sleep

Expand All @@ -24,11 +25,18 @@ def __init__(self):
"tank-0005-ln",
]

self.cb_port = 9235
self.cb_node = "tank-0003-ln"
self.port_forward = None

def run_test(self):
try:
# Wait for all nodes to wake up. ln_init will start automatically
self.setup_network()

# Test circuit breaker API
self.test_circuit_breaker_api()

# Send a payment across channels opened automatically by ln_init
self.pay_invoice(sender="tank-0005-ln", recipient="tank-0003-ln")

Expand Down Expand Up @@ -120,6 +128,23 @@ def scenario_open_channels(self):
self.log.info(f"Running scenario from: {scenario_file}")
self.warnet(f"run {scenario_file} --source_dir={self.scen_dir} --debug")

def test_circuit_breaker_api(self):
self.log.info("Testing Circuit Breaker API with direct kubectl commands")

# Test /info endpoint
info_cmd = f"kubectl exec {self.cb_node} -c circuitbreaker -- wget -qO - 127.0.0.1:{self.cb_port}/api/info"
info = json.loads(subprocess.check_output(info_cmd, shell=True).decode())
assert "nodeKey" in info, "Circuit breaker info missing nodeKey"
self.log.info(f"Got node info: {info}")

# Test /limits endpoint
limits_cmd = f"kubectl exec {self.cb_node} -c circuitbreaker -- wget -qO - 127.0.0.1:{self.cb_port}/api/limits"
limits = json.loads(subprocess.check_output(limits_cmd, shell=True).decode())
assert "limits" in limits, "Circuit breaker limits missing"
self.log.info(f"Got limits: {limits}")

self.log.info("✅ Circuit Breaker API tests passed")


if __name__ == "__main__":
test = LNBasicTest()
Expand Down
Loading