Skip to content

Commit

Permalink
bolt2: coop-close channel between two nodes
Browse files Browse the repository at this point in the history
Signed-off-by: Vincenzo Palazzo <[email protected]>
  • Loading branch information
vincenzopalazzo committed Mar 9, 2022
1 parent 22a05fc commit c464f7d
Show file tree
Hide file tree
Showing 4 changed files with 290 additions and 1 deletion.
1 change: 1 addition & 0 deletions lnprototest/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@
negotiated,
DualFundAccept,
Wait,
CloseChannel,
)
from .structure import Sequence, OneOf, AnyOrder, TryAll
from .runner import (
Expand Down
13 changes: 13 additions & 0 deletions lnprototest/event.py
Original file line number Diff line number Diff line change
Expand Up @@ -532,6 +532,19 @@ def action(self, runner: "Runner") -> bool:
return True


class CloseChannel(Event):
"""Implementing the lnprototest event related to the
close channel operation.
BOLT 2"""
def __init__(self, channel_id: str):
super(CloseChannel, self).__init__()
self.channel_id = channel_id

def action(self, runner: "Runner") -> bool:
super().action(runner)
return runner.close_channel(self.channel_id)


def msg_to_stash(runner: "Runner", event: Event, msg: Message) -> None:
"""ExpectMsg and Msg save every field to the stash, in order"""
fields = msg.to_py()
Expand Down
13 changes: 12 additions & 1 deletion tests/helpers.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import bitcoin.core
import coincurve
from typing import Tuple
from lnprototest import privkey_expand
from lnprototest import privkey_expand, KeySet

# Here are the keys to spend funds, derived from BIP32 seed
# `0000000000000000000000000000000000000000000000000000000000000001`:
Expand Down Expand Up @@ -134,3 +134,14 @@ def pubkey_of(privkey: str) -> str:
return (
coincurve.PublicKey.from_secret(privkey_expand(privkey).secret).format().hex()
)


def gen_random_keyset(counter: int = 20) -> KeySet:
"""Helper function to generate a random keyset with the possibility."""
return KeySet(
revocation_base_secret=f"{counter + 1}",
payment_base_secret=f"{counter + 2}",
htlc_base_secret=f"{counter + 3}",
delayed_payment_base_secret=f"{counter + 4}",
shachain_seed="00" * 32,
)
264 changes: 264 additions & 0 deletions tests/test_bolt2-01-close_channel.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,264 @@
#! /usr/bin/env python3
# Variations on open_channel, accepter + opener perspectives

from lnprototest import (
TryAll,
Connect,
Block,
FundChannel,
ExpectMsg,
ExpectTx,
Msg,
RawMsg,
AcceptFunding,
CreateFunding,
Commit,
Runner,
remote_funding_pubkey,
remote_revocation_basepoint,
remote_payment_basepoint,
remote_htlc_basepoint,
remote_per_commitment_point,
remote_delayed_payment_basepoint,
Side,
CheckEq,
msat,
remote_funding_privkey,
regtest_hash,
bitfield, CloseChannel, Wait, OneOf,
)
from lnprototest.stash import (
sent,
rcvd,
commitsig_to_send,
commitsig_to_recv,
channel_id,
funding_txid,
funding_tx,
funding,
)
from helpers import utxo, tx_spendable, funding_amount_for_utxo, pubkey_of, gen_random_keyset


def test_close_channel_shutdown_msg(runner: Runner) -> None:
"""Close the channel with the other peer, and check if the
shutdown message works in the expected way."""
local_funding_privkey = "20"

local_keyset = gen_random_keyset()

test = [
Block(blockheight=102, txs=[tx_spendable]),
Connect(connprivkey="02"),
ExpectMsg("init"),
TryAll(
# BOLT-a12da24dd0102c170365124782b46d9710950ac1 #9:
# | 20/21 | `option_anchor_outputs` | Anchor outputs
Msg("init", globalfeatures="", features=bitfield(13, 21)),
# BOLT #9:
# | 12/13 | `option_static_remotekey` | Static key for remote output
Msg("init", globalfeatures="", features=bitfield(13)),
# And not.
Msg("init", globalfeatures="", features=""),
),
TryAll(
# Accepter side: we initiate a new channel.
[
Msg(
"open_channel",
chain_hash=regtest_hash,
temporary_channel_id="00" * 32,
funding_satoshis=funding_amount_for_utxo(0),
push_msat=0,
dust_limit_satoshis=546,
max_htlc_value_in_flight_msat=4294967295,
channel_reserve_satoshis=9998,
htlc_minimum_msat=0,
feerate_per_kw=253,
# We use 5, because c-lightning runner uses 6, so this is different.
to_self_delay=5,
max_accepted_htlcs=483,
funding_pubkey=pubkey_of(local_funding_privkey),
revocation_basepoint=local_keyset.revocation_basepoint(),
payment_basepoint=local_keyset.payment_basepoint(),
delayed_payment_basepoint=local_keyset.delayed_payment_basepoint(),
htlc_basepoint=local_keyset.htlc_basepoint(),
first_per_commitment_point=local_keyset.per_commit_point(0),
channel_flags=1,
),
# Ignore unknown odd messages
TryAll([], RawMsg(bytes.fromhex("270F"))),
ExpectMsg(
"accept_channel",
temporary_channel_id=sent(),
funding_pubkey=remote_funding_pubkey(),
revocation_basepoint=remote_revocation_basepoint(),
payment_basepoint=remote_payment_basepoint(),
delayed_payment_basepoint=remote_delayed_payment_basepoint(),
htlc_basepoint=remote_htlc_basepoint(),
first_per_commitment_point=remote_per_commitment_point(0),
minimum_depth=3,
channel_reserve_satoshis=9998,
),
# Ignore unknown odd messages
TryAll([], RawMsg(bytes.fromhex("270F"))),
# Create and stash Funding object and FundingTx
CreateFunding(
*utxo(0),
local_node_privkey="02",
local_funding_privkey=local_funding_privkey,
remote_node_privkey=runner.get_node_privkey(),
remote_funding_privkey=remote_funding_privkey()
),
Commit(
funding=funding(),
opener=Side.local,
local_keyset=local_keyset,
local_to_self_delay=rcvd("to_self_delay", int),
remote_to_self_delay=sent("to_self_delay", int),
local_amount=msat(sent("funding_satoshis", int)),
remote_amount=0,
local_dust_limit=546,
remote_dust_limit=546,
feerate=253,
local_features=sent("init.features"),
remote_features=rcvd("init.features"),
),
Msg(
"funding_created",
temporary_channel_id=rcvd(),
funding_txid=funding_txid(),
funding_output_index=0,
signature=commitsig_to_send(),
),
ExpectMsg(
"funding_signed",
channel_id=channel_id(),
signature=commitsig_to_recv(),
),
# Mine it and get it deep enough to confirm channel.
Block(blockheight=103, number=3, txs=[funding_tx()]),
ExpectMsg(
"funding_locked",
channel_id=channel_id(),
next_per_commitment_point="032405cbd0f41225d5f203fe4adac8401321a9e05767c5f8af97d51d2e81fbb206",
),
Msg(
"funding_locked",
channel_id=channel_id(),
next_per_commitment_point="027eed8389cf8eb715d73111b73d94d2c2d04bf96dc43dfd5b0970d80b3617009d",
),
# Ignore unknown odd messages
TryAll([], RawMsg(bytes.fromhex("270F"))),
],
# Now we test the 'opener' side of an open_channel (node initiates)
[
FundChannel(amount=999877),
# This gives a channel of 999877sat
ExpectMsg(
"open_channel",
chain_hash=regtest_hash,
funding_satoshis=999877,
push_msat=0,
dust_limit_satoshis=546,
htlc_minimum_msat=0,
channel_reserve_satoshis=9998,
to_self_delay=6,
funding_pubkey=remote_funding_pubkey(),
revocation_basepoint=remote_revocation_basepoint(),
payment_basepoint=remote_payment_basepoint(),
delayed_payment_basepoint=remote_delayed_payment_basepoint(),
htlc_basepoint=remote_htlc_basepoint(),
first_per_commitment_point=remote_per_commitment_point(0),
# FIXME: Check more fields!
channel_flags="01",
),
Msg(
"accept_channel",
temporary_channel_id=rcvd(),
dust_limit_satoshis=546,
max_htlc_value_in_flight_msat=4294967295,
channel_reserve_satoshis=9998,
htlc_minimum_msat=0,
minimum_depth=3,
max_accepted_htlcs=483,
# We use 5, because c-lightning runner uses 6, so this is different.
to_self_delay=5,
funding_pubkey=pubkey_of(local_funding_privkey),
revocation_basepoint=local_keyset.revocation_basepoint(),
payment_basepoint=local_keyset.payment_basepoint(),
delayed_payment_basepoint=local_keyset.delayed_payment_basepoint(),
htlc_basepoint=local_keyset.htlc_basepoint(),
first_per_commitment_point=local_keyset.per_commit_point(0),
),
# Ignore unknown odd messages
TryAll([], RawMsg(bytes.fromhex("270F"))),
ExpectMsg(
"funding_created", temporary_channel_id=rcvd("temporary_channel_id")
),
# Now we can finally stash the funding information.
AcceptFunding(
rcvd("funding_created.funding_txid"),
funding_output_index=rcvd(
"funding_created.funding_output_index", int
),
funding_amount=rcvd("open_channel.funding_satoshis", int),
local_node_privkey="02",
local_funding_privkey=local_funding_privkey,
remote_node_privkey=runner.get_node_privkey(),
remote_funding_privkey=remote_funding_privkey(),
),
Commit(
funding=funding(),
opener=Side.remote,
local_keyset=local_keyset,
local_to_self_delay=rcvd("open_channel.to_self_delay", int),
remote_to_self_delay=sent("accept_channel.to_self_delay", int),
local_amount=0,
remote_amount=msat(rcvd("open_channel.funding_satoshis", int)),
local_dust_limit=sent("accept_channel.dust_limit_satoshis", int),
remote_dust_limit=rcvd("open_channel.dust_limit_satoshis", int),
feerate=rcvd("open_channel.feerate_per_kw", int),
local_features=sent("init.features"),
remote_features=rcvd("init.features"),
),
# Now we've created commit, we can check sig is valid!
CheckEq(rcvd("funding_created.signature"), commitsig_to_recv()),
Msg(
"funding_signed",
channel_id=channel_id(),
signature=commitsig_to_send(),
),
# It will broadcast tx
ExpectTx(rcvd("funding_created.funding_txid")),
# Mine three blocks to confirm channel.
Block(blockheight=103, number=3),
Msg(
"funding_locked",
channel_id=sent(),
next_per_commitment_point=local_keyset.per_commit_point(1),
),
ExpectMsg(
"funding_locked",
channel_id=sent(),
next_per_commitment_point=remote_per_commitment_point(1),
),
# Ignore unknown odd messages
TryAll([], RawMsg(bytes.fromhex("270F"))),
# After the funding locked the node update with the
# channel data the other side with a channel update.
ExpectMsg("channel_update"),
Block(blockheight=106, number=6),
# TODO this should be optional
ExpectMsg("announcement_signatures"),
CloseChannel(channel_id=sent()),
ExpectMsg(
"shutdown",
#TODO: injects the scriptpubkey here
channel_id=sent(),
),
],
),
]

runner.run(test)

0 comments on commit c464f7d

Please sign in to comment.