Fiber Test Lab — local, reproducible failure scenarios for Fiber payments

Hi everyone,

I built Fiber Test Lab while trying to learn Fiber, and I’d really value your feedback

It started from a problem I kept hitting: I wanted to write tests for what happens when a payment fails — not enough outbound liquidity, no route, an expired invoice, or a peer that goes offline halfway — and I couldn’t.

Those are exactly the states that you cannot easily force on a shared testnet when you want them.

So I ended up building a tool that makes these scenarios reproducible locally.

fiber-lab up two-hop-route starts real FNN nodes plus a CKB devnet in Docker, opens the channels, runs the payment, checks the outcome, and records every RPC call along the way.

The same scenario, the same pinned versions, and the same result.

That was the whole point.

Install: npm i -D fiber-test-lab

Repository: fiber-test-lab

The project was built during the “Gone in 60ms” hackathon, and I’ve continued working on it since.

What it does today

A scenario is simply a YAML file that describes the topology and what you expect to happen.

Example:

name: two-hop-bottleneck

nodes: [alice, bob, charlie]

channels:
  - { from: alice, to: bob, capacity: 500 }
  - { from: bob, to: charlie, capacity: 200 }

seed:
  - { action: send_payment, from: alice, to: charlie, amount: 180 }

expect:
  status: failed
  reason: no_route_found

The project currently provides several main features:

  • CLI: fiber-lab up | seed | reset | logs | list | ui — every command supports --json, and the exit codes separate a broken configuration (1) from a Docker/RPC failure (2) from an assertion that didn’t match (3), so CI can tell them apart.

  • Nine scenarios so far: direct channel, two-hop route, insufficient capacity, a bottlenecked middle hop, channel drain, round trip, expired invoice, peer offline, and one paying RUSD over a UDT channel.

  • Vitest test-kit: setupScenario(), expectPaymentSucceeds(), and expectPaymentFails(reason) — so you can assert on this from your own test suite.

  • Isolated runs: every run is isolated by a run-id. The network and containers are all named after it, so two runs in two terminals don’t collide.

  • Complete RPC run-log: every RPC goes into a run-log, including successful calls. After a reset, the containers are gone, so that file is the only record of what happened.

  • Run viewer: fiber-lab ui draws the channels, shows where the balance sits on each one, and displays every RPC call with its timing. It fills in live while a run builds. Screenshots are available in the README.

Things I ran into that might be useful to you

These are the things I’d have liked to know earlier, all against FNN 0.8.0.

Every failure comes back as -32000

So I ended up matching on substrings of the message, which I’m not happy about but couldn’t see a way around.

peer_offline and insufficient_outbound produce the same message

When I killed a peer mid-payment, the error was "max outbound liquidity 0 … insufficient" — word for word what a real liquidity shortage gives you.

I now check list_peers first and only fall back to reading the message.

This one cost me an evening, and I mention it because any tool that reads the message alone will tell you “insufficient liquidity” when your peer is simply gone.

The default gossip interval made my multi-hop tests flaky

At 60 seconds, the route often wasn’t known to the sender yet when the test fired.

I set it to 2000 milliseconds on the devnet nodes, and it became reliable.

Almost all the time is spent waiting for ChannelReady

One measured two-hop run took:

  • 8.8s to build and start the containers

  • 5.4s until they were healthy

  • 40.2s and 39.1s to open the two channels

  • 2.0s for the actual payment

96 seconds in total.

I had assumed Docker was the slow part.

It isn’t.

What’s not done, and I’d rather say so

  • insufficient_inbound, reserve_violation, channel_not_ready, and amount_out_of_range are in my error list, but I haven’t managed to force any of them against a real node yet, so I don’t fully trust them.

  • Devnet isn’t mainnet. Block time, fees, and some limits differ, so this doesn’t replace testing on a real testnet.

  • peer-offline is simulated with docker kill, which isn’t a real network partition.

  • Comparing two runs side by side, to show determinism visually rather than just claiming it, is still on my list.

I’m still learning Fiber, so corrections are genuinely welcome.

If you spot anything I’ve misunderstood, or have a failure scenario that would be useful to reproduce, I’d be happy to hear about it.

Thanks for taking a look

1 Like