Skip to content
Give LND's watchtowers a shot with your own simulated channel breach
Branch: master
Clone or download
Fetching latest commit…
Cannot retrieve the latest commit at this time.
Type Name Latest commit message Commit time
Failed to load latest commit information.

Watchtower Example Demo

Give LND's watchtowers a shot with your own simulated channel breach!


  • lnd
  • btcd
  • 5 different terminal windows open


To run watchtowers at the time of writing, we'll need to be on the latest release v0.7.0-beta-rc1 as it's the only release that includes the code from this pull request. With that version of LND's code, we'll recompile and run through the exercise.

These commands assume you've already installed LND before. Make sure you've turned off any running nodes before doing this.

cd $GOPATH/src/
git pull
git checkout v0.7.0-beta-rc1
make clean && make && make install


1. Initial Setup

We need to setup our three nodes, the attacker, the victim, and the watchtower. Config files have already been created for them, you just need to get them started.

# Window 1

# Window 2

# Window 3

# Window 4

# Window 5
cmd/ create # Enter 'password', 'password', 'n', and hit enter for the last one
cmd/ create # Enter 'password', 'password', 'n', and hit enter for the last one
cmd/ create # Enter 'password', 'password', 'n', and hit enter for the last one

2. Configure the Watchtower

In order to configure our victim to use our watchtower, we need to know the watchtower node's pubkey. Let's grab it:

# Window 5
cmd/ getinfo # Copy the "identity_pubkey" from this

Then open up nodes/victim/lnd.conf, and uncomment the bottom and add the watchtower's pubkey to the indicated spot. It should look something like this:


You'll then need to restart the victim node with a CTRL+C and running the following:

# Window 2

# Window 5
cmd/ unlock # Enter 'password' from the create command

You'll know it worked if it starts successfully and you see the following logs in Window 2:

[INF] WTCL: Acquired new session with id=<watchtower-pubkey>

3. Getting Funds

In order to try out an attack, we need to fund the attacker with some simnet bitcoin. Due to the way btcd works, we'll need to restart it to get some money going to the attacker. Run the following in order, but in their respective windows:

# Window 5
cmd/ newaddress p2wkh # Copy the address

# Window 1, CTRL+C then run
cmd/ --miningaddr=[address] # Use the address from Window 5

# Window 5
cmd/ generate 500 # This may take a few seconds

You can confirm it worked by checking the attacker's node wallet balance:

# Window 5
cmd/ walletbalance

4. Running the Attack

Now that everything's in place, we'll begin our attack. The first thing we need to do is connect the two nodes as peers, and open a channel from the attacker to the victim.

# Window 5
cmd/ getinfo # Copy the "identity_pubkey" from this
cmd/ connect <pubkey>@localhost:30011 # Use the pubkey from the previous command

cmd/ openchannel <pubkey> 2000000
cmd/ generate 10 # To confirm blocks for channel open
cmd/ listchannels # To view the open channel

You should see the channel in the results of listchannels after this. Now we need to make a quick backup of the attacker's current state, where all of the channel capacity is on their side. Run the following:

# Window 5
cp -r nodes/attacker nodes/attacker-evil

We'll then make a large payment to the victim, that we'll try to steal back:

# Window 5
cmd/ addinvoice 1000000 # Copy "pay_req" from this
cmd/ sendpayment --pay_req [pay_req] # Use the pay_req from before
cmd/ listchannels # Confirm the "remote_balance" is now 1000000

With that payment made, you'll want to turn off your attacker node in window 3 with a ctrl+c, and copy the old directory back in:

# Window 3
mv nodes/attacker nodes/attacker-honest
mv nodes/attacker-evil nodes/attacker

Now let's assume some time has passed, and our attacker has been waiting for the victim's node to go offline. Go ahead and ctrl+c the node to stop it in window 2, and then let's start up the attacker in window 3 again:

# Window 3

# Window 5
cmd/ unlock # Enter 'password' from create
cmd/ listchannels

You should see in the output of listchannels that the entire balance is back in local_balance, ready for the attack. Now all we need to do is broadcast the old state by doing a force close. Grab the "channel_point" from listchannels for the arguments to the next command, split where the colon is:

# Window 5
cmd/ closechannel --force <channel-point-txid> <channel-point-index>

For instance, "channel_point": "4c53040:0" would be closechannel --force 4c53040 0.

Now if we mine just one more block so that the force close transaction is mined, the watchtower will spring into action and rectify the situation. Go ahead and run:

# Window 5
cmd/ generate 1

You'll know it all worked if you see the following in the watchtower's logs in Window 4:

[INF] WTWR: Found 1 breach in (height=1366, hash=<hash>)
[INF] WTWR: Dispatching punisher for client <victim-pubkey>, breach-txid=<breach-txid>
[INF] WTWR: Publishing justice transaction for client=<victim-pubkey> with txid=<justice-txid>
[INF] LNWL: Inserting unconfirmed transaction <justice-txid>
[INF] WTWR: Punishment for client <victim-pubkey> with breach-txid=<justice-txid> dispatched

Mine a few more blocks, start the victim's node back up, and you'll find the reward (minus fees) sitting in the victim's wallet:

# Window 5
cmd/ generate 10

# Window 2

# Window 5
cmd/ unlock # Enter 'password' from create
cmd/ walletbalance

If you want to run through this all again, or you made a mistake along the way, just shut down your lightning nodes and run cmd/ to revert back to the original state.


lnd / lncli / btcd / btcctl: command not found

Make sure you installed all of the requirements, and they're accessible in your $PATH.

unable to parse private watchtower address

Make sure you copied the victim's pubkey into nodes/watchtower/lnd.conf correctly.

Peer <pubkey> is not online

Just rerun the connect command to have the node check if the other is online. When you restart one of your nodes, it can take a little time for them to see each other as online again.

Block number out of range

Just generate a bunch of new blocks on btcd using cmd/ generate 100. It should fix itself.

cannot force close channel with state: ChanStatusDefault|ChanStatusBorked|ChanStatusLocalDataLoss

Your attacker node somehow got informed that their state was out of date, so it won't let you try to do a malicious force close. This is probably because you left your victim node online when swapping in the malicious state.

You can’t perform that action at this time.