Another penalty transaction on regtest with Core Lightning

LNROOM #9December 27, 2022

In this episode 9 we see how a node l1 can try cheat a node l2 by doing a snapshot of its database when the balances of the channel benefit him then spending some sats and finally closing unilaterally the channel using the snapshot database where the last commitment transaction has been already revocated.

Transcript with corrections and improvements

Hi guys, welcome to the LN Room, I'm Tony Aldon and today in this episode 9 we are going to talk about penalty transactions and Core Lightning.

The question is: When does a penalty transaction happens?

It happens for instance when a node l1 with a channel opened with a node l2 try to cheat the node l2 by sending a revocated commitment transaction to the blockchain that benefits him and when the node l2 see the fraud on chain early enough to reply by sending a penalty transaction that spends to him the output that the node l1 can't spend yet (because of a time lock).

In this episode we see how a node l1 can try cheat a node l2 by doing a snapshot of its database when the balances of the channel benefit him then spendind some sats and finally closing unilaterally the channel using the snapshot database where the last commitment transaction has been already revocated.

In the episode a penalty transaction managed by Core Lightning, we also looked at a node l1 trying to cheat another node by sending a revocated commitment transaction but with a different approach. You might find it interesting too.

Let's go.

Set up a Lightning Network on regtest with 2 nodes and a channel

As we did in all the previous videos, we use the script contrib/startup_regtest.sh to set up a Lightning Network running on regtest. This network has 2 nodes l1 and l2. The node l1 starts with 1btc and 1000000sat locked in a channel opened with the node l2:

◉ tony@tony:~/lnroom/lightning:[git»(HEAD detached at v22.11.1)]
$ source contrib/startup_regtest.sh
lightning-cli is /home/tony/lnroom/lightning/cli/lightning-cli
lightningd is /home/tony/lnroom/lightning/lightningd/lightningd
Useful commands:
  start_ln 3: start three nodes, l1, l2, l3
  connect 1 2: connect l1 and l2
  fund_nodes: connect all nodes with channels, in a row
  stop_ln: shutdown
  destroy_ln: remove ln directories
◉ tony@tony:~/lnroom/lightning:[git»(HEAD detached at v22.11.1)]
$ start_ln 2
Bitcoin Core starting
awaiting bitcoind...
Making "default" bitcoind wallet.
[1] 28341
[2] 28376
WARNING: eatmydata not found: instal it for faster testing
Commands:
        l1-cli, l1-log,
        l2-cli, l2-log,
        bt-cli, stop_ln, fund_nodes
◉ tony@tony:~/lnroom/lightning:[git»(HEAD detached at v22.11.1)]
$ alias l1-cli
alias l1-cli='/home/tony/lnroom/lightning/cli/lightning-cli --lightning-dir=/tmp/l1-regtest'
◉ tony@tony:~/lnroom/lightning:[git»(HEAD detached at v22.11.1)]
$ fund_nodes
Mining into address bcrt1ql0337077n934he8l0hwn2js52294yp3fzjmwlc... done.
bitcoind balance: 50.00000000
Waiting for lightning node funds... found.
Funding channel from node 1 to node 2. Waiting for confirmation... done.

Database snapshot of the node l1

Before we do a snapshot of l1's database, we take a look of the funds in the lightning wallets and the balances of the channel 103x1x0 opened between the node l1 and l2 using the sub-command listfunds of lightning-cli. We see that the node l1 has 98999846sat (1btc minus 1000000sat plus fees to fund the channel) in its wallet and 1000000sat in his side of the channel 103x1x0 with a total amount of 1000000sat:

◉ tony@tony:~/lnroom/lightning:[git»(HEAD detached at v22.11.1)]
$ l1-cli listfunds | jq .outputs
[
  {
    "txid": "3ee9bc6bb4f98be53b64d79a77b88dd3d66eef445994a08c7f1aa2e017ae2274",
    "output": 1,
    "amount_msat": 98999846000,
    "scriptpubkey": "0014b3aa21fdb4a48726eaa4e1b0f886df042cce226f",
    "address": "bcrt1qkw4zrld55jrjd64yuxc03pklqskvugn079prvj",
    "status": "confirmed",
    "blockheight": 103,
    "reserved": false
  }
]
◉ tony@tony:~/lnroom/lightning:[git»(HEAD detached at v22.11.1)]
$ l1-cli listfunds | jq .channels
[
  {
    "peer_id": "0311c4890bc6c2142bc21da4c773c40aafe52744774d359f034b137b01d660ce09",
    "connected": true,
    "state": "CHANNELD_NORMAL",
    "short_channel_id": "103x1x0",
    "our_amount_msat": 1000000000,
    "amount_msat": 1000000000,
    "funding_txid": "3ee9bc6bb4f98be53b64d79a77b88dd3d66eef445994a08c7f1aa2e017ae2274",
    "funding_output": 0
  }
]

The node l2 has no sats in its wallet and no sats in his side of the channel 103x1x0 opened with the node l1:

◉ tony@tony:~/lnroom/lightning:[git»(HEAD detached at v22.11.1)]
$ l2-cli listfunds | jq .outputs
[]
◉ tony@tony:~/lnroom/lightning:[git»(HEAD detached at v22.11.1)]
$ l2-cli listfunds | jq .channels
[
  {
    "peer_id": "024b8ece67648d560ad5357153e577eb71ff157541731047bdb9ab91f708cba62e",
    "connected": true,
    "state": "CHANNELD_NORMAL",
    "short_channel_id": "103x1x0",
    "our_amount_msat": 0,
    "amount_msat": 1000000000,
    "funding_txid": "3ee9bc6bb4f98be53b64d79a77b88dd3d66eef445994a08c7f1aa2e017ae2274",
    "funding_output": 0
  }
]

Now we do a snapshot of l1's database that we call lightningd.sqlite3.bak. This is a good move for the node l1 that want to try to cheat the node l2 in a moment because the state of the channel right now if it goes online by signing the last commitment transaction will send it back to him the total amount of the channel (by spending his output after a time lock in case of an unilateral close).

Note that the base directory of the node l1 is /tmp/l1-regtest/:

◉ tony@tony:~/lnroom/lightning:[git»(HEAD detached at v22.11.1)]
$ cd /tmp/l1-regtest/regtest/
◉ tony@tony:/tmp/l1-regtest/regtest:
$ ls -1
accounts.sqlite3
emergency.recover
gossip_store
hsm_secret
lightningd.sqlite3
lightning-rpc
◉ tony@tony:/tmp/l1-regtest/regtest:
$ cp lightningd.sqlite3 lightningd.sqlite3.bak

We keep the database snapshot for later.

The node l1 pays an invoice to the node l2

In this section, the node l2 creates an invoice for an amount of 20000sat and the node l1 pays that invoice as we can see below:

◉ tony@tony:/tmp/l1-regtest/regtest:
$ l2-cli invoice 20000sat inv "pizza" | jq .bolt11
"lnbcrt200u1p36kfdesp5qlsckdq0se4mw4shzxumx50ndwpu44lu7z007yntuhjjq5y0vn5qpp5pqwtdy4z47zs9pd3e062xvvpv0dn0m2dwj0pp38s3nfqzjrpgkdqdqgwp5h57npxqyjw5qcqp29qx3qysgqza5f5u5efj5a7qlswuxn2qh955696pxnfg43cj07xg529gnhyem5ymq58tujdz6st5g5ap7yrzgczfvnft0unhke7fhhdd0euyqfcpqppu7wua"
◉ tony@tony:/tmp/l1-regtest/regtest:
$ l1-cli pay lnbcrt200u1p36kfdesp5qlsckdq0se4mw4shzxumx50ndwpu44lu7z007yntuhjjq5y0vn5qpp5pqwtdy4z47zs9pd3e062xvvpv0dn0m2dwj0pp38s3nfqzjrpgkdqdqgwp5h57npxqyjw5qcqp29qx3qysgqza5f5u5efj5a7qlswuxn2qh955696pxnfg43cj07xg529gnhyem5ymq58tujdz6st5g5ap7yrzgczfvnft0unhke7fhhdd0euyqfcpqppu7wua
{
   "destination": "0311c4890bc6c2142bc21da4c773c40aafe52744774d359f034b137b01d660ce09",
   "payment_hash": "081cb692a2af850285b1cbf4a3318163db37ed4d749e10c4f08cd2014861459a",
   "created_at": 1672160710.331,
   "parts": 1,
   "amount_msat": 20000000,
   "amount_sent_msat": 20000000,
   "payment_preimage": "3fd9b15590a5a20ea55b2f8a58e02f0adac0778fda8a5983946906ed9455d022",
   "status": "complete"
}

This has the effect of advancing the state of the channel. Now, the node l2 has 20000sat it can spend in that channel and the node l1 980000sat as we can see using the sub-command listfunds like this:

◉ tony@tony:/tmp/l1-regtest/regtest:
$ l2-cli listfunds | jq .channels
[
  {
    "peer_id": "024b8ece67648d560ad5357153e577eb71ff157541731047bdb9ab91f708cba62e",
    "connected": true,
    "state": "CHANNELD_NORMAL",
    "short_channel_id": "103x1x0",
    "our_amount_msat": 20000000,
    "amount_msat": 1000000000,
    "funding_txid": "3ee9bc6bb4f98be53b64d79a77b88dd3d66eef445994a08c7f1aa2e017ae2274",
    "funding_output": 0
  }
]
◉ tony@tony:/tmp/l1-regtest/regtest:
$ l1-cli listfunds | jq .channels
[
  {
    "peer_id": "0311c4890bc6c2142bc21da4c773c40aafe52744774d359f034b137b01d660ce09",
    "connected": true,
    "state": "CHANNELD_NORMAL",
    "short_channel_id": "103x1x0",
    "our_amount_msat": 980000000,
    "amount_msat": 1000000000,
    "funding_txid": "3ee9bc6bb4f98be53b64d79a77b88dd3d66eef445994a08c7f1aa2e017ae2274",
    "funding_output": 0
  }
]

If the nodes l1 and l2 close the channel mutually (or unilaterally with one of the last commitment transactions) 980000sat (minus fees) would be transfered to the node l1 and 20000sat to the node l2.

But this is not what we are going to do.

The node l1 tries to cheat the node l2 by doing a unilateral close of the channel with the snapshot database

In section, the node l1 will try to cheat the node l2 by sending to the regtest chain the revocated commitment transaction that benefits him (with the whole amount of the channel). To do so the node l1 will close the channel unilaterally with a wrong state of the channel while being disconnected from the node l2.

First, we stop the node l1:

◉ tony@tony:/tmp/l1-regtest/regtest:
$ l1-cli stop
"Shutdown complete"
[1]-  Done                    test -f "/tmp/l$i-$network/lightningd-$network.pid" || $EATMYDATA "$LIGHTNINGD" "--lightning-dir=/tmp/l$i-$network"  (wd: ~/lnroom/lightning)
(wd now: /tmp/l1-regtest/regtest)

Then we roll back the l1's database:

◉ tony@tony:/tmp/l1-regtest/regtest:
$ cp lightningd.sqlite3.bak lightningd.sqlite3

Now we restart the node l1 using the flag --offline of lightningd in order not to reconnect neither communicate with the node l2 (remember that we try to cheat him):

◉ tony@tony:/tmp/l1-regtest/regtest:
$ /home/tony/lnroom/lightning/lightningd/lightningd \--lightning-dir=/tmp/l1-regtest \--offline \--daemon

We can verify that we are effectively not connected to the node l2 like this:

◉ tony@tony:/tmp/l1-regtest/regtest:
$ l1-cli listpeers -F | grep connected
peers[0].connected=false

Now the node l1 running on the snapshot database has an older state of the channel being the current state. Specifically he still has the whole amount of the channel 103x1x0 on his side:

◉ tony@tony:/tmp/l1-regtest/regtest:
$ l1-cli listfunds | jq .channels
[
  {
    "peer_id": "0311c4890bc6c2142bc21da4c773c40aafe52744774d359f034b137b01d660ce09",
    "connected": false,
    "state": "CHANNELD_NORMAL",
    "short_channel_id": "103x1x0",
    "our_amount_msat": 1000000000,
    "amount_msat": 1000000000,
    "funding_txid": "3ee9bc6bb4f98be53b64d79a77b88dd3d66eef445994a08c7f1aa2e017ae2274",
    "funding_output": 0
  }
]

Now the node l1 close unilaterally the channel with an old state of the channel which has for effect to send to the regtest chain a revocated commitment transaction in that case that benefit the node l1. Long story short l1 tries to cheat l2:

◉ tony@tony:/tmp/l1-regtest/regtest:
$ l1-cli close 103x1x0 1
# peer is offline, will negotiate once they reconnect (1 seconds before unilateral close).
# Timed out, forcing close.
{
   "tx": "02000000017422ae17e0a21a7f8ca0945944ef6ed6d38db8779ad7643be58bf9b46bbce93e0000000000854d7580024a01000000000000220020d7e74f80605c585b4dd6d0304c626beed9f1d14cc7c738e5a890945a72947a08903e0f00000000002200209d8427536e0c80e41eceb6388627bfda1dcb9f114b03a59ce3f816fccbbbd2132c2eca20",
   "txid": "589b636e4b2952fc51033a7c4278cccbddb15bbe6e0c6927df55af43f7275bb1",
   "type": "unilateral"
}

The node l2 watching for the funding transaction to be spent sends its penalty transaction

As we are running the regtest chain ourselves the cheated transaction is siting in the mempool waiting for a block including it to be mined.

So, node l2 does not suspect anything yet. It has still 20000sat on his side of the channel with the node l1 with which it is no longer connected as we can see below:

◉ tony@tony:/tmp/l1-regtest/regtest:
$ l2-cli listfunds | jq .channels
[
  {
    "peer_id": "024b8ece67648d560ad5357153e577eb71ff157541731047bdb9ab91f708cba62e",
    "connected": false,
    "state": "CHANNELD_NORMAL",
    "short_channel_id": "103x1x0",
    "our_amount_msat": 20000000,
    "amount_msat": 1000000000,
    "funding_txid": "3ee9bc6bb4f98be53b64d79a77b88dd3d66eef445994a08c7f1aa2e017ae2274",
    "funding_output": 0
  }
]

Specifically the node l2 is trying to reconnect to the node l1 as we can read in the attribute status:

"status": [
  "CHANNELD_NORMAL:Will attempt reconnect in 128 seconds"
],

of the object returned by the sub-command listpeers:

◉ tony@tony:/tmp/l1-regtest/regtest:
$ l2-cli listpeers | jq
{
  "peers": [
    {
      "id": "024b8ece67648d560ad5357153e577eb71ff157541731047bdb9ab91f708cba62e",
      "connected": false,
      "channels": [
        {
          "state": "CHANNELD_NORMAL",
          "scratch_txid": "5bc1062cf80e87a3f51892383ce4c2515c4ab31d38a1b9c1199091d9e0af0952",
          "last_tx_fee_msat": 284000,
          "feerate": {
            "perkw": 253,
            "perkb": 1012
          },
          "short_channel_id": "103x1x0",
          "direction": 1,
          "channel_id": "ca66614515ca5c8139de87f748f556697682e53bc85de8aa64baf3a6d3ce8ccd",
          "funding_txid": "3ee9bc6bb4f98be53b64d79a77b88dd3d66eef445994a08c7f1aa2e017ae2274",
          "funding_outnum": 0,
          "close_to_addr": "bcrt1qquh432s5ay8gg94cqt927wvlc8c9puadvuq95w",
          "close_to": "0014072f58aa14e90e8416b802caaf399fc1f050f3ad",
          "private": false,
          "opener": "remote",
          "alias": {
            "local": "7721670x4646220x41974"
          },
          "features": [
            "option_static_remotekey",
            "option_anchor_outputs"
          ],
          "funding": {
            "local_funds_msat": "0msat",
            "remote_funds_msat": "1000000000msat",
            "pushed_msat": 0
          },
          "to_us_msat": 20000000,
          "min_to_us_msat": 0,
          "max_to_us_msat": 20000000,
          "total_msat": 1000000000,
          "fee_base_msat": 1,
          "fee_proportional_millionths": 10,
          "dust_limit_msat": 546000,
          "max_total_htlc_in_msat": 18446744073709552000,
          "their_reserve_msat": 10000000,
          "our_reserve_msat": 10000000,
          "spendable_msat": 10000000,
          "receivable_msat": 968598000,
          "minimum_htlc_in_msat": 0,
          "minimum_htlc_out_msat": 0,
          "maximum_htlc_out_msat": 990000000,
          "their_to_self_delay": 6,
          "our_to_self_delay": 6,
          "max_accepted_htlcs": 483,
          "state_changes": [
            {
              "timestamp": "2022-12-27T16:46:41.597Z",
              "old_state": "DUALOPEND_OPEN_INIT",
              "new_state": "DUALOPEND_AWAITING_LOCKIN",
              "cause": "remote",
              "message": "Sigs exchanged, waiting for lock-in"
            },
            {
              "timestamp": "2022-12-27T16:46:46.322Z",
              "old_state": "DUALOPEND_AWAITING_LOCKIN",
              "new_state": "CHANNELD_NORMAL",
              "cause": "remote",
              "message": "Lockin complete"
            }
          ],
          "status": [
            "CHANNELD_NORMAL:Will attempt reconnect in 128 seconds"
          ],
          "in_payments_offered": 1,
          "in_offered_msat": 20000000,
          "in_payments_fulfilled": 1,
          "in_fulfilled_msat": 20000000,
          "out_payments_offered": 0,
          "out_offered_msat": 0,
          "out_payments_fulfilled": 0,
          "out_fulfilled_msat": 0,
          "htlcs": []
        }
      ]
    }
  ]
}

Let's mine one block on regtest:

◉ tony@tony:/tmp/l1-regtest/regtest:
$ bitcoin-cli -regtest -rpcwallet=default getnewaddress
bcrt1q3ljpd8zth5gn987jus2g94mlmpddheqgjktdek
◉ tony@tony:/tmp/l1-regtest/regtest:
$ bitcoin-cli -regtest generatetoaddress 1 bcrt1q3ljpd8zth5gn987jus2g94mlmpddheqgjktdek > /dev/null

The cheated transaction being mined, the node l1 is now waiting 5 more blocks (corresponding to "our_to_self_delay": 6 minus the previous mined block) as we can read in the attribute status:

"status": [
  "ONCHAIN:Tracking our own unilateral close",
  "ONCHAIN:3 outputs unresolved: in 5 blocks will spend DELAYED_OUTPUT_TO_US (589b636e4b2952fc51033a7c4278cccbddb15bbe6e0c6927df55af43f7275bb1:1) using OUR_DELAYED_RETURN_TO_WALLET"
],

of the object returned by the sub-command listpeers:

◉ tony@tony:/tmp/l1-regtest/regtest:
$ l1-cli listpeers | jq
{
  "peers": [
    {
      "id": "0311c4890bc6c2142bc21da4c773c40aafe52744774d359f034b137b01d660ce09",
      "connected": false,
      "channels": [
        {
          "state": "ONCHAIN",
          "scratch_txid": "589b636e4b2952fc51033a7c4278cccbddb15bbe6e0c6927df55af43f7275bb1",
          "last_tx_fee_msat": 614000,
          "feerate": {
            "perkw": 253,
            "perkb": 1012
          },
          "owner": "onchaind",
          "short_channel_id": "103x1x0",
          "direction": 0,
          "channel_id": "ca66614515ca5c8139de87f748f556697682e53bc85de8aa64baf3a6d3ce8ccd",
          "funding_txid": "3ee9bc6bb4f98be53b64d79a77b88dd3d66eef445994a08c7f1aa2e017ae2274",
          "funding_outnum": 0,
          "close_to_addr": "bcrt1qde5hpdkqfnaxsxtg45xr6cypncdwn60sv6s9e0",
          "close_to": "00146e6970b6c04cfa681968ad0c3d60819e1ae9e9f0",
          "private": false,
          "opener": "local",
          "closer": "local",
          "alias": {
            "local": "5744536x13446725x44002"
          },
          "features": [
            "option_static_remotekey",
            "option_anchor_outputs"
          ],
          "funding": {
            "local_funds_msat": "1000000000msat",
            "remote_funds_msat": "0msat",
            "pushed_msat": 0
          },
          "to_us_msat": 1000000000,
          "min_to_us_msat": 1000000000,
          "max_to_us_msat": 1000000000,
          "total_msat": 1000000000,
          "fee_base_msat": 1,
          "fee_proportional_millionths": 10,
          "dust_limit_msat": 546000,
          "max_total_htlc_in_msat": 18446744073709552000,
          "their_reserve_msat": 10000000,
          "our_reserve_msat": 10000000,
          "spendable_msat": 988598000,
          "receivable_msat": 0,
          "minimum_htlc_in_msat": 0,
          "minimum_htlc_out_msat": 0,
          "maximum_htlc_out_msat": 990000000,
          "their_to_self_delay": 6,
          "our_to_self_delay": 6,
          "max_accepted_htlcs": 483,
          "state_changes": [
            {
              "timestamp": "2022-12-27T16:46:41.597Z",
              "old_state": "DUALOPEND_OPEN_INIT",
              "new_state": "DUALOPEND_AWAITING_LOCKIN",
              "cause": "user",
              "message": "Sigs exchanged, waiting for lock-in"
            },
            {
              "timestamp": "2022-12-27T16:46:46.230Z",
              "old_state": "DUALOPEND_AWAITING_LOCKIN",
              "new_state": "CHANNELD_NORMAL",
              "cause": "user",
              "message": "Lockin complete"
            },
            {
              "timestamp": "2022-12-27T17:09:42.006Z",
              "old_state": "CHANNELD_NORMAL",
              "new_state": "CHANNELD_SHUTTING_DOWN",
              "cause": "user",
              "message": "User or plugin invoked close command"
            },
            {
              "timestamp": "2022-12-27T17:09:43.009Z",
              "old_state": "CHANNELD_SHUTTING_DOWN",
              "new_state": "AWAITING_UNILATERAL",
              "cause": "user",
              "message": "Forcibly closed by `close` command timeout"
            },
            {
              "timestamp": "2022-12-27T17:12:01.964Z",
              "old_state": "AWAITING_UNILATERAL",
              "new_state": "FUNDING_SPEND_SEEN",
              "cause": "user",
              "message": "Onchain funding spend"
            },
            {
              "timestamp": "2022-12-27T17:12:02.018Z",
              "old_state": "FUNDING_SPEND_SEEN",
              "new_state": "ONCHAIN",
              "cause": "user",
              "message": "Onchain init reply"
            }
          ],
          "status": [
            "ONCHAIN:Tracking our own unilateral close",
            "ONCHAIN:3 outputs unresolved: in 5 blocks will spend DELAYED_OUTPUT_TO_US (589b636e4b2952fc51033a7c4278cccbddb15bbe6e0c6927df55af43f7275bb1:1) using OUR_DELAYED_RETURN_TO_WALLET"
          ],
          "in_payments_offered": 0,
          "in_offered_msat": 0,
          "in_payments_fulfilled": 0,
          "in_fulfilled_msat": 0,
          "out_payments_offered": 0,
          "out_offered_msat": 0,
          "out_payments_fulfilled": 0,
          "out_fulfilled_msat": 0,
          "htlcs": []
        }
      ]
    }
  ]
}

While the node l1 is waiting 5 blocks to conclude is fraud, the node l2, which was still running, has already counterattacked by sending its penalty transaction as we can read in the attribute status:

"status": [
  "ONCHAIN:Tracking their illegal close: taking all funds",
  "ONCHAIN:3 outputs unresolved: waiting confirmation that we spent DELAYED_CHEAT_OUTPUT_TO_THEM (589b636e4b2952fc51033a7c4278cccbddb15bbe6e0c6927df55af43f7275bb1:1) using OUR_PENALTY_TX"
],

of the object returned by the sub-command listpeers:

◉ tony@tony:/tmp/l1-regtest/regtest:
$ l2-cli listpeers | jq
{
  "peers": [
    {
      "id": "024b8ece67648d560ad5357153e577eb71ff157541731047bdb9ab91f708cba62e",
      "connected": false,
      "channels": [
        {
          "state": "ONCHAIN",
          "scratch_txid": "5bc1062cf80e87a3f51892383ce4c2515c4ab31d38a1b9c1199091d9e0af0952",
          "last_tx_fee_msat": 284000,
          "feerate": {
            "perkw": 253,
            "perkb": 1012
          },
          "owner": "onchaind",
          "short_channel_id": "103x1x0",
          "direction": 1,
          "channel_id": "ca66614515ca5c8139de87f748f556697682e53bc85de8aa64baf3a6d3ce8ccd",
          "funding_txid": "3ee9bc6bb4f98be53b64d79a77b88dd3d66eef445994a08c7f1aa2e017ae2274",
          "funding_outnum": 0,
          "close_to_addr": "bcrt1qquh432s5ay8gg94cqt927wvlc8c9puadvuq95w",
          "close_to": "0014072f58aa14e90e8416b802caaf399fc1f050f3ad",
          "private": false,
          "opener": "remote",
          "closer": "remote",
          "alias": {
            "local": "7721670x4646220x41974"
          },
          "features": [
            "option_static_remotekey",
            "option_anchor_outputs"
          ],
          "funding": {
            "local_funds_msat": "0msat",
            "remote_funds_msat": "1000000000msat",
            "pushed_msat": 0
          },
          "to_us_msat": 20000000,
          "min_to_us_msat": 0,
          "max_to_us_msat": 20000000,
          "total_msat": 1000000000,
          "fee_base_msat": 1,
          "fee_proportional_millionths": 10,
          "dust_limit_msat": 546000,
          "max_total_htlc_in_msat": 18446744073709552000,
          "their_reserve_msat": 10000000,
          "our_reserve_msat": 10000000,
          "spendable_msat": 10000000,
          "receivable_msat": 968598000,
          "minimum_htlc_in_msat": 0,
          "minimum_htlc_out_msat": 0,
          "maximum_htlc_out_msat": 990000000,
          "their_to_self_delay": 6,
          "our_to_self_delay": 6,
          "max_accepted_htlcs": 483,
          "state_changes": [
            {
              "timestamp": "2022-12-27T16:46:41.597Z",
              "old_state": "DUALOPEND_OPEN_INIT",
              "new_state": "DUALOPEND_AWAITING_LOCKIN",
              "cause": "remote",
              "message": "Sigs exchanged, waiting for lock-in"
            },
            {
              "timestamp": "2022-12-27T16:46:46.322Z",
              "old_state": "DUALOPEND_AWAITING_LOCKIN",
              "new_state": "CHANNELD_NORMAL",
              "cause": "remote",
              "message": "Lockin complete"
            },
            {
              "timestamp": "2022-12-27T17:12:04.519Z",
              "old_state": "CHANNELD_NORMAL",
              "new_state": "AWAITING_UNILATERAL",
              "cause": "onchain",
              "message": "Funding transaction spent"
            },
            {
              "timestamp": "2022-12-27T17:12:04.520Z",
              "old_state": "AWAITING_UNILATERAL",
              "new_state": "FUNDING_SPEND_SEEN",
              "cause": "onchain",
              "message": "Onchain funding spend"
            },
            {
              "timestamp": "2022-12-27T17:12:04.746Z",
              "old_state": "FUNDING_SPEND_SEEN",
              "new_state": "ONCHAIN",
              "cause": "onchain",
              "message": "Onchain init reply"
            }
          ],
          "status": [
            "ONCHAIN:Tracking their illegal close: taking all funds",
            "ONCHAIN:3 outputs unresolved: waiting confirmation that we spent DELAYED_CHEAT_OUTPUT_TO_THEM (589b636e4b2952fc51033a7c4278cccbddb15bbe6e0c6927df55af43f7275bb1:1) using OUR_PENALTY_TX"
          ],
          "in_payments_offered": 1,
          "in_offered_msat": 20000000,
          "in_payments_fulfilled": 1,
          "in_fulfilled_msat": 20000000,
          "out_payments_offered": 0,
          "out_offered_msat": 0,
          "out_payments_fulfilled": 0,
          "out_fulfilled_msat": 0,
          "htlcs": []
        }
      ]
    }
  ]
}

After mining one block

◉ tony@tony:/tmp/l1-regtest/regtest:
$ bitcoin-cli -regtest generatetoaddress 1 bcrt1q3ljpd8zth5gn987jus2g94mlmpddheqgjktdek > /dev/null

we check by looking at the status attribute of listpeers sub-command that the penalty transaction has been applied:

◉ tony@tony:/tmp/l1-regtest/regtest:
$ l2-cli listpeers | jq
{
  "peers": [
    {
      "id": "024b8ece67648d560ad5357153e577eb71ff157541731047bdb9ab91f708cba62e",
      "connected": false,
      "channels": [
        {
          "state": "ONCHAIN",
          "scratch_txid": "5bc1062cf80e87a3f51892383ce4c2515c4ab31d38a1b9c1199091d9e0af0952",
          "last_tx_fee_msat": 284000,
          "feerate": {
            "perkw": 253,
            "perkb": 1012
          },
          "owner": "onchaind",
          "short_channel_id": "103x1x0",
          "direction": 1,
          "channel_id": "ca66614515ca5c8139de87f748f556697682e53bc85de8aa64baf3a6d3ce8ccd",
          "funding_txid": "3ee9bc6bb4f98be53b64d79a77b88dd3d66eef445994a08c7f1aa2e017ae2274",
          "funding_outnum": 0,
          "close_to_addr": "bcrt1qquh432s5ay8gg94cqt927wvlc8c9puadvuq95w",
          "close_to": "0014072f58aa14e90e8416b802caaf399fc1f050f3ad",
          "private": false,
          "opener": "remote",
          "closer": "remote",
          "alias": {
            "local": "7721670x4646220x41974"
          },
          "features": [
            "option_static_remotekey",
            "option_anchor_outputs"
          ],
          "funding": {
            "local_funds_msat": "0msat",
            "remote_funds_msat": "1000000000msat",
            "pushed_msat": 0
          },
          "to_us_msat": 20000000,
          "min_to_us_msat": 0,
          "max_to_us_msat": 20000000,
          "total_msat": 1000000000,
          "fee_base_msat": 1,
          "fee_proportional_millionths": 10,
          "dust_limit_msat": 546000,
          "max_total_htlc_in_msat": 18446744073709552000,
          "their_reserve_msat": 10000000,
          "our_reserve_msat": 10000000,
          "spendable_msat": 10000000,
          "receivable_msat": 968598000,
          "minimum_htlc_in_msat": 0,
          "minimum_htlc_out_msat": 0,
          "maximum_htlc_out_msat": 990000000,
          "their_to_self_delay": 6,
          "our_to_self_delay": 6,
          "max_accepted_htlcs": 483,
          "state_changes": [
            {
              "timestamp": "2022-12-27T16:46:41.597Z",
              "old_state": "DUALOPEND_OPEN_INIT",
              "new_state": "DUALOPEND_AWAITING_LOCKIN",
              "cause": "remote",
              "message": "Sigs exchanged, waiting for lock-in"
            },
            {
              "timestamp": "2022-12-27T16:46:46.322Z",
              "old_state": "DUALOPEND_AWAITING_LOCKIN",
              "new_state": "CHANNELD_NORMAL",
              "cause": "remote",
              "message": "Lockin complete"
            },
            {
              "timestamp": "2022-12-27T17:12:04.519Z",
              "old_state": "CHANNELD_NORMAL",
              "new_state": "AWAITING_UNILATERAL",
              "cause": "onchain",
              "message": "Funding transaction spent"
            },
            {
              "timestamp": "2022-12-27T17:12:04.520Z",
              "old_state": "AWAITING_UNILATERAL",
              "new_state": "FUNDING_SPEND_SEEN",
              "cause": "onchain",
              "message": "Onchain funding spend"
            },
            {
              "timestamp": "2022-12-27T17:12:04.746Z",
              "old_state": "FUNDING_SPEND_SEEN",
              "new_state": "ONCHAIN",
              "cause": "onchain",
              "message": "Onchain init reply"
            }
          ],
          "status": [
            "ONCHAIN:Tracking their illegal close: taking all funds",
            "ONCHAIN:All outputs resolved: waiting 99 more blocks before forgetting channel"
          ],
          "in_payments_offered": 1,
          "in_offered_msat": 20000000,
          "in_payments_fulfilled": 1,
          "in_fulfilled_msat": 20000000,
          "out_payments_offered": 0,
          "out_offered_msat": 0,
          "out_payments_fulfilled": 0,
          "out_fulfilled_msat": 0,
          "htlcs": []
        }
      ]
    }
  ]
}

After mining 99 more blocks the 2 nodes can forget the channel:

◉ tony@tony:/tmp/l1-regtest/regtest:
$ bitcoin-cli -regtest generatetoaddress 99 bcrt1q3ljpd8zth5gn987jus2g94mlmpddheqgjktdek > /dev/null

And the final repartition of the sats is the following:

◉ tony@tony:/tmp/l1-regtest/regtest:
$ l2-cli listfunds | jq .outputs
[
  {
    "txid": "0c5a6189149e3900ffb8c4e4c050ba081ce3c2da55402e8d47272a87d0a544cc",
    "output": 0,
    "amount_msat": 998935000,
    "scriptpubkey": "0014072f58aa14e90e8416b802caaf399fc1f050f3ad",
    "address": "bcrt1qquh432s5ay8gg94cqt927wvlc8c9puadvuq95w",
    "status": "confirmed",
    "blockheight": 110,
    "reserved": false
  }
]
◉ tony@tony:/tmp/l1-regtest/regtest:
$ l1-cli listfunds | jq .outputs
[
  {
    "txid": "3ee9bc6bb4f98be53b64d79a77b88dd3d66eef445994a08c7f1aa2e017ae2274",
    "output": 1,
    "amount_msat": 98999846000,
    "scriptpubkey": "0014b3aa21fdb4a48726eaa4e1b0f886df042cce226f",
    "address": "bcrt1qkw4zrld55jrjd64yuxc03pklqskvugn079prvj",
    "status": "confirmed",
    "blockheight": 103,
    "reserved": false
  }
]

We are done with this part of the video where the node l1 tried to cheat the node l2 and failed due to the penalty transaction broadcast by the node l2.

test_channel_lease_lessor_cheat test function

In the following test (test_channel_lease_lessor_cheat) a dual funded channel is opened between a node l1 and l2 with 500000sat in both sides. l1 pays an invoice of 10000sat to l2. l2 makes a snapshot of its database. l2 pays an invoice of 100000sat to l1. Then both nodes are stopped. l2 roll back its database to use the snapshot database where the balances of the channel benefit him. While the node l1 is still "sleeping", the node l2 close unilaterally the channel which has for effect to try to cheat the node l1 by sending a revocated transaction. Finally, the node l1 restarted it sends its penalty transaction.

@unittest.skipIf(TEST_NETWORK != 'regtest', 'elementsd doesnt yet support PSBT features we need')
@pytest.mark.openchannel('v2')
@unittest.skipIf(os.getenv('TEST_DB_PROVIDER', 'sqlite3') != 'sqlite3', "Makes use of the sqlite3 db")
@pytest.mark.developer("requres 'dev-queryrates'")
def test_channel_lease_lessor_cheat(node_factory, bitcoind, chainparams):
    '''
    Check that lessee can recover funds if lessor cheats
    '''
    balance_snaps = os.path.join(os.getcwd(), 'tests/plugins/balance_snaps.py')
    opts = [{'funder-policy': 'match', 'funder-policy-mod': 100,
             'lease-fee-base-sat': '100sat', 'lease-fee-basis': 100,
             'may_reconnect': True, 'allow_warning': True,
             'plugin': balance_snaps},
            {'funder-policy': 'match', 'funder-policy-mod': 100,
             'lease-fee-base-sat': '100sat', 'lease-fee-basis': 100,
             'may_reconnect': True, 'allow_broken_log': True,
             'plugin': balance_snaps}]
    l1, l2, = node_factory.get_nodes(2, opts=opts)
    amount = 500000
    feerate = 2000
    l1.fundwallet(20000000)
    l2.fundwallet(20000000)

    l1.rpc.connect(l2.info['id'], 'localhost', l2.port)
    rates = l1.rpc.dev_queryrates(l2.info['id'], amount, amount)
    wait_for(lambda: len(l1.rpc.listpeers(l2.info['id'])['peers']) == 0)
    l1.rpc.connect(l2.info['id'], 'localhost', l2.port)
    # l1 leases a channel from l2
    l1.rpc.fundchannel(l2.info['id'], amount, request_amt=amount,
                       feerate='{}perkw'.format(feerate),
                       compact_lease=rates['compact_lease'])

    bitcoind.generate_block(6)
    l1.daemon.wait_for_log('to CHANNELD_NORMAL')
    wait_for(lambda: [c['active'] for c in l1.rpc.listchannels(l1.get_channel_scid(l2))['channels']] == [True, True])
    wait_for(lambda: [c['active'] for c in l2.rpc.listchannels(l2.get_channel_scid(l1))['channels']] == [True, True])
    # send some payments, mine a block or two
    inv = l2.rpc.invoice(10**4, '1', 'no_1')
    l1.rpc.pay(inv['bolt11'])

    bitcoind.generate_block(1)

    # make database snapshot of l2
    l2.stop()
    l2_db_path = os.path.join(l2.daemon.lightning_dir, chainparams['name'], 'lightningd.sqlite3')
    l2_db_path_bak = os.path.join(l2.daemon.lightning_dir, chainparams['name'], 'lightningd.sqlite3.bak')
    copyfile(l2_db_path, l2_db_path_bak)
    l2.start(wait_for_bitcoind_sync=True)
    l1.rpc.connect(l2.info['id'], 'localhost', l2.port)
    sync_blockheight(bitcoind, [l2])

    # push some money from l2->l1, so the commit counter advances
    inv = l1.rpc.invoice(10**5, '2', 'no_2')
    l2.rpc.pay(inv['bolt11'])

    # stop both nodes, roll back l2's database
    l2.stop()
    l1.stop()
    copyfile(l2_db_path_bak, l2_db_path)

    # start l2 and force close channel with l1 while l1 is still offline
    l2.start()
    sync_blockheight(bitcoind, [l2])
    l2.rpc.close(l1.info['id'], 1, force_lease_closed=True)
    bitcoind.generate_block(1, wait_for_mempool=1)

    l1.start()
    sync_blockheight(bitcoind, [l1])
    l1.daemon.wait_for_logs(['Broadcasting OUR_PENALTY_TX',
                             ' Propose handling THEIR_REVOKED_UNILATERAL/DELAYED_CHEAT_OUTPUT_TO_THEM by OUR_PENALTY_TX'])

    bitcoind.generate_block(1, wait_for_mempool=1)
    # l2 sees that l1 has spent their coins!
    l2.daemon.wait_for_log('Unknown spend of OUR_UNILATERAL/DELAYED_OUTPUT_TO_US by')

We are done.

I hope you enjoyed the video.

See next time.

Terminal sessions

We ran the following commands in this order:

$ source contrib/startup_regtest.sh
$ start_ln 2
$ alias l1-cli
$ fund_nodes
$ l1-cli listfunds | jq .outputs
$ l1-cli listfunds | jq .channels
$ l2-cli listfunds | jq .outputs
$ l2-cli listfunds | jq .channels
$ cd /tmp/l1-regtest/regtest/
$ ls -1
$ cp lightningd.sqlite3 lightningd.sqlite3.bak
$ l2-cli invoice 20000sat inv "pizza" | jq .bolt11
$ l1-cli pay lnbcrt200u1p36kfdesp5qlsckdq0se4mw4shzxumx50ndwpu44lu7z007yntuhjjq5y0vn5qpp5pqwtdy4z47zs9pd3e062xvvpv0dn0m2dwj0pp38s3nfqzjrpgkdqdqgwp5h57npxqyjw5qcqp29qx3qysgqza5f5u5efj5a7qlswuxn2qh955696pxnfg43cj07xg529gnhyem5ymq58tujdz6st5g5ap7yrzgczfvnft0unhke7fhhdd0euyqfcpqppu7wua
$ l2-cli listfunds | jq .channels
$ l1-cli listfunds | jq .channels
$ l1-cli stop
$ cp lightningd.sqlite3.bak lightningd.sqlite3
$ /home/tony/lnroom/lightning/lightningd/lightningd --lightning-dir=/tmp/l1-regtest --offline --daemon
$ l1-cli listpeers -F | grep connected
$ l1-cli listfunds | jq .channels
$ l1-cli close 103x1x0 1
$ l2-cli listfunds | jq .channels
$ l2-cli listpeers | jq
$ bitcoin-cli -regtest -rpcwallet=default getnewaddress
$ bitcoin-cli -regtest generatetoaddress 1 bcrt1q3ljpd8zth5gn987jus2g94mlmpddheqgjktdek > /dev/null
$ l1-cli listpeers | jq
$ l2-cli listpeers | jq
$ bitcoin-cli -regtest generatetoaddress 1 bcrt1q3ljpd8zth5gn987jus2g94mlmpddheqgjktdek > /dev/null
$ l2-cli listpeers | jq
$ bitcoin-cli -regtest generatetoaddress 99 bcrt1q3ljpd8zth5gn987jus2g94mlmpddheqgjktdek > /dev/null
$ l2-cli listfunds | jq .outputs
$ l1-cli listfunds | jq .outputs

And below you can read the terminal session (command lines and outputs):

◉ tony@tony:~/lnroom/lightning:[git»(HEAD detached at v22.11.1)]
$ source contrib/startup_regtest.sh
lightning-cli is /home/tony/lnroom/lightning/cli/lightning-cli
lightningd is /home/tony/lnroom/lightning/lightningd/lightningd
Useful commands:
  start_ln 3: start three nodes, l1, l2, l3
  connect 1 2: connect l1 and l2
  fund_nodes: connect all nodes with channels, in a row
  stop_ln: shutdown
  destroy_ln: remove ln directories
◉ tony@tony:~/lnroom/lightning:[git»(HEAD detached at v22.11.1)]
$ start_ln 2
Bitcoin Core starting
awaiting bitcoind...
Making "default" bitcoind wallet.
[1] 28341
[2] 28376
WARNING: eatmydata not found: instal it for faster testing
Commands:
        l1-cli, l1-log,
        l2-cli, l2-log,
        bt-cli, stop_ln, fund_nodes
◉ tony@tony:~/lnroom/lightning:[git»(HEAD detached at v22.11.1)]
$ alias l1-cli
alias l1-cli='/home/tony/lnroom/lightning/cli/lightning-cli --lightning-dir=/tmp/l1-regtest'
◉ tony@tony:~/lnroom/lightning:[git»(HEAD detached at v22.11.1)]
$ fund_nodes
Mining into address bcrt1ql0337077n934he8l0hwn2js52294yp3fzjmwlc... done.
bitcoind balance: 50.00000000
Waiting for lightning node funds... found.
Funding channel from node 1 to node 2. Waiting for confirmation... done.
◉ tony@tony:~/lnroom/lightning:[git»(HEAD detached at v22.11.1)]
$ l1-cli listfunds | jq .outputs
[
  {
    "txid": "3ee9bc6bb4f98be53b64d79a77b88dd3d66eef445994a08c7f1aa2e017ae2274",
    "output": 1,
    "amount_msat": 98999846000,
    "scriptpubkey": "0014b3aa21fdb4a48726eaa4e1b0f886df042cce226f",
    "address": "bcrt1qkw4zrld55jrjd64yuxc03pklqskvugn079prvj",
    "status": "confirmed",
    "blockheight": 103,
    "reserved": false
  }
]
◉ tony@tony:~/lnroom/lightning:[git»(HEAD detached at v22.11.1)]
$ l1-cli listfunds | jq .channels
[
  {
    "peer_id": "0311c4890bc6c2142bc21da4c773c40aafe52744774d359f034b137b01d660ce09",
    "connected": true,
    "state": "CHANNELD_NORMAL",
    "short_channel_id": "103x1x0",
    "our_amount_msat": 1000000000,
    "amount_msat": 1000000000,
    "funding_txid": "3ee9bc6bb4f98be53b64d79a77b88dd3d66eef445994a08c7f1aa2e017ae2274",
    "funding_output": 0
  }
]
◉ tony@tony:~/lnroom/lightning:[git»(HEAD detached at v22.11.1)]
$ l2-cli listfunds | jq .outputs
[]
◉ tony@tony:~/lnroom/lightning:[git»(HEAD detached at v22.11.1)]
$ l2-cli listfunds | jq .channels
[
  {
    "peer_id": "024b8ece67648d560ad5357153e577eb71ff157541731047bdb9ab91f708cba62e",
    "connected": true,
    "state": "CHANNELD_NORMAL",
    "short_channel_id": "103x1x0",
    "our_amount_msat": 0,
    "amount_msat": 1000000000,
    "funding_txid": "3ee9bc6bb4f98be53b64d79a77b88dd3d66eef445994a08c7f1aa2e017ae2274",
    "funding_output": 0
  }
]
◉ tony@tony:~/lnroom/lightning:[git»(HEAD detached at v22.11.1)]
$ cd /tmp/l1-regtest/regtest/
◉ tony@tony:/tmp/l1-regtest/regtest:
$ ls -1
accounts.sqlite3
emergency.recover
gossip_store
hsm_secret
lightningd.sqlite3
lightning-rpc
◉ tony@tony:/tmp/l1-regtest/regtest:
$ cp lightningd.sqlite3 lightningd.sqlite3.bak
◉ tony@tony:/tmp/l1-regtest/regtest:
$ l2-cli invoice 20000sat inv "pizza" | jq .bolt11
"lnbcrt200u1p36kfdesp5qlsckdq0se4mw4shzxumx50ndwpu44lu7z007yntuhjjq5y0vn5qpp5pqwtdy4z47zs9pd3e062xvvpv0dn0m2dwj0pp38s3nfqzjrpgkdqdqgwp5h57npxqyjw5qcqp29qx3qysgqza5f5u5efj5a7qlswuxn2qh955696pxnfg43cj07xg529gnhyem5ymq58tujdz6st5g5ap7yrzgczfvnft0unhke7fhhdd0euyqfcpqppu7wua"
◉ tony@tony:/tmp/l1-regtest/regtest:
$ l1-cli pay lnbcrt200u1p36kfdesp5qlsckdq0se4mw4shzxumx50ndwpu44lu7z007yntuhjjq5y0vn5qpp5pqwtdy4z47zs9pd3e062xvvpv0dn0m2dwj0pp38s3nfqzjrpgkdqdqgwp5h57npxqyjw5qcqp29qx3qysgqza5f5u5efj5a7qlswuxn2qh955696pxnfg43cj07xg529gnhyem5ymq58tujdz6st5g5ap7yrzgczfvnft0unhke7fhhdd0euyqfcpqppu7wua
{
   "destination": "0311c4890bc6c2142bc21da4c773c40aafe52744774d359f034b137b01d660ce09",
   "payment_hash": "081cb692a2af850285b1cbf4a3318163db37ed4d749e10c4f08cd2014861459a",
   "created_at": 1672160710.331,
   "parts": 1,
   "amount_msat": 20000000,
   "amount_sent_msat": 20000000,
   "payment_preimage": "3fd9b15590a5a20ea55b2f8a58e02f0adac0778fda8a5983946906ed9455d022",
   "status": "complete"
}
◉ tony@tony:/tmp/l1-regtest/regtest:
$ l2-cli listfunds | jq .channels
[
  {
    "peer_id": "024b8ece67648d560ad5357153e577eb71ff157541731047bdb9ab91f708cba62e",
    "connected": true,
    "state": "CHANNELD_NORMAL",
    "short_channel_id": "103x1x0",
    "our_amount_msat": 20000000,
    "amount_msat": 1000000000,
    "funding_txid": "3ee9bc6bb4f98be53b64d79a77b88dd3d66eef445994a08c7f1aa2e017ae2274",
    "funding_output": 0
  }
]
◉ tony@tony:/tmp/l1-regtest/regtest:
$ l1-cli listfunds | jq .channels
[
  {
    "peer_id": "0311c4890bc6c2142bc21da4c773c40aafe52744774d359f034b137b01d660ce09",
    "connected": true,
    "state": "CHANNELD_NORMAL",
    "short_channel_id": "103x1x0",
    "our_amount_msat": 980000000,
    "amount_msat": 1000000000,
    "funding_txid": "3ee9bc6bb4f98be53b64d79a77b88dd3d66eef445994a08c7f1aa2e017ae2274",
    "funding_output": 0
  }
]
◉ tony@tony:/tmp/l1-regtest/regtest:
$ l1-cli stop
"Shutdown complete"
[1]-  Done                    test -f "/tmp/l$i-$network/lightningd-$network.pid" || $EATMYDATA "$LIGHTNINGD" "--lightning-dir=/tmp/l$i-$network"  (wd: ~/lnroom/lightning)
(wd now: /tmp/l1-regtest/regtest)
◉ tony@tony:/tmp/l1-regtest/regtest:
$ cp lightningd.sqlite3.bak lightningd.sqlite3
◉ tony@tony:/tmp/l1-regtest/regtest:
$ /home/tony/lnroom/lightning/lightningd/lightningd \--lightning-dir=/tmp/l1-regtest \--offline \--daemon
◉ tony@tony:/tmp/l1-regtest/regtest:
$ l1-cli listpeers -F | grep connected
peers[0].connected=false
◉ tony@tony:/tmp/l1-regtest/regtest:
$ l1-cli listfunds | jq .channels
[
  {
    "peer_id": "0311c4890bc6c2142bc21da4c773c40aafe52744774d359f034b137b01d660ce09",
    "connected": false,
    "state": "CHANNELD_NORMAL",
    "short_channel_id": "103x1x0",
    "our_amount_msat": 1000000000,
    "amount_msat": 1000000000,
    "funding_txid": "3ee9bc6bb4f98be53b64d79a77b88dd3d66eef445994a08c7f1aa2e017ae2274",
    "funding_output": 0
  }
]
◉ tony@tony:/tmp/l1-regtest/regtest:
$ l1-cli close 103x1x0 1
# peer is offline, will negotiate once they reconnect (1 seconds before unilateral close).
# Timed out, forcing close.
{
   "tx": "02000000017422ae17e0a21a7f8ca0945944ef6ed6d38db8779ad7643be58bf9b46bbce93e0000000000854d7580024a01000000000000220020d7e74f80605c585b4dd6d0304c626beed9f1d14cc7c738e5a890945a72947a08903e0f00000000002200209d8427536e0c80e41eceb6388627bfda1dcb9f114b03a59ce3f816fccbbbd2132c2eca20",
   "txid": "589b636e4b2952fc51033a7c4278cccbddb15bbe6e0c6927df55af43f7275bb1",
   "type": "unilateral"
}
◉ tony@tony:/tmp/l1-regtest/regtest:
$ l2-cli listfunds | jq .channels
[
  {
    "peer_id": "024b8ece67648d560ad5357153e577eb71ff157541731047bdb9ab91f708cba62e",
    "connected": false,
    "state": "CHANNELD_NORMAL",
    "short_channel_id": "103x1x0",
    "our_amount_msat": 20000000,
    "amount_msat": 1000000000,
    "funding_txid": "3ee9bc6bb4f98be53b64d79a77b88dd3d66eef445994a08c7f1aa2e017ae2274",
    "funding_output": 0
  }
]
◉ tony@tony:/tmp/l1-regtest/regtest:
$ l2-cli listpeers | jq
{
  "peers": [
    {
      "id": "024b8ece67648d560ad5357153e577eb71ff157541731047bdb9ab91f708cba62e",
      "connected": false,
      "channels": [
        {
          "state": "CHANNELD_NORMAL",
          "scratch_txid": "5bc1062cf80e87a3f51892383ce4c2515c4ab31d38a1b9c1199091d9e0af0952",
          "last_tx_fee_msat": 284000,
          "feerate": {
            "perkw": 253,
            "perkb": 1012
          },
          "short_channel_id": "103x1x0",
          "direction": 1,
          "channel_id": "ca66614515ca5c8139de87f748f556697682e53bc85de8aa64baf3a6d3ce8ccd",
          "funding_txid": "3ee9bc6bb4f98be53b64d79a77b88dd3d66eef445994a08c7f1aa2e017ae2274",
          "funding_outnum": 0,
          "close_to_addr": "bcrt1qquh432s5ay8gg94cqt927wvlc8c9puadvuq95w",
          "close_to": "0014072f58aa14e90e8416b802caaf399fc1f050f3ad",
          "private": false,
          "opener": "remote",
          "alias": {
            "local": "7721670x4646220x41974"
          },
          "features": [
            "option_static_remotekey",
            "option_anchor_outputs"
          ],
          "funding": {
            "local_funds_msat": "0msat",
            "remote_funds_msat": "1000000000msat",
            "pushed_msat": 0
          },
          "to_us_msat": 20000000,
          "min_to_us_msat": 0,
          "max_to_us_msat": 20000000,
          "total_msat": 1000000000,
          "fee_base_msat": 1,
          "fee_proportional_millionths": 10,
          "dust_limit_msat": 546000,
          "max_total_htlc_in_msat": 18446744073709552000,
          "their_reserve_msat": 10000000,
          "our_reserve_msat": 10000000,
          "spendable_msat": 10000000,
          "receivable_msat": 968598000,
          "minimum_htlc_in_msat": 0,
          "minimum_htlc_out_msat": 0,
          "maximum_htlc_out_msat": 990000000,
          "their_to_self_delay": 6,
          "our_to_self_delay": 6,
          "max_accepted_htlcs": 483,
          "state_changes": [
            {
              "timestamp": "2022-12-27T16:46:41.597Z",
              "old_state": "DUALOPEND_OPEN_INIT",
              "new_state": "DUALOPEND_AWAITING_LOCKIN",
              "cause": "remote",
              "message": "Sigs exchanged, waiting for lock-in"
            },
            {
              "timestamp": "2022-12-27T16:46:46.322Z",
              "old_state": "DUALOPEND_AWAITING_LOCKIN",
              "new_state": "CHANNELD_NORMAL",
              "cause": "remote",
              "message": "Lockin complete"
            }
          ],
          "status": [
            "CHANNELD_NORMAL:Will attempt reconnect in 128 seconds"
          ],
          "in_payments_offered": 1,
          "in_offered_msat": 20000000,
          "in_payments_fulfilled": 1,
          "in_fulfilled_msat": 20000000,
          "out_payments_offered": 0,
          "out_offered_msat": 0,
          "out_payments_fulfilled": 0,
          "out_fulfilled_msat": 0,
          "htlcs": []
        }
      ]
    }
  ]
}
◉ tony@tony:/tmp/l1-regtest/regtest:
$ bitcoin-cli -regtest -rpcwallet=default getnewaddress
bcrt1q3ljpd8zth5gn987jus2g94mlmpddheqgjktdek
◉ tony@tony:/tmp/l1-regtest/regtest:
$ bitcoin-cli -regtest generatetoaddress 1 bcrt1q3ljpd8zth5gn987jus2g94mlmpddheqgjktdek > /dev/null
◉ tony@tony:/tmp/l1-regtest/regtest:
$ l1-cli listpeers | jq
{
  "peers": [
    {
      "id": "0311c4890bc6c2142bc21da4c773c40aafe52744774d359f034b137b01d660ce09",
      "connected": false,
      "channels": [
        {
          "state": "ONCHAIN",
          "scratch_txid": "589b636e4b2952fc51033a7c4278cccbddb15bbe6e0c6927df55af43f7275bb1",
          "last_tx_fee_msat": 614000,
          "feerate": {
            "perkw": 253,
            "perkb": 1012
          },
          "owner": "onchaind",
          "short_channel_id": "103x1x0",
          "direction": 0,
          "channel_id": "ca66614515ca5c8139de87f748f556697682e53bc85de8aa64baf3a6d3ce8ccd",
          "funding_txid": "3ee9bc6bb4f98be53b64d79a77b88dd3d66eef445994a08c7f1aa2e017ae2274",
          "funding_outnum": 0,
          "close_to_addr": "bcrt1qde5hpdkqfnaxsxtg45xr6cypncdwn60sv6s9e0",
          "close_to": "00146e6970b6c04cfa681968ad0c3d60819e1ae9e9f0",
          "private": false,
          "opener": "local",
          "closer": "local",
          "alias": {
            "local": "5744536x13446725x44002"
          },
          "features": [
            "option_static_remotekey",
            "option_anchor_outputs"
          ],
          "funding": {
            "local_funds_msat": "1000000000msat",
            "remote_funds_msat": "0msat",
            "pushed_msat": 0
          },
          "to_us_msat": 1000000000,
          "min_to_us_msat": 1000000000,
          "max_to_us_msat": 1000000000,
          "total_msat": 1000000000,
          "fee_base_msat": 1,
          "fee_proportional_millionths": 10,
          "dust_limit_msat": 546000,
          "max_total_htlc_in_msat": 18446744073709552000,
          "their_reserve_msat": 10000000,
          "our_reserve_msat": 10000000,
          "spendable_msat": 988598000,
          "receivable_msat": 0,
          "minimum_htlc_in_msat": 0,
          "minimum_htlc_out_msat": 0,
          "maximum_htlc_out_msat": 990000000,
          "their_to_self_delay": 6,
          "our_to_self_delay": 6,
          "max_accepted_htlcs": 483,
          "state_changes": [
            {
              "timestamp": "2022-12-27T16:46:41.597Z",
              "old_state": "DUALOPEND_OPEN_INIT",
              "new_state": "DUALOPEND_AWAITING_LOCKIN",
              "cause": "user",
              "message": "Sigs exchanged, waiting for lock-in"
            },
            {
              "timestamp": "2022-12-27T16:46:46.230Z",
              "old_state": "DUALOPEND_AWAITING_LOCKIN",
              "new_state": "CHANNELD_NORMAL",
              "cause": "user",
              "message": "Lockin complete"
            },
            {
              "timestamp": "2022-12-27T17:09:42.006Z",
              "old_state": "CHANNELD_NORMAL",
              "new_state": "CHANNELD_SHUTTING_DOWN",
              "cause": "user",
              "message": "User or plugin invoked close command"
            },
            {
              "timestamp": "2022-12-27T17:09:43.009Z",
              "old_state": "CHANNELD_SHUTTING_DOWN",
              "new_state": "AWAITING_UNILATERAL",
              "cause": "user",
              "message": "Forcibly closed by `close` command timeout"
            },
            {
              "timestamp": "2022-12-27T17:12:01.964Z",
              "old_state": "AWAITING_UNILATERAL",
              "new_state": "FUNDING_SPEND_SEEN",
              "cause": "user",
              "message": "Onchain funding spend"
            },
            {
              "timestamp": "2022-12-27T17:12:02.018Z",
              "old_state": "FUNDING_SPEND_SEEN",
              "new_state": "ONCHAIN",
              "cause": "user",
              "message": "Onchain init reply"
            }
          ],
          "status": [
            "ONCHAIN:Tracking our own unilateral close",
            "ONCHAIN:3 outputs unresolved: in 5 blocks will spend DELAYED_OUTPUT_TO_US (589b636e4b2952fc51033a7c4278cccbddb15bbe6e0c6927df55af43f7275bb1:1) using OUR_DELAYED_RETURN_TO_WALLET"
          ],
          "in_payments_offered": 0,
          "in_offered_msat": 0,
          "in_payments_fulfilled": 0,
          "in_fulfilled_msat": 0,
          "out_payments_offered": 0,
          "out_offered_msat": 0,
          "out_payments_fulfilled": 0,
          "out_fulfilled_msat": 0,
          "htlcs": []
        }
      ]
    }
  ]
}
◉ tony@tony:/tmp/l1-regtest/regtest:
$ l2-cli listpeers | jq
{
  "peers": [
    {
      "id": "024b8ece67648d560ad5357153e577eb71ff157541731047bdb9ab91f708cba62e",
      "connected": false,
      "channels": [
        {
          "state": "ONCHAIN",
          "scratch_txid": "5bc1062cf80e87a3f51892383ce4c2515c4ab31d38a1b9c1199091d9e0af0952",
          "last_tx_fee_msat": 284000,
          "feerate": {
            "perkw": 253,
            "perkb": 1012
          },
          "owner": "onchaind",
          "short_channel_id": "103x1x0",
          "direction": 1,
          "channel_id": "ca66614515ca5c8139de87f748f556697682e53bc85de8aa64baf3a6d3ce8ccd",
          "funding_txid": "3ee9bc6bb4f98be53b64d79a77b88dd3d66eef445994a08c7f1aa2e017ae2274",
          "funding_outnum": 0,
          "close_to_addr": "bcrt1qquh432s5ay8gg94cqt927wvlc8c9puadvuq95w",
          "close_to": "0014072f58aa14e90e8416b802caaf399fc1f050f3ad",
          "private": false,
          "opener": "remote",
          "closer": "remote",
          "alias": {
            "local": "7721670x4646220x41974"
          },
          "features": [
            "option_static_remotekey",
            "option_anchor_outputs"
          ],
          "funding": {
            "local_funds_msat": "0msat",
            "remote_funds_msat": "1000000000msat",
            "pushed_msat": 0
          },
          "to_us_msat": 20000000,
          "min_to_us_msat": 0,
          "max_to_us_msat": 20000000,
          "total_msat": 1000000000,
          "fee_base_msat": 1,
          "fee_proportional_millionths": 10,
          "dust_limit_msat": 546000,
          "max_total_htlc_in_msat": 18446744073709552000,
          "their_reserve_msat": 10000000,
          "our_reserve_msat": 10000000,
          "spendable_msat": 10000000,
          "receivable_msat": 968598000,
          "minimum_htlc_in_msat": 0,
          "minimum_htlc_out_msat": 0,
          "maximum_htlc_out_msat": 990000000,
          "their_to_self_delay": 6,
          "our_to_self_delay": 6,
          "max_accepted_htlcs": 483,
          "state_changes": [
            {
              "timestamp": "2022-12-27T16:46:41.597Z",
              "old_state": "DUALOPEND_OPEN_INIT",
              "new_state": "DUALOPEND_AWAITING_LOCKIN",
              "cause": "remote",
              "message": "Sigs exchanged, waiting for lock-in"
            },
            {
              "timestamp": "2022-12-27T16:46:46.322Z",
              "old_state": "DUALOPEND_AWAITING_LOCKIN",
              "new_state": "CHANNELD_NORMAL",
              "cause": "remote",
              "message": "Lockin complete"
            },
            {
              "timestamp": "2022-12-27T17:12:04.519Z",
              "old_state": "CHANNELD_NORMAL",
              "new_state": "AWAITING_UNILATERAL",
              "cause": "onchain",
              "message": "Funding transaction spent"
            },
            {
              "timestamp": "2022-12-27T17:12:04.520Z",
              "old_state": "AWAITING_UNILATERAL",
              "new_state": "FUNDING_SPEND_SEEN",
              "cause": "onchain",
              "message": "Onchain funding spend"
            },
            {
              "timestamp": "2022-12-27T17:12:04.746Z",
              "old_state": "FUNDING_SPEND_SEEN",
              "new_state": "ONCHAIN",
              "cause": "onchain",
              "message": "Onchain init reply"
            }
          ],
          "status": [
            "ONCHAIN:Tracking their illegal close: taking all funds",
            "ONCHAIN:3 outputs unresolved: waiting confirmation that we spent DELAYED_CHEAT_OUTPUT_TO_THEM (589b636e4b2952fc51033a7c4278cccbddb15bbe6e0c6927df55af43f7275bb1:1) using OUR_PENALTY_TX"
          ],
          "in_payments_offered": 1,
          "in_offered_msat": 20000000,
          "in_payments_fulfilled": 1,
          "in_fulfilled_msat": 20000000,
          "out_payments_offered": 0,
          "out_offered_msat": 0,
          "out_payments_fulfilled": 0,
          "out_fulfilled_msat": 0,
          "htlcs": []
        }
      ]
    }
  ]
}
◉ tony@tony:/tmp/l1-regtest/regtest:
$ bitcoin-cli -regtest generatetoaddress 1 bcrt1q3ljpd8zth5gn987jus2g94mlmpddheqgjktdek > /dev/null
◉ tony@tony:/tmp/l1-regtest/regtest:
$ l2-cli listpeers | jq
{
  "peers": [
    {
      "id": "024b8ece67648d560ad5357153e577eb71ff157541731047bdb9ab91f708cba62e",
      "connected": false,
      "channels": [
        {
          "state": "ONCHAIN",
          "scratch_txid": "5bc1062cf80e87a3f51892383ce4c2515c4ab31d38a1b9c1199091d9e0af0952",
          "last_tx_fee_msat": 284000,
          "feerate": {
            "perkw": 253,
            "perkb": 1012
          },
          "owner": "onchaind",
          "short_channel_id": "103x1x0",
          "direction": 1,
          "channel_id": "ca66614515ca5c8139de87f748f556697682e53bc85de8aa64baf3a6d3ce8ccd",
          "funding_txid": "3ee9bc6bb4f98be53b64d79a77b88dd3d66eef445994a08c7f1aa2e017ae2274",
          "funding_outnum": 0,
          "close_to_addr": "bcrt1qquh432s5ay8gg94cqt927wvlc8c9puadvuq95w",
          "close_to": "0014072f58aa14e90e8416b802caaf399fc1f050f3ad",
          "private": false,
          "opener": "remote",
          "closer": "remote",
          "alias": {
            "local": "7721670x4646220x41974"
          },
          "features": [
            "option_static_remotekey",
            "option_anchor_outputs"
          ],
          "funding": {
            "local_funds_msat": "0msat",
            "remote_funds_msat": "1000000000msat",
            "pushed_msat": 0
          },
          "to_us_msat": 20000000,
          "min_to_us_msat": 0,
          "max_to_us_msat": 20000000,
          "total_msat": 1000000000,
          "fee_base_msat": 1,
          "fee_proportional_millionths": 10,
          "dust_limit_msat": 546000,
          "max_total_htlc_in_msat": 18446744073709552000,
          "their_reserve_msat": 10000000,
          "our_reserve_msat": 10000000,
          "spendable_msat": 10000000,
          "receivable_msat": 968598000,
          "minimum_htlc_in_msat": 0,
          "minimum_htlc_out_msat": 0,
          "maximum_htlc_out_msat": 990000000,
          "their_to_self_delay": 6,
          "our_to_self_delay": 6,
          "max_accepted_htlcs": 483,
          "state_changes": [
            {
              "timestamp": "2022-12-27T16:46:41.597Z",
              "old_state": "DUALOPEND_OPEN_INIT",
              "new_state": "DUALOPEND_AWAITING_LOCKIN",
              "cause": "remote",
              "message": "Sigs exchanged, waiting for lock-in"
            },
            {
              "timestamp": "2022-12-27T16:46:46.322Z",
              "old_state": "DUALOPEND_AWAITING_LOCKIN",
              "new_state": "CHANNELD_NORMAL",
              "cause": "remote",
              "message": "Lockin complete"
            },
            {
              "timestamp": "2022-12-27T17:12:04.519Z",
              "old_state": "CHANNELD_NORMAL",
              "new_state": "AWAITING_UNILATERAL",
              "cause": "onchain",
              "message": "Funding transaction spent"
            },
            {
              "timestamp": "2022-12-27T17:12:04.520Z",
              "old_state": "AWAITING_UNILATERAL",
              "new_state": "FUNDING_SPEND_SEEN",
              "cause": "onchain",
              "message": "Onchain funding spend"
            },
            {
              "timestamp": "2022-12-27T17:12:04.746Z",
              "old_state": "FUNDING_SPEND_SEEN",
              "new_state": "ONCHAIN",
              "cause": "onchain",
              "message": "Onchain init reply"
            }
          ],
          "status": [
            "ONCHAIN:Tracking their illegal close: taking all funds",
            "ONCHAIN:All outputs resolved: waiting 99 more blocks before forgetting channel"
          ],
          "in_payments_offered": 1,
          "in_offered_msat": 20000000,
          "in_payments_fulfilled": 1,
          "in_fulfilled_msat": 20000000,
          "out_payments_offered": 0,
          "out_offered_msat": 0,
          "out_payments_fulfilled": 0,
          "out_fulfilled_msat": 0,
          "htlcs": []
        }
      ]
    }
  ]
}
◉ tony@tony:/tmp/l1-regtest/regtest:
$ bitcoin-cli -regtest generatetoaddress 99 bcrt1q3ljpd8zth5gn987jus2g94mlmpddheqgjktdek > /dev/null
◉ tony@tony:/tmp/l1-regtest/regtest:
$ l2-cli listfunds | jq .outputs
[
  {
    "txid": "0c5a6189149e3900ffb8c4e4c050ba081ce3c2da55402e8d47272a87d0a544cc",
    "output": 0,
    "amount_msat": 998935000,
    "scriptpubkey": "0014072f58aa14e90e8416b802caaf399fc1f050f3ad",
    "address": "bcrt1qquh432s5ay8gg94cqt927wvlc8c9puadvuq95w",
    "status": "confirmed",
    "blockheight": 110,
    "reserved": false
  }
]
◉ tony@tony:/tmp/l1-regtest/regtest:
$ l1-cli listfunds | jq .outputs
[
  {
    "txid": "3ee9bc6bb4f98be53b64d79a77b88dd3d66eef445994a08c7f1aa2e017ae2274",
    "output": 1,
    "amount_msat": 98999846000,
    "scriptpubkey": "0014b3aa21fdb4a48726eaa4e1b0f886df042cce226f",
    "address": "bcrt1qkw4zrld55jrjd64yuxc03pklqskvugn079prvj",
    "status": "confirmed",
    "blockheight": 103,
    "reserved": false
  }
]

Resources