diff --git a/.flake8 b/.flake8 new file mode 100644 index 0000000..e0ea542 --- /dev/null +++ b/.flake8 @@ -0,0 +1,3 @@ +[flake8] +max-line-length = 88 +extend-ignore = E203 \ No newline at end of file diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 02feecc..1fba9c2 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -19,10 +19,14 @@ jobs: - name: Install dependencies run: | python -m pip install --upgrade pip - pip install flake8 pytest + pip install flake8 pytest black if [ -f requirements.txt ]; then pip install -r requirements.txt; fi pip install -e . - - name: Lint with flake8 + - name: Code style check + run: | + pip install black + black . --check --diff + - name: flake8 check run: | flake8 --ignore=E501,E731,W503 - name: Test with pytest diff --git a/.gitignore b/.gitignore index eb15f15..d365131 100644 --- a/.gitignore +++ b/.gitignore @@ -5,3 +5,4 @@ tags **/github-merge.py .venv **.egg-info +.idea diff --git a/Makefile b/Makefile index b20d523..7dff355 100644 --- a/Makefile +++ b/Makefile @@ -13,7 +13,7 @@ check-pytest-found: check: check-pytest-found $(PYTEST) $(PYTEST_ARGS) $(TEST_DIR) -check-source: check-flake8 check-mypy check-internal-tests +check-source: check-fmt check-flake8 check-mypy check-internal-tests check-flake8: flake8 --ignore=E501,E731,W503 @@ -29,5 +29,11 @@ check-quotes/%: % check-quotes: $(PYTHONFILES:%=check-quotes/%) +check-fmt: + black --check . + +fmt: + black . + TAGS: etags `find . -name '*.py'` diff --git a/lnprototest/__init__.py b/lnprototest/__init__.py index e74bff2..76cce01 100644 --- a/lnprototest/__init__.py +++ b/lnprototest/__init__.py @@ -11,17 +11,66 @@ """ from .errors import EventError, SpecFileError -from .event import Event, Connect, Disconnect, Msg, RawMsg, ExpectMsg, MustNotMsg, Block, ExpectTx, FundChannel, InitRbf, Invoice, AddHtlc, CheckEq, ExpectError, ResolvableInt, ResolvableStr, Resolvable, ResolvableBool, msat, negotiated, DualFundAccept, Wait +from .event import ( + Event, + Connect, + Disconnect, + Msg, + RawMsg, + ExpectMsg, + MustNotMsg, + Block, + ExpectTx, + FundChannel, + InitRbf, + Invoice, + AddHtlc, + CheckEq, + ExpectError, + ResolvableInt, + ResolvableStr, + Resolvable, + ResolvableBool, + msat, + negotiated, + DualFundAccept, + Wait, +) from .structure import Sequence, OneOf, AnyOrder, TryAll -from .runner import Runner, Conn, remote_revocation_basepoint, remote_payment_basepoint, remote_delayed_payment_basepoint, remote_htlc_basepoint, remote_per_commitment_point, remote_per_commitment_secret, remote_funding_pubkey, remote_funding_privkey +from .runner import ( + Runner, + Conn, + remote_revocation_basepoint, + remote_payment_basepoint, + remote_delayed_payment_basepoint, + remote_htlc_basepoint, + remote_per_commitment_point, + remote_per_commitment_secret, + remote_funding_pubkey, + remote_funding_privkey, +) from .dummyrunner import DummyRunner -from .namespace import peer_message_namespace, namespace, assign_namespace, make_namespace +from .namespace import ( + peer_message_namespace, + namespace, + assign_namespace, + make_namespace, +) from .bitfield import bitfield, has_bit, bitfield_len from .signature import SigType, Sig from .keyset import KeySet from .commit_tx import Commit, HTLC, UpdateCommit from .utils import Side, regtest_hash, privkey_expand, wait_for -from .funding import AcceptFunding, CreateFunding, CreateDualFunding, Funding, AddInput, AddOutput, FinalizeFunding, AddWitnesses +from .funding import ( + AcceptFunding, + CreateFunding, + CreateDualFunding, + Funding, + AddInput, + AddOutput, + FinalizeFunding, + AddWitnesses, +) from .proposals import dual_fund_csv, channel_type_csv __all__ = [ diff --git a/lnprototest/backend/__init__.py b/lnprototest/backend/__init__.py index 5fab85c..1857eeb 100644 --- a/lnprototest/backend/__init__.py +++ b/lnprototest/backend/__init__.py @@ -1,7 +1,4 @@ from .backend import Backend from .bitcoind import Bitcoind -__all__ = [ - "Backend", - "Bitcoind" -] +__all__ = ["Backend", "Bitcoind"] diff --git a/lnprototest/backend/bitcoind.py b/lnprototest/backend/bitcoind.py index 06178d0..e9a4a55 100644 --- a/lnprototest/backend/bitcoind.py +++ b/lnprototest/backend/bitcoind.py @@ -9,6 +9,7 @@ import subprocess import logging +from typing import Any, Callable from ephemeral_port_reserve import reserve from bitcoin.rpc import RawProxy from .backend import Backend @@ -22,26 +23,28 @@ class BitcoinProxy: throwaway connections. This is easier than to reach into the RPC library to close, reopen and reauth upon failure. """ - def __init__(self, btc_conf_file, *args, **kwargs): + + def __init__(self, btc_conf_file: str, *args: Any, **kwargs: Any): self.btc_conf_file = btc_conf_file - def __getattr__(self, name): - if name.startswith('__') and name.endswith('__'): + def __getattr__(self, name: str) -> Callable: + if name.startswith("__") and name.endswith("__"): # Python internal stuff raise AttributeError - def f(*args): + def f(*args: Any) -> Callable: self.__proxy = RawProxy(btc_conf_file=self.btc_conf_file) - logging.debug("Calling {name} with arguments {args}".format( - name=name, - args=args - )) + logging.debug( + "Calling {name} with arguments {args}".format(name=name, args=args) + ) res = self.__proxy._call(name, *args) - logging.debug("Result for {name} call: {res}".format( - name=name, - res=res, - )) + logging.debug( + "Result for {name} call: {res}".format( + name=name, + res=res, + ) + ) return res # Make debuggers show rather than None: # Sanity check raise ValueError("bitcoind not initialized") - self.btc_version = self.rpc.getnetworkinfo()['version'] + self.btc_version = self.rpc.getnetworkinfo()["version"] assert self.btc_version is not None logging.info("Bitcoin Core version {}".format(self.btc_version)) if self.btc_version >= 210000: @@ -102,14 +107,16 @@ def start(self) -> None: assert self.proc.stdout # Wait for it to startup. - while b'Done loading' not in self.proc.stdout.readline(): + while b"Done loading" not in self.proc.stdout.readline(): pass self.version_compatibility() # Block #1. # Privkey the coinbase spends to: # cUB4V7VCk6mX32981TWviQVLkj3pa2zBcXrjMZ9QwaZB5Kojhp59 - self.rpc.submitblock('0000002006226e46111a0b59caaf126043eb5bbf28c34f3a5e332a1fc7b2b73cf188910f84591a56720aabc8023cecf71801c5e0f9d049d0c550ab42412ad12a67d89f3a3dbb6c60ffff7f200400000001020000000001010000000000000000000000000000000000000000000000000000000000000000ffffffff03510101ffffffff0200f2052a0100000016001419f5016f07fe815f611df3a2a0802dbd74e634c40000000000000000266a24aa21a9ede2f61c3f71d1defd3fa999dfa36953755c690689799962b48bebd836974e8cf90120000000000000000000000000000000000000000000000000000000000000000000000000') + self.rpc.submitblock( + "0000002006226e46111a0b59caaf126043eb5bbf28c34f3a5e332a1fc7b2b73cf188910f84591a56720aabc8023cecf71801c5e0f9d049d0c550ab42412ad12a67d89f3a3dbb6c60ffff7f200400000001020000000001010000000000000000000000000000000000000000000000000000000000000000ffffffff03510101ffffffff0200f2052a0100000016001419f5016f07fe815f611df3a2a0802dbd74e634c40000000000000000266a24aa21a9ede2f61c3f71d1defd3fa999dfa36953755c690689799962b48bebd836974e8cf90120000000000000000000000000000000000000000000000000000000000000000000000000" + ) self.rpc.generatetoaddress(100, self.rpc.getnewaddress()) def stop(self) -> None: @@ -119,5 +126,5 @@ def restart(self) -> None: # Only restart if we have to. if self.rpc.getblockcount() != 101 or self.rpc.getrawmempool() != []: self.stop() - shutil.rmtree(os.path.join(self.bitcoin_dir, 'regtest')) + shutil.rmtree(os.path.join(self.bitcoin_dir, "regtest")) self.start() diff --git a/lnprototest/bitfield.py b/lnprototest/bitfield.py index eae8fd5..b84461a 100644 --- a/lnprototest/bitfield.py +++ b/lnprototest/bitfield.py @@ -33,5 +33,5 @@ def bitfield(*args: int) -> str: bytelen = (max(args) + 8) // 8 bfield = bytearray(bytelen) for bitnum in args: - bfield[bytelen - 1 - bitnum // 8] |= (1 << (bitnum % 8)) + bfield[bytelen - 1 - bitnum // 8] |= 1 << (bitnum % 8) return bfield.hex() diff --git a/lnprototest/clightning/__init__.py b/lnprototest/clightning/__init__.py index fc959a4..b03199b 100644 --- a/lnprototest/clightning/__init__.py +++ b/lnprototest/clightning/__init__.py @@ -16,6 +16,4 @@ from .clightning import Runner -__all__ = [ - "Runner" -] +__all__ = ["Runner"] diff --git a/lnprototest/clightning/clightning.py b/lnprototest/clightning/clightning.py index 346956b..d2bc7dc 100644 --- a/lnprototest/clightning/clightning.py +++ b/lnprototest/clightning/clightning.py @@ -30,18 +30,24 @@ from typing import Dict, Any, Callable, List, Optional, cast TIMEOUT = int(os.getenv("TIMEOUT", "30")) -LIGHTNING_SRC = os.path.join(os.getcwd(), os.getenv("LIGHTNING_SRC", '../lightning/')) +LIGHTNING_SRC = os.path.join(os.getcwd(), os.getenv("LIGHTNING_SRC", "../lightning/")) class CLightningConn(lnprototest.Conn): def __init__(self, connprivkey: str, port: int): super().__init__(connprivkey) # FIXME: pyln.proto.wire should just use coincurve PrivateKey! - self.connection = pyln.proto.wire.connect(pyln.proto.wire.PrivateKey(bytes.fromhex(self.connprivkey.to_hex())), - # FIXME: Ask node for pubkey - pyln.proto.wire.PublicKey(bytes.fromhex("0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798")), - '127.0.0.1', - port) + self.connection = pyln.proto.wire.connect( + pyln.proto.wire.PrivateKey(bytes.fromhex(self.connprivkey.to_hex())), + # FIXME: Ask node for pubkey + pyln.proto.wire.PublicKey( + bytes.fromhex( + "0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798" + ) + ), + "127.0.0.1", + port, + ) class Runner(lnprototest.Runner): @@ -51,7 +57,7 @@ def __init__(self, config: Any): self.fundchannel_future: Optional[Any] = None self.is_fundchannel_kill = False - directory = tempfile.mkdtemp(prefix='lnpt-cl-') + directory = tempfile.mkdtemp(prefix="lnpt-cl-") self.bitcoind = Bitcoind(directory) self.bitcoind.start() self.executor = futures.ThreadPoolExecutor(max_workers=20) @@ -65,49 +71,66 @@ def __init__(self, config: Any): for flag in config.getoption("runner_args"): self.startup_flags.append("--{}".format(flag)) - opts = subprocess.run(['{}/lightningd/lightningd'.format(LIGHTNING_SRC), - '--list-features-only'], - stdout=subprocess.PIPE, check=True).stdout.decode('utf-8').splitlines() + opts = ( + subprocess.run( + [ + "{}/lightningd/lightningd".format(LIGHTNING_SRC), + "--list-features-only", + ], + stdout=subprocess.PIPE, + check=True, + ) + .stdout.decode("utf-8") + .splitlines() + ) self.options: Dict[str, str] = {} for o in opts: if o.startswith("supports_"): self.options[o] = "true" else: - k, v = o.split('/') + k, v = o.split("/") self.options[k] = v def get_keyset(self) -> KeySet: - return KeySet(revocation_base_secret='0000000000000000000000000000000000000000000000000000000000000011', - payment_base_secret='0000000000000000000000000000000000000000000000000000000000000012', - delayed_payment_base_secret='0000000000000000000000000000000000000000000000000000000000000013', - htlc_base_secret='0000000000000000000000000000000000000000000000000000000000000014', - shachain_seed='FF' * 32) + return KeySet( + revocation_base_secret="0000000000000000000000000000000000000000000000000000000000000011", + payment_base_secret="0000000000000000000000000000000000000000000000000000000000000012", + delayed_payment_base_secret="0000000000000000000000000000000000000000000000000000000000000013", + htlc_base_secret="0000000000000000000000000000000000000000000000000000000000000014", + shachain_seed="FF" * 32, + ) def get_node_privkey(self) -> str: - return '01' + return "01" def get_node_bitcoinkey(self) -> str: - return '0000000000000000000000000000000000000000000000000000000000000010' + return "0000000000000000000000000000000000000000000000000000000000000010" def start(self) -> None: - self.proc = subprocess.Popen(['{}/lightningd/lightningd'.format(LIGHTNING_SRC), - '--lightning-dir={}'.format(self.lightning_dir), - '--funding-confirms=3', - '--dev-force-privkey=0000000000000000000000000000000000000000000000000000000000000001', - '--dev-force-bip32-seed=0000000000000000000000000000000000000000000000000000000000000001', - '--dev-force-channel-secrets=0000000000000000000000000000000000000000000000000000000000000010/0000000000000000000000000000000000000000000000000000000000000011/0000000000000000000000000000000000000000000000000000000000000012/0000000000000000000000000000000000000000000000000000000000000013/0000000000000000000000000000000000000000000000000000000000000014/FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF', - '--dev-bitcoind-poll=1', - '--dev-fast-gossip', - '--dev-no-htlc-timeout', - '--bind-addr=127.0.0.1:{}'.format(self.lightning_port), - '--network=regtest', - '--bitcoin-rpcuser=rpcuser', - '--bitcoin-rpcpassword=rpcpass', - '--bitcoin-rpcport={}'.format(self.bitcoind.port), - '--log-level=debug', - '--log-file=log'] - + self.startup_flags) - self.rpc = pyln.client.LightningRpc(os.path.join(self.lightning_dir, "regtest", "lightning-rpc")) + self.proc = subprocess.Popen( + [ + "{}/lightningd/lightningd".format(LIGHTNING_SRC), + "--lightning-dir={}".format(self.lightning_dir), + "--funding-confirms=3", + "--dev-force-privkey=0000000000000000000000000000000000000000000000000000000000000001", + "--dev-force-bip32-seed=0000000000000000000000000000000000000000000000000000000000000001", + "--dev-force-channel-secrets=0000000000000000000000000000000000000000000000000000000000000010/0000000000000000000000000000000000000000000000000000000000000011/0000000000000000000000000000000000000000000000000000000000000012/0000000000000000000000000000000000000000000000000000000000000013/0000000000000000000000000000000000000000000000000000000000000014/FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF", + "--dev-bitcoind-poll=1", + "--dev-fast-gossip", + "--dev-no-htlc-timeout", + "--bind-addr=127.0.0.1:{}".format(self.lightning_port), + "--network=regtest", + "--bitcoin-rpcuser=rpcuser", + "--bitcoin-rpcpassword=rpcpass", + "--bitcoin-rpcport={}".format(self.bitcoind.port), + "--log-level=debug", + "--log-file=log", + ] + + self.startup_flags + ) + self.rpc = pyln.client.LightningRpc( + os.path.join(self.lightning_dir, "regtest", "lightning-rpc") + ) def node_ready(rpc: pyln.client.LightningRpc) -> bool: try: @@ -147,7 +170,7 @@ def stop(self) -> None: def connect(self, event: Event, connprivkey: str) -> None: self.add_conn(CLightningConn(connprivkey, self.lightning_port)) - def __enter__(self) -> 'Runner': + def __enter__(self) -> "Runner": self.start() return self @@ -155,7 +178,7 @@ def __exit__(self, type: Any, value: Any, tb: Any) -> None: self.stop() def restart(self) -> None: - if self.config.getoption('verbose'): + if self.config.getoption("verbose"): print("[RESTART]") for cb in self.cleanup_callbacks: cb() @@ -183,7 +206,7 @@ def add_blocks(self, event: Event, txs: List[str], n: int) -> None: self.bitcoind.rpc.sendrawtransaction(tx) self.bitcoind.rpc.generatetoaddress(n, self.bitcoind.rpc.getnewaddress()) - wait_for(lambda: self.rpc.getinfo()['blockheight'] == self.getblockheight()) + wait_for(lambda: self.rpc.getinfo()["blockheight"] == self.getblockheight()) def recv(self, event: Event, conn: Conn, outbuf: bytes) -> None: try: @@ -191,116 +214,156 @@ def recv(self, event: Event, conn: Conn, outbuf: bytes) -> None: except BrokenPipeError: # This happens when they've sent an error and closed; try # reading it to figure out what went wrong. - fut = self.executor.submit(cast(CLightningConn, conn).connection.read_message) + fut = self.executor.submit( + cast(CLightningConn, conn).connection.read_message + ) try: msg = fut.result(1) except futures.TimeoutError: msg = None if msg: - raise EventError(event, "Connection closed after sending {}".format(msg.hex())) + raise EventError( + event, "Connection closed after sending {}".format(msg.hex()) + ) else: raise EventError(event, "Connection closed") - def fundchannel(self, - event: Event, - conn: Conn, - amount: int, - feerate: int = 253, - expect_fail: bool = False) -> None: + def fundchannel( + self, + event: Event, + conn: Conn, + amount: int, + feerate: int = 253, + expect_fail: bool = False, + ) -> None: """ - event - the event which cause this, for error logging - conn - which conn (i.e. peer) to fund. - amount - amount to fund the channel with - feerate - feerate, in kiloweights - expect_fail - true if this command is expected to error/fail + event - the event which cause this, for error logging + conn - which conn (i.e. peer) to fund. + amount - amount to fund the channel with + feerate - feerate, in kiloweights + expect_fail - true if this command is expected to error/fail """ # First, check that another fundchannel isn't already running if self.fundchannel_future: if not self.fundchannel_future.done(): - raise RuntimeError("{} called fundchannel while another channel funding (fundchannel/init_rbf) is still in process".format(event)) + raise RuntimeError( + "{} called fundchannel while another channel funding (fundchannel/init_rbf) is still in process".format( + event + ) + ) self.fundchannel_future = None - def _fundchannel(runner: Runner, conn: Conn, amount: int, feerate: int, expect_fail: bool = False) -> str: + def _fundchannel( + runner: Runner, + conn: Conn, + amount: int, + feerate: int, + expect_fail: bool = False, + ) -> str: peer_id = conn.pubkey.format().hex() # Need to supply feerate here, since regtest cannot estimate fees - return runner.rpc.fundchannel(peer_id, amount, feerate='{}perkw'.format(feerate)) + return runner.rpc.fundchannel( + peer_id, amount, feerate="{}perkw".format(feerate) + ) def _done(fut: Any) -> None: exception = fut.exception(0) if exception and not self.is_fundchannel_kill and not expect_fail: - raise(exception) + raise (exception) self.fundchannel_future = None self.is_fundchannel_kill = False self.cleanup_callbacks.remove(self.kill_fundchannel) - fut = self.executor.submit(_fundchannel, self, conn, amount, feerate, expect_fail) + fut = self.executor.submit( + _fundchannel, self, conn, amount, feerate, expect_fail + ) fut.add_done_callback(_done) self.fundchannel_future = fut self.cleanup_callbacks.append(self.kill_fundchannel) - def init_rbf(self, event: Event, conn: Conn, - channel_id: str, amount: int, - utxo_txid: str, utxo_outnum: int, feerate: int) -> None: + def init_rbf( + self, + event: Event, + conn: Conn, + channel_id: str, + amount: int, + utxo_txid: str, + utxo_outnum: int, + feerate: int, + ) -> None: if self.fundchannel_future: self.kill_fundchannel() startweight = 42 + 172 # base weight, funding output # Build a utxo using the given utxo - fmt_feerate = '{}perkw'.format(feerate) - utxos = ['{}:{}'.format(utxo_txid, utxo_outnum)] - initial_psbt = self.rpc.utxopsbt(amount, - fmt_feerate, - startweight, utxos, - reservedok=True, - min_witness_weight=110, - locktime=0, excess_as_change=True)['psbt'] + fmt_feerate = "{}perkw".format(feerate) + utxos = ["{}:{}".format(utxo_txid, utxo_outnum)] + initial_psbt = self.rpc.utxopsbt( + amount, + fmt_feerate, + startweight, + utxos, + reservedok=True, + min_witness_weight=110, + locktime=0, + excess_as_change=True, + )["psbt"] def _run_rbf(runner: Runner, conn: Conn) -> Dict[str, Any]: - bump = runner.rpc.openchannel_bump(channel_id, amount, initial_psbt, - funding_feerate=fmt_feerate) - update = runner.rpc.openchannel_update(channel_id, bump['psbt']) + bump = runner.rpc.openchannel_bump( + channel_id, amount, initial_psbt, funding_feerate=fmt_feerate + ) + update = runner.rpc.openchannel_update(channel_id, bump["psbt"]) # Run until they're done sending us updates - while not update['commitments_secured']: - update = runner.rpc.openchannel_update(channel_id, update['psbt']) - signed_psbt = runner.rpc.signpsbt(update['psbt'])['signed_psbt'] + while not update["commitments_secured"]: + update = runner.rpc.openchannel_update(channel_id, update["psbt"]) + signed_psbt = runner.rpc.signpsbt(update["psbt"])["signed_psbt"] return runner.rpc.openchannel_signed(channel_id, signed_psbt) def _done(fut: Any) -> None: exception = fut.exception(0) if exception: - raise(exception) + raise (exception) fut = self.executor.submit(_run_rbf, self, conn) fut.add_done_callback(_done) def invoice(self, event: Event, amount: int, preimage: str) -> None: - self.rpc.invoice(msatoshi=amount, - label=str(event), - description='invoice from {}'.format(event), - preimage=preimage) + self.rpc.invoice( + msatoshi=amount, + label=str(event), + description="invoice from {}".format(event), + preimage=preimage, + ) def accept_add_fund(self, event: Event) -> None: - self.rpc.call('funderupdate', {'policy': 'match', - 'policy_mod': 100, - 'fuzz_percent': 0, - 'leases_only': False}) - - def addhtlc(self, event: Event, conn: Conn, - amount: int, preimage: str) -> None: + self.rpc.call( + "funderupdate", + { + "policy": "match", + "policy_mod": 100, + "fuzz_percent": 0, + "leases_only": False, + }, + ) + + def addhtlc(self, event: Event, conn: Conn, amount: int, preimage: str) -> None: payhash = hashlib.sha256(bytes.fromhex(preimage)).hexdigest() routestep = { - 'msatoshi': amount, - 'id': conn.pubkey.format().hex(), + "msatoshi": amount, + "id": conn.pubkey.format().hex(), # We internally add one. - 'delay': 4, + "delay": 4, # We actually ignore this. - 'channel': '1x1x1' + "channel": "1x1x1", } self.rpc.sendpay([routestep], payhash) - def get_output_message(self, conn: Conn, event: Event, timeout: int = TIMEOUT) -> Optional[bytes]: + def get_output_message( + self, conn: Conn, event: Event, timeout: int = TIMEOUT + ) -> Optional[bytes]: fut = self.executor.submit(cast(CLightningConn, conn).connection.read_message) try: return fut.result(timeout) @@ -315,7 +378,13 @@ def check_error(self, event: Event, conn: Conn) -> Optional[str]: return None return msg.hex() - def check_final_error(self, event: Event, conn: Conn, expected: bool, must_not_events: List[MustNotMsg]) -> None: + def check_final_error( + self, + event: Event, + conn: Conn, + expected: bool, + must_not_events: List[MustNotMsg], + ) -> None: if not expected: # Inject raw packet to ensure it hangs up *after* processing all previous ones. cast(CLightningConn, conn).connection.connection.send(bytes(18)) @@ -326,14 +395,14 @@ def check_final_error(self, event: Event, conn: Conn, expected: bool, must_not_e break for e in must_not_events: if e.matches(binmsg): - raise EventError(event, "Got msg banned by {}: {}" - .format(e, binmsg.hex())) + raise EventError( + event, "Got msg banned by {}: {}".format(e, binmsg.hex()) + ) # Don't assume it's a message type we know! - msgtype = struct.unpack('>H', binmsg[:2])[0] - if msgtype == namespace().get_msgtype('error').number: - raise EventError(event, "Got error msg: {}" - .format(binmsg.hex())) + msgtype = struct.unpack(">H", binmsg[:2])[0] + if msgtype == namespace().get_msgtype("error").number: + raise EventError(event, "Got error msg: {}".format(binmsg.hex())) cast(CLightningConn, conn).connection.connection.close() @@ -345,8 +414,16 @@ def expect_tx(self, event: Event, txid: str) -> None: try: wait_for(lambda: revtxid in self.bitcoind.rpc.getrawmempool()) except ValueError: - raise EventError(event, "Did not broadcast the txid {}, just {}" - .format(revtxid, [(txid, self.bitcoind.rpc.getrawtransaction(txid)) for txid in self.bitcoind.rpc.getrawmempool()])) + raise EventError( + event, + "Did not broadcast the txid {}, just {}".format( + revtxid, + [ + (txid, self.bitcoind.rpc.getrawtransaction(txid)) + for txid in self.bitcoind.rpc.getrawmempool() + ], + ), + ) def has_option(self, optname: str) -> Optional[str]: """Returns None if it doesn't support, otherwise 'even' or 'odd' (required or supported)""" @@ -355,6 +432,6 @@ def has_option(self, optname: str) -> Optional[str]: return None def add_startup_flag(self, flag: str) -> None: - if self.config.getoption('verbose'): - print('[ADD STARTUP FLAG \'{}\']'.format(flag)) + if self.config.getoption("verbose"): + print("[ADD STARTUP FLAG '{}']".format(flag)) self.startup_flags.append("--{}".format(flag)) diff --git a/lnprototest/commit_tx.py b/lnprototest/commit_tx.py index 28ed1d3..9d75106 100644 --- a/lnprototest/commit_tx.py +++ b/lnprototest/commit_tx.py @@ -1,6 +1,14 @@ #! /usr/bin/python3 # FIXME: clean this up for use as pyln.proto.tx -from bitcoin.core import COutPoint, CTxOut, CTxIn, Hash160, CMutableTransaction, CTxWitness, CScriptWitness +from bitcoin.core import ( + COutPoint, + CTxOut, + CTxIn, + Hash160, + CMutableTransaction, + CTxWitness, + CScriptWitness, +) import bitcoin.core.script as script from bitcoin.core.script import CScript import struct @@ -19,12 +27,14 @@ class HTLC(object): - def __init__(self, - owner: Side, - amount_msat: int, - payment_secret: str, - cltv_expiry: int, - onion_routing_packet: str): + def __init__( + self, + owner: Side, + amount_msat: int, + payment_secret: str, + cltv_expiry: int, + onion_routing_packet: str, + ): """A HTLC offered by @owner""" self.owner = owner self.amount_msat = amount_msat @@ -39,7 +49,9 @@ def payment_hash(self) -> str: return self.raw_payment_hash().hex() def __str__(self) -> str: - return "htlc({},{},{})".format(self.owner, self.amount_msat, self.payment_hash()) + return "htlc({},{},{})".format( + self.owner, self.amount_msat, self.payment_hash() + ) @staticmethod def htlc_timeout_fee(feerate_per_kw: int, option_anchor_outputs: bool) -> int: @@ -67,20 +79,22 @@ def htlc_success_fee(feerate_per_kw: int, option_anchor_outputs: bool) -> int: class Commitment(object): - def __init__(self, - funding: Funding, - opener: Side, - local_keyset: KeySet, - remote_keyset: KeySet, - local_to_self_delay: int, - remote_to_self_delay: int, - local_amount: int, - remote_amount: int, - local_dust_limit: int, - remote_dust_limit: int, - feerate: int, - option_static_remotekey: bool, - option_anchor_outputs: bool): + def __init__( + self, + funding: Funding, + opener: Side, + local_keyset: KeySet, + remote_keyset: KeySet, + local_to_self_delay: int, + remote_to_self_delay: int, + local_amount: int, + remote_amount: int, + local_dust_limit: int, + remote_dust_limit: int, + feerate: int, + option_static_remotekey: bool, + option_anchor_outputs: bool, + ): self.opener = opener self.funding = funding self.feerate = feerate @@ -97,7 +111,7 @@ def __init__(self, @staticmethod def ripemd160(b: bytes) -> bytes: - hasher = hashlib.new('ripemd160') + hasher = hashlib.new("ripemd160") hasher.update(b) return hasher.digest() @@ -117,13 +131,14 @@ def revocation_privkey(self, side: Side) -> coincurve.PrivateKey: # ... # revocationprivkey = revocation_basepoint_secret * SHA256(revocation_basepoint || per_commitment_point) # + per_commitment_secret * SHA256(per_commitment_point || revocation_basepoint) - revocation_tweak = sha256(revocation_basepoint.format() - + per_commitment_point.format()).digest() - val = revocation_basepoint_secret.multiply(revocation_tweak, - update=False) + revocation_tweak = sha256( + revocation_basepoint.format() + per_commitment_point.format() + ).digest() + val = revocation_basepoint_secret.multiply(revocation_tweak, update=False) - per_commit_tweak = sha256(per_commitment_point.format() - + revocation_basepoint.format()).digest() + per_commit_tweak = sha256( + per_commitment_point.format() + revocation_basepoint.format() + ).digest() val2 = per_commitment_secret.multiply(per_commit_tweak, update=False) return val.add(val2.secret, update=False) @@ -132,7 +147,9 @@ def revocation_pubkey(self, side: Side) -> coincurve.PublicKey: """Derive the pubkey used for side's commitment transaction.""" return coincurve.PublicKey.from_secret(self.revocation_privkey(side).secret) - def _basepoint_tweak(self, basesecret: coincurve.PrivateKey, side: Side) -> coincurve.PrivateKey: + def _basepoint_tweak( + self, basesecret: coincurve.PrivateKey, side: Side + ) -> coincurve.PrivateKey: # BOLT #3: # ### `localpubkey`, `local_htlcpubkey`, `remote_htlcpubkey`, # `local_delayedpubkey`, and `remote_delayedpubkey` Derivation @@ -150,7 +167,9 @@ def _basepoint_tweak(self, basesecret: coincurve.PrivateKey, side: Side) -> coin def delayed_pubkey(self, side: Side) -> coincurve.PublicKey: """Generate local delayed_pubkey for this side""" - privkey = self._basepoint_tweak(self.keyset[side].delayed_payment_base_secret, side) + privkey = self._basepoint_tweak( + self.keyset[side].delayed_payment_base_secret, side + ) return coincurve.PublicKey.from_secret(privkey.secret) def to_remote_pubkey(self, side: Side) -> coincurve.PublicKey: @@ -163,13 +182,26 @@ def to_remote_pubkey(self, side: Side) -> coincurve.PublicKey: if self.option_static_remotekey: privkey = self.keyset[not side].payment_base_secret else: - privkey = self._basepoint_tweak(self.keyset[not side].payment_base_secret, side) - print("to-remote for side {}: self->payment = {} (local would be {}), per_commit_point = {}, keyset->self_payment_key = {}" - .format(side, - coincurve.PublicKey.from_secret(self.keyset[not side].payment_base_secret.secret).format().hex(), - coincurve.PublicKey.from_secret(self.keyset[Side.local].payment_base_secret.secret).format().hex(), - self.keyset[side].per_commit_point(self.commitnum), - coincurve.PublicKey.from_secret(privkey.secret).format().hex())) + privkey = self._basepoint_tweak( + self.keyset[not side].payment_base_secret, side + ) + print( + "to-remote for side {}: self->payment = {} (local would be {}), per_commit_point = {}, keyset->self_payment_key = {}".format( + side, + coincurve.PublicKey.from_secret( + self.keyset[not side].payment_base_secret.secret + ) + .format() + .hex(), + coincurve.PublicKey.from_secret( + self.keyset[Side.local].payment_base_secret.secret + ) + .format() + .hex(), + self.keyset[side].per_commit_point(self.commitnum), + coincurve.PublicKey.from_secret(privkey.secret).format().hex(), + ) + ) return coincurve.PublicKey.from_secret(privkey.secret) def local_htlc_pubkey(self, side: Side) -> coincurve.PublicKey: @@ -217,16 +249,19 @@ def channel_id_v2(self) -> str: return sha256(local_key.format() + remote_key.format()).digest().hex() @staticmethod - def obscured_commit_num(opener_payment_basepoint: coincurve.PublicKey, - non_opener_payment_basepoint: coincurve.PublicKey, - commitnum: int) -> int: + def obscured_commit_num( + opener_payment_basepoint: coincurve.PublicKey, + non_opener_payment_basepoint: coincurve.PublicKey, + commitnum: int, + ) -> int: # BOLT #3: # The 48-bit commitment number is obscured by `XOR` with the lower 48 bits of: # # SHA256(payment_basepoint from open_channel || payment_basepoint from accept_channel) - shabytes = sha256(opener_payment_basepoint.format() - + non_opener_payment_basepoint.format()).digest()[-6:] - obscurer = struct.unpack('>Q', bytes(2) + shabytes)[0] + shabytes = sha256( + opener_payment_basepoint.format() + non_opener_payment_basepoint.format() + ).digest()[-6:] + obscurer = struct.unpack(">Q", bytes(2) + shabytes)[0] return commitnum ^ obscurer def _fee(self, num_untrimmed_htlcs: int) -> int: @@ -270,15 +305,19 @@ def _to_local_output(self, fee: int, side: Side) -> Tuple[script.CScript, int]: # # OP_ENDIF # OP_CHECKSIG - to_self_script = script.CScript([script.OP_IF, - self.revocation_pubkey(side).format(), - script.OP_ELSE, - self.self_delay[side], - script.OP_CHECKSEQUENCEVERIFY, - script.OP_DROP, - self.delayed_pubkey(side).format(), - script.OP_ENDIF, - script.OP_CHECKSIG]) + to_self_script = script.CScript( + [ + script.OP_IF, + self.revocation_pubkey(side).format(), + script.OP_ELSE, + self.self_delay[side], + script.OP_CHECKSEQUENCEVERIFY, + script.OP_DROP, + self.delayed_pubkey(side).format(), + script.OP_ENDIF, + script.OP_CHECKSIG, + ] + ) # BOLT #3: The amounts for each output MUST be rounded down to whole # satoshis. If this amount, minus the fees for the HTLC transaction, @@ -312,18 +351,24 @@ def _to_remote_output(self, fee: int, side: Side) -> Tuple[script.CScript, int]: # # Otherwise, this output is a simple P2WPKH to `remotepubkey`. if self.option_anchor_outputs: - redeemscript = script.CScript([self.to_remote_pubkey(side).format(), - script.OP_CHECKSIGVERIFY, - 1, - script.OP_CHECKSEQUENCEVERIFY]) - cscript = script.CScript([script.OP_0, - sha256(redeemscript).digest()]) + redeemscript = script.CScript( + [ + self.to_remote_pubkey(side).format(), + script.OP_CHECKSIGVERIFY, + 1, + script.OP_CHECKSEQUENCEVERIFY, + ] + ) + cscript = script.CScript([script.OP_0, sha256(redeemscript).digest()]) else: - cscript = CScript([script.OP_0, - Hash160(self.to_remote_pubkey(side).format())]) + cscript = CScript( + [script.OP_0, Hash160(self.to_remote_pubkey(side).format())] + ) return cscript, amount_to_other - def _offered_htlc_output(self, htlc: HTLC, side: Side) -> Tuple[script.CScript, int]: + def _offered_htlc_output( + self, htlc: HTLC, side: Side + ) -> Tuple[script.CScript, int]: # BOLT-a12da24dd0102c170365124782b46d9710950ac1 #3: This output sends funds to either an HTLC-timeout # transaction after the HTLC-timeout or to the remote node # using the payment preimage or the revocation key. The output @@ -369,39 +414,45 @@ def _offered_htlc_output(self, htlc: HTLC, side: Side) -> Tuple[script.CScript, csvcheck = [1, script.OP_CHECKSEQUENCEVERIFY, script.OP_DROP] else: csvcheck = [] - htlc_script = script.CScript([script.OP_DUP, - script.OP_HASH160, - Hash160(self.revocation_pubkey(side).format()), - script.OP_EQUAL, - script.OP_IF, - script.OP_CHECKSIG, - script.OP_ELSE, - self.remote_htlc_pubkey(side).format(), - script.OP_SWAP, - script.OP_SIZE, - 32, - script.OP_EQUAL, - script.OP_NOTIF, - script.OP_DROP, - 2, - script.OP_SWAP, - self.local_htlc_pubkey(side).format(), - 2, - script.OP_CHECKMULTISIG, - script.OP_ELSE, - script.OP_HASH160, - self.ripemd160(htlc.raw_payment_hash()), - script.OP_EQUALVERIFY, - script.OP_CHECKSIG, - script.OP_ENDIF] - + csvcheck - + [script.OP_ENDIF]) + htlc_script = script.CScript( + [ + script.OP_DUP, + script.OP_HASH160, + Hash160(self.revocation_pubkey(side).format()), + script.OP_EQUAL, + script.OP_IF, + script.OP_CHECKSIG, + script.OP_ELSE, + self.remote_htlc_pubkey(side).format(), + script.OP_SWAP, + script.OP_SIZE, + 32, + script.OP_EQUAL, + script.OP_NOTIF, + script.OP_DROP, + 2, + script.OP_SWAP, + self.local_htlc_pubkey(side).format(), + 2, + script.OP_CHECKMULTISIG, + script.OP_ELSE, + script.OP_HASH160, + self.ripemd160(htlc.raw_payment_hash()), + script.OP_EQUALVERIFY, + script.OP_CHECKSIG, + script.OP_ENDIF, + ] + + csvcheck + + [script.OP_ENDIF] + ) # BOLT #3: The amounts for each output MUST be rounded down to whole # satoshis. return htlc_script, htlc.amount_msat // 1000 - def _received_htlc_output(self, htlc: HTLC, side: Side) -> Tuple[script.CScript, int]: + def _received_htlc_output( + self, htlc: HTLC, side: Side + ) -> Tuple[script.CScript, int]: # BOLT-a12da24dd0102c170365124782b46d9710950ac1 #3: # This output sends funds to either the remote node after the # HTLC-timeout or using the revocation key, or to an HTLC-success @@ -450,36 +501,40 @@ def _received_htlc_output(self, htlc: HTLC, side: Side) -> Tuple[script.CScript, else: csvcheck = [] - htlc_script = script.CScript([script.OP_DUP, - script.OP_HASH160, - Hash160(self.revocation_pubkey(side).format()), - script.OP_EQUAL, - script.OP_IF, - script.OP_CHECKSIG, - script.OP_ELSE, - self.remote_htlc_pubkey(side).format(), - script.OP_SWAP, - script.OP_SIZE, - 32, - script.OP_EQUAL, - script.OP_IF, - script.OP_HASH160, - self.ripemd160(htlc.raw_payment_hash()), - script.OP_EQUALVERIFY, - 2, - script.OP_SWAP, - self.local_htlc_pubkey(side).format(), - 2, - script.OP_CHECKMULTISIG, - script.OP_ELSE, - script.OP_DROP, - htlc.cltv_expiry, - script.OP_CHECKLOCKTIMEVERIFY, - script.OP_DROP, - script.OP_CHECKSIG, - script.OP_ENDIF] - + csvcheck - + [script.OP_ENDIF]) + htlc_script = script.CScript( + [ + script.OP_DUP, + script.OP_HASH160, + Hash160(self.revocation_pubkey(side).format()), + script.OP_EQUAL, + script.OP_IF, + script.OP_CHECKSIG, + script.OP_ELSE, + self.remote_htlc_pubkey(side).format(), + script.OP_SWAP, + script.OP_SIZE, + 32, + script.OP_EQUAL, + script.OP_IF, + script.OP_HASH160, + self.ripemd160(htlc.raw_payment_hash()), + script.OP_EQUALVERIFY, + 2, + script.OP_SWAP, + self.local_htlc_pubkey(side).format(), + 2, + script.OP_CHECKMULTISIG, + script.OP_ELSE, + script.OP_DROP, + htlc.cltv_expiry, + script.OP_CHECKLOCKTIMEVERIFY, + script.OP_DROP, + script.OP_CHECKSIG, + script.OP_ENDIF, + ] + + csvcheck + + [script.OP_ENDIF] + ) # BOLT #3: The amounts for each output MUST be rounded down to whole # satoshis. @@ -495,15 +550,18 @@ def _anchor_out(self, side: Side) -> CTxOut: # OP_ENDIF # ... # The amount of the output is fixed at 330 sats - redeemscript = CScript([self.funding.funding_pubkey(side).format(), - script.OP_CHECKSIG, - script.OP_IFDUP, - script.OP_NOTIF, - 16, - script.OP_CHECKSEQUENCEVERIFY, - script.OP_ENDIF]) - return CTxOut(330, - CScript([script.OP_0, sha256(redeemscript).digest()])) + redeemscript = CScript( + [ + self.funding.funding_pubkey(side).format(), + script.OP_CHECKSIG, + script.OP_IFDUP, + script.OP_NOTIF, + 16, + script.OP_CHECKSEQUENCEVERIFY, + script.OP_ENDIF, + ] + ) + return CTxOut(330, CScript([script.OP_0, sha256(redeemscript).digest()])) def untrimmed_htlcs(self, side: Side) -> List[HTLC]: htlcs = [] @@ -518,7 +576,9 @@ def untrimmed_htlcs(self, side: Side) -> List[HTLC]: # [Offered HTLC Outputs](#offered-htlc-outputs). if htlc.owner == side: # FIXME: Use Millisatoshi type? - if htlc.amount_msat - msat(htlc.htlc_timeout_fee(self.feerate, self.option_anchor_outputs)) < msat(self.dust_limit[side]): + if htlc.amount_msat - msat( + htlc.htlc_timeout_fee(self.feerate, self.option_anchor_outputs) + ) < msat(self.dust_limit[side]): continue else: # BOLT #3: @@ -529,7 +589,9 @@ def untrimmed_htlcs(self, side: Side) -> List[HTLC]: # - otherwise: # - MUST be generated as specified in # [Received HTLC Outputs](#received-htlc-outputs). - if htlc.amount_msat - msat(htlc.htlc_success_fee(self.feerate, self.option_anchor_outputs)) < msat(self.dust_limit[side]): + if htlc.amount_msat - msat( + htlc.htlc_success_fee(self.feerate, self.option_anchor_outputs) + ) < msat(self.dust_limit[side]): continue htlcs.append(htlc) @@ -544,22 +606,29 @@ def htlc_outputs(self, side: Side) -> List[Tuple[HTLC, int, bytes]]: redeemscript, sats = self._offered_htlc_output(htlc, side) else: redeemscript, sats = self._received_htlc_output(htlc, side) - ret.append((CTxOut(sats, - CScript([script.OP_0, sha256(redeemscript).digest()])), - htlc.cltv_expiry, - redeemscript)) + ret.append( + ( + CTxOut(sats, CScript([script.OP_0, sha256(redeemscript).digest()])), + htlc.cltv_expiry, + redeemscript, + ) + ) return ret - def _unsigned_tx(self, side: Side) -> Tuple[CMutableTransaction, List[Optional[HTLC]]]: + def _unsigned_tx( + self, side: Side + ) -> Tuple[CMutableTransaction, List[Optional[HTLC]]]: """Create the commitment transaction. -Returns it and a list of matching HTLCs for each output + Returns it and a list of matching HTLCs for each output """ - ocn = self.obscured_commit_num(self.keyset[self.opener].raw_payment_basepoint(), - self.keyset[not self.opener].raw_payment_basepoint(), - self.commitnum) + ocn = self.obscured_commit_num( + self.keyset[self.opener].raw_payment_basepoint(), + self.keyset[not self.opener].raw_payment_basepoint(), + self.commitnum, + ) # BOLT #3: # ## Commitment Transaction @@ -569,8 +638,10 @@ def _unsigned_tx(self, side: Side) -> Tuple[CMutableTransaction, List[Optional[H # * `txin[0]` sequence: upper 8 bits are 0x80, lower 24 bits are upper 24 bits of the obscured commitment number # * `txin[0]` script bytes: 0 # * `txin[0]` witness: `0 ` - txin = CTxIn(COutPoint(bytes.fromhex(self.funding.txid), self.funding.output_index), - nSequence=0x80000000 | (ocn >> 24)) + txin = CTxIn( + COutPoint(bytes.fromhex(self.funding.txid), self.funding.output_index), + nSequence=0x80000000 | (ocn >> 24), + ) # txouts, with ctlv_timeouts (for htlc output tiebreak) and htlc txouts: List[Tuple[CTxOut, int, Optional[HTLC]]] = [] @@ -581,11 +652,18 @@ def _unsigned_tx(self, side: Side) -> Tuple[CMutableTransaction, List[Optional[H redeemscript, sats = self._offered_htlc_output(htlc, side) else: redeemscript, sats = self._received_htlc_output(htlc, side) - print("*** Got htlc redeemscript {} / {}".format(redeemscript, redeemscript.hex())) - txouts.append((CTxOut(sats, - CScript([script.OP_0, sha256(redeemscript).digest()])), - htlc.cltv_expiry, - htlc)) + print( + "*** Got htlc redeemscript {} / {}".format( + redeemscript, redeemscript.hex() + ) + ) + txouts.append( + ( + CTxOut(sats, CScript([script.OP_0, sha256(redeemscript).digest()])), + htlc.cltv_expiry, + htlc, + ) + ) have_htlcs = True num_untrimmed_htlcs = len(txouts) @@ -594,17 +672,20 @@ def _unsigned_tx(self, side: Side) -> Tuple[CMutableTransaction, List[Optional[H have_outputs = [False, False] out_redeemscript, sats = self._to_local_output(fee, side) if sats >= self.dust_limit[side]: - txouts.append((CTxOut(sats, - CScript([script.OP_0, sha256(out_redeemscript).digest()])), - 0, - None)) + txouts.append( + ( + CTxOut( + sats, CScript([script.OP_0, sha256(out_redeemscript).digest()]) + ), + 0, + None, + ) + ) have_outputs[side] = True cscript, sats = self._to_remote_output(fee, side) if sats >= self.dust_limit[side]: - txouts.append((CTxOut(sats, cscript), - 0, - None)) + txouts.append((CTxOut(sats, cscript), 0, None)) have_outputs[not side] = True # BOLT-a12da24dd0102c170365124782b46d9710950ac1 #3: @@ -617,13 +698,9 @@ def _unsigned_tx(self, side: Side) -> Tuple[CMutableTransaction, List[Optional[H # `to_remote_anchor` output if self.option_anchor_outputs: if have_htlcs or have_outputs[side]: - txouts.append((self._anchor_out(side), - 0, - None)) + txouts.append((self._anchor_out(side), 0, None)) if have_htlcs or have_outputs[not side]: - txouts.append((self._anchor_out(not side), # type: ignore - 0, - None)) + txouts.append((self._anchor_out(not side), 0, None)) # type: ignore # BOLT #3: # ## Transaction Input and Output Ordering @@ -645,19 +722,25 @@ def _unsigned_tx(self, side: Side) -> Tuple[CMutableTransaction, List[Optional[H # * version: 2 # * locktime: upper 8 bits are 0x20, lower 24 bits are the # lower 24 bits of the obscured commitment number - return (CMutableTransaction(vin=[txin], - vout=[txout[0] for txout in txouts], - nVersion=2, - nLockTime=0x20000000 | (ocn & 0x00FFFFFF)), - [txout[2] for txout in txouts]) - - def htlc_tx(self, - commit_tx: CMutableTransaction, - outnum: int, - side: Side, - amount_sat: int, - locktime: int, - option_anchor_outputs: bool) -> CMutableTransaction: + return ( + CMutableTransaction( + vin=[txin], + vout=[txout[0] for txout in txouts], + nVersion=2, + nLockTime=0x20000000 | (ocn & 0x00FFFFFF), + ), + [txout[2] for txout in txouts], + ) + + def htlc_tx( + self, + commit_tx: CMutableTransaction, + outnum: int, + side: Side, + amount_sat: int, + locktime: int, + option_anchor_outputs: bool, + ) -> CMutableTransaction: # BOLT #3: # ## HTLC-Timeout and HTLC-Success Transactions # @@ -679,8 +762,7 @@ def htlc_tx(self, else: sequence = 0 - txin = CTxIn(COutPoint(commit_tx.GetTxid(), outnum), - nSequence=sequence) + txin = CTxIn(COutPoint(commit_tx.GetTxid(), outnum), nSequence=sequence) # BOLT #3: # ## HTLC-Timeout and HTLC-Success Transactions @@ -701,28 +783,32 @@ def htlc_tx(self, # # OP_ENDIF # OP_CHECKSIG - redeemscript = script.CScript([script.OP_IF, - self.revocation_pubkey(side).format(), - script.OP_ELSE, - self.self_delay[side], - script.OP_CHECKSEQUENCEVERIFY, - script.OP_DROP, - self.delayed_pubkey(side).format(), - script.OP_ENDIF, - script.OP_CHECKSIG]) + redeemscript = script.CScript( + [ + script.OP_IF, + self.revocation_pubkey(side).format(), + script.OP_ELSE, + self.self_delay[side], + script.OP_CHECKSEQUENCEVERIFY, + script.OP_DROP, + self.delayed_pubkey(side).format(), + script.OP_ENDIF, + script.OP_CHECKSIG, + ] + ) print("htlc redeemscript = {}".format(redeemscript.hex())) - txout = CTxOut(amount_sat, - CScript([script.OP_0, sha256(redeemscript).digest()])) + txout = CTxOut( + amount_sat, CScript([script.OP_0, sha256(redeemscript).digest()]) + ) # BOLT #3: # ## HTLC-Timeout and HTLC-Success Transactions # ... # * version: 2 # * locktime: `0` for HTLC-success, `cltv_expiry` for HTLC-timeout - return CMutableTransaction(vin=[txin], - vout=[txout], - nVersion=2, - nLockTime=locktime) + return CMutableTransaction( + vin=[txin], vout=[txout], nVersion=2, nLockTime=locktime + ) def local_unsigned_tx(self) -> CMutableTransaction: return self._unsigned_tx(Side.local)[0] @@ -731,26 +817,35 @@ def remote_unsigned_tx(self) -> CMutableTransaction: return self._unsigned_tx(Side.remote)[0] def _sig(self, privkey: coincurve.PrivateKey, tx: CMutableTransaction) -> Sig: - sighash = script.SignatureHash(self.funding.redeemscript(), tx, inIdx=0, - hashtype=script.SIGHASH_ALL, - amount=self.funding.amount, - sigversion=script.SIGVERSION_WITNESS_V0) + sighash = script.SignatureHash( + self.funding.redeemscript(), + tx, + inIdx=0, + hashtype=script.SIGHASH_ALL, + amount=self.funding.amount, + sigversion=script.SIGVERSION_WITNESS_V0, + ) return Sig(privkey.secret.hex(), sighash.hex()) def local_sig(self, tx: CMutableTransaction) -> Sig: return self._sig(self.funding.bitcoin_privkeys[Side.local], tx) def remote_sig(self, tx: CMutableTransaction) -> Sig: - print('Signing {} redeemscript keys {} and {}: {} amount = {}\ntx: {}'.format( - Side.remote, - self.funding.funding_pubkey(Side.local).format().hex(), - self.funding.funding_pubkey(Side.remote).format().hex(), - self.funding.redeemscript().hex(), - self.funding.amount, - tx.serialize().hex())) + print( + "Signing {} redeemscript keys {} and {}: {} amount = {}\ntx: {}".format( + Side.remote, + self.funding.funding_pubkey(Side.local).format().hex(), + self.funding.funding_pubkey(Side.remote).format().hex(), + self.funding.redeemscript().hex(), + self.funding.amount, + tx.serialize().hex(), + ) + ) return self._sig(self.funding.bitcoin_privkeys[Side.remote], tx) - def htlc_txs(self, side: Side) -> List[Tuple[CMutableTransaction, script.CScript, int]]: + def htlc_txs( + self, side: Side + ) -> List[Tuple[CMutableTransaction, script.CScript, int]]: """Return unsigned HTLC txs (+ redeemscript, input sats) in output order""" # So we need the HTLCs in output order, which is why we had _unsigned_tx # return them. @@ -772,11 +867,20 @@ def htlc_txs(self, side: Side) -> List[Tuple[CMutableTransaction, script.CScript fee = htlc.htlc_success_fee(self.feerate, self.option_anchor_outputs) locktime = 0 - ret.append((self.htlc_tx(commit_tx, outnum, side, - (htlc.amount_msat - msat(fee)) // 1000, - locktime, - self.option_anchor_outputs), - redeemscript, sats)) + ret.append( + ( + self.htlc_tx( + commit_tx, + outnum, + side, + (htlc.amount_msat - msat(fee)) // 1000, + locktime, + self.option_anchor_outputs, + ), + redeemscript, + sats, + ) + ) return ret @@ -799,10 +903,14 @@ def htlc_sigs(self, signer: Side, side: Side) -> List[Sig]: else: hashtype = script.SIGHASH_ALL - sighash = script.SignatureHash(redeemscript, htlc_tx, inIdx=0, - hashtype=hashtype, - amount=sats, - sigversion=script.SIGVERSION_WITNESS_V0) + sighash = script.SignatureHash( + redeemscript, + htlc_tx, + inIdx=0, + hashtype=hashtype, + amount=sats, + sigversion=script.SIGVERSION_WITNESS_V0, + ) privkey = self._basepoint_tweak(self.keyset[signer].htlc_base_secret, side) sigs.append(Sig(privkey.secret.hex(), sighash.hex())) @@ -812,39 +920,56 @@ def signed_tx(self, unsigned_tx: CMutableTransaction) -> CMutableTransaction: # BOLT #3: # * `txin[0]` witness: `0 ` tx = unsigned_tx.copy() - sighash = script.SignatureHash(self.funding.redeemscript(), tx, inIdx=0, - hashtype=script.SIGHASH_ALL, - amount=self.funding.amount, - sigversion=script.SIGVERSION_WITNESS_V0) - sigs = [key.sign(sighash, hasher=None) for key in self.funding.funding_privkeys_for_tx()] - tx.wit = CTxWitness([CScriptWitness([bytes(), - sigs[0] + bytes([script.SIGHASH_ALL]), - sigs[1] + bytes([script.SIGHASH_ALL]), - self.funding.redeemscript()])]) + sighash = script.SignatureHash( + self.funding.redeemscript(), + tx, + inIdx=0, + hashtype=script.SIGHASH_ALL, + amount=self.funding.amount, + sigversion=script.SIGVERSION_WITNESS_V0, + ) + sigs = [ + key.sign(sighash, hasher=None) + for key in self.funding.funding_privkeys_for_tx() + ] + tx.wit = CTxWitness( + [ + CScriptWitness( + [ + bytes(), + sigs[0] + bytes([script.SIGHASH_ALL]), + sigs[1] + bytes([script.SIGHASH_ALL]), + self.funding.redeemscript(), + ] + ) + ] + ) return tx -ResolvableFunding = Union[Funding, Callable[['Runner', 'Event', str], Funding]] +ResolvableFunding = Union[Funding, Callable[["Runner", "Event", str], Funding]] class Commit(Event): - def __init__(self, - opener: Side, - local_keyset: KeySet, - funding: ResolvableFunding, - local_to_self_delay: ResolvableInt, - remote_to_self_delay: ResolvableInt, - local_amount: ResolvableInt, - remote_amount: ResolvableInt, - local_dust_limit: ResolvableInt, - remote_dust_limit: ResolvableInt, - feerate: ResolvableInt, - local_features: ResolvableStr, - remote_features: ResolvableStr): + def __init__( + self, + opener: Side, + local_keyset: KeySet, + funding: ResolvableFunding, + local_to_self_delay: ResolvableInt, + remote_to_self_delay: ResolvableInt, + local_amount: ResolvableInt, + remote_amount: ResolvableInt, + local_dust_limit: ResolvableInt, + remote_dust_limit: ResolvableInt, + feerate: ResolvableInt, + local_features: ResolvableStr, + remote_features: ResolvableStr, + ): """Stashes a commitment transaction as 'Commit'. -Note that local_to_self_delay is dictated by the remote side, and -remote_to_self_delay is dicated by the local side! + Note that local_to_self_delay is dictated by the remote side, and + remote_to_self_delay is dicated by the local side! """ super().__init__() @@ -868,40 +993,55 @@ def __init__(self, def action(self, runner: Runner) -> bool: super().action(runner) - static_remotekey = self.resolve_arg('option_static_remotekey', runner, self.static_remotekey) - anchor_outputs = self.resolve_arg('option_anchor_outputs', runner, self.anchor_outputs) + static_remotekey = self.resolve_arg( + "option_static_remotekey", runner, self.static_remotekey + ) + anchor_outputs = self.resolve_arg( + "option_anchor_outputs", runner, self.anchor_outputs + ) # BOLT-a12da24dd0102c170365124782b46d9710950ac1 #9: # | Bits | Name | Description | Context | Dependencies # ... # | 20/21 | `option_anchor_outputs` | Anchor outputs | IN | `option_static_remotekey` | if anchor_outputs and not static_remotekey: - raise EventError(self, "Cannot have option_anchor_outputs without option_static_remotekey") - - commit = Commitment(local_keyset=self.local_keyset, - remote_keyset=runner.get_keyset(), - opener=self.opener, - option_static_remotekey=static_remotekey, - option_anchor_outputs=anchor_outputs, - **self.resolve_args(runner, - {'funding': self.funding, - 'local_to_self_delay': self.local_to_self_delay, - 'remote_to_self_delay': self.remote_to_self_delay, - 'local_amount': self.local_amount, - 'remote_amount': self.remote_amount, - 'local_dust_limit': self.local_dust_limit, - 'remote_dust_limit': self.remote_dust_limit, - 'feerate': self.feerate})) - runner.add_stash('Commit', commit) + raise EventError( + self, + "Cannot have option_anchor_outputs without option_static_remotekey", + ) + + commit = Commitment( + local_keyset=self.local_keyset, + remote_keyset=runner.get_keyset(), + opener=self.opener, + option_static_remotekey=static_remotekey, + option_anchor_outputs=anchor_outputs, + **self.resolve_args( + runner, + { + "funding": self.funding, + "local_to_self_delay": self.local_to_self_delay, + "remote_to_self_delay": self.remote_to_self_delay, + "local_amount": self.local_amount, + "remote_amount": self.remote_amount, + "local_dust_limit": self.local_dust_limit, + "remote_dust_limit": self.remote_dust_limit, + "feerate": self.feerate, + }, + ) + ) + runner.add_stash("Commit", commit) return True class UpdateCommit(Event): - def __init__(self, - new_htlcs: List[Tuple[HTLC, int]] = [], - resolved_htlcs: List[HTLC] = [], - failed_htlcs: List[HTLC] = [], - new_feerate: Optional[ResolvableInt] = None): + def __init__( + self, + new_htlcs: List[Tuple[HTLC, int]] = [], + resolved_htlcs: List[HTLC] = [], + failed_htlcs: List[HTLC] = [], + new_feerate: Optional[ResolvableInt] = None, + ): super().__init__() self.new_htlcs = new_htlcs self.resolved_htlcs = resolved_htlcs @@ -911,7 +1051,7 @@ def __init__(self, def action(self, runner: Runner) -> bool: super().action(runner) - commit: Commitment = runner.get_stash(self, 'Commit') + commit: Commitment = runner.get_stash(self, "Commit") for htlc, htlc_id in self.new_htlcs: if not commit.add_htlc(htlc, htlc_id): raise SpecFileError(self, "Already have htlc id {}".format(htlc_id)) @@ -923,7 +1063,7 @@ def action(self, runner: Runner) -> bool: raise SpecFileError(self, "Cannot resolve missing htlc {}".format(htlc)) if self.new_feerate is not None: - commit.feerate = self.resolve_arg('feerate', runner, self.new_feerate) + commit.feerate = self.resolve_arg("feerate", runner, self.new_feerate) commit.inc_commitnum() return True @@ -941,18 +1081,35 @@ def test_commitment_number() -> None: # INTERNAL: local_payment_basepoint_secret: 111111111111111111111111111111111111111111111111111111111111111101 # ... # INTERNAL: remote_payment_basepoint_secret: 444444444444444444444444444444444444444444444444444444444444444401 - opener_pubkey = coincurve.PublicKey.from_secret(bytes.fromhex('1111111111111111111111111111111111111111111111111111111111111111')) - non_opener_pubkey = coincurve.PublicKey.from_secret(bytes.fromhex('4444444444444444444444444444444444444444444444444444444444444444')) + opener_pubkey = coincurve.PublicKey.from_secret( + bytes.fromhex( + "1111111111111111111111111111111111111111111111111111111111111111" + ) + ) + non_opener_pubkey = coincurve.PublicKey.from_secret( + bytes.fromhex( + "4444444444444444444444444444444444444444444444444444444444444444" + ) + ) # BOLT #3: Here are the points used to derive the obscuring factor # for the commitment number: # local_payment_basepoint: 034f355bdcb7cc0af728ef3cceb9615d90684bb5b2ca5f859ab0f0b704075871aa # remote_payment_basepoint: 032c0b7cf95324a07d05398b240174dc0c2be444d96b159aa6c7f7b1e668680991 # # obscured commitment number = 0x2bb038521914 ^ 42 - assert opener_pubkey.format().hex() == '034f355bdcb7cc0af728ef3cceb9615d90684bb5b2ca5f859ab0f0b704075871aa' - assert non_opener_pubkey.format().hex() == '032c0b7cf95324a07d05398b240174dc0c2be444d96b159aa6c7f7b1e668680991' + assert ( + opener_pubkey.format().hex() + == "034f355bdcb7cc0af728ef3cceb9615d90684bb5b2ca5f859ab0f0b704075871aa" + ) + assert ( + non_opener_pubkey.format().hex() + == "032c0b7cf95324a07d05398b240174dc0c2be444d96b159aa6c7f7b1e668680991" + ) - assert Commitment.obscured_commit_num(opener_pubkey, non_opener_pubkey, 42) == 0x2bb038521914 ^ 42 + assert ( + Commitment.obscured_commit_num(opener_pubkey, non_opener_pubkey, 42) + == 0x2BB038521914 ^ 42 + ) def revhex(h: str) -> str: @@ -961,46 +1118,55 @@ def revhex(h: str) -> str: def test_simple_commitment() -> None: # We use '99' where the results shouldn't matter. - c = Commitment(funding=Funding(funding_txid=revhex('8984484a580b825b9972d7adb15050b3ab624ccd731946b3eeddb92f4e7ef6be'), - funding_output_index=0, - funding_amount=10000000, - local_node_privkey='99', - # BOLT #3: - # local_funding_privkey: 30ff4956bbdd3222d44cc5e8a1261dab1e07957bdac5ae88fe3261ef321f374901 - local_funding_privkey='30ff4956bbdd3222d44cc5e8a1261dab1e07957bdac5ae88fe3261ef321f3749', - remote_node_privkey='99', - # BOLT #3: - # INTERNAL: remote_funding_privkey: 1552dfba4f6cf29a62a0af13c8d6981d36d0ef8d61ba10fb0fe90da7634d7e1301 - remote_funding_privkey='1552dfba4f6cf29a62a0af13c8d6981d36d0ef8d61ba10fb0fe90da7634d7e13'), - opener=Side.local, - - # BOLT #3: - # INTERNAL: local_payment_basepoint_secret: 111111111111111111111111111111111111111111111111111111111111111101 - local_keyset=KeySet(revocation_base_secret='99', - payment_base_secret='1111111111111111111111111111111111111111111111111111111111111111', - htlc_base_secret='1111111111111111111111111111111111111111111111111111111111111111', - # BOLT #3: - # INTERNAL: local_delayed_payment_basepoint_secret: 333333333333333333333333333333333333333333333333333333333333333301 - delayed_payment_base_secret='3333333333333333333333333333333333333333333333333333333333333333', - shachain_seed='99' * 32), - # BOLT #3: - # INTERNAL: remote_revocation_basepoint_secret: 222222222222222222222222222222222222222222222222222222222222222201 - remote_keyset=KeySet(revocation_base_secret='2222222222222222222222222222222222222222222222222222222222222222', - # BOLT #3: - # INTERNAL: remote_payment_basepoint_secret: 444444444444444444444444444444444444444444444444444444444444444401 - payment_base_secret='4444444444444444444444444444444444444444444444444444444444444444', - htlc_base_secret='4444444444444444444444444444444444444444444444444444444444444444', - delayed_payment_base_secret='99', - shachain_seed='99' * 32), - local_to_self_delay=144, - remote_to_self_delay=145, - local_amount=7000000000, - remote_amount=3000000000, - local_dust_limit=546, - remote_dust_limit=546, - feerate=15000, - option_static_remotekey=False, - option_anchor_outputs=False) + c = Commitment( + funding=Funding( + funding_txid=revhex( + "8984484a580b825b9972d7adb15050b3ab624ccd731946b3eeddb92f4e7ef6be" + ), + funding_output_index=0, + funding_amount=10000000, + local_node_privkey="99", + # BOLT #3: + # local_funding_privkey: 30ff4956bbdd3222d44cc5e8a1261dab1e07957bdac5ae88fe3261ef321f374901 + local_funding_privkey="30ff4956bbdd3222d44cc5e8a1261dab1e07957bdac5ae88fe3261ef321f3749", + remote_node_privkey="99", + # BOLT #3: + # INTERNAL: remote_funding_privkey: 1552dfba4f6cf29a62a0af13c8d6981d36d0ef8d61ba10fb0fe90da7634d7e1301 + remote_funding_privkey="1552dfba4f6cf29a62a0af13c8d6981d36d0ef8d61ba10fb0fe90da7634d7e13", + ), + opener=Side.local, + # BOLT #3: + # INTERNAL: local_payment_basepoint_secret: 111111111111111111111111111111111111111111111111111111111111111101 + local_keyset=KeySet( + revocation_base_secret="99", + payment_base_secret="1111111111111111111111111111111111111111111111111111111111111111", + htlc_base_secret="1111111111111111111111111111111111111111111111111111111111111111", + # BOLT #3: + # INTERNAL: local_delayed_payment_basepoint_secret: 333333333333333333333333333333333333333333333333333333333333333301 + delayed_payment_base_secret="3333333333333333333333333333333333333333333333333333333333333333", + shachain_seed="99" * 32, + ), + # BOLT #3: + # INTERNAL: remote_revocation_basepoint_secret: 222222222222222222222222222222222222222222222222222222222222222201 + remote_keyset=KeySet( + revocation_base_secret="2222222222222222222222222222222222222222222222222222222222222222", + # BOLT #3: + # INTERNAL: remote_payment_basepoint_secret: 444444444444444444444444444444444444444444444444444444444444444401 + payment_base_secret="4444444444444444444444444444444444444444444444444444444444444444", + htlc_base_secret="4444444444444444444444444444444444444444444444444444444444444444", + delayed_payment_base_secret="99", + shachain_seed="99" * 32, + ), + local_to_self_delay=144, + remote_to_self_delay=145, + local_amount=7000000000, + remote_amount=3000000000, + local_dust_limit=546, + remote_dust_limit=546, + feerate=15000, + option_static_remotekey=False, + option_anchor_outputs=False, + ) # Make sure undefined field are not used. c.keyset[Side.local].revocation_base_secret = None @@ -1012,7 +1178,7 @@ def test_simple_commitment() -> None: # x_local_per_commitment_secret: 1f1e1d1c1b1a191817161514131211100f0e0d0c0b0a0908070605040302010001 # This is not derived as expected, but defined :( - c.keyset[Side.local].raw_per_commit_secret = lambda _: coincurve.PrivateKey(bytes.fromhex('1f1e1d1c1b1a191817161514131211100f0e0d0c0b0a09080706050403020100')) # type: ignore + c.keyset[Side.local].raw_per_commit_secret = lambda _: coincurve.PrivateKey(bytes.fromhex("1f1e1d1c1b1a191817161514131211100f0e0d0c0b0a09080706050403020100")) # type: ignore # BOLT #3: # commitment_number: 42 @@ -1037,18 +1203,40 @@ def test_simple_commitment() -> None: out_redeemscript, sats = c._to_local_output(fee, Side.local) assert sats == 6989140 - assert out_redeemscript == bytes.fromhex('63210212a140cd0c6539d07cd08dfe09984dec3251ea808b892efeac3ede9402bf2b1967029000b2752103fd5960528dc152014952efdb702a88f71e3c1653b2314431701ec77e57fde83c68ac') + assert out_redeemscript == bytes.fromhex( + "63210212a140cd0c6539d07cd08dfe09984dec3251ea808b892efeac3ede9402bf2b1967029000b2752103fd5960528dc152014952efdb702a88f71e3c1653b2314431701ec77e57fde83c68ac" + ) out_redeemscript, sats = c._to_remote_output(fee, Side.local) assert sats == 3000000 - assert out_redeemscript == CScript([script.OP_0, Hash160(bytes.fromhex('0394854aa6eab5b2a8122cc726e9dded053a2184d88256816826d6231c068d4a5b'))]) + assert out_redeemscript == CScript( + [ + script.OP_0, + Hash160( + bytes.fromhex( + "0394854aa6eab5b2a8122cc726e9dded053a2184d88256816826d6231c068d4a5b" + ) + ), + ] + ) # FIXME: We don't yet have a routine to fill the witness, so we cmp txid. tx, _ = c._unsigned_tx(Side.local) - assert tx.GetTxid() == CMutableTransaction.deserialize(bytes.fromhex('02000000000101bef67e4e2fb9ddeeb3461973cd4c62abb35050b1add772995b820b584a488489000000000038b02b8002c0c62d0000000000160014ccf1af2f2aabee14bb40fa3851ab2301de84311054a56a00000000002200204adb4e2f00643db396dd120d4e7dc17625f5f2c11a40d857accc862d6b7dd80e0400473044022051b75c73198c6deee1a875871c3961832909acd297c6b908d59e3319e5185a46022055c419379c5051a78d00dbbce11b5b664a0c22815fbcc6fcef6b1937c383693901483045022100f51d2e566a70ba740fc5d8c0f07b9b93d2ed741c3c0860c613173de7d39e7968022041376d520e9c0e1ad52248ddf4b22e12be8763007df977253ef45a4ca3bdb7c001475221023da092f6980e58d2c037173180e9a465476026ee50f96695963e8efe436f54eb21030e9f7b623d2ccc7c9bd44d66d5ce21ce504c0acf6385a132cec6d3c39fa711c152ae3e195220')).GetTxid() - - assert c.remote_sig(tx) == Sig('3045022100f51d2e566a70ba740fc5d8c0f07b9b93d2ed741c3c0860c613173de7d39e7968022041376d520e9c0e1ad52248ddf4b22e12be8763007df977253ef45a4ca3bdb7c0') - assert c.local_sig(tx) == Sig('3044022051b75c73198c6deee1a875871c3961832909acd297c6b908d59e3319e5185a46022055c419379c5051a78d00dbbce11b5b664a0c22815fbcc6fcef6b1937c3836939') + assert ( + tx.GetTxid() + == CMutableTransaction.deserialize( + bytes.fromhex( + "02000000000101bef67e4e2fb9ddeeb3461973cd4c62abb35050b1add772995b820b584a488489000000000038b02b8002c0c62d0000000000160014ccf1af2f2aabee14bb40fa3851ab2301de84311054a56a00000000002200204adb4e2f00643db396dd120d4e7dc17625f5f2c11a40d857accc862d6b7dd80e0400473044022051b75c73198c6deee1a875871c3961832909acd297c6b908d59e3319e5185a46022055c419379c5051a78d00dbbce11b5b664a0c22815fbcc6fcef6b1937c383693901483045022100f51d2e566a70ba740fc5d8c0f07b9b93d2ed741c3c0860c613173de7d39e7968022041376d520e9c0e1ad52248ddf4b22e12be8763007df977253ef45a4ca3bdb7c001475221023da092f6980e58d2c037173180e9a465476026ee50f96695963e8efe436f54eb21030e9f7b623d2ccc7c9bd44d66d5ce21ce504c0acf6385a132cec6d3c39fa711c152ae3e195220" + ) + ).GetTxid() + ) + + assert c.remote_sig(tx) == Sig( + "3045022100f51d2e566a70ba740fc5d8c0f07b9b93d2ed741c3c0860c613173de7d39e7968022041376d520e9c0e1ad52248ddf4b22e12be8763007df977253ef45a4ca3bdb7c0" + ) + assert c.local_sig(tx) == Sig( + "3044022051b75c73198c6deee1a875871c3961832909acd297c6b908d59e3319e5185a46022055c419379c5051a78d00dbbce11b5b664a0c22815fbcc6fcef6b1937c3836939" + ) htlcs = [] # BOLT #3: @@ -1056,31 +1244,31 @@ def test_simple_commitment() -> None: # htlc 0 amount_msat: 1000000 # htlc 0 expiry: 500 # htlc 0 payment_preimage: 0000000000000000000000000000000000000000000000000000000000000000 - htlcs.append(HTLC(Side.remote, 1000000, '00' * 32, 500, '00' * 1366)) + htlcs.append(HTLC(Side.remote, 1000000, "00" * 32, 500, "00" * 1366)) # BOLT #3: # htlc 1 direction: remote->local # htlc 1 amount_msat: 2000000 # htlc 1 expiry: 501 # htlc 1 payment_preimage: 0101010101010101010101010101010101010101010101010101010101010101 - htlcs.append(HTLC(Side.remote, 2000000, '01' * 32, 501, '00' * 1366)) + htlcs.append(HTLC(Side.remote, 2000000, "01" * 32, 501, "00" * 1366)) # BOLT #3: # htlc 2 direction: local->remote # htlc 2 amount_msat: 2000000 # htlc 2 expiry: 502 # htlc 2 payment_preimage: 0202020202020202020202020202020202020202020202020202020202020202 - htlcs.append(HTLC(Side.local, 2000000, '02' * 32, 502, '00' * 1366)) + htlcs.append(HTLC(Side.local, 2000000, "02" * 32, 502, "00" * 1366)) # BOLT #3: # htlc 3 direction: local->remote # htlc 3 amount_msat: 3000000 # htlc 3 expiry: 503 # htlc 3 payment_preimage: 0303030303030303030303030303030303030303030303030303030303030303 - htlcs.append(HTLC(Side.local, 3000000, '03' * 32, 503, '00' * 1366)) + htlcs.append(HTLC(Side.local, 3000000, "03" * 32, 503, "00" * 1366)) # BOLT #3: # htlc 4 direction: remote->local # htlc 4 amount_msat: 4000000 # htlc 4 expiry: 504 # htlc 4 payment_preimage: 0404040404040404040404040404040404040404040404040404040404040404 - htlcs.append(HTLC(Side.remote, 4000000, '04' * 32, 504, '00' * 1366)) + htlcs.append(HTLC(Side.remote, 4000000, "04" * 32, 504, "00" * 1366)) # BOLT #3: # name: commitment tx with all five HTLCs untrimmed (minimum feerate) @@ -1111,16 +1299,33 @@ def test_simple_commitment() -> None: # remote_htlc_signature = 3045022100daee1808f9861b6c3ecd14f7b707eca02dd6bdfc714ba2f33bc8cdba507bb182022026654bf8863af77d74f51f4e0b62d461a019561bb12acb120d3f7195d148a554 # # signature for output 4 (HTLC 4) # remote_htlc_signature = 304402207e0410e45454b0978a623f36a10626ef17b27d9ad44e2760f98cfa3efb37924f0220220bd8acd43ecaa916a80bd4f919c495a2c58982ce7c8625153f8596692a801d - (0, - Sig('30440220275b0c325a5e9355650dc30c0eccfbc7efb23987c24b556b9dfdd40effca18d202206caceb2c067836c51f296740c7ae807ffcbfbf1dd3a0d56b6de9a5b247985f06'), - Sig('304402204fd4928835db1ccdfc40f5c78ce9bd65249b16348df81f0c44328dcdefc97d630220194d3869c38bc732dd87d13d2958015e2fc16829e74cd4377f84d215c0b70606'), - '02000000000101bef67e4e2fb9ddeeb3461973cd4c62abb35050b1add772995b820b584a488489000000000038b02b8007e80300000000000022002052bfef0479d7b293c27e0f1eb294bea154c63a3294ef092c19af51409bce0e2ad007000000000000220020403d394747cae42e98ff01734ad5c08f82ba123d3d9a620abda88989651e2ab5d007000000000000220020748eba944fedc8827f6b06bc44678f93c0f9e6078b35c6331ed31e75f8ce0c2db80b000000000000220020c20b5d1f8584fd90443e7b7b720136174fa4b9333c261d04dbbd012635c0f419a00f0000000000002200208c48d15160397c9731df9bc3b236656efb6665fbfe92b4a6878e88a499f741c4c0c62d0000000000160014ccf1af2f2aabee14bb40fa3851ab2301de843110e0a06a00000000002200204adb4e2f00643db396dd120d4e7dc17625f5f2c11a40d857accc862d6b7dd80e04004730440220275b0c325a5e9355650dc30c0eccfbc7efb23987c24b556b9dfdd40effca18d202206caceb2c067836c51f296740c7ae807ffcbfbf1dd3a0d56b6de9a5b247985f060147304402204fd4928835db1ccdfc40f5c78ce9bd65249b16348df81f0c44328dcdefc97d630220194d3869c38bc732dd87d13d2958015e2fc16829e74cd4377f84d215c0b7060601475221023da092f6980e58d2c037173180e9a465476026ee50f96695963e8efe436f54eb21030e9f7b623d2ccc7c9bd44d66d5ce21ce504c0acf6385a132cec6d3c39fa711c152ae3e195220', - (Sig('304402206a6e59f18764a5bf8d4fa45eebc591566689441229c918b480fb2af8cc6a4aeb02205248f273be447684b33e3c8d1d85a8e0ca9fa0bae9ae33f0527ada9c162919a6'), - Sig('3045022100d5275b3619953cb0c3b5aa577f04bc512380e60fa551762ce3d7a1bb7401cff9022037237ab0dac3fe100cde094e82e2bed9ba0ed1bb40154b48e56aa70f259e608b'), - Sig('304402201b63ec807771baf4fdff523c644080de17f1da478989308ad13a58b51db91d360220568939d38c9ce295adba15665fa68f51d967e8ed14a007b751540a80b325f202'), - Sig('3045022100daee1808f9861b6c3ecd14f7b707eca02dd6bdfc714ba2f33bc8cdba507bb182022026654bf8863af77d74f51f4e0b62d461a019561bb12acb120d3f7195d148a554'), - Sig('304402207e0410e45454b0978a623f36a10626ef17b27d9ad44e2760f98cfa3efb37924f0220220bd8acd43ecaa916a80bd4f919c495a2c58982ce7c8625153f8596692a801d'))), - + ( + 0, + Sig( + "30440220275b0c325a5e9355650dc30c0eccfbc7efb23987c24b556b9dfdd40effca18d202206caceb2c067836c51f296740c7ae807ffcbfbf1dd3a0d56b6de9a5b247985f06" + ), + Sig( + "304402204fd4928835db1ccdfc40f5c78ce9bd65249b16348df81f0c44328dcdefc97d630220194d3869c38bc732dd87d13d2958015e2fc16829e74cd4377f84d215c0b70606" + ), + "02000000000101bef67e4e2fb9ddeeb3461973cd4c62abb35050b1add772995b820b584a488489000000000038b02b8007e80300000000000022002052bfef0479d7b293c27e0f1eb294bea154c63a3294ef092c19af51409bce0e2ad007000000000000220020403d394747cae42e98ff01734ad5c08f82ba123d3d9a620abda88989651e2ab5d007000000000000220020748eba944fedc8827f6b06bc44678f93c0f9e6078b35c6331ed31e75f8ce0c2db80b000000000000220020c20b5d1f8584fd90443e7b7b720136174fa4b9333c261d04dbbd012635c0f419a00f0000000000002200208c48d15160397c9731df9bc3b236656efb6665fbfe92b4a6878e88a499f741c4c0c62d0000000000160014ccf1af2f2aabee14bb40fa3851ab2301de843110e0a06a00000000002200204adb4e2f00643db396dd120d4e7dc17625f5f2c11a40d857accc862d6b7dd80e04004730440220275b0c325a5e9355650dc30c0eccfbc7efb23987c24b556b9dfdd40effca18d202206caceb2c067836c51f296740c7ae807ffcbfbf1dd3a0d56b6de9a5b247985f060147304402204fd4928835db1ccdfc40f5c78ce9bd65249b16348df81f0c44328dcdefc97d630220194d3869c38bc732dd87d13d2958015e2fc16829e74cd4377f84d215c0b7060601475221023da092f6980e58d2c037173180e9a465476026ee50f96695963e8efe436f54eb21030e9f7b623d2ccc7c9bd44d66d5ce21ce504c0acf6385a132cec6d3c39fa711c152ae3e195220", + ( + Sig( + "304402206a6e59f18764a5bf8d4fa45eebc591566689441229c918b480fb2af8cc6a4aeb02205248f273be447684b33e3c8d1d85a8e0ca9fa0bae9ae33f0527ada9c162919a6" + ), + Sig( + "3045022100d5275b3619953cb0c3b5aa577f04bc512380e60fa551762ce3d7a1bb7401cff9022037237ab0dac3fe100cde094e82e2bed9ba0ed1bb40154b48e56aa70f259e608b" + ), + Sig( + "304402201b63ec807771baf4fdff523c644080de17f1da478989308ad13a58b51db91d360220568939d38c9ce295adba15665fa68f51d967e8ed14a007b751540a80b325f202" + ), + Sig( + "3045022100daee1808f9861b6c3ecd14f7b707eca02dd6bdfc714ba2f33bc8cdba507bb182022026654bf8863af77d74f51f4e0b62d461a019561bb12acb120d3f7195d148a554" + ), + Sig( + "304402207e0410e45454b0978a623f36a10626ef17b27d9ad44e2760f98cfa3efb37924f0220220bd8acd43ecaa916a80bd4f919c495a2c58982ce7c8625153f8596692a801d" + ), + ), + ), # BOLT #3: # local_feerate_per_kw: 647 # ... @@ -1138,16 +1343,33 @@ def test_simple_commitment() -> None: # remote_htlc_signature = 30450221009b1c987ba599ee3bde1dbca776b85481d70a78b681a8d84206723e2795c7cac002207aac84ad910f8598c4d1c0ea2e3399cf6627a4e3e90131315bc9f038451ce39d # # signature for output 4 (HTLC 4) # remote_htlc_signature = 3045022100cc28030b59f0914f45b84caa983b6f8effa900c952310708c2b5b00781117022022027ba2ccdf94d03c6d48b327f183f6e28c8a214d089b9227f94ac4f85315274f0 - (647, - Sig('304502210094bfd8f5572ac0157ec76a9551b6c5216a4538c07cd13a51af4a54cb26fa14320220768efce8ce6f4a5efac875142ff19237c011343670adf9c7ac69704a120d1163'), - Sig('3045022100a5c01383d3ec646d97e40f44318d49def817fcd61a0ef18008a665b3e151785502203e648efddd5838981ef55ec954be69c4a652d021e6081a100d034de366815e9b'), - '02000000000101bef67e4e2fb9ddeeb3461973cd4c62abb35050b1add772995b820b584a488489000000000038b02b8007e80300000000000022002052bfef0479d7b293c27e0f1eb294bea154c63a3294ef092c19af51409bce0e2ad007000000000000220020403d394747cae42e98ff01734ad5c08f82ba123d3d9a620abda88989651e2ab5d007000000000000220020748eba944fedc8827f6b06bc44678f93c0f9e6078b35c6331ed31e75f8ce0c2db80b000000000000220020c20b5d1f8584fd90443e7b7b720136174fa4b9333c261d04dbbd012635c0f419a00f0000000000002200208c48d15160397c9731df9bc3b236656efb6665fbfe92b4a6878e88a499f741c4c0c62d0000000000160014ccf1af2f2aabee14bb40fa3851ab2301de843110e09c6a00000000002200204adb4e2f00643db396dd120d4e7dc17625f5f2c11a40d857accc862d6b7dd80e040048304502210094bfd8f5572ac0157ec76a9551b6c5216a4538c07cd13a51af4a54cb26fa14320220768efce8ce6f4a5efac875142ff19237c011343670adf9c7ac69704a120d116301483045022100a5c01383d3ec646d97e40f44318d49def817fcd61a0ef18008a665b3e151785502203e648efddd5838981ef55ec954be69c4a652d021e6081a100d034de366815e9b01475221023da092f6980e58d2c037173180e9a465476026ee50f96695963e8efe436f54eb21030e9f7b623d2ccc7c9bd44d66d5ce21ce504c0acf6385a132cec6d3c39fa711c152ae3e195220', - (Sig('30440220385a5afe75632f50128cbb029ee95c80156b5b4744beddc729ad339c9ca432c802202ba5f48550cad3379ac75b9b4fedb86a35baa6947f16ba5037fb8b11ab343740'), - Sig('304402207ceb6678d4db33d2401fdc409959e57c16a6cb97a30261d9c61f29b8c58d34b90220084b4a17b4ca0e86f2d798b3698ca52de5621f2ce86f80bed79afa66874511b0'), - Sig('304402206a401b29a0dff0d18ec903502c13d83e7ec019450113f4a7655a4ce40d1f65ba0220217723a084e727b6ca0cc8b6c69c014a7e4a01fcdcba3e3993f462a3c574d833'), - Sig('30450221009b1c987ba599ee3bde1dbca776b85481d70a78b681a8d84206723e2795c7cac002207aac84ad910f8598c4d1c0ea2e3399cf6627a4e3e90131315bc9f038451ce39d'), - Sig('3045022100cc28030b59f0914f45b84caa983b6f8effa900c952310708c2b5b00781117022022027ba2ccdf94d03c6d48b327f183f6e28c8a214d089b9227f94ac4f85315274f0'))), - + ( + 647, + Sig( + "304502210094bfd8f5572ac0157ec76a9551b6c5216a4538c07cd13a51af4a54cb26fa14320220768efce8ce6f4a5efac875142ff19237c011343670adf9c7ac69704a120d1163" + ), + Sig( + "3045022100a5c01383d3ec646d97e40f44318d49def817fcd61a0ef18008a665b3e151785502203e648efddd5838981ef55ec954be69c4a652d021e6081a100d034de366815e9b" + ), + "02000000000101bef67e4e2fb9ddeeb3461973cd4c62abb35050b1add772995b820b584a488489000000000038b02b8007e80300000000000022002052bfef0479d7b293c27e0f1eb294bea154c63a3294ef092c19af51409bce0e2ad007000000000000220020403d394747cae42e98ff01734ad5c08f82ba123d3d9a620abda88989651e2ab5d007000000000000220020748eba944fedc8827f6b06bc44678f93c0f9e6078b35c6331ed31e75f8ce0c2db80b000000000000220020c20b5d1f8584fd90443e7b7b720136174fa4b9333c261d04dbbd012635c0f419a00f0000000000002200208c48d15160397c9731df9bc3b236656efb6665fbfe92b4a6878e88a499f741c4c0c62d0000000000160014ccf1af2f2aabee14bb40fa3851ab2301de843110e09c6a00000000002200204adb4e2f00643db396dd120d4e7dc17625f5f2c11a40d857accc862d6b7dd80e040048304502210094bfd8f5572ac0157ec76a9551b6c5216a4538c07cd13a51af4a54cb26fa14320220768efce8ce6f4a5efac875142ff19237c011343670adf9c7ac69704a120d116301483045022100a5c01383d3ec646d97e40f44318d49def817fcd61a0ef18008a665b3e151785502203e648efddd5838981ef55ec954be69c4a652d021e6081a100d034de366815e9b01475221023da092f6980e58d2c037173180e9a465476026ee50f96695963e8efe436f54eb21030e9f7b623d2ccc7c9bd44d66d5ce21ce504c0acf6385a132cec6d3c39fa711c152ae3e195220", + ( + Sig( + "30440220385a5afe75632f50128cbb029ee95c80156b5b4744beddc729ad339c9ca432c802202ba5f48550cad3379ac75b9b4fedb86a35baa6947f16ba5037fb8b11ab343740" + ), + Sig( + "304402207ceb6678d4db33d2401fdc409959e57c16a6cb97a30261d9c61f29b8c58d34b90220084b4a17b4ca0e86f2d798b3698ca52de5621f2ce86f80bed79afa66874511b0" + ), + Sig( + "304402206a401b29a0dff0d18ec903502c13d83e7ec019450113f4a7655a4ce40d1f65ba0220217723a084e727b6ca0cc8b6c69c014a7e4a01fcdcba3e3993f462a3c574d833" + ), + Sig( + "30450221009b1c987ba599ee3bde1dbca776b85481d70a78b681a8d84206723e2795c7cac002207aac84ad910f8598c4d1c0ea2e3399cf6627a4e3e90131315bc9f038451ce39d" + ), + Sig( + "3045022100cc28030b59f0914f45b84caa983b6f8effa900c952310708c2b5b00781117022022027ba2ccdf94d03c6d48b327f183f6e28c8a214d089b9227f94ac4f85315274f0" + ), + ), + ), # BOLT #3: # local_feerate_per_kw: 648 # ... @@ -1163,15 +1385,30 @@ def test_simple_commitment() -> None: # remote_htlc_signature = 3045022100aa91932e305292cf9969cc23502bbf6cef83a5df39c95ad04a707c4f4fed5c7702207099fc0f3a9bfe1e7683c0e9aa5e76c5432eb20693bf4cb182f04d383dc9c8c2 # # signature for output 3 (HTLC 4) # remote_htlc_signature = 3044022035cac88040a5bba420b1c4257235d5015309113460bc33f2853cd81ca36e632402202fc94fd3e81e9d34a9d01782a0284f3044370d03d60f3fc041e2da088d2de58f - (648, - Sig('3045022100a2270d5950c89ae0841233f6efea9c951898b301b2e89e0adbd2c687b9f32efa02207943d90f95b9610458e7c65a576e149750ff3accaacad004cd85e70b235e27de'), - Sig('3044022072714e2fbb93cdd1c42eb0828b4f2eff143f717d8f26e79d6ada4f0dcb681bbe02200911be4e5161dd6ebe59ff1c58e1997c4aea804f81db6b698821db6093d7b057'), - '02000000000101bef67e4e2fb9ddeeb3461973cd4c62abb35050b1add772995b820b584a488489000000000038b02b8006d007000000000000220020403d394747cae42e98ff01734ad5c08f82ba123d3d9a620abda88989651e2ab5d007000000000000220020748eba944fedc8827f6b06bc44678f93c0f9e6078b35c6331ed31e75f8ce0c2db80b000000000000220020c20b5d1f8584fd90443e7b7b720136174fa4b9333c261d04dbbd012635c0f419a00f0000000000002200208c48d15160397c9731df9bc3b236656efb6665fbfe92b4a6878e88a499f741c4c0c62d0000000000160014ccf1af2f2aabee14bb40fa3851ab2301de8431104e9d6a00000000002200204adb4e2f00643db396dd120d4e7dc17625f5f2c11a40d857accc862d6b7dd80e0400483045022100a2270d5950c89ae0841233f6efea9c951898b301b2e89e0adbd2c687b9f32efa02207943d90f95b9610458e7c65a576e149750ff3accaacad004cd85e70b235e27de01473044022072714e2fbb93cdd1c42eb0828b4f2eff143f717d8f26e79d6ada4f0dcb681bbe02200911be4e5161dd6ebe59ff1c58e1997c4aea804f81db6b698821db6093d7b05701475221023da092f6980e58d2c037173180e9a465476026ee50f96695963e8efe436f54eb21030e9f7b623d2ccc7c9bd44d66d5ce21ce504c0acf6385a132cec6d3c39fa711c152ae3e195220', - (Sig('3044022062ef2e77591409d60d7817d9bb1e71d3c4a2931d1a6c7c8307422c84f001a251022022dad9726b0ae3fe92bda745a06f2c00f92342a186d84518588cf65f4dfaada8'), - Sig('3045022100e968cbbb5f402ed389fdc7f6cd2a80ed650bb42c79aeb2a5678444af94f6c78502204b47a1cb24ab5b0b6fe69fe9cfc7dba07b9dd0d8b95f372c1d9435146a88f8d4'), - Sig('3045022100aa91932e305292cf9969cc23502bbf6cef83a5df39c95ad04a707c4f4fed5c7702207099fc0f3a9bfe1e7683c0e9aa5e76c5432eb20693bf4cb182f04d383dc9c8c2'), - Sig('3044022035cac88040a5bba420b1c4257235d5015309113460bc33f2853cd81ca36e632402202fc94fd3e81e9d34a9d01782a0284f3044370d03d60f3fc041e2da088d2de58f'))), - + ( + 648, + Sig( + "3045022100a2270d5950c89ae0841233f6efea9c951898b301b2e89e0adbd2c687b9f32efa02207943d90f95b9610458e7c65a576e149750ff3accaacad004cd85e70b235e27de" + ), + Sig( + "3044022072714e2fbb93cdd1c42eb0828b4f2eff143f717d8f26e79d6ada4f0dcb681bbe02200911be4e5161dd6ebe59ff1c58e1997c4aea804f81db6b698821db6093d7b057" + ), + "02000000000101bef67e4e2fb9ddeeb3461973cd4c62abb35050b1add772995b820b584a488489000000000038b02b8006d007000000000000220020403d394747cae42e98ff01734ad5c08f82ba123d3d9a620abda88989651e2ab5d007000000000000220020748eba944fedc8827f6b06bc44678f93c0f9e6078b35c6331ed31e75f8ce0c2db80b000000000000220020c20b5d1f8584fd90443e7b7b720136174fa4b9333c261d04dbbd012635c0f419a00f0000000000002200208c48d15160397c9731df9bc3b236656efb6665fbfe92b4a6878e88a499f741c4c0c62d0000000000160014ccf1af2f2aabee14bb40fa3851ab2301de8431104e9d6a00000000002200204adb4e2f00643db396dd120d4e7dc17625f5f2c11a40d857accc862d6b7dd80e0400483045022100a2270d5950c89ae0841233f6efea9c951898b301b2e89e0adbd2c687b9f32efa02207943d90f95b9610458e7c65a576e149750ff3accaacad004cd85e70b235e27de01473044022072714e2fbb93cdd1c42eb0828b4f2eff143f717d8f26e79d6ada4f0dcb681bbe02200911be4e5161dd6ebe59ff1c58e1997c4aea804f81db6b698821db6093d7b05701475221023da092f6980e58d2c037173180e9a465476026ee50f96695963e8efe436f54eb21030e9f7b623d2ccc7c9bd44d66d5ce21ce504c0acf6385a132cec6d3c39fa711c152ae3e195220", + ( + Sig( + "3044022062ef2e77591409d60d7817d9bb1e71d3c4a2931d1a6c7c8307422c84f001a251022022dad9726b0ae3fe92bda745a06f2c00f92342a186d84518588cf65f4dfaada8" + ), + Sig( + "3045022100e968cbbb5f402ed389fdc7f6cd2a80ed650bb42c79aeb2a5678444af94f6c78502204b47a1cb24ab5b0b6fe69fe9cfc7dba07b9dd0d8b95f372c1d9435146a88f8d4" + ), + Sig( + "3045022100aa91932e305292cf9969cc23502bbf6cef83a5df39c95ad04a707c4f4fed5c7702207099fc0f3a9bfe1e7683c0e9aa5e76c5432eb20693bf4cb182f04d383dc9c8c2" + ), + Sig( + "3044022035cac88040a5bba420b1c4257235d5015309113460bc33f2853cd81ca36e632402202fc94fd3e81e9d34a9d01782a0284f3044370d03d60f3fc041e2da088d2de58f" + ), + ), + ), # BOLT #3: # local_feerate_per_kw: 2069 # ... @@ -1187,15 +1424,30 @@ def test_simple_commitment() -> None: # remote_htlc_signature = 3045022100d4e69d363de993684eae7b37853c40722a4c1b4a7b588ad7b5d8a9b5006137a102207a069c628170ee34be5612747051bdcc087466dbaa68d5756ea81c10155aef18 # # signature for output 3 (HTLC 4) # remote_htlc_signature = 30450221008ec888e36e4a4b3dc2ed6b823319855b2ae03006ca6ae0d9aa7e24bfc1d6f07102203b0f78885472a67ff4fe5916c0bb669487d659527509516fc3a08e87a2cc0a7c - (2069, - Sig('304402203ca8f31c6a47519f83255dc69f1894d9a6d7476a19f498d31eaf0cd3a85eeb63022026fd92dc752b33905c4c838c528b692a8ad4ced959990b5d5ee2ff940fa90eea'), - Sig('3044022001d55e488b8b035b2dd29d50b65b530923a416d47f377284145bc8767b1b6a75022019bb53ddfe1cefaf156f924777eaaf8fdca1810695a7d0a247ad2afba8232eb4'), - '02000000000101bef67e4e2fb9ddeeb3461973cd4c62abb35050b1add772995b820b584a488489000000000038b02b8006d007000000000000220020403d394747cae42e98ff01734ad5c08f82ba123d3d9a620abda88989651e2ab5d007000000000000220020748eba944fedc8827f6b06bc44678f93c0f9e6078b35c6331ed31e75f8ce0c2db80b000000000000220020c20b5d1f8584fd90443e7b7b720136174fa4b9333c261d04dbbd012635c0f419a00f0000000000002200208c48d15160397c9731df9bc3b236656efb6665fbfe92b4a6878e88a499f741c4c0c62d0000000000160014ccf1af2f2aabee14bb40fa3851ab2301de84311077956a00000000002200204adb4e2f00643db396dd120d4e7dc17625f5f2c11a40d857accc862d6b7dd80e040047304402203ca8f31c6a47519f83255dc69f1894d9a6d7476a19f498d31eaf0cd3a85eeb63022026fd92dc752b33905c4c838c528b692a8ad4ced959990b5d5ee2ff940fa90eea01473044022001d55e488b8b035b2dd29d50b65b530923a416d47f377284145bc8767b1b6a75022019bb53ddfe1cefaf156f924777eaaf8fdca1810695a7d0a247ad2afba8232eb401475221023da092f6980e58d2c037173180e9a465476026ee50f96695963e8efe436f54eb21030e9f7b623d2ccc7c9bd44d66d5ce21ce504c0acf6385a132cec6d3c39fa711c152ae3e195220', - (Sig('3045022100d1cf354de41c1369336cf85b225ed033f1f8982a01be503668df756a7e668b66022001254144fb4d0eecc61908fccc3388891ba17c5d7a1a8c62bdd307e5a513f992'), - Sig('3045022100d065569dcb94f090345402736385efeb8ea265131804beac06dd84d15dd2d6880220664feb0b4b2eb985fadb6ec7dc58c9334ea88ce599a9be760554a2d4b3b5d9f4'), - Sig('3045022100d4e69d363de993684eae7b37853c40722a4c1b4a7b588ad7b5d8a9b5006137a102207a069c628170ee34be5612747051bdcc087466dbaa68d5756ea81c10155aef18'), - Sig('30450221008ec888e36e4a4b3dc2ed6b823319855b2ae03006ca6ae0d9aa7e24bfc1d6f07102203b0f78885472a67ff4fe5916c0bb669487d659527509516fc3a08e87a2cc0a7c'))), - + ( + 2069, + Sig( + "304402203ca8f31c6a47519f83255dc69f1894d9a6d7476a19f498d31eaf0cd3a85eeb63022026fd92dc752b33905c4c838c528b692a8ad4ced959990b5d5ee2ff940fa90eea" + ), + Sig( + "3044022001d55e488b8b035b2dd29d50b65b530923a416d47f377284145bc8767b1b6a75022019bb53ddfe1cefaf156f924777eaaf8fdca1810695a7d0a247ad2afba8232eb4" + ), + "02000000000101bef67e4e2fb9ddeeb3461973cd4c62abb35050b1add772995b820b584a488489000000000038b02b8006d007000000000000220020403d394747cae42e98ff01734ad5c08f82ba123d3d9a620abda88989651e2ab5d007000000000000220020748eba944fedc8827f6b06bc44678f93c0f9e6078b35c6331ed31e75f8ce0c2db80b000000000000220020c20b5d1f8584fd90443e7b7b720136174fa4b9333c261d04dbbd012635c0f419a00f0000000000002200208c48d15160397c9731df9bc3b236656efb6665fbfe92b4a6878e88a499f741c4c0c62d0000000000160014ccf1af2f2aabee14bb40fa3851ab2301de84311077956a00000000002200204adb4e2f00643db396dd120d4e7dc17625f5f2c11a40d857accc862d6b7dd80e040047304402203ca8f31c6a47519f83255dc69f1894d9a6d7476a19f498d31eaf0cd3a85eeb63022026fd92dc752b33905c4c838c528b692a8ad4ced959990b5d5ee2ff940fa90eea01473044022001d55e488b8b035b2dd29d50b65b530923a416d47f377284145bc8767b1b6a75022019bb53ddfe1cefaf156f924777eaaf8fdca1810695a7d0a247ad2afba8232eb401475221023da092f6980e58d2c037173180e9a465476026ee50f96695963e8efe436f54eb21030e9f7b623d2ccc7c9bd44d66d5ce21ce504c0acf6385a132cec6d3c39fa711c152ae3e195220", + ( + Sig( + "3045022100d1cf354de41c1369336cf85b225ed033f1f8982a01be503668df756a7e668b66022001254144fb4d0eecc61908fccc3388891ba17c5d7a1a8c62bdd307e5a513f992" + ), + Sig( + "3045022100d065569dcb94f090345402736385efeb8ea265131804beac06dd84d15dd2d6880220664feb0b4b2eb985fadb6ec7dc58c9334ea88ce599a9be760554a2d4b3b5d9f4" + ), + Sig( + "3045022100d4e69d363de993684eae7b37853c40722a4c1b4a7b588ad7b5d8a9b5006137a102207a069c628170ee34be5612747051bdcc087466dbaa68d5756ea81c10155aef18" + ), + Sig( + "30450221008ec888e36e4a4b3dc2ed6b823319855b2ae03006ca6ae0d9aa7e24bfc1d6f07102203b0f78885472a67ff4fe5916c0bb669487d659527509516fc3a08e87a2cc0a7c" + ), + ), + ), # BOLT #3: # local_feerate_per_kw: 2070 # ... @@ -1209,14 +1461,27 @@ def test_simple_commitment() -> None: # remote_htlc_signature = 3044022071e9357619fd8d29a411dc053b326a5224c5d11268070e88ecb981b174747c7a02202b763ae29a9d0732fa8836dd8597439460b50472183f420021b768981b4f7cf6 # # signature for output 2 (HTLC 4) # remote_htlc_signature = 3045022100c9458a4d2cbb741705577deb0a890e5cb90ee141be0400d3162e533727c9cb2102206edcf765c5dc5e5f9b976ea8149bf8607b5a0efb30691138e1231302b640d2a4 - (2070, - Sig('30440220443cb07f650aebbba14b8bc8d81e096712590f524c5991ac0ed3bbc8fd3bd0c7022028a635f548e3ca64b19b69b1ea00f05b22752f91daf0b6dab78e62ba52eb7fd0'), - Sig('3045022100f2377f7a67b7fc7f4e2c0c9e3a7de935c32417f5668eda31ea1db401b7dc53030220415fdbc8e91d0f735e70c21952342742e25249b0d062d43efbfc564499f37526'), - '02000000000101bef67e4e2fb9ddeeb3461973cd4c62abb35050b1add772995b820b584a488489000000000038b02b8005d007000000000000220020403d394747cae42e98ff01734ad5c08f82ba123d3d9a620abda88989651e2ab5b80b000000000000220020c20b5d1f8584fd90443e7b7b720136174fa4b9333c261d04dbbd012635c0f419a00f0000000000002200208c48d15160397c9731df9bc3b236656efb6665fbfe92b4a6878e88a499f741c4c0c62d0000000000160014ccf1af2f2aabee14bb40fa3851ab2301de843110da966a00000000002200204adb4e2f00643db396dd120d4e7dc17625f5f2c11a40d857accc862d6b7dd80e04004730440220443cb07f650aebbba14b8bc8d81e096712590f524c5991ac0ed3bbc8fd3bd0c7022028a635f548e3ca64b19b69b1ea00f05b22752f91daf0b6dab78e62ba52eb7fd001483045022100f2377f7a67b7fc7f4e2c0c9e3a7de935c32417f5668eda31ea1db401b7dc53030220415fdbc8e91d0f735e70c21952342742e25249b0d062d43efbfc564499f3752601475221023da092f6980e58d2c037173180e9a465476026ee50f96695963e8efe436f54eb21030e9f7b623d2ccc7c9bd44d66d5ce21ce504c0acf6385a132cec6d3c39fa711c152ae3e195220', - (Sig('3045022100eed143b1ee4bed5dc3cde40afa5db3e7354cbf9c44054b5f713f729356f08cf7022077161d171c2bbd9badf3c9934de65a4918de03bbac1450f715275f75b103f891'), - Sig('3044022071e9357619fd8d29a411dc053b326a5224c5d11268070e88ecb981b174747c7a02202b763ae29a9d0732fa8836dd8597439460b50472183f420021b768981b4f7cf6'), - Sig('3045022100c9458a4d2cbb741705577deb0a890e5cb90ee141be0400d3162e533727c9cb2102206edcf765c5dc5e5f9b976ea8149bf8607b5a0efb30691138e1231302b640d2a4'))), - + ( + 2070, + Sig( + "30440220443cb07f650aebbba14b8bc8d81e096712590f524c5991ac0ed3bbc8fd3bd0c7022028a635f548e3ca64b19b69b1ea00f05b22752f91daf0b6dab78e62ba52eb7fd0" + ), + Sig( + "3045022100f2377f7a67b7fc7f4e2c0c9e3a7de935c32417f5668eda31ea1db401b7dc53030220415fdbc8e91d0f735e70c21952342742e25249b0d062d43efbfc564499f37526" + ), + "02000000000101bef67e4e2fb9ddeeb3461973cd4c62abb35050b1add772995b820b584a488489000000000038b02b8005d007000000000000220020403d394747cae42e98ff01734ad5c08f82ba123d3d9a620abda88989651e2ab5b80b000000000000220020c20b5d1f8584fd90443e7b7b720136174fa4b9333c261d04dbbd012635c0f419a00f0000000000002200208c48d15160397c9731df9bc3b236656efb6665fbfe92b4a6878e88a499f741c4c0c62d0000000000160014ccf1af2f2aabee14bb40fa3851ab2301de843110da966a00000000002200204adb4e2f00643db396dd120d4e7dc17625f5f2c11a40d857accc862d6b7dd80e04004730440220443cb07f650aebbba14b8bc8d81e096712590f524c5991ac0ed3bbc8fd3bd0c7022028a635f548e3ca64b19b69b1ea00f05b22752f91daf0b6dab78e62ba52eb7fd001483045022100f2377f7a67b7fc7f4e2c0c9e3a7de935c32417f5668eda31ea1db401b7dc53030220415fdbc8e91d0f735e70c21952342742e25249b0d062d43efbfc564499f3752601475221023da092f6980e58d2c037173180e9a465476026ee50f96695963e8efe436f54eb21030e9f7b623d2ccc7c9bd44d66d5ce21ce504c0acf6385a132cec6d3c39fa711c152ae3e195220", + ( + Sig( + "3045022100eed143b1ee4bed5dc3cde40afa5db3e7354cbf9c44054b5f713f729356f08cf7022077161d171c2bbd9badf3c9934de65a4918de03bbac1450f715275f75b103f891" + ), + Sig( + "3044022071e9357619fd8d29a411dc053b326a5224c5d11268070e88ecb981b174747c7a02202b763ae29a9d0732fa8836dd8597439460b50472183f420021b768981b4f7cf6" + ), + Sig( + "3045022100c9458a4d2cbb741705577deb0a890e5cb90ee141be0400d3162e533727c9cb2102206edcf765c5dc5e5f9b976ea8149bf8607b5a0efb30691138e1231302b640d2a4" + ), + ), + ), # BOLT #3: # local_feerate_per_kw: 2194 # ... @@ -1230,14 +1495,27 @@ def test_simple_commitment() -> None: # remote_htlc_signature = 30440220155d3b90c67c33a8321996a9be5b82431b0c126613be751d400669da9d5c696702204318448bcd48824439d2c6a70be6e5747446be47ff45977cf41672bdc9b6b12d # # signature for output 2 (HTLC 4) # remote_htlc_signature = 3045022100a12a9a473ece548584aabdd051779025a5ed4077c4b7aa376ec7a0b1645e5a48022039490b333f53b5b3e2ddde1d809e492cba2b3e5fc3a436cd3ffb4cd3d500fa5a - (2194, - Sig('304402203b1b010c109c2ecbe7feb2d259b9c4126bd5dc99ee693c422ec0a5781fe161ba0220571fe4e2c649dea9c7aaf7e49b382962f6a3494963c97d80fef9a430ca3f7061'), - Sig('3045022100d33c4e541aa1d255d41ea9a3b443b3b822ad8f7f86862638aac1f69f8f760577022007e2a18e6931ce3d3a804b1c78eda1de17dbe1fb7a95488c9a4ec86203953348'), - '02000000000101bef67e4e2fb9ddeeb3461973cd4c62abb35050b1add772995b820b584a488489000000000038b02b8005d007000000000000220020403d394747cae42e98ff01734ad5c08f82ba123d3d9a620abda88989651e2ab5b80b000000000000220020c20b5d1f8584fd90443e7b7b720136174fa4b9333c261d04dbbd012635c0f419a00f0000000000002200208c48d15160397c9731df9bc3b236656efb6665fbfe92b4a6878e88a499f741c4c0c62d0000000000160014ccf1af2f2aabee14bb40fa3851ab2301de84311040966a00000000002200204adb4e2f00643db396dd120d4e7dc17625f5f2c11a40d857accc862d6b7dd80e040047304402203b1b010c109c2ecbe7feb2d259b9c4126bd5dc99ee693c422ec0a5781fe161ba0220571fe4e2c649dea9c7aaf7e49b382962f6a3494963c97d80fef9a430ca3f706101483045022100d33c4e541aa1d255d41ea9a3b443b3b822ad8f7f86862638aac1f69f8f760577022007e2a18e6931ce3d3a804b1c78eda1de17dbe1fb7a95488c9a4ec8620395334801475221023da092f6980e58d2c037173180e9a465476026ee50f96695963e8efe436f54eb21030e9f7b623d2ccc7c9bd44d66d5ce21ce504c0acf6385a132cec6d3c39fa711c152ae3e195220', - (Sig('30450221009ed2f0a67f99e29c3c8cf45c08207b765980697781bb727fe0b1416de0e7622902206052684229bc171419ed290f4b615c943f819c0262414e43c5b91dcf72ddcf44'), - Sig('30440220155d3b90c67c33a8321996a9be5b82431b0c126613be751d400669da9d5c696702204318448bcd48824439d2c6a70be6e5747446be47ff45977cf41672bdc9b6b12d'), - Sig('3045022100a12a9a473ece548584aabdd051779025a5ed4077c4b7aa376ec7a0b1645e5a48022039490b333f53b5b3e2ddde1d809e492cba2b3e5fc3a436cd3ffb4cd3d500fa5a'))), - + ( + 2194, + Sig( + "304402203b1b010c109c2ecbe7feb2d259b9c4126bd5dc99ee693c422ec0a5781fe161ba0220571fe4e2c649dea9c7aaf7e49b382962f6a3494963c97d80fef9a430ca3f7061" + ), + Sig( + "3045022100d33c4e541aa1d255d41ea9a3b443b3b822ad8f7f86862638aac1f69f8f760577022007e2a18e6931ce3d3a804b1c78eda1de17dbe1fb7a95488c9a4ec86203953348" + ), + "02000000000101bef67e4e2fb9ddeeb3461973cd4c62abb35050b1add772995b820b584a488489000000000038b02b8005d007000000000000220020403d394747cae42e98ff01734ad5c08f82ba123d3d9a620abda88989651e2ab5b80b000000000000220020c20b5d1f8584fd90443e7b7b720136174fa4b9333c261d04dbbd012635c0f419a00f0000000000002200208c48d15160397c9731df9bc3b236656efb6665fbfe92b4a6878e88a499f741c4c0c62d0000000000160014ccf1af2f2aabee14bb40fa3851ab2301de84311040966a00000000002200204adb4e2f00643db396dd120d4e7dc17625f5f2c11a40d857accc862d6b7dd80e040047304402203b1b010c109c2ecbe7feb2d259b9c4126bd5dc99ee693c422ec0a5781fe161ba0220571fe4e2c649dea9c7aaf7e49b382962f6a3494963c97d80fef9a430ca3f706101483045022100d33c4e541aa1d255d41ea9a3b443b3b822ad8f7f86862638aac1f69f8f760577022007e2a18e6931ce3d3a804b1c78eda1de17dbe1fb7a95488c9a4ec8620395334801475221023da092f6980e58d2c037173180e9a465476026ee50f96695963e8efe436f54eb21030e9f7b623d2ccc7c9bd44d66d5ce21ce504c0acf6385a132cec6d3c39fa711c152ae3e195220", + ( + Sig( + "30450221009ed2f0a67f99e29c3c8cf45c08207b765980697781bb727fe0b1416de0e7622902206052684229bc171419ed290f4b615c943f819c0262414e43c5b91dcf72ddcf44" + ), + Sig( + "30440220155d3b90c67c33a8321996a9be5b82431b0c126613be751d400669da9d5c696702204318448bcd48824439d2c6a70be6e5747446be47ff45977cf41672bdc9b6b12d" + ), + Sig( + "3045022100a12a9a473ece548584aabdd051779025a5ed4077c4b7aa376ec7a0b1645e5a48022039490b333f53b5b3e2ddde1d809e492cba2b3e5fc3a436cd3ffb4cd3d500fa5a" + ), + ), + ), # BOLT #3: # local_feerate_per_kw: 2195 # ... @@ -1249,13 +1527,24 @@ def test_simple_commitment() -> None: # remote_htlc_signature = 3045022100a8a78fa1016a5c5c3704f2e8908715a3cef66723fb95f3132ec4d2d05cd84fb4022025ac49287b0861ec21932405f5600cbce94313dbde0e6c5d5af1b3366d8afbfc # # signature for output 1 (HTLC 4) # remote_htlc_signature = 3045022100e769cb156aa2f7515d126cef7a69968629620ce82afcaa9e210969de6850df4602200b16b3f3486a229a48aadde520dbee31ae340dbadaffae74fbb56681fef27b92 - (2195, - Sig('304402203b12d44254244b8ff3bb4129b0920fd45120ab42f553d9976394b099d500c99e02205e95bb7a3164852ef0c48f9e0eaf145218f8e2c41251b231f03cbdc4f29a5429'), - Sig('304402205e2f76d4657fb732c0dfc820a18a7301e368f5799e06b7828007633741bda6df0220458009ae59d0c6246065c419359e05eb2a4b4ef4a1b310cc912db44eb7924298'), - '02000000000101bef67e4e2fb9ddeeb3461973cd4c62abb35050b1add772995b820b584a488489000000000038b02b8004b80b000000000000220020c20b5d1f8584fd90443e7b7b720136174fa4b9333c261d04dbbd012635c0f419a00f0000000000002200208c48d15160397c9731df9bc3b236656efb6665fbfe92b4a6878e88a499f741c4c0c62d0000000000160014ccf1af2f2aabee14bb40fa3851ab2301de843110b8976a00000000002200204adb4e2f00643db396dd120d4e7dc17625f5f2c11a40d857accc862d6b7dd80e040047304402203b12d44254244b8ff3bb4129b0920fd45120ab42f553d9976394b099d500c99e02205e95bb7a3164852ef0c48f9e0eaf145218f8e2c41251b231f03cbdc4f29a54290147304402205e2f76d4657fb732c0dfc820a18a7301e368f5799e06b7828007633741bda6df0220458009ae59d0c6246065c419359e05eb2a4b4ef4a1b310cc912db44eb792429801475221023da092f6980e58d2c037173180e9a465476026ee50f96695963e8efe436f54eb21030e9f7b623d2ccc7c9bd44d66d5ce21ce504c0acf6385a132cec6d3c39fa711c152ae3e195220', - (Sig('3045022100a8a78fa1016a5c5c3704f2e8908715a3cef66723fb95f3132ec4d2d05cd84fb4022025ac49287b0861ec21932405f5600cbce94313dbde0e6c5d5af1b3366d8afbfc'), - Sig('3045022100e769cb156aa2f7515d126cef7a69968629620ce82afcaa9e210969de6850df4602200b16b3f3486a229a48aadde520dbee31ae340dbadaffae74fbb56681fef27b92'))), - + ( + 2195, + Sig( + "304402203b12d44254244b8ff3bb4129b0920fd45120ab42f553d9976394b099d500c99e02205e95bb7a3164852ef0c48f9e0eaf145218f8e2c41251b231f03cbdc4f29a5429" + ), + Sig( + "304402205e2f76d4657fb732c0dfc820a18a7301e368f5799e06b7828007633741bda6df0220458009ae59d0c6246065c419359e05eb2a4b4ef4a1b310cc912db44eb7924298" + ), + "02000000000101bef67e4e2fb9ddeeb3461973cd4c62abb35050b1add772995b820b584a488489000000000038b02b8004b80b000000000000220020c20b5d1f8584fd90443e7b7b720136174fa4b9333c261d04dbbd012635c0f419a00f0000000000002200208c48d15160397c9731df9bc3b236656efb6665fbfe92b4a6878e88a499f741c4c0c62d0000000000160014ccf1af2f2aabee14bb40fa3851ab2301de843110b8976a00000000002200204adb4e2f00643db396dd120d4e7dc17625f5f2c11a40d857accc862d6b7dd80e040047304402203b12d44254244b8ff3bb4129b0920fd45120ab42f553d9976394b099d500c99e02205e95bb7a3164852ef0c48f9e0eaf145218f8e2c41251b231f03cbdc4f29a54290147304402205e2f76d4657fb732c0dfc820a18a7301e368f5799e06b7828007633741bda6df0220458009ae59d0c6246065c419359e05eb2a4b4ef4a1b310cc912db44eb792429801475221023da092f6980e58d2c037173180e9a465476026ee50f96695963e8efe436f54eb21030e9f7b623d2ccc7c9bd44d66d5ce21ce504c0acf6385a132cec6d3c39fa711c152ae3e195220", + ( + Sig( + "3045022100a8a78fa1016a5c5c3704f2e8908715a3cef66723fb95f3132ec4d2d05cd84fb4022025ac49287b0861ec21932405f5600cbce94313dbde0e6c5d5af1b3366d8afbfc" + ), + Sig( + "3045022100e769cb156aa2f7515d126cef7a69968629620ce82afcaa9e210969de6850df4602200b16b3f3486a229a48aadde520dbee31ae340dbadaffae74fbb56681fef27b92" + ), + ), + ), # BOLT #3: # local_feerate_per_kw: 3702 # ... @@ -1267,13 +1556,24 @@ def test_simple_commitment() -> None: # remote_htlc_signature = 3045022100dfb73b4fe961b31a859b2bb1f4f15cabab9265016dd0272323dc6a9e85885c54022059a7b87c02861ee70662907f25ce11597d7b68d3399443a831ae40e777b76bdb # # signature for output 1 (HTLC 4) # remote_htlc_signature = 3045022100ea9dc2a7c3c3640334dab733bb4e036e32a3106dc707b24227874fa4f7da746802204d672f7ac0fe765931a8df10b81e53a3242dd32bd9dc9331eb4a596da87954e9 - (3702, - Sig('304402200e930a43c7951162dc15a2b7344f48091c74c70f7024e7116e900d8bcfba861c022066fa6cbda3929e21daa2e7e16a4b948db7e8919ef978402360d1095ffdaff7b0'), - Sig('3045022100c1a3b0b60ca092ed5080121f26a74a20cec6bdee3f8e47bae973fcdceb3eda5502207d467a9873c939bf3aa758014ae67295fedbca52412633f7e5b2670fc7c381c1'), - '02000000000101bef67e4e2fb9ddeeb3461973cd4c62abb35050b1add772995b820b584a488489000000000038b02b8004b80b000000000000220020c20b5d1f8584fd90443e7b7b720136174fa4b9333c261d04dbbd012635c0f419a00f0000000000002200208c48d15160397c9731df9bc3b236656efb6665fbfe92b4a6878e88a499f741c4c0c62d0000000000160014ccf1af2f2aabee14bb40fa3851ab2301de8431106f916a00000000002200204adb4e2f00643db396dd120d4e7dc17625f5f2c11a40d857accc862d6b7dd80e040047304402200e930a43c7951162dc15a2b7344f48091c74c70f7024e7116e900d8bcfba861c022066fa6cbda3929e21daa2e7e16a4b948db7e8919ef978402360d1095ffdaff7b001483045022100c1a3b0b60ca092ed5080121f26a74a20cec6bdee3f8e47bae973fcdceb3eda5502207d467a9873c939bf3aa758014ae67295fedbca52412633f7e5b2670fc7c381c101475221023da092f6980e58d2c037173180e9a465476026ee50f96695963e8efe436f54eb21030e9f7b623d2ccc7c9bd44d66d5ce21ce504c0acf6385a132cec6d3c39fa711c152ae3e195220', - (Sig('3045022100dfb73b4fe961b31a859b2bb1f4f15cabab9265016dd0272323dc6a9e85885c54022059a7b87c02861ee70662907f25ce11597d7b68d3399443a831ae40e777b76bdb'), - Sig('3045022100ea9dc2a7c3c3640334dab733bb4e036e32a3106dc707b24227874fa4f7da746802204d672f7ac0fe765931a8df10b81e53a3242dd32bd9dc9331eb4a596da87954e9'))), - + ( + 3702, + Sig( + "304402200e930a43c7951162dc15a2b7344f48091c74c70f7024e7116e900d8bcfba861c022066fa6cbda3929e21daa2e7e16a4b948db7e8919ef978402360d1095ffdaff7b0" + ), + Sig( + "3045022100c1a3b0b60ca092ed5080121f26a74a20cec6bdee3f8e47bae973fcdceb3eda5502207d467a9873c939bf3aa758014ae67295fedbca52412633f7e5b2670fc7c381c1" + ), + "02000000000101bef67e4e2fb9ddeeb3461973cd4c62abb35050b1add772995b820b584a488489000000000038b02b8004b80b000000000000220020c20b5d1f8584fd90443e7b7b720136174fa4b9333c261d04dbbd012635c0f419a00f0000000000002200208c48d15160397c9731df9bc3b236656efb6665fbfe92b4a6878e88a499f741c4c0c62d0000000000160014ccf1af2f2aabee14bb40fa3851ab2301de8431106f916a00000000002200204adb4e2f00643db396dd120d4e7dc17625f5f2c11a40d857accc862d6b7dd80e040047304402200e930a43c7951162dc15a2b7344f48091c74c70f7024e7116e900d8bcfba861c022066fa6cbda3929e21daa2e7e16a4b948db7e8919ef978402360d1095ffdaff7b001483045022100c1a3b0b60ca092ed5080121f26a74a20cec6bdee3f8e47bae973fcdceb3eda5502207d467a9873c939bf3aa758014ae67295fedbca52412633f7e5b2670fc7c381c101475221023da092f6980e58d2c037173180e9a465476026ee50f96695963e8efe436f54eb21030e9f7b623d2ccc7c9bd44d66d5ce21ce504c0acf6385a132cec6d3c39fa711c152ae3e195220", + ( + Sig( + "3045022100dfb73b4fe961b31a859b2bb1f4f15cabab9265016dd0272323dc6a9e85885c54022059a7b87c02861ee70662907f25ce11597d7b68d3399443a831ae40e777b76bdb" + ), + Sig( + "3045022100ea9dc2a7c3c3640334dab733bb4e036e32a3106dc707b24227874fa4f7da746802204d672f7ac0fe765931a8df10b81e53a3242dd32bd9dc9331eb4a596da87954e9" + ), + ), + ), # BOLT #3: # local_feerate_per_kw: 3703 # ... @@ -1283,12 +1583,21 @@ def test_simple_commitment() -> None: # num_htlcs: 1 # # signature for output 0 (HTLC 4) # remote_htlc_signature = 3044022044f65cf833afdcb9d18795ca93f7230005777662539815b8a601eeb3e57129a902206a4bf3e53392affbba52640627defa8dc8af61c958c9e827b2798ab45828abdd - (3703, - Sig('3044022047305531dd44391dce03ae20f8735005c615eb077a974edb0059ea1a311857d602202e0ed6972fbdd1e8cb542b06e0929bc41b2ddf236e04cb75edd56151f4197506'), - Sig('30450221008b7c191dd46893b67b628e618d2dc8e81169d38bade310181ab77d7c94c6675e02203b4dd131fd7c9deb299560983dcdc485545c98f989f7ae8180c28289f9e6bdb0'), - '02000000000101bef67e4e2fb9ddeeb3461973cd4c62abb35050b1add772995b820b584a488489000000000038b02b8003a00f0000000000002200208c48d15160397c9731df9bc3b236656efb6665fbfe92b4a6878e88a499f741c4c0c62d0000000000160014ccf1af2f2aabee14bb40fa3851ab2301de843110eb936a00000000002200204adb4e2f00643db396dd120d4e7dc17625f5f2c11a40d857accc862d6b7dd80e0400473044022047305531dd44391dce03ae20f8735005c615eb077a974edb0059ea1a311857d602202e0ed6972fbdd1e8cb542b06e0929bc41b2ddf236e04cb75edd56151f4197506014830450221008b7c191dd46893b67b628e618d2dc8e81169d38bade310181ab77d7c94c6675e02203b4dd131fd7c9deb299560983dcdc485545c98f989f7ae8180c28289f9e6bdb001475221023da092f6980e58d2c037173180e9a465476026ee50f96695963e8efe436f54eb21030e9f7b623d2ccc7c9bd44d66d5ce21ce504c0acf6385a132cec6d3c39fa711c152ae3e195220', - (Sig('3044022044f65cf833afdcb9d18795ca93f7230005777662539815b8a601eeb3e57129a902206a4bf3e53392affbba52640627defa8dc8af61c958c9e827b2798ab45828abdd'),)), - + ( + 3703, + Sig( + "3044022047305531dd44391dce03ae20f8735005c615eb077a974edb0059ea1a311857d602202e0ed6972fbdd1e8cb542b06e0929bc41b2ddf236e04cb75edd56151f4197506" + ), + Sig( + "30450221008b7c191dd46893b67b628e618d2dc8e81169d38bade310181ab77d7c94c6675e02203b4dd131fd7c9deb299560983dcdc485545c98f989f7ae8180c28289f9e6bdb0" + ), + "02000000000101bef67e4e2fb9ddeeb3461973cd4c62abb35050b1add772995b820b584a488489000000000038b02b8003a00f0000000000002200208c48d15160397c9731df9bc3b236656efb6665fbfe92b4a6878e88a499f741c4c0c62d0000000000160014ccf1af2f2aabee14bb40fa3851ab2301de843110eb936a00000000002200204adb4e2f00643db396dd120d4e7dc17625f5f2c11a40d857accc862d6b7dd80e0400473044022047305531dd44391dce03ae20f8735005c615eb077a974edb0059ea1a311857d602202e0ed6972fbdd1e8cb542b06e0929bc41b2ddf236e04cb75edd56151f4197506014830450221008b7c191dd46893b67b628e618d2dc8e81169d38bade310181ab77d7c94c6675e02203b4dd131fd7c9deb299560983dcdc485545c98f989f7ae8180c28289f9e6bdb001475221023da092f6980e58d2c037173180e9a465476026ee50f96695963e8efe436f54eb21030e9f7b623d2ccc7c9bd44d66d5ce21ce504c0acf6385a132cec6d3c39fa711c152ae3e195220", + ( + Sig( + "3044022044f65cf833afdcb9d18795ca93f7230005777662539815b8a601eeb3e57129a902206a4bf3e53392affbba52640627defa8dc8af61c958c9e827b2798ab45828abdd" + ), + ), + ), # BOLT #3: # local_feerate_per_kw: 4914 # ... @@ -1298,12 +1607,21 @@ def test_simple_commitment() -> None: # num_htlcs: 1 # # signature for output 0 (HTLC 4) # remote_htlc_signature = 3045022100fcb38506bfa11c02874092a843d0cc0a8613c23b639832564a5f69020cb0f6ba02206508b9e91eaa001425c190c68ee5f887e1ad5b1b314002e74db9dbd9e42dbecf - (4914, - Sig('304402206a2679efa3c7aaffd2a447fd0df7aba8792858b589750f6a1203f9259173198a022008d52a0e77a99ab533c36206cb15ad7aeb2aa72b93d4b571e728cb5ec2f6fe26'), - Sig('304402206d6cb93969d39177a09d5d45b583f34966195b77c7e585cf47ac5cce0c90cefb022031d71ae4e33a4e80df7f981d696fbdee517337806a3c7138b7491e2cbb077a0e'), - '02000000000101bef67e4e2fb9ddeeb3461973cd4c62abb35050b1add772995b820b584a488489000000000038b02b8003a00f0000000000002200208c48d15160397c9731df9bc3b236656efb6665fbfe92b4a6878e88a499f741c4c0c62d0000000000160014ccf1af2f2aabee14bb40fa3851ab2301de843110ae8f6a00000000002200204adb4e2f00643db396dd120d4e7dc17625f5f2c11a40d857accc862d6b7dd80e040047304402206a2679efa3c7aaffd2a447fd0df7aba8792858b589750f6a1203f9259173198a022008d52a0e77a99ab533c36206cb15ad7aeb2aa72b93d4b571e728cb5ec2f6fe260147304402206d6cb93969d39177a09d5d45b583f34966195b77c7e585cf47ac5cce0c90cefb022031d71ae4e33a4e80df7f981d696fbdee517337806a3c7138b7491e2cbb077a0e01475221023da092f6980e58d2c037173180e9a465476026ee50f96695963e8efe436f54eb21030e9f7b623d2ccc7c9bd44d66d5ce21ce504c0acf6385a132cec6d3c39fa711c152ae3e195220', - (Sig('3045022100fcb38506bfa11c02874092a843d0cc0a8613c23b639832564a5f69020cb0f6ba02206508b9e91eaa001425c190c68ee5f887e1ad5b1b314002e74db9dbd9e42dbecf'),)), - + ( + 4914, + Sig( + "304402206a2679efa3c7aaffd2a447fd0df7aba8792858b589750f6a1203f9259173198a022008d52a0e77a99ab533c36206cb15ad7aeb2aa72b93d4b571e728cb5ec2f6fe26" + ), + Sig( + "304402206d6cb93969d39177a09d5d45b583f34966195b77c7e585cf47ac5cce0c90cefb022031d71ae4e33a4e80df7f981d696fbdee517337806a3c7138b7491e2cbb077a0e" + ), + "02000000000101bef67e4e2fb9ddeeb3461973cd4c62abb35050b1add772995b820b584a488489000000000038b02b8003a00f0000000000002200208c48d15160397c9731df9bc3b236656efb6665fbfe92b4a6878e88a499f741c4c0c62d0000000000160014ccf1af2f2aabee14bb40fa3851ab2301de843110ae8f6a00000000002200204adb4e2f00643db396dd120d4e7dc17625f5f2c11a40d857accc862d6b7dd80e040047304402206a2679efa3c7aaffd2a447fd0df7aba8792858b589750f6a1203f9259173198a022008d52a0e77a99ab533c36206cb15ad7aeb2aa72b93d4b571e728cb5ec2f6fe260147304402206d6cb93969d39177a09d5d45b583f34966195b77c7e585cf47ac5cce0c90cefb022031d71ae4e33a4e80df7f981d696fbdee517337806a3c7138b7491e2cbb077a0e01475221023da092f6980e58d2c037173180e9a465476026ee50f96695963e8efe436f54eb21030e9f7b623d2ccc7c9bd44d66d5ce21ce504c0acf6385a132cec6d3c39fa711c152ae3e195220", + ( + Sig( + "3045022100fcb38506bfa11c02874092a843d0cc0a8613c23b639832564a5f69020cb0f6ba02206508b9e91eaa001425c190c68ee5f887e1ad5b1b314002e74db9dbd9e42dbecf" + ), + ), + ), # BOLT #3: # local_feerate_per_kw: 4915 # ... @@ -1311,12 +1629,17 @@ def test_simple_commitment() -> None: # # local_signature = 3045022100a012691ba6cea2f73fa8bac37750477e66363c6d28813b0bb6da77c8eb3fb0270220365e99c51304b0b1a6ab9ea1c8500db186693e39ec1ad5743ee231b0138384b9 # output commit_tx: 02000000000101bef67e4e2fb9ddeeb3461973cd4c62abb35050b1add772995b820b584a488489000000000038b02b8002c0c62d0000000000160014ccf1af2f2aabee14bb40fa3851ab2301de843110fa926a00000000002200204adb4e2f00643db396dd120d4e7dc17625f5f2c11a40d857accc862d6b7dd80e0400483045022100a012691ba6cea2f73fa8bac37750477e66363c6d28813b0bb6da77c8eb3fb0270220365e99c51304b0b1a6ab9ea1c8500db186693e39ec1ad5743ee231b0138384b90147304402200769ba89c7330dfa4feba447b6e322305f12ac7dac70ec6ba997ed7c1b598d0802204fe8d337e7fee781f9b7b1a06e580b22f4f79d740059560191d7db53f876555201475221023da092f6980e58d2c037173180e9a465476026ee50f96695963e8efe436f54eb21030e9f7b623d2ccc7c9bd44d66d5ce21ce504c0acf6385a132cec6d3c39fa711c152ae3e195220 # num_htlcs: 0 - (4915, - Sig('3045022100a012691ba6cea2f73fa8bac37750477e66363c6d28813b0bb6da77c8eb3fb0270220365e99c51304b0b1a6ab9ea1c8500db186693e39ec1ad5743ee231b0138384b9'), - Sig('304402200769ba89c7330dfa4feba447b6e322305f12ac7dac70ec6ba997ed7c1b598d0802204fe8d337e7fee781f9b7b1a06e580b22f4f79d740059560191d7db53f8765552'), - '02000000000101bef67e4e2fb9ddeeb3461973cd4c62abb35050b1add772995b820b584a488489000000000038b02b8002c0c62d0000000000160014ccf1af2f2aabee14bb40fa3851ab2301de843110fa926a00000000002200204adb4e2f00643db396dd120d4e7dc17625f5f2c11a40d857accc862d6b7dd80e0400483045022100a012691ba6cea2f73fa8bac37750477e66363c6d28813b0bb6da77c8eb3fb0270220365e99c51304b0b1a6ab9ea1c8500db186693e39ec1ad5743ee231b0138384b90147304402200769ba89c7330dfa4feba447b6e322305f12ac7dac70ec6ba997ed7c1b598d0802204fe8d337e7fee781f9b7b1a06e580b22f4f79d740059560191d7db53f876555201475221023da092f6980e58d2c037173180e9a465476026ee50f96695963e8efe436f54eb21030e9f7b623d2ccc7c9bd44d66d5ce21ce504c0acf6385a132cec6d3c39fa711c152ae3e195220', - ()), - + ( + 4915, + Sig( + "3045022100a012691ba6cea2f73fa8bac37750477e66363c6d28813b0bb6da77c8eb3fb0270220365e99c51304b0b1a6ab9ea1c8500db186693e39ec1ad5743ee231b0138384b9" + ), + Sig( + "304402200769ba89c7330dfa4feba447b6e322305f12ac7dac70ec6ba997ed7c1b598d0802204fe8d337e7fee781f9b7b1a06e580b22f4f79d740059560191d7db53f8765552" + ), + "02000000000101bef67e4e2fb9ddeeb3461973cd4c62abb35050b1add772995b820b584a488489000000000038b02b8002c0c62d0000000000160014ccf1af2f2aabee14bb40fa3851ab2301de843110fa926a00000000002200204adb4e2f00643db396dd120d4e7dc17625f5f2c11a40d857accc862d6b7dd80e0400483045022100a012691ba6cea2f73fa8bac37750477e66363c6d28813b0bb6da77c8eb3fb0270220365e99c51304b0b1a6ab9ea1c8500db186693e39ec1ad5743ee231b0138384b90147304402200769ba89c7330dfa4feba447b6e322305f12ac7dac70ec6ba997ed7c1b598d0802204fe8d337e7fee781f9b7b1a06e580b22f4f79d740059560191d7db53f876555201475221023da092f6980e58d2c037173180e9a465476026ee50f96695963e8efe436f54eb21030e9f7b623d2ccc7c9bd44d66d5ce21ce504c0acf6385a132cec6d3c39fa711c152ae3e195220", + (), + ), # BOLT #3: # local_feerate_per_kw: 9651180 # ... @@ -1324,12 +1647,17 @@ def test_simple_commitment() -> None: # # local_signature = 30440220514f977bf7edc442de8ce43ace9686e5ebdc0f893033f13e40fb46c8b8c6e1f90220188006227d175f5c35da0b092c57bea82537aed89f7778204dc5bacf4f29f2b9 # output commit_tx: 02000000000101bef67e4e2fb9ddeeb3461973cd4c62abb35050b1add772995b820b584a488489000000000038b02b800222020000000000002200204adb4e2f00643db396dd120d4e7dc17625f5f2c11a40d857accc862d6b7dd80ec0c62d0000000000160014ccf1af2f2aabee14bb40fa3851ab2301de84311004004730440220514f977bf7edc442de8ce43ace9686e5ebdc0f893033f13e40fb46c8b8c6e1f90220188006227d175f5c35da0b092c57bea82537aed89f7778204dc5bacf4f29f2b901473044022037f83ff00c8e5fb18ae1f918ffc24e54581775a20ff1ae719297ef066c71caa9022039c529cccd89ff6c5ed1db799614533844bd6d101da503761c45c713996e3bbd01475221023da092f6980e58d2c037173180e9a465476026ee50f96695963e8efe436f54eb21030e9f7b623d2ccc7c9bd44d66d5ce21ce504c0acf6385a132cec6d3c39fa711c152ae3e195220 # num_htlcs: 0 - (9651180, - Sig('30440220514f977bf7edc442de8ce43ace9686e5ebdc0f893033f13e40fb46c8b8c6e1f90220188006227d175f5c35da0b092c57bea82537aed89f7778204dc5bacf4f29f2b9'), - Sig('3044022037f83ff00c8e5fb18ae1f918ffc24e54581775a20ff1ae719297ef066c71caa9022039c529cccd89ff6c5ed1db799614533844bd6d101da503761c45c713996e3bbd'), - '02000000000101bef67e4e2fb9ddeeb3461973cd4c62abb35050b1add772995b820b584a488489000000000038b02b800222020000000000002200204adb4e2f00643db396dd120d4e7dc17625f5f2c11a40d857accc862d6b7dd80ec0c62d0000000000160014ccf1af2f2aabee14bb40fa3851ab2301de84311004004730440220514f977bf7edc442de8ce43ace9686e5ebdc0f893033f13e40fb46c8b8c6e1f90220188006227d175f5c35da0b092c57bea82537aed89f7778204dc5bacf4f29f2b901473044022037f83ff00c8e5fb18ae1f918ffc24e54581775a20ff1ae719297ef066c71caa9022039c529cccd89ff6c5ed1db799614533844bd6d101da503761c45c713996e3bbd01475221023da092f6980e58d2c037173180e9a465476026ee50f96695963e8efe436f54eb21030e9f7b623d2ccc7c9bd44d66d5ce21ce504c0acf6385a132cec6d3c39fa711c152ae3e195220', - ()), - + ( + 9651180, + Sig( + "30440220514f977bf7edc442de8ce43ace9686e5ebdc0f893033f13e40fb46c8b8c6e1f90220188006227d175f5c35da0b092c57bea82537aed89f7778204dc5bacf4f29f2b9" + ), + Sig( + "3044022037f83ff00c8e5fb18ae1f918ffc24e54581775a20ff1ae719297ef066c71caa9022039c529cccd89ff6c5ed1db799614533844bd6d101da503761c45c713996e3bbd" + ), + "02000000000101bef67e4e2fb9ddeeb3461973cd4c62abb35050b1add772995b820b584a488489000000000038b02b800222020000000000002200204adb4e2f00643db396dd120d4e7dc17625f5f2c11a40d857accc862d6b7dd80ec0c62d0000000000160014ccf1af2f2aabee14bb40fa3851ab2301de84311004004730440220514f977bf7edc442de8ce43ace9686e5ebdc0f893033f13e40fb46c8b8c6e1f90220188006227d175f5c35da0b092c57bea82537aed89f7778204dc5bacf4f29f2b901473044022037f83ff00c8e5fb18ae1f918ffc24e54581775a20ff1ae719297ef066c71caa9022039c529cccd89ff6c5ed1db799614533844bd6d101da503761c45c713996e3bbd01475221023da092f6980e58d2c037173180e9a465476026ee50f96695963e8efe436f54eb21030e9f7b623d2ccc7c9bd44d66d5ce21ce504c0acf6385a132cec6d3c39fa711c152ae3e195220", + (), + ), # BOLT #3: # local_feerate_per_kw: 9651181 # ... @@ -1337,11 +1665,17 @@ def test_simple_commitment() -> None: # # local_signature = 3044022031a82b51bd014915fe68928d1abf4b9885353fb896cac10c3fdd88d7f9c7f2e00220716bda819641d2c63e65d3549b6120112e1aeaf1742eed94a471488e79e206b1 # output commit_tx: 02000000000101bef67e4e2fb9ddeeb3461973cd4c62abb35050b1add772995b820b584a488489000000000038b02b8001c0c62d0000000000160014ccf1af2f2aabee14bb40fa3851ab2301de8431100400473044022031a82b51bd014915fe68928d1abf4b9885353fb896cac10c3fdd88d7f9c7f2e00220716bda819641d2c63e65d3549b6120112e1aeaf1742eed94a471488e79e206b101473044022064901950be922e62cbe3f2ab93de2b99f37cff9fc473e73e394b27f88ef0731d02206d1dfa227527b4df44a07599289e207d6fd9cca60c0365682dcd3deaf739567e01475221023da092f6980e58d2c037173180e9a465476026ee50f96695963e8efe436f54eb21030e9f7b623d2ccc7c9bd44d66d5ce21ce504c0acf6385a132cec6d3c39fa711c152ae3e195220 # num_htlcs: 0 - (9651181, - Sig('3044022031a82b51bd014915fe68928d1abf4b9885353fb896cac10c3fdd88d7f9c7f2e00220716bda819641d2c63e65d3549b6120112e1aeaf1742eed94a471488e79e206b1'), - Sig('3044022064901950be922e62cbe3f2ab93de2b99f37cff9fc473e73e394b27f88ef0731d02206d1dfa227527b4df44a07599289e207d6fd9cca60c0365682dcd3deaf739567e'), - '02000000000101bef67e4e2fb9ddeeb3461973cd4c62abb35050b1add772995b820b584a488489000000000038b02b8001c0c62d0000000000160014ccf1af2f2aabee14bb40fa3851ab2301de8431100400473044022031a82b51bd014915fe68928d1abf4b9885353fb896cac10c3fdd88d7f9c7f2e00220716bda819641d2c63e65d3549b6120112e1aeaf1742eed94a471488e79e206b101473044022064901950be922e62cbe3f2ab93de2b99f37cff9fc473e73e394b27f88ef0731d02206d1dfa227527b4df44a07599289e207d6fd9cca60c0365682dcd3deaf739567e01475221023da092f6980e58d2c037173180e9a465476026ee50f96695963e8efe436f54eb21030e9f7b623d2ccc7c9bd44d66d5ce21ce504c0acf6385a132cec6d3c39fa711c152ae3e195220', - ()) + ( + 9651181, + Sig( + "3044022031a82b51bd014915fe68928d1abf4b9885353fb896cac10c3fdd88d7f9c7f2e00220716bda819641d2c63e65d3549b6120112e1aeaf1742eed94a471488e79e206b1" + ), + Sig( + "3044022064901950be922e62cbe3f2ab93de2b99f37cff9fc473e73e394b27f88ef0731d02206d1dfa227527b4df44a07599289e207d6fd9cca60c0365682dcd3deaf739567e" + ), + "02000000000101bef67e4e2fb9ddeeb3461973cd4c62abb35050b1add772995b820b584a488489000000000038b02b8001c0c62d0000000000160014ccf1af2f2aabee14bb40fa3851ab2301de8431100400473044022031a82b51bd014915fe68928d1abf4b9885353fb896cac10c3fdd88d7f9c7f2e00220716bda819641d2c63e65d3549b6120112e1aeaf1742eed94a471488e79e206b101473044022064901950be922e62cbe3f2ab93de2b99f37cff9fc473e73e394b27f88ef0731d02206d1dfa227527b4df44a07599289e207d6fd9cca60c0365682dcd3deaf739567e01475221023da092f6980e58d2c037173180e9a465476026ee50f96695963e8efe436f54eb21030e9f7b623d2ccc7c9bd44d66d5ce21ce504c0acf6385a132cec6d3c39fa711c152ae3e195220", + (), + ), ] for feerate, localsig, remotesig, committx, htlc_sigs in table: @@ -1350,7 +1684,10 @@ def test_simple_commitment() -> None: assert c.local_sig(tx) == localsig assert c.remote_sig(tx) == remotesig # We don't (yet) generate witnesses, so compare txids. - assert tx.GetTxid() == CMutableTransaction.deserialize(bytes.fromhex(committx)).GetTxid() + assert ( + tx.GetTxid() + == CMutableTransaction.deserialize(bytes.fromhex(committx)).GetTxid() + ) sigs = c.htlc_sigs(Side.remote, Side.local) assert tuple(sigs) == htlc_sigs @@ -1578,46 +1915,55 @@ def test_anchor_commitment() -> None: tests = json.loads(yamlstr) # We use '99' where the results shouldn't matter. - c = Commitment(funding=Funding(funding_txid=revhex('8984484a580b825b9972d7adb15050b3ab624ccd731946b3eeddb92f4e7ef6be'), - funding_output_index=0, - funding_amount=10000000, - local_node_privkey='99', - # BOLT #3: - # local_funding_privkey: 30ff4956bbdd3222d44cc5e8a1261dab1e07957bdac5ae88fe3261ef321f374901 - local_funding_privkey='30ff4956bbdd3222d44cc5e8a1261dab1e07957bdac5ae88fe3261ef321f3749', - remote_node_privkey='99', - # BOLT #3: - # INTERNAL: remote_funding_privkey: 1552dfba4f6cf29a62a0af13c8d6981d36d0ef8d61ba10fb0fe90da7634d7e1301 - remote_funding_privkey='1552dfba4f6cf29a62a0af13c8d6981d36d0ef8d61ba10fb0fe90da7634d7e13'), - opener=Side.local, - - # BOLT #3: - # INTERNAL: local_payment_basepoint_secret: 111111111111111111111111111111111111111111111111111111111111111101 - local_keyset=KeySet(revocation_base_secret='99', - payment_base_secret='1111111111111111111111111111111111111111111111111111111111111111', - htlc_base_secret='1111111111111111111111111111111111111111111111111111111111111111', - # BOLT #3: - # INTERNAL: local_delayed_payment_basepoint_secret: 333333333333333333333333333333333333333333333333333333333333333301 - delayed_payment_base_secret='3333333333333333333333333333333333333333333333333333333333333333', - shachain_seed='99' * 32), - # BOLT #3: - # INTERNAL: remote_revocation_basepoint_secret: 222222222222222222222222222222222222222222222222222222222222222201 - remote_keyset=KeySet(revocation_base_secret='2222222222222222222222222222222222222222222222222222222222222222', - # BOLT #3: - # INTERNAL: remote_payment_basepoint_secret: 444444444444444444444444444444444444444444444444444444444444444401 - payment_base_secret='4444444444444444444444444444444444444444444444444444444444444444', - htlc_base_secret='4444444444444444444444444444444444444444444444444444444444444444', - delayed_payment_base_secret='99', - shachain_seed='99' * 32), - local_to_self_delay=144, - remote_to_self_delay=145, - local_amount=7000000000, - remote_amount=3000000000, - local_dust_limit=546, - remote_dust_limit=546, - feerate=15000, - option_static_remotekey=True, - option_anchor_outputs=True) + c = Commitment( + funding=Funding( + funding_txid=revhex( + "8984484a580b825b9972d7adb15050b3ab624ccd731946b3eeddb92f4e7ef6be" + ), + funding_output_index=0, + funding_amount=10000000, + local_node_privkey="99", + # BOLT #3: + # local_funding_privkey: 30ff4956bbdd3222d44cc5e8a1261dab1e07957bdac5ae88fe3261ef321f374901 + local_funding_privkey="30ff4956bbdd3222d44cc5e8a1261dab1e07957bdac5ae88fe3261ef321f3749", + remote_node_privkey="99", + # BOLT #3: + # INTERNAL: remote_funding_privkey: 1552dfba4f6cf29a62a0af13c8d6981d36d0ef8d61ba10fb0fe90da7634d7e1301 + remote_funding_privkey="1552dfba4f6cf29a62a0af13c8d6981d36d0ef8d61ba10fb0fe90da7634d7e13", + ), + opener=Side.local, + # BOLT #3: + # INTERNAL: local_payment_basepoint_secret: 111111111111111111111111111111111111111111111111111111111111111101 + local_keyset=KeySet( + revocation_base_secret="99", + payment_base_secret="1111111111111111111111111111111111111111111111111111111111111111", + htlc_base_secret="1111111111111111111111111111111111111111111111111111111111111111", + # BOLT #3: + # INTERNAL: local_delayed_payment_basepoint_secret: 333333333333333333333333333333333333333333333333333333333333333301 + delayed_payment_base_secret="3333333333333333333333333333333333333333333333333333333333333333", + shachain_seed="99" * 32, + ), + # BOLT #3: + # INTERNAL: remote_revocation_basepoint_secret: 222222222222222222222222222222222222222222222222222222222222222201 + remote_keyset=KeySet( + revocation_base_secret="2222222222222222222222222222222222222222222222222222222222222222", + # BOLT #3: + # INTERNAL: remote_payment_basepoint_secret: 444444444444444444444444444444444444444444444444444444444444444401 + payment_base_secret="4444444444444444444444444444444444444444444444444444444444444444", + htlc_base_secret="4444444444444444444444444444444444444444444444444444444444444444", + delayed_payment_base_secret="99", + shachain_seed="99" * 32, + ), + local_to_self_delay=144, + remote_to_self_delay=145, + local_amount=7000000000, + remote_amount=3000000000, + local_dust_limit=546, + remote_dust_limit=546, + feerate=15000, + option_static_remotekey=True, + option_anchor_outputs=True, + ) # Make sure undefined field are not used. c.keyset[Side.local].revocation_base_secret = None @@ -1629,18 +1975,23 @@ def test_anchor_commitment() -> None: # x_local_per_commitment_secret: 1f1e1d1c1b1a191817161514131211100f0e0d0c0b0a0908070605040302010001 # This is not derived as expected, but defined :( - c.keyset[Side.local].raw_per_commit_secret = lambda _: coincurve.PrivateKey(bytes.fromhex('1f1e1d1c1b1a191817161514131211100f0e0d0c0b0a09080706050403020100')) # type: ignore + c.keyset[Side.local].raw_per_commit_secret = lambda _: coincurve.PrivateKey(bytes.fromhex("1f1e1d1c1b1a191817161514131211100f0e0d0c0b0a09080706050403020100")) # type: ignore # BOLT #3: # commitment_number: 42 c.commitnum = 42 # First test is different: it has no HTLCs. - c.feerate = tests[0]['FeePerKw'] + c.feerate = tests[0]["FeePerKw"] tx, _ = c._unsigned_tx(Side.local) # We don't (yet) generate witnesses, so compare txids. - assert tx.GetTxid() == CMutableTransaction.deserialize(bytes.fromhex(tests[0]['ExpectedCommitmentTxHex'])).GetTxid() - assert c.remote_sig(tx) == Sig(tests[0]['RemoteSigHex']) + assert ( + tx.GetTxid() + == CMutableTransaction.deserialize( + bytes.fromhex(tests[0]["ExpectedCommitmentTxHex"]) + ).GetTxid() + ) + assert c.remote_sig(tx) == Sig(tests[0]["RemoteSigHex"]) # Add HTLCs for remaining tests. htlcs = [] @@ -1649,50 +2000,60 @@ def test_anchor_commitment() -> None: # htlc 0 amount_msat: 1000000 # htlc 0 expiry: 500 # htlc 0 payment_preimage: 0000000000000000000000000000000000000000000000000000000000000000 - htlcs.append(HTLC(Side.remote, 1000000, '00' * 32, 500, '00' * 1366)) + htlcs.append(HTLC(Side.remote, 1000000, "00" * 32, 500, "00" * 1366)) # BOLT #3: # htlc 1 direction: remote->local # htlc 1 amount_msat: 2000000 # htlc 1 expiry: 501 # htlc 1 payment_preimage: 0101010101010101010101010101010101010101010101010101010101010101 - htlcs.append(HTLC(Side.remote, 2000000, '01' * 32, 501, '00' * 1366)) + htlcs.append(HTLC(Side.remote, 2000000, "01" * 32, 501, "00" * 1366)) # BOLT #3: # htlc 2 direction: local->remote # htlc 2 amount_msat: 2000000 # htlc 2 expiry: 502 # htlc 2 payment_preimage: 0202020202020202020202020202020202020202020202020202020202020202 - htlcs.append(HTLC(Side.local, 2000000, '02' * 32, 502, '00' * 1366)) + htlcs.append(HTLC(Side.local, 2000000, "02" * 32, 502, "00" * 1366)) # BOLT #3: # htlc 3 direction: local->remote # htlc 3 amount_msat: 3000000 # htlc 3 expiry: 503 # htlc 3 payment_preimage: 0303030303030303030303030303030303030303030303030303030303030303 - htlcs.append(HTLC(Side.local, 3000000, '03' * 32, 503, '00' * 1366)) + htlcs.append(HTLC(Side.local, 3000000, "03" * 32, 503, "00" * 1366)) # BOLT #3: # htlc 4 direction: remote->local # htlc 4 amount_msat: 4000000 # htlc 4 expiry: 504 # htlc 4 payment_preimage: 0404040404040404040404040404040404040404040404040404040404040404 - htlcs.append(HTLC(Side.remote, 4000000, '04' * 32, 504, '00' * 1366)) + htlcs.append(HTLC(Side.remote, 4000000, "04" * 32, 504, "00" * 1366)) for i, h in enumerate(htlcs): c.add_htlc(h, i) for test in tests[1:]: - c.amounts[Side.local] = test['LocalBalance'] - c.amounts[Side.remote] = test['RemoteBalance'] - c.feerate = test['FeePerKw'] + c.amounts[Side.local] = test["LocalBalance"] + c.amounts[Side.remote] = test["RemoteBalance"] + c.feerate = test["FeePerKw"] tx, _ = c._unsigned_tx(Side.local) # We don't (yet) generate witnesses, so compare txids. - assert tx.GetTxid() == CMutableTransaction.deserialize(bytes.fromhex(test['ExpectedCommitmentTxHex'])).GetTxid() - assert c.remote_sig(tx) == Sig(test['RemoteSigHex']) + assert ( + tx.GetTxid() + == CMutableTransaction.deserialize( + bytes.fromhex(test["ExpectedCommitmentTxHex"]) + ).GetTxid() + ) + assert c.remote_sig(tx) == Sig(test["RemoteSigHex"]) # This is tx, redeemscript, sats for each HTLC. htlc_info = c.htlc_txs(Side.local) sigs = c.htlc_sigs(Side.remote, Side.local) assert len(htlc_info) == len(sigs) - assert len(sigs) == len(test['HtlcDescs']) - - for sig, htlc, desc in zip(sigs, htlc_info, test['HtlcDescs']): - assert htlc[0].GetTxid() == CMutableTransaction.deserialize(bytes.fromhex(desc['ResolutionTxHex'])).GetTxid() - assert sig == Sig(desc['RemoteSigHex']) + assert len(sigs) == len(test["HtlcDescs"]) + + for sig, htlc, desc in zip(sigs, htlc_info, test["HtlcDescs"]): + assert ( + htlc[0].GetTxid() + == CMutableTransaction.deserialize( + bytes.fromhex(desc["ResolutionTxHex"]) + ).GetTxid() + ) + assert sig == Sig(desc["RemoteSigHex"]) diff --git a/lnprototest/dummyrunner.py b/lnprototest/dummyrunner.py index 9a24ed2..2d88084 100644 --- a/lnprototest/dummyrunner.py +++ b/lnprototest/dummyrunner.py @@ -5,7 +5,13 @@ from .event import Event, ExpectMsg, MustNotMsg from typing import List, Optional from .keyset import KeySet -from pyln.proto.message import Message, FieldType, DynamicArrayType, EllipsisArrayType, SizedArrayType +from pyln.proto.message import ( + Message, + FieldType, + DynamicArrayType, + EllipsisArrayType, + SizedArrayType, +) from typing import Any @@ -18,22 +24,24 @@ def _is_dummy(self) -> bool: return True def get_keyset(self) -> KeySet: - return KeySet(revocation_base_secret='11', - payment_base_secret='12', - htlc_base_secret='14', - delayed_payment_base_secret='13', - shachain_seed='FF' * 32) + return KeySet( + revocation_base_secret="11", + payment_base_secret="12", + htlc_base_secret="14", + delayed_payment_base_secret="13", + shachain_seed="FF" * 32, + ) def add_startup_flag(self, flag: str) -> None: - if self.config.getoption('verbose'): - print('[ADD STARTUP FLAG {}]'.format(flag)) + if self.config.getoption("verbose"): + print("[ADD STARTUP FLAG {}]".format(flag)) return def get_node_privkey(self) -> str: - return '01' + return "01" def get_node_bitcoinkey(self) -> str: - return '10' + return "10" def has_option(self, optname: str) -> Optional[str]: return None @@ -46,12 +54,12 @@ def stop(self) -> None: def restart(self) -> None: super().restart() - if self.config.getoption('verbose'): + if self.config.getoption("verbose"): print("[RESTART]") self.blockheight = 102 def connect(self, event: Event, connprivkey: str) -> None: - if self.config.getoption('verbose'): + if self.config.getoption("verbose"): print("[CONNECT {} {}]".format(event, connprivkey)) self.add_conn(Conn(connprivkey)) @@ -59,83 +67,114 @@ def getblockheight(self) -> int: return self.blockheight def trim_blocks(self, newheight: int) -> None: - if self.config.getoption('verbose'): + if self.config.getoption("verbose"): print("[TRIMBLOCK TO HEIGHT {}]".format(newheight)) self.blockheight = newheight def add_blocks(self, event: Event, txs: List[str], n: int) -> None: - if self.config.getoption('verbose'): + if self.config.getoption("verbose"): print("[ADDBLOCKS {} WITH {} TXS]".format(n, len(txs))) self.blockheight += n def disconnect(self, event: Event, conn: Conn) -> None: super().disconnect(event, conn) - if self.config.getoption('verbose'): + if self.config.getoption("verbose"): print("[DISCONNECT {}]".format(conn)) def recv(self, event: Event, conn: Conn, outbuf: bytes) -> None: - if self.config.getoption('verbose'): + if self.config.getoption("verbose"): print("[RECV {} {}]".format(event, outbuf.hex())) - def fundchannel(self, - event: Event, - conn: Conn, - amount: int, - feerate: int = 253, - expect_fail: bool = False) -> None: - if self.config.getoption('verbose'): - print("[FUNDCHANNEL TO {} for {} at feerate {}. Expect fail? {}]" - .format(conn, amount, feerate, expect_fail)) - - def init_rbf(self, event: Event, conn: Conn, - channel_id: str, amount: int, - utxo_txid: str, utxo_outnum: int, feerate: int) -> None: - if self.config.getoption('verbose'): - print("[INIT_RBF TO {} (channel {}) for {} at feerate {}. {}:{}" - .format(conn, channel_id, amount, feerate, utxo_txid, utxo_outnum)) + def fundchannel( + self, + event: Event, + conn: Conn, + amount: int, + feerate: int = 253, + expect_fail: bool = False, + ) -> None: + if self.config.getoption("verbose"): + print( + "[FUNDCHANNEL TO {} for {} at feerate {}. Expect fail? {}]".format( + conn, amount, feerate, expect_fail + ) + ) + + def init_rbf( + self, + event: Event, + conn: Conn, + channel_id: str, + amount: int, + utxo_txid: str, + utxo_outnum: int, + feerate: int, + ) -> None: + if self.config.getoption("verbose"): + print( + "[INIT_RBF TO {} (channel {}) for {} at feerate {}. {}:{}".format( + conn, channel_id, amount, feerate, utxo_txid, utxo_outnum + ) + ) def invoice(self, event: Event, amount: int, preimage: str) -> None: - if self.config.getoption('verbose'): - print("[INVOICE for {} with PREIMAGE {}]" - .format(amount, preimage)) + if self.config.getoption("verbose"): + print("[INVOICE for {} with PREIMAGE {}]".format(amount, preimage)) def accept_add_fund(self, event: Event) -> None: - if self.config.getoption('verbose'): + if self.config.getoption("verbose"): print("[ACCEPT_ADD_FUND]") - def addhtlc(self, event: Event, conn: Conn, - amount: int, preimage: str) -> None: - if self.config.getoption('verbose'): - print("[ADDHTLC TO {} for {} with PREIMAGE {}]" - .format(conn, amount, preimage)) + def addhtlc(self, event: Event, conn: Conn, amount: int, preimage: str) -> None: + if self.config.getoption("verbose"): + print( + "[ADDHTLC TO {} for {} with PREIMAGE {}]".format(conn, amount, preimage) + ) @staticmethod def fake_field(ftype: FieldType) -> str: if isinstance(ftype, DynamicArrayType) or isinstance(ftype, EllipsisArrayType): # Byte arrays are literal hex strings - if ftype.elemtype.name == 'byte': - return '' - return '[]' + if ftype.elemtype.name == "byte": + return "" + return "[]" elif isinstance(ftype, SizedArrayType): # Byte arrays are literal hex strings - if ftype.elemtype.name == 'byte': - return '00' * ftype.arraysize - return '[' + ','.join([DummyRunner.fake_field(ftype.elemtype)] * ftype.arraysize) + ']' - elif ftype.name in ('byte', 'u8', 'u16', 'u32', 'u64', 'tu16', 'tu32', 'tu64', 'bigsize', 'varint'): - return '0' - elif ftype.name in ('chain_hash', 'channel_id', 'sha256'): - return '00' * 32 - elif ftype.name == 'point': - return '038f1573b4238a986470d250ce87c7a91257b6ba3baf2a0b14380c4e1e532c209d' - elif ftype.name == 'short_channel_id': - return '0x0x0' - elif ftype.name == 'signature': - return '01' * 64 + if ftype.elemtype.name == "byte": + return "00" * ftype.arraysize + return ( + "[" + + ",".join([DummyRunner.fake_field(ftype.elemtype)] * ftype.arraysize) + + "]" + ) + elif ftype.name in ( + "byte", + "u8", + "u16", + "u32", + "u64", + "tu16", + "tu32", + "tu64", + "bigsize", + "varint", + ): + return "0" + elif ftype.name in ("chain_hash", "channel_id", "sha256"): + return "00" * 32 + elif ftype.name == "point": + return "038f1573b4238a986470d250ce87c7a91257b6ba3baf2a0b14380c4e1e532c209d" + elif ftype.name == "short_channel_id": + return "0x0x0" + elif ftype.name == "signature": + return "01" * 64 else: - raise NotImplementedError("don't know how to fake {} type!".format(ftype.name)) + raise NotImplementedError( + "don't know how to fake {} type!".format(ftype.name) + ) def get_output_message(self, conn: Conn, event: ExpectMsg) -> Optional[bytes]: - if self.config.getoption('verbose'): + if self.config.getoption("verbose"): print("[GET_OUTPUT_MESSAGE {}]".format(conn)) # We make the message they were expecting. @@ -151,14 +190,20 @@ def get_output_message(self, conn: Conn, event: ExpectMsg) -> Optional[bytes]: return binmsg.getvalue() def expect_tx(self, event: Event, txid: str) -> None: - if self.config.getoption('verbose'): + if self.config.getoption("verbose"): print("[EXPECT-TX {}]".format(txid)) def check_error(self, event: Event, conn: Conn) -> Optional[str]: super().check_error(event, conn) - if self.config.getoption('verbose'): + if self.config.getoption("verbose"): print("[CHECK-ERROR {}]".format(event)) return "Dummy error" - def check_final_error(self, event: Event, conn: Conn, expected: bool, must_not_events: List[MustNotMsg]) -> None: + def check_final_error( + self, + event: Event, + conn: Conn, + expected: bool, + must_not_events: List[MustNotMsg], + ) -> None: pass diff --git a/lnprototest/errors.py b/lnprototest/errors.py index 4872748..2c9cecb 100644 --- a/lnprototest/errors.py +++ b/lnprototest/errors.py @@ -1,21 +1,24 @@ #! /usr/bin/python3 from typing import TYPE_CHECKING + if TYPE_CHECKING: from .event import Event class EventError(Exception): """Error thrown when the runner fails in some way""" - def __init__(self, event: 'Event', message: str): + + def __init__(self, event: "Event", message: str): self.eventpath = [event] self.message = message - def add_path(self, event: 'Event') -> None: + def add_path(self, event: "Event") -> None: self.eventpath = [event] + self.eventpath class SpecFileError(Exception): """Error thrown when the specification file has an error""" - def __init__(self, event: 'Event', message: str): + + def __init__(self, event: "Event", message: str): self.event = event self.message = message diff --git a/lnprototest/event.py b/lnprototest/event.py index 9270a05..14f5965 100644 --- a/lnprototest/event.py +++ b/lnprototest/event.py @@ -20,45 +20,49 @@ # Type for arguments: either strings, or functions to call at runtime -ResolvableStr = Union[str, Callable[['Runner', 'Event', str], str]] -ResolvableInt = Union[int, Callable[['Runner', 'Event', str], int]] -ResolvableBool = Union[int, Callable[['Runner', 'Event', str], bool]] -Resolvable = Union[Any, Callable[['Runner', 'Event', str], Any]] +ResolvableStr = Union[str, Callable[["Runner", "Event", str], str]] +ResolvableInt = Union[int, Callable[["Runner", "Event", str], int]] +ResolvableBool = Union[int, Callable[["Runner", "Event", str], bool]] +Resolvable = Union[Any, Callable[["Runner", "Event", str], Any]] class Event(object): """Abstract base class for events.""" + def __init__(self) -> None: # From help(traceback.extract_stack): # Each item in the list is a quadruple (filename, # line number, function name, text), and the entries are in order # from oldest to newest stack frame. - self.name = 'unknown' + self.name = "unknown" for s in reversed(traceback.extract_stack()): # Ignore constructor calls, like this one. - if s[2] != '__init__': - self.name = "{}:{}:{}".format(type(self).__name__, - os.path.basename(s[0]), s[1]) + if s[2] != "__init__": + self.name = "{}:{}:{}".format( + type(self).__name__, os.path.basename(s[0]), s[1] + ) break - def enabled(self, runner: 'Runner') -> bool: + def enabled(self, runner: "Runner") -> bool: """Returns whether it should be enabled for this run. Usually True""" return True - def action(self, runner: 'Runner') -> bool: + def action(self, runner: "Runner") -> bool: """action() returns the False if it needs to be called again""" - if runner.config.getoption('verbose'): + if runner.config.getoption("verbose"): print("# running {}:".format(self)) return True - def resolve_arg(self, fieldname: str, runner: 'Runner', arg: Resolvable) -> Any: + def resolve_arg(self, fieldname: str, runner: "Runner", arg: Resolvable) -> Any: """If this is a string, return it, otherwise call it to get result""" if callable(arg): return arg(runner, self, fieldname) else: return arg - def resolve_args(self, runner: 'Runner', kwargs: Dict[str, Resolvable]) -> Dict[str, Any]: + def resolve_args( + self, runner: "Runner", kwargs: Dict[str, Resolvable] + ) -> Dict[str, Any]: """Take a dict of args, replace callables with their return values""" ret: Dict[str, str] = {} for field, str_or_func in kwargs.items(): @@ -71,11 +75,12 @@ def __repr__(self) -> str: class PerConnEvent(Event): """An event which takes a connprivkey arg""" + def __init__(self, connprivkey: Optional[str]): super().__init__() self.connprivkey = connprivkey - def find_conn(self, runner: 'Runner') -> 'Conn': + def find_conn(self, runner: "Runner") -> "Conn": """Helper for events which have a connection""" conn = runner.find_conn(self.connprivkey) if conn is None: @@ -83,21 +88,25 @@ def find_conn(self, runner: 'Runner') -> 'Conn': # None means "same as last used/created" raise SpecFileError(self, "No current connection") else: - raise SpecFileError(self, "Unknown connection {}".format(self.connprivkey)) + raise SpecFileError( + self, "Unknown connection {}".format(self.connprivkey) + ) return conn class Connect(Event): """Connect to the runner, as if a node with private key connprivkey""" + def __init__(self, connprivkey: str): self.connprivkey = connprivkey super().__init__() - def action(self, runner: 'Runner') -> bool: + def action(self, runner: "Runner") -> bool: super().action(runner) if runner.find_conn(self.connprivkey): - raise SpecFileError(self, "Already have connection to {}" - .format(self.connprivkey)) + raise SpecFileError( + self, "Already have connection to {}".format(self.connprivkey) + ) # This is a hack: if we've already got a connection, wait 1 second # for gossip to be processed before connecting another one! if len(runner.conns) != 0: @@ -108,12 +117,13 @@ def action(self, runner: 'Runner') -> bool: class MustNotMsg(PerConnEvent): """Indicate that this connection must never send any of these message types.""" + def __init__(self, must_not: str, connprivkey: Optional[str] = None): super().__init__(connprivkey) self.must_not = must_not def matches(self, binmsg: bytes) -> bool: - msgnum = struct.unpack('>H', binmsg[0:2])[0] + msgnum = struct.unpack(">H", binmsg[0:2])[0] msgtype = namespace().get_msgtype_by_number(msgnum) if msgtype: name = msgtype.name @@ -122,7 +132,7 @@ def matches(self, binmsg: bytes) -> bool: return name == self.must_not - def action(self, runner: 'Runner') -> bool: + def action(self, runner: "Runner") -> bool: super().action(runner) self.find_conn(runner).must_not_events.append(self) return True @@ -130,10 +140,11 @@ def action(self, runner: 'Runner') -> bool: class Disconnect(PerConnEvent): """Disconnect the runner from the node whose private key is connprivkey: default is last connection specified""" + def __init__(self, connprivkey: Optional[str] = None): super().__init__(connprivkey) - def action(self, runner: 'Runner') -> bool: + def action(self, runner: "Runner") -> bool: super().action(runner) runner.disconnect(self, self.find_conn(runner)) return True @@ -141,8 +152,13 @@ def action(self, runner: 'Runner') -> bool: class Msg(PerConnEvent): """Feed a message to the runner (via optional given connection)""" - def __init__(self, msgtypename: str, connprivkey: Optional[str] = None, - **kwargs: Union[ResolvableStr, ResolvableInt]): + + def __init__( + self, + msgtypename: str, + connprivkey: Optional[str] = None, + **kwargs: Union[ResolvableStr, ResolvableInt] + ): super().__init__(connprivkey) self.msgtype = namespace().get_msgtype(msgtypename) @@ -150,7 +166,7 @@ def __init__(self, msgtypename: str, connprivkey: Optional[str] = None, raise SpecFileError(self, "Unknown msgtype {}".format(msgtypename)) self.kwargs = kwargs - def action(self, runner: 'Runner') -> bool: + def action(self, runner: "Runner") -> bool: super().action(runner) # Now we have runner, we can fill in all the message fields message = Message(self.msgtype, **self.resolve_args(runner, self.kwargs)) @@ -166,24 +182,30 @@ def action(self, runner: 'Runner') -> bool: class Wait(PerConnEvent): """Put a delay in a test, to allow time for things to happen - on the node's end """ + on the node's end""" + def __init__(self, delay_s: int): self.delay_s = delay_s - def action(self, runner: 'Runner') -> bool: + def action(self, runner: "Runner") -> bool: time.sleep(self.delay_s) return True class RawMsg(PerConnEvent): """Feed a raw binary, or raw Message to the runner (via optional given connection)""" - def __init__(self, message: Union[Resolvable, bytes, Message], connprivkey: Optional[str] = None): + + def __init__( + self, + message: Union[Resolvable, bytes, Message], + connprivkey: Optional[str] = None, + ): super().__init__(connprivkey) self.message = message - def action(self, runner: 'Runner') -> bool: + def action(self, runner: "Runner") -> bool: super().action(runner) - msg = self.resolve_arg('binmsg', runner, self.message) + msg = self.resolve_arg("binmsg", runner, self.message) if isinstance(msg, Message): buf = io.BytesIO() msg.write(buf) @@ -198,20 +220,21 @@ def action(self, runner: 'Runner') -> bool: class ExpectMsg(PerConnEvent): """Wait for a message from the runner. -Args is the (usually incomplete) message which it should match. -if_match is the function to call if it matches: should raise an -exception if it's not satisfied. ignore function to ignore unexpected -messages: it returns a list of messages to reply with, or None if the -message should not be ignored: by default, it is ignore_gossip_queries. + Args is the (usually incomplete) message which it should match. + if_match is the function to call if it matches: should raise an + exception if it's not satisfied. ignore function to ignore unexpected + messages: it returns a list of messages to reply with, or None if the + message should not be ignored: by default, it is ignore_gossip_queries. """ - def _default_if_match(self, msg: Message, runner: 'Runner') -> None: + + def _default_if_match(self, msg: Message, runner: "Runner") -> None: pass @staticmethod def ignore_pings(msg: Message) -> Optional[List[Message]]: """Function to ignore pings (and respond with pongs appropriately)""" - if msg.messagetype.name != 'ping': + if msg.messagetype.name != "ping": return None # BOLT #1: @@ -222,23 +245,26 @@ def ignore_pings(msg: Message) -> Optional[List[Message]]: # to `num_pong_bytes`. # - otherwise (`num_pong_bytes` is **not** less than 65532): # - MUST ignore the `ping`. - if msg.fields['num_pong_bytes'] >= 65532: + if msg.fields["num_pong_bytes"] >= 65532: return [] # A node sending a `pong` message: # - SHOULD set `ignored` to 0s. # - MUST NOT set `ignored` to sensitive data such as secrets or # portions of initialized - outmsg = Message(namespace().get_msgtype('pong'), - ignored='00' * msg.fields['num_pong_bytes']) + outmsg = Message( + namespace().get_msgtype("pong"), ignored="00" * msg.fields["num_pong_bytes"] + ) return [outmsg] @staticmethod def ignore_gossip_queries(msg: Message) -> Optional[List[Message]]: """Ignore gossip_timestamp_filter, query_channel_range and query_short_channel_ids. Respond to pings.""" - if msg.messagetype.name in ('gossip_timestamp_filter', - 'query_channel_range', - 'query_short_channel_ids'): + if msg.messagetype.name in ( + "gossip_timestamp_filter", + "query_channel_range", + "query_short_channel_ids", + ): return [] return ExpectMsg.ignore_pings(msg) @@ -254,11 +280,14 @@ def ignore_all_gossip(msg: Message) -> Optional[List[Message]]: return [] return ExpectMsg.ignore_pings(msg) - def __init__(self, msgtypename: str, - if_match: Callable[['ExpectMsg', Message, 'Runner'], None] = _default_if_match, - ignore: Optional[Callable[[Message], Optional[List[Message]]]] = None, - connprivkey: Optional[str] = None, - **kwargs: Union[str, Resolvable]): + def __init__( + self, + msgtypename: str, + if_match: Callable[["ExpectMsg", Message, "Runner"], None] = _default_if_match, + ignore: Optional[Callable[[Message], Optional[List[Message]]]] = None, + connprivkey: Optional[str] = None, + **kwargs: Union[str, Resolvable] + ): super().__init__(connprivkey) self.msgtype = namespace().get_msgtype(msgtypename) if not self.msgtype: @@ -270,7 +299,7 @@ def __init__(self, msgtypename: str, ignore = self.ignore_gossip_queries self.ignore = ignore - def message_match(self, runner: 'Runner', msg: Message) -> Optional[str]: + def message_match(self, runner: "Runner", msg: Message) -> Optional[str]: """Does this message match what we expect?""" partmessage = Message(self.msgtype, **self.resolve_args(runner, self.kwargs)) @@ -280,7 +309,7 @@ def message_match(self, runner: 'Runner', msg: Message) -> Optional[str]: msg_to_stash(runner, self, msg) return ret - def action(self, runner: 'Runner') -> bool: + def action(self, runner: "Runner") -> bool: super().action(runner) conn = self.find_conn(runner) while True: @@ -290,14 +319,17 @@ def action(self, runner: 'Runner') -> bool: for e in conn.must_not_events: if e.matches(binmsg): - raise EventError(self, "Got msg banned by {}: {}" - .format(e, binmsg.hex())) + raise EventError( + self, "Got msg banned by {}: {}".format(e, binmsg.hex()) + ) # Might be completely unknown to namespace. try: msg = Message.read(namespace(), io.BytesIO(binmsg)) except ValueError as ve: - raise EventError(self, "Runner gave bad msg {}: {}".format(binmsg.hex(), ve)) + raise EventError( + self, "Runner gave bad msg {}: {}".format(binmsg.hex(), ve) + ) # Ignore function may tell us to respond. response = self.ignore(msg) @@ -317,72 +349,89 @@ def action(self, runner: 'Runner') -> bool: class Block(Event): - """Generate a block, at blockheight, with optional txs. + """Generate a block, at blockheight, with optional txs.""" - """ - def __init__(self, blockheight: int, number: int = 1, txs: List[ResolvableStr] = []): + def __init__( + self, blockheight: int, number: int = 1, txs: List[ResolvableStr] = [] + ): super().__init__() self.blockheight = blockheight self.number = number self.txs = txs - def action(self, runner: 'Runner') -> bool: + def action(self, runner: "Runner") -> bool: super().action(runner) # Oops, did they ask us to produce a block with no predecessor? if runner.getblockheight() + 1 < self.blockheight: - raise SpecFileError(self, "Cannot generate block #{} at height {}". - format(self.blockheight, runner.getblockheight())) + raise SpecFileError( + self, + "Cannot generate block #{} at height {}".format( + self.blockheight, runner.getblockheight() + ), + ) # Throw away blocks we're replacing. if runner.getblockheight() >= self.blockheight: runner.trim_blocks(self.blockheight - 1) # Add new one - runner.add_blocks(self, [self.resolve_arg('tx', runner, tx) for tx in self.txs], self.number) + runner.add_blocks( + self, [self.resolve_arg("tx", runner, tx) for tx in self.txs], self.number + ) assert runner.getblockheight() == self.blockheight - 1 + self.number return True class ExpectTx(Event): - """Expect the runner to broadcast a transaction + """Expect the runner to broadcast a transaction""" - """ def __init__(self, txid: ResolvableStr): super().__init__() self.txid = txid - def action(self, runner: 'Runner') -> bool: + def action(self, runner: "Runner") -> bool: super().action(runner) - runner.expect_tx(self, self.resolve_arg('txid', runner, self.txid)) + runner.expect_tx(self, self.resolve_arg("txid", runner, self.txid)) return True class FundChannel(PerConnEvent): """Tell the runner to fund a channel with this peer.""" - def __init__(self, amount: ResolvableInt, feerate: ResolvableInt = 253, expect_fail: ResolvableBool = False, connprivkey: Optional[str] = None): + + def __init__( + self, + amount: ResolvableInt, + feerate: ResolvableInt = 253, + expect_fail: ResolvableBool = False, + connprivkey: Optional[str] = None, + ): super().__init__(connprivkey) self.amount = amount self.feerate = feerate self.expect_fail = expect_fail - def action(self, runner: 'Runner') -> bool: + def action(self, runner: "Runner") -> bool: super().action(runner) - runner.fundchannel(self, - self.find_conn(runner), - self.resolve_arg('amount', runner, self.amount), - self.resolve_arg('feerate', runner, self.feerate), - self.resolve_arg('expect_fail', runner, self.expect_fail)) + runner.fundchannel( + self, + self.find_conn(runner), + self.resolve_arg("amount", runner, self.amount), + self.resolve_arg("feerate", runner, self.feerate), + self.resolve_arg("expect_fail", runner, self.expect_fail), + ) return True class InitRbf(PerConnEvent): - def __init__(self, - channel_id: ResolvableStr, - amount: ResolvableInt, - utxo_tx: ResolvableStr, - utxo_outnum: ResolvableInt, - feerate: int, - connprivkey: Optional[str] = None): + def __init__( + self, + channel_id: ResolvableStr, + amount: ResolvableInt, + utxo_tx: ResolvableStr, + utxo_outnum: ResolvableInt, + feerate: int, + connprivkey: Optional[str] = None, + ): super().__init__(connprivkey) self.channel_id = channel_id self.amount = amount @@ -390,18 +439,20 @@ def __init__(self, self.utxo_tx = utxo_tx self.utxo_outnum = utxo_outnum - def action(self, runner: 'Runner') -> bool: + def action(self, runner: "Runner") -> bool: super().action(runner) - utxo_tx = self.resolve_arg('utxo_tx', runner, self.utxo_tx) + utxo_tx = self.resolve_arg("utxo_tx", runner, self.utxo_tx) txid = CTransaction.deserialize(bytes.fromhex(utxo_tx)).GetTxid()[::-1].hex() - runner.init_rbf(self, - self.find_conn(runner), - self.resolve_arg('channel_id', runner, self.channel_id), - self.resolve_arg('amount', runner, self.amount), - txid, - self.resolve_arg('utxo_outnum', runner, self.utxo_outnum), - self.feerate) + runner.init_rbf( + self, + self.find_conn(runner), + self.resolve_arg("channel_id", runner, self.channel_id), + self.resolve_arg("amount", runner, self.amount), + txid, + self.resolve_arg("utxo_outnum", runner, self.utxo_outnum), + self.feerate, + ) return True @@ -412,24 +463,32 @@ def __init__(self, amount: int, preimage: ResolvableStr): self.preimage = preimage self.amount = amount - def action(self, runner: 'Runner') -> bool: + def action(self, runner: "Runner") -> bool: super().action(runner) - runner.invoice(self, self.amount, - check_hex(self.resolve_arg('preimage', runner, self.preimage), 64)) + runner.invoice( + self, + self.amount, + check_hex(self.resolve_arg("preimage", runner, self.preimage), 64), + ) return True class AddHtlc(PerConnEvent): - def __init__(self, amount: int, preimage: ResolvableStr, connprivkey: Optional[str] = None): + def __init__( + self, amount: int, preimage: ResolvableStr, connprivkey: Optional[str] = None + ): super().__init__(connprivkey) self.preimage = preimage self.amount = amount - def action(self, runner: 'Runner') -> bool: + def action(self, runner: "Runner") -> bool: super().action(runner) - runner.addhtlc(self, self.find_conn(runner), - self.amount, - check_hex(self.resolve_arg('preimage', runner, self.preimage), 64)) + runner.addhtlc( + self, + self.find_conn(runner), + self.amount, + check_hex(self.resolve_arg("preimage", runner, self.preimage), 64), + ) return True @@ -437,7 +496,7 @@ class DualFundAccept(Event): def __init__(self) -> None: super().__init__() - def action(self, runner: 'Runner') -> bool: + def action(self, runner: "Runner") -> bool: super().action(runner) runner.accept_add_fund(self) return True @@ -447,7 +506,7 @@ class ExpectError(PerConnEvent): def __init__(self, connprivkey: Optional[str] = None): super().__init__(connprivkey) - def action(self, runner: 'Runner') -> bool: + def action(self, runner: "Runner") -> bool: super().action(runner) error = runner.check_error(self, self.find_conn(runner)) if error is None: @@ -457,22 +516,23 @@ def action(self, runner: 'Runner') -> bool: class CheckEq(Event): """Event to check a condition is true""" + def __init__(self, a: Resolvable, b: Resolvable): super().__init__() self.a = a self.b = b - def action(self, runner: 'Runner') -> bool: + def action(self, runner: "Runner") -> bool: super().action(runner) - a = self.resolve_arg('a', runner, self.a) - b = self.resolve_arg('b', runner, self.b) + a = self.resolve_arg("a", runner, self.a) + b = self.resolve_arg("b", runner, self.b) # dummy runner generates dummy fields. if a != b and not runner._is_dummy(): raise EventError(self, "{} != {}".format(a, b)) return True -def msg_to_stash(runner: 'Runner', event: Event, msg: Message) -> None: +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() @@ -486,20 +546,23 @@ def cmp_obj(obj: Any, expected: Any, prefix: str) -> Optional[str]: if isinstance(expected, collections.abc.Mapping): for k, v in expected.items(): if k not in obj: - return "Missing field {}".format(prefix + '.' + k) - diff = cmp_obj(obj[k], v, prefix + '.' + k) + return "Missing field {}".format(prefix + "." + k) + diff = cmp_obj(obj[k], v, prefix + "." + k) if diff: return diff - elif not isinstance(expected, str) and isinstance(expected, collections.abc.Sequence): + elif not isinstance(expected, str) and isinstance( + expected, collections.abc.Sequence + ): # Should we allow expected to be shorter? if len(expected) != len(obj): - return "Expected {} elements, got {} in {}: expected {} not {}".format(len(expected), len(obj), - prefix, expected, obj) + return "Expected {} elements, got {} in {}: expected {} not {}".format( + len(expected), len(obj), prefix, expected, obj + ) for i in range(len(expected)): diff = cmp_obj(obj[i], expected[i], "{}[{}]".format(prefix, i)) if diff: return diff - elif isinstance(expected, str) and expected.startswith('Sig('): + elif isinstance(expected, str) and expected.startswith("Sig("): # Special handling for signature comparisons. if Sig.from_str(expected) != Sig.from_str(obj): return "{}: signature mismatch {} != {}".format(prefix, obj, expected) @@ -527,35 +590,41 @@ def msat(sats: int) -> int: @overload -def msat(sats: Callable[['Runner', 'Event', str], int]) -> Callable[['Runner', 'Event', str], int]: +def msat( + sats: Callable[["Runner", "Event", str], int] +) -> Callable[["Runner", "Event", str], int]: ... def msat(sats: ResolvableInt) -> ResolvableInt: """Convert a field from statoshis to millisatoshis""" - def _msat(runner: 'Runner', event: Event, field: str) -> int: + + def _msat(runner: "Runner", event: Event, field: str) -> int: if callable(sats): return 1000 * sats(runner, event, field) else: return 1000 * sats + if callable(sats): return _msat else: return 1000 * sats -def negotiated(a_features: ResolvableStr, - b_features: ResolvableStr, - included: List[int] = [], - excluded: List[int] = []) -> ResolvableBool: +def negotiated( + a_features: ResolvableStr, + b_features: ResolvableStr, + included: List[int] = [], + excluded: List[int] = [], +) -> ResolvableBool: def has_feature(fbit: int, featurebits: str) -> bool: # Feature bits go in optional/compulsory pairs. altfbit = fbit ^ 1 return has_bit(featurebits, fbit) or has_bit(featurebits, altfbit) - def _negotiated(runner: 'Runner', event: Event, field: str) -> bool: - a = event.resolve_arg('features', runner, a_features) - b = event.resolve_arg('features', runner, b_features) + def _negotiated(runner: "Runner", event: Event, field: str) -> bool: + a = event.resolve_arg("features", runner, a_features) + b = event.resolve_arg("features", runner, b_features) for i in included: if not has_feature(i, a) or not has_feature(i, b): diff --git a/lnprototest/funding.py b/lnprototest/funding.py index 3f8edb5..6f2a94c 100644 --- a/lnprototest/funding.py +++ b/lnprototest/funding.py @@ -9,11 +9,22 @@ from hashlib import sha256 import coincurve import io -from bitcoin.core import COutPoint, CScript, CTxIn, CTxOut, CMutableTransaction, CTxWitness, CTxInWitness, CScriptWitness, Hash160, CTransaction +from bitcoin.core import ( + COutPoint, + CScript, + CTxIn, + CTxOut, + CMutableTransaction, + CTxWitness, + CTxInWitness, + CScriptWitness, + Hash160, + CTransaction, +) import bitcoin.core.script as script from bitcoin.wallet import P2WPKHBitcoinAddress -ResolvableFunding = Union['Funding', Callable[['Runner', 'Event', str], 'Funding']] +ResolvableFunding = Union["Funding", Callable[["Runner", "Event", str], "Funding"]] def txid_raw(tx: str) -> str: @@ -22,24 +33,30 @@ def txid_raw(tx: str) -> str: class Funding(object): - def __init__(self, - funding_txid: str, - funding_output_index: int, - funding_amount: int, - local_node_privkey: str, - local_funding_privkey: str, - remote_node_privkey: str, - remote_funding_privkey: str, - chain_hash: str = regtest_hash, - locktime: int = 0): + def __init__( + self, + funding_txid: str, + funding_output_index: int, + funding_amount: int, + local_node_privkey: str, + local_funding_privkey: str, + remote_node_privkey: str, + remote_funding_privkey: str, + chain_hash: str = regtest_hash, + locktime: int = 0, + ): self.chain_hash = chain_hash self.txid = funding_txid self.output_index = funding_output_index self.amount = funding_amount - self.bitcoin_privkeys = [privkey_expand(local_funding_privkey), - privkey_expand(remote_funding_privkey)] - self.node_privkeys = [privkey_expand(local_node_privkey), - privkey_expand(remote_node_privkey)] + self.bitcoin_privkeys = [ + privkey_expand(local_funding_privkey), + privkey_expand(remote_funding_privkey), + ] + self.node_privkeys = [ + privkey_expand(local_node_privkey), + privkey_expand(remote_node_privkey), + ] self.tx = None self.locktime = locktime self.outputs: List[Dict[str, Any]] = [] @@ -47,12 +64,16 @@ def __init__(self, def tx_hex(self) -> str: if not self.tx: - return '' + return "" return self.tx.serialize().hex() @staticmethod - def sort_by_keys(key_one: coincurve.PublicKey, key_two: coincurve.PublicKey, - val_one: Any, val_two: Any) -> Tuple[Any, Any]: + def sort_by_keys( + key_one: coincurve.PublicKey, + key_two: coincurve.PublicKey, + val_one: Any, + val_two: Any, + ) -> Tuple[Any, Any]: """In many places we have to sort elements into key or nodeid order""" # BOLT #3: # ## Funding Transaction Output @@ -74,143 +95,176 @@ def node_id_sort(self, local: Any, remote: Any) -> Tuple[Any, Any]: # nodes operating the channel, such that `node_id_1` is the # lexicographically-lesser of the two compressed keys sorted in # ascending lexicographic order. - return self.sort_by_keys(self.node_id(Side.local), - self.node_id(Side.remote), - local, remote) + return self.sort_by_keys( + self.node_id(Side.local), self.node_id(Side.remote), local, remote + ) @staticmethod - def redeemscript_keys(key_one: coincurve.PublicKey, key_two: coincurve.PublicKey) -> CScript: - return CScript([script.OP_2] - + [k.format() for k in Funding.sort_by_keys(key_one, key_two, key_one, key_two)] - + [script.OP_2, - script.OP_CHECKMULTISIG]) + def redeemscript_keys( + key_one: coincurve.PublicKey, key_two: coincurve.PublicKey + ) -> CScript: + return CScript( + [script.OP_2] + + [ + k.format() + for k in Funding.sort_by_keys(key_one, key_two, key_one, key_two) + ] + + [script.OP_2, script.OP_CHECKMULTISIG] + ) def redeemscript(self) -> CScript: key_a, key_b = self.funding_pubkeys_for_tx() return self.redeemscript_keys(key_a, key_b) @staticmethod - def locking_script_keys(key_one: coincurve.PublicKey, key_two: coincurve.PublicKey) -> CScript: - return CScript([script.OP_0, sha256(Funding.redeemscript_keys(key_one, key_two)).digest()]) + def locking_script_keys( + key_one: coincurve.PublicKey, key_two: coincurve.PublicKey + ) -> CScript: + return CScript( + [script.OP_0, sha256(Funding.redeemscript_keys(key_one, key_two)).digest()] + ) def locking_script(self) -> CScript: a, b = self.funding_pubkeys_for_tx() return self.locking_script_keys(a, b) @staticmethod - def start(local_node_privkey: str, - local_funding_privkey: str, - remote_node_privkey: str, - remote_funding_privkey: str, - funding_sats: int, - locktime: int, - chain_hash: str = regtest_hash) -> 'Funding': + def start( + local_node_privkey: str, + local_funding_privkey: str, + remote_node_privkey: str, + remote_funding_privkey: str, + funding_sats: int, + locktime: int, + chain_hash: str = regtest_hash, + ) -> "Funding": # Create dummy one to start: we will fill in txid at the end - return Funding('', 0, funding_sats, - local_node_privkey, - local_funding_privkey, - remote_node_privkey, - remote_funding_privkey, - chain_hash, locktime) - - def add_input(self, - serial_id: int, - prevtx: str, - prevtx_vout: int, - script_sig: str, - sequence: int, - privkey: str = None) -> None: + return Funding( + "", + 0, + funding_sats, + local_node_privkey, + local_funding_privkey, + remote_node_privkey, + remote_funding_privkey, + chain_hash, + locktime, + ) + + def add_input( + self, + serial_id: int, + prevtx: str, + prevtx_vout: int, + script_sig: str, + sequence: int, + privkey: str = None, + ) -> None: # the dummy runner sends empty info, skip if len(prevtx) == 0: return # Find the txid of the transaction prev_tx = CTransaction.deserialize(bytes.fromhex(prevtx)) - txin = CTxIn(COutPoint(prev_tx.GetTxid(), prevtx_vout), - nSequence=sequence) + txin = CTxIn(COutPoint(prev_tx.GetTxid(), prevtx_vout), nSequence=sequence) # Get the previous output for its outscript + value prev_vout = prev_tx.vout[prevtx_vout] - self.inputs.append({'input': txin, - 'serial_id': serial_id, - 'sats': prev_vout.nValue, - 'prev_outscript': prev_vout.scriptPubKey.hex(), - 'redeemscript': script_sig, - 'privkey': privkey, - }) - - def add_output(self, - serial_id: int, - script: str, - sats: int) -> None: + self.inputs.append( + { + "input": txin, + "serial_id": serial_id, + "sats": prev_vout.nValue, + "prev_outscript": prev_vout.scriptPubKey.hex(), + "redeemscript": script_sig, + "privkey": privkey, + } + ) + + def add_output(self, serial_id: int, script: str, sats: int) -> None: txout = CTxOut(sats, CScript(bytes.fromhex(script))) - self.outputs.append({'output': txout, - 'serial_id': serial_id}) + self.outputs.append({"output": txout, "serial_id": serial_id}) def our_witnesses(self) -> str: - """ Extract expected witness data for our node """ + """Extract expected witness data for our node""" witnesses = [] # these were sorted in `build_tx` for _in in self.inputs: - if not _in['privkey']: + if not _in["privkey"]: continue - wit = _in['sig'] - print('witness is ... ', wit) + wit = _in["sig"] + print("witness is ... ", wit) elems = [] for e in wit.scriptWitness.stack: - elems.append('{{witness={0}}}'.format(e.hex())) - witnesses.append('{{witness_element=[{0}]}}'.format(','.join(elems))) - val = '[{}]'.format(','.join(witnesses)) - print('witnesses are', val) + elems.append("{{witness={0}}}".format(e.hex())) + witnesses.append("{{witness_element=[{0}]}}".format(",".join(elems))) + val = "[{}]".format(",".join(witnesses)) + print("witnesses are", val) return val def sign_our_inputs(self) -> None: assert self.tx is not None for idx, _in in enumerate(self.inputs): - privkey = _in['privkey'] + privkey = _in["privkey"] - if privkey and 'sig' not in _in: - print('signing our input for tx', self.tx.serialize().hex()) + if privkey and "sig" not in _in: + print("signing our input for tx", self.tx.serialize().hex()) inkey = privkey_expand(privkey) inkey_pub = coincurve.PublicKey.from_secret(inkey.secret) # Really horrid hack to produce a signature for the # multisig utxo in tests/helpers.py - if privkey == '38204720bc4f9647fd58c6d0a4bd3a6dd2be16d8e4273c4d1bdd5774e8c51eaf': - redeemscript = bytes.fromhex('51210253cdf835e328346a4f19de099cf3d42d4a7041e073cd4057a1c4fd7cdbb1228f2103ae903722f21f85e651b8f9b18fc854084fb90eeb76452bdcfd0cb43a16a382a221036c264d68a9727afdc75949f7d7fa71910ae9ae8001a1fbffa6f7ce000976597c21036429fa8a4ef0b2b1d5cb553e34eeb90a32ab19fae1f0024f332ab4f74283a7282103d4232f19ea85051e7b76bf5f01d03e17eea8751463dee36d71413a739de1a92755ae') + if ( + privkey + == "38204720bc4f9647fd58c6d0a4bd3a6dd2be16d8e4273c4d1bdd5774e8c51eaf" + ): + redeemscript = bytes.fromhex( + "51210253cdf835e328346a4f19de099cf3d42d4a7041e073cd4057a1c4fd7cdbb1228f2103ae903722f21f85e651b8f9b18fc854084fb90eeb76452bdcfd0cb43a16a382a221036c264d68a9727afdc75949f7d7fa71910ae9ae8001a1fbffa6f7ce000976597c21036429fa8a4ef0b2b1d5cb553e34eeb90a32ab19fae1f0024f332ab4f74283a7282103d4232f19ea85051e7b76bf5f01d03e17eea8751463dee36d71413a739de1a92755ae" + ) else: - address = P2WPKHBitcoinAddress.from_scriptPubKey(CScript([script.OP_0, Hash160(inkey_pub.format())])) + address = P2WPKHBitcoinAddress.from_scriptPubKey( + CScript([script.OP_0, Hash160(inkey_pub.format())]) + ) redeemscript = address.to_redeemScript() - sighash = script.SignatureHash(redeemscript, - self.tx, idx, script.SIGHASH_ALL, - amount=_in['sats'], - sigversion=script.SIGVERSION_WITNESS_V0) + sighash = script.SignatureHash( + redeemscript, + self.tx, + idx, + script.SIGHASH_ALL, + amount=_in["sats"], + sigversion=script.SIGVERSION_WITNESS_V0, + ) sig = inkey.sign(sighash, hasher=None) + bytes([script.SIGHASH_ALL]) - if privkey == '38204720bc4f9647fd58c6d0a4bd3a6dd2be16d8e4273c4d1bdd5774e8c51eaf': - _in['sig'] = CTxInWitness(CScriptWitness([bytes([]), sig, redeemscript])) + if ( + privkey + == "38204720bc4f9647fd58c6d0a4bd3a6dd2be16d8e4273c4d1bdd5774e8c51eaf" + ): + _in["sig"] = CTxInWitness( + CScriptWitness([bytes([]), sig, redeemscript]) + ) else: - _in['sig'] = CTxInWitness(CScriptWitness([sig, inkey_pub.format()])) + _in["sig"] = CTxInWitness(CScriptWitness([sig, inkey_pub.format()])) def add_witnesses(self, witness_stack: List[Dict[str, Any]]) -> str: assert self.tx is not None wits = [] for idx, _in in enumerate(self.inputs): - if 'sig' in _in: - wits.append(_in['sig']) + if "sig" in _in: + wits.append(_in["sig"]) continue if not len(witness_stack): continue - elems = witness_stack.pop(0)['witness_element'] + elems = witness_stack.pop(0)["witness_element"] stack = [] for elem in elems: - stack.append(bytes.fromhex(elem['witness'])) + stack.append(bytes.fromhex(elem["witness"])) wits.append(CTxInWitness(CScriptWitness(stack))) @@ -219,18 +273,21 @@ def add_witnesses(self, witness_stack: List[Dict[str, Any]]) -> str: def build_tx(self) -> str: # Sort inputs/outputs by serial number - self.inputs = sorted(self.inputs, key=lambda k: k['serial_id']) - self.outputs = sorted(self.outputs, key=lambda k: k['serial_id']) - - self.tx = CMutableTransaction([i['input'] for i in self.inputs], - [o['output'] for o in self.outputs], - nVersion=2, nLockTime=self.locktime) + self.inputs = sorted(self.inputs, key=lambda k: k["serial_id"]) + self.outputs = sorted(self.outputs, key=lambda k: k["serial_id"]) + + self.tx = CMutableTransaction( + [i["input"] for i in self.inputs], + [o["output"] for o in self.outputs], + nVersion=2, + nLockTime=self.locktime, + ) assert self.tx is not None self.txid = self.tx.GetTxid().hex() # Set the output index for the funding output locking_script = self.locking_script() - for i, out in enumerate([o['output'] for o in self.outputs]): + for i, out in enumerate([o["output"] for o in self.outputs]): if out.scriptPubKey == locking_script: self.output_index = i self.amount = out.nValue @@ -238,45 +295,64 @@ def build_tx(self) -> str: return self.tx.serialize().hex() @staticmethod - def from_utxo(txid_in: str, - tx_index_in: int, - sats: int, - privkey: str, - fee: int, - local_node_privkey: str, - local_funding_privkey: str, - remote_node_privkey: str, - remote_funding_privkey: str, - chain_hash: str = regtest_hash) -> Tuple['Funding', str]: + def from_utxo( + txid_in: str, + tx_index_in: int, + sats: int, + privkey: str, + fee: int, + local_node_privkey: str, + local_funding_privkey: str, + remote_node_privkey: str, + remote_funding_privkey: str, + chain_hash: str = regtest_hash, + ) -> Tuple["Funding", str]: """Make a funding transaction by spending this utxo using privkey: return Funding, tx.""" # Create dummy one to start: we will fill in txid at the end. - funding = Funding('', 0, sats - fee, - local_node_privkey, - local_funding_privkey, - remote_node_privkey, - remote_funding_privkey, - chain_hash) + funding = Funding( + "", + 0, + sats - fee, + local_node_privkey, + local_funding_privkey, + remote_node_privkey, + remote_funding_privkey, + chain_hash, + ) # input private key. inkey = privkey_expand(privkey) inkey_pub = coincurve.PublicKey.from_secret(inkey.secret) # use RBF'able input (requirement for dual-funded things) - txin = CTxIn(COutPoint(bytes.fromhex(txid_in), tx_index_in), nSequence=0xFFFFFFFD) - txout = CTxOut(sats - fee, CScript([script.OP_0, sha256(funding.redeemscript()).digest()])) - tx = CMutableTransaction([txin], [txout], nVersion=2, nLockTime=funding.locktime) + txin = CTxIn( + COutPoint(bytes.fromhex(txid_in), tx_index_in), nSequence=0xFFFFFFFD + ) + txout = CTxOut( + sats - fee, CScript([script.OP_0, sha256(funding.redeemscript()).digest()]) + ) + tx = CMutableTransaction( + [txin], [txout], nVersion=2, nLockTime=funding.locktime + ) # now fill in funding txid. funding.txid = tx.GetTxid().hex() funding.tx = tx # while we're here, sign the transaction. - address = P2WPKHBitcoinAddress.from_scriptPubKey(CScript([script.OP_0, Hash160(inkey_pub.format())])) - - sighash = script.SignatureHash(address.to_redeemScript(), - tx, 0, script.SIGHASH_ALL, amount=sats, - sigversion=script.SIGVERSION_WITNESS_V0) + address = P2WPKHBitcoinAddress.from_scriptPubKey( + CScript([script.OP_0, Hash160(inkey_pub.format())]) + ) + + sighash = script.SignatureHash( + address.to_redeemScript(), + tx, + 0, + script.SIGHASH_ALL, + amount=sats, + sigversion=script.SIGVERSION_WITNESS_V0, + ) sig = inkey.sign(sighash, hasher=None) + bytes([script.SIGHASH_ALL]) tx.wit = CTxWitness([CTxInWitness(CScriptWitness([sig, inkey_pub.format()]))]) @@ -309,63 +385,75 @@ def funding_pubkeys_for_tx(self) -> Tuple[coincurve.PublicKey, coincurve.PublicK # * Where `pubkey1` is the lexicographically lesser of the two # `funding_pubkey` in compressed format, and where `pubkey2` is the # lexicographically greater of the two. - return self.sort_by_keys(self.funding_pubkey(Side.local), - self.funding_pubkey(Side.remote), - self.funding_pubkey(Side.local), - self.funding_pubkey(Side.remote)) - - def funding_privkeys_for_tx(self) -> Tuple[coincurve.PrivateKey, coincurve.PrivateKey]: + return self.sort_by_keys( + self.funding_pubkey(Side.local), + self.funding_pubkey(Side.remote), + self.funding_pubkey(Side.local), + self.funding_pubkey(Side.remote), + ) + + def funding_privkeys_for_tx( + self, + ) -> Tuple[coincurve.PrivateKey, coincurve.PrivateKey]: """Returns funding private keys, in tx order""" - return self.sort_by_keys(self.funding_pubkey(Side.local), - self.funding_pubkey(Side.remote), - self.bitcoin_privkeys[Side.local], - self.bitcoin_privkeys[Side.remote]) + return self.sort_by_keys( + self.funding_pubkey(Side.local), + self.funding_pubkey(Side.remote), + self.bitcoin_privkeys[Side.local], + self.bitcoin_privkeys[Side.remote], + ) def node_id(self, side: Side) -> coincurve.PublicKey: return coincurve.PublicKey.from_secret(self.node_privkeys[side].secret) def node_ids(self) -> Tuple[coincurve.PublicKey, coincurve.PublicKey]: """Returns node pubkeys, in order""" - return self.node_id_sort(self.node_id(Side.local), - self.node_id(Side.remote)) + return self.node_id_sort(self.node_id(Side.local), self.node_id(Side.remote)) def node_id_privkeys(self) -> Tuple[coincurve.PrivateKey, coincurve.PrivateKey]: """Returns node private keys, in order""" - return self.node_id_sort(self.node_privkeys[Side.local], - self.node_privkeys[Side.remote]) + return self.node_id_sort( + self.node_privkeys[Side.local], self.node_privkeys[Side.remote] + ) - def funding_pubkeys_for_gossip(self) -> Tuple[coincurve.PublicKey, coincurve.PublicKey]: + def funding_pubkeys_for_gossip( + self, + ) -> Tuple[coincurve.PublicKey, coincurve.PublicKey]: """Returns funding public keys, in gossip order""" - return self.node_id_sort(self.funding_pubkey(Side.local), - self.funding_pubkey(Side.remote)) + return self.node_id_sort( + self.funding_pubkey(Side.local), self.funding_pubkey(Side.remote) + ) - def funding_privkeys_for_gossip(self) -> Tuple[coincurve.PublicKey, coincurve.PublicKey]: + def funding_privkeys_for_gossip( + self, + ) -> Tuple[coincurve.PublicKey, coincurve.PublicKey]: """Returns funding private keys, in gossip order""" - return self.node_id_sort(self.bitcoin_privkeys[Side.local], - self.bitcoin_privkeys[Side.remote]) + return self.node_id_sort( + self.bitcoin_privkeys[Side.local], self.bitcoin_privkeys[Side.remote] + ) - def _unsigned_channel_announcment(self, - features: str, - short_channel_id: str) -> Message: + def _unsigned_channel_announcment( + self, features: str, short_channel_id: str + ) -> Message: """Produce a channel_announcement message with dummy sigs""" node_ids = self.node_ids() bitcoin_keys = self.funding_pubkeys_for_gossip() - return Message(namespace().get_msgtype('channel_announcement'), - node_signature_1=Sig(bytes(64)), - node_signature_2=Sig(bytes(64)), - bitcoin_signature_1=Sig(bytes(64)), - bitcoin_signature_2=Sig(bytes(64)), - features=features, - chain_hash=self.chain_hash, - short_channel_id=short_channel_id, - node_id_1=node_ids[0].format(), - node_id_2=node_ids[1].format(), - bitcoin_key_1=bitcoin_keys[0].format(), - bitcoin_key_2=bitcoin_keys[1].format()) - - def channel_announcement(self, - short_channel_id: str, - features: str) -> Message: + return Message( + namespace().get_msgtype("channel_announcement"), + node_signature_1=Sig(bytes(64)), + node_signature_2=Sig(bytes(64)), + bitcoin_signature_1=Sig(bytes(64)), + bitcoin_signature_2=Sig(bytes(64)), + features=features, + chain_hash=self.chain_hash, + short_channel_id=short_channel_id, + node_id_1=node_ids[0].format(), + node_id_2=node_ids[1].format(), + bitcoin_key_1=bitcoin_keys[0].format(), + bitcoin_key_2=bitcoin_keys[1].format(), + ) + + def channel_announcement(self, short_channel_id: str, features: str) -> Message: """Produce a (signed) channel_announcement message""" ann = self._unsigned_channel_announcment(features, short_channel_id) # BOLT #7: @@ -376,35 +464,41 @@ def channel_announcement(self, buf = io.BytesIO() ann.write(buf) # Note the first two 'type' bytes! - h = sha256(sha256(buf.getvalue()[2 + 256:]).digest()).digest() + h = sha256(sha256(buf.getvalue()[2 + 256 :]).digest()).digest() # BOLT #7: # - MUST set `node_signature_1` and `node_signature_2` to valid # signatures of the hash `h` (using `node_id_1` and `node_id_2`'s # respective secrets). node_privkeys = self.node_id_privkeys() - ann.set_field('node_signature_1', Sig(node_privkeys[0].secret.hex(), h.hex())) - ann.set_field('node_signature_2', Sig(node_privkeys[1].secret.hex(), h.hex())) + ann.set_field("node_signature_1", Sig(node_privkeys[0].secret.hex(), h.hex())) + ann.set_field("node_signature_2", Sig(node_privkeys[1].secret.hex(), h.hex())) bitcoin_privkeys = self.funding_privkeys_for_gossip() # - MUST set `bitcoin_signature_1` and `bitcoin_signature_2` to valid # signatures of the hash `h` (using `bitcoin_key_1` and # `bitcoin_key_2`'s respective secrets). - ann.set_field('bitcoin_signature_1', Sig(bitcoin_privkeys[0].secret.hex(), h.hex())) - ann.set_field('bitcoin_signature_2', Sig(bitcoin_privkeys[1].secret.hex(), h.hex())) + ann.set_field( + "bitcoin_signature_1", Sig(bitcoin_privkeys[0].secret.hex(), h.hex()) + ) + ann.set_field( + "bitcoin_signature_2", Sig(bitcoin_privkeys[1].secret.hex(), h.hex()) + ) return ann - def channel_update(self, - short_channel_id: str, - side: Side, - disable: bool, - cltv_expiry_delta: int, - htlc_minimum_msat: int, - fee_base_msat: int, - fee_proportional_millionths: int, - timestamp: int, - htlc_maximum_msat: Optional[int]) -> Message: + def channel_update( + self, + short_channel_id: str, + side: Side, + disable: bool, + cltv_expiry_delta: int, + htlc_minimum_msat: int, + fee_base_msat: int, + fee_proportional_millionths: int, + timestamp: int, + htlc_maximum_msat: Optional[int], + ) -> Message: # BOLT #7: The `channel_flags` bitfield is used to indicate the # direction of the channel: it identifies the node that this update # originated from and signals various options concerning the @@ -440,19 +534,21 @@ def channel_update(self, message_flags |= 1 # Begin with a fake signature. - update = Message(namespace().get_msgtype('channel_update'), - short_channel_id=short_channel_id, - signature=Sig(bytes(64)), - chain_hash=self.chain_hash, - timestamp=timestamp, - message_flags=message_flags, - channel_flags=channel_flags, - cltv_expiry_delta=cltv_expiry_delta, - htlc_minimum_msat=htlc_minimum_msat, - fee_base_msat=fee_base_msat, - fee_proportional_millionths=fee_proportional_millionths) + update = Message( + namespace().get_msgtype("channel_update"), + short_channel_id=short_channel_id, + signature=Sig(bytes(64)), + chain_hash=self.chain_hash, + timestamp=timestamp, + message_flags=message_flags, + channel_flags=channel_flags, + cltv_expiry_delta=cltv_expiry_delta, + htlc_minimum_msat=htlc_minimum_msat, + fee_base_msat=fee_base_msat, + fee_proportional_millionths=fee_proportional_millionths, + ) if htlc_maximum_msat: - update.set_field('htlc_maximum_msat', htlc_maximum_msat) + update.set_field("htlc_maximum_msat", htlc_maximum_msat) # BOLT #7: # - MUST set `signature` to the signature of the double-SHA256 of the @@ -460,28 +556,34 @@ def channel_update(self, buf = io.BytesIO() update.write(buf) # Note the first two 'type' bytes! - h = sha256(sha256(buf.getvalue()[2 + 64:]).digest()).digest() + h = sha256(sha256(buf.getvalue()[2 + 64 :]).digest()).digest() - update.set_field('signature', Sig(self.node_privkeys[side].secret.hex(), h.hex())) + update.set_field( + "signature", Sig(self.node_privkeys[side].secret.hex(), h.hex()) + ) return update - def node_announcement(self, - side: Side, - features: str, - rgb_color: Tuple[int, int, int], - alias: str, - addresses: bytes, - timestamp: int) -> Message: + def node_announcement( + self, + side: Side, + features: str, + rgb_color: Tuple[int, int, int], + alias: str, + addresses: bytes, + timestamp: int, + ) -> Message: # Begin with a fake signature. - ann = Message(namespace().get_msgtype('node_announcement'), - signature=Sig(bytes(64)), - features=features, - timestamp=timestamp, - node_id=self.node_id(side).format().hex(), - rgb_color=bytes(rgb_color).hex(), - alias=bytes(alias, encoding='utf-8').zfill(32), - addresses=addresses) + ann = Message( + namespace().get_msgtype("node_announcement"), + signature=Sig(bytes(64)), + features=features, + timestamp=timestamp, + node_id=self.node_id(side).format().hex(), + rgb_color=bytes(rgb_color).hex(), + alias=bytes(alias, encoding="utf-8").zfill(32), + addresses=addresses, + ) # BOLT #7: # - MUST set `signature` to the signature of the double-SHA256 of the entire @@ -489,9 +591,9 @@ def node_announcement(self, buf = io.BytesIO() ann.write(buf) # Note the first two 'type' bytes! - h = sha256(sha256(buf.getvalue()[2 + 64:]).digest()).digest() + h = sha256(sha256(buf.getvalue()[2 + 64 :]).digest()).digest() - ann.set_field('signature', Sig(self.node_privkeys[side].secret.hex(), h.hex())) + ann.set_field("signature", Sig(self.node_privkeys[side].secret.hex(), h.hex())) return ann def close_tx(self, fee: int, privkey_dest: str) -> str: @@ -500,40 +602,61 @@ def close_tx(self, fee: int, privkey_dest: str) -> str: out_privkey = privkey_expand(privkey_dest) - txout = CTxOut(self.amount - fee, - CScript([script.OP_0, - Hash160(coincurve.PublicKey.from_secret(out_privkey.secret).format())])) + txout = CTxOut( + self.amount - fee, + CScript( + [ + script.OP_0, + Hash160( + coincurve.PublicKey.from_secret(out_privkey.secret).format() + ), + ] + ), + ) tx = CMutableTransaction(vin=[txin], vout=[txout]) - sighash = script.SignatureHash(self.redeemscript(), tx, inIdx=0, - hashtype=script.SIGHASH_ALL, - amount=self.amount, - sigversion=script.SIGVERSION_WITNESS_V0) - - sigs = [key.sign(sighash, hasher=None) for key in self.funding_privkeys_for_tx()] + sighash = script.SignatureHash( + self.redeemscript(), + tx, + inIdx=0, + hashtype=script.SIGHASH_ALL, + amount=self.amount, + sigversion=script.SIGVERSION_WITNESS_V0, + ) + + sigs = [ + key.sign(sighash, hasher=None) for key in self.funding_privkeys_for_tx() + ] # BOLT #3: # ## Closing Transaction # ... # * `txin[0]` witness: `0 ` - witness = CScriptWitness([bytes(), - sigs[0] + bytes([script.SIGHASH_ALL]), - sigs[1] + bytes([script.SIGHASH_ALL]), - self.redeemscript()]) + witness = CScriptWitness( + [ + bytes(), + sigs[0] + bytes([script.SIGHASH_ALL]), + sigs[1] + bytes([script.SIGHASH_ALL]), + self.redeemscript(), + ] + ) tx.wit = CTxWitness([CTxInWitness(witness)]) return tx.serialize().hex() class AcceptFunding(Event): """Event to accept funding information from a peer. Stashes 'Funding'.""" - def __init__(self, - funding_txid: ResolvableStr, - funding_output_index: ResolvableInt, - funding_amount: ResolvableInt, - local_node_privkey: ResolvableStr, - local_funding_privkey: ResolvableStr, - remote_node_privkey: ResolvableStr, - remote_funding_privkey: ResolvableStr, - chain_hash: str = regtest_hash): + + def __init__( + self, + funding_txid: ResolvableStr, + funding_output_index: ResolvableInt, + funding_amount: ResolvableInt, + local_node_privkey: ResolvableStr, + local_funding_privkey: ResolvableStr, + remote_node_privkey: ResolvableStr, + remote_funding_privkey: ResolvableStr, + chain_hash: str = regtest_hash, + ): super().__init__() self.funding_txid = funding_txid self.funding_output_index = funding_output_index @@ -547,32 +670,41 @@ def __init__(self, def action(self, runner: Runner) -> bool: super().action(runner) - funding = Funding(chain_hash=self.chain_hash, - **self.resolve_args(runner, - {'funding_txid': self.funding_txid, - 'funding_output_index': self.funding_output_index, - 'funding_amount': self.funding_amount, - 'local_node_privkey': self.local_node_privkey, - 'local_funding_privkey': self.local_funding_privkey, - 'remote_node_privkey': self.remote_node_privkey, - 'remote_funding_privkey': self.remote_funding_privkey})) - runner.add_stash('Funding', funding) + funding = Funding( + chain_hash=self.chain_hash, + **self.resolve_args( + runner, + { + "funding_txid": self.funding_txid, + "funding_output_index": self.funding_output_index, + "funding_amount": self.funding_amount, + "local_node_privkey": self.local_node_privkey, + "local_funding_privkey": self.local_funding_privkey, + "remote_node_privkey": self.remote_node_privkey, + "remote_funding_privkey": self.remote_funding_privkey, + }, + ) + ) + runner.add_stash("Funding", funding) return True class CreateFunding(Event): """Event to create a funding tx from a P2WPKH UTXO. Stashes 'Funding' and 'FundingTx'.""" - def __init__(self, - txid_in: str, - tx_index_in: int, - sats_in: int, - spending_privkey: str, - fee: int, - local_node_privkey: ResolvableStr, - local_funding_privkey: ResolvableStr, - remote_node_privkey: ResolvableStr, - remote_funding_privkey: ResolvableStr, - chain_hash: str = regtest_hash): + + def __init__( + self, + txid_in: str, + tx_index_in: int, + sats_in: int, + spending_privkey: str, + fee: int, + local_node_privkey: ResolvableStr, + local_funding_privkey: ResolvableStr, + remote_node_privkey: ResolvableStr, + remote_funding_privkey: ResolvableStr, + chain_hash: str = regtest_hash, + ): super().__init__() self.txid_in = txid_in self.tx_index_in = tx_index_in @@ -588,34 +720,43 @@ def __init__(self, def action(self, runner: Runner) -> bool: super().action(runner) - funding, tx = Funding.from_utxo(self.txid_in, - self.tx_index_in, - self.sats_in, - self.spending_privkey, - self.fee, - chain_hash=self.chain_hash, - **self.resolve_args(runner, - {'local_node_privkey': self.local_node_privkey, - 'local_funding_privkey': self.local_funding_privkey, - 'remote_node_privkey': self.remote_node_privkey, - 'remote_funding_privkey': self.remote_funding_privkey})) - - runner.add_stash('Funding', funding) - runner.add_stash('FundingTx', tx) + funding, tx = Funding.from_utxo( + self.txid_in, + self.tx_index_in, + self.sats_in, + self.spending_privkey, + self.fee, + chain_hash=self.chain_hash, + **self.resolve_args( + runner, + { + "local_node_privkey": self.local_node_privkey, + "local_funding_privkey": self.local_funding_privkey, + "remote_node_privkey": self.remote_node_privkey, + "remote_funding_privkey": self.remote_funding_privkey, + }, + ) + ) + + runner.add_stash("Funding", funding) + runner.add_stash("FundingTx", tx) return True class CreateDualFunding(Event): """Event to create a 'dual-funded' funding tx. Stashes 'Funding'""" - def __init__(self, - fee: int, - funding_sats: ResolvableInt, - locktime: ResolvableInt, - local_node_privkey: str, - local_funding_privkey: str, - remote_node_privkey: str, - remote_funding_privkey: ResolvableStr, - chain_hash: str = regtest_hash): + + def __init__( + self, + fee: int, + funding_sats: ResolvableInt, + locktime: ResolvableInt, + local_node_privkey: str, + local_funding_privkey: str, + remote_node_privkey: str, + remote_funding_privkey: ResolvableStr, + chain_hash: str = regtest_hash, + ): super().__init__() self.funding_sats = funding_sats @@ -629,31 +770,37 @@ def __init__(self, def action(self, runner: Runner) -> bool: super().action(runner) - funding = Funding.start(local_node_privkey=self.local_node_privkey, - local_funding_privkey=self.local_funding_privkey, - chain_hash=self.chain_hash, - **self.resolve_args(runner, - { - 'funding_sats': self.funding_sats, - 'remote_funding_privkey': self.remote_funding_privkey, - 'remote_node_privkey': self.remote_node_privkey, - 'locktime': self.locktime - })) - - runner.add_stash('Funding', funding) + funding = Funding.start( + local_node_privkey=self.local_node_privkey, + local_funding_privkey=self.local_funding_privkey, + chain_hash=self.chain_hash, + **self.resolve_args( + runner, + { + "funding_sats": self.funding_sats, + "remote_funding_privkey": self.remote_funding_privkey, + "remote_node_privkey": self.remote_node_privkey, + "locktime": self.locktime, + }, + ) + ) + + runner.add_stash("Funding", funding) return True class AddInput(Event): - def __init__(self, - funding: ResolvableFunding, - serial_id: ResolvableInt, - prevtx: ResolvableStr, - prevtx_vout: ResolvableInt, - script_sig: ResolvableStr, - sequence: ResolvableInt = 0xFFFFFFFD, - privkey: str = None): + def __init__( + self, + funding: ResolvableFunding, + serial_id: ResolvableInt, + prevtx: ResolvableStr, + prevtx_vout: ResolvableInt, + script_sig: ResolvableStr, + sequence: ResolvableInt = 0xFFFFFFFD, + privkey: str = None, + ): super().__init__() self.funding = funding self.privkey = privkey @@ -665,23 +812,31 @@ def __init__(self, def action(self, runner: Runner) -> bool: super().action(runner) - funding = self.resolve_arg('funding', runner, self.funding) - funding.add_input(**self.resolve_args(runner, - {'serial_id': self.serial_id, - 'prevtx': self.prevtx, - 'prevtx_vout': self.prevtx_vout, - 'script_sig': self.script_sig, - 'sequence': self.sequence, - 'privkey': self.privkey})) + funding = self.resolve_arg("funding", runner, self.funding) + funding.add_input( + **self.resolve_args( + runner, + { + "serial_id": self.serial_id, + "prevtx": self.prevtx, + "prevtx_vout": self.prevtx_vout, + "script_sig": self.script_sig, + "sequence": self.sequence, + "privkey": self.privkey, + }, + ) + ) return True class AddOutput(Event): - def __init__(self, - funding: ResolvableFunding, - serial_id: ResolvableInt, - sats: ResolvableInt, - script: ResolvableStr): + def __init__( + self, + funding: ResolvableFunding, + serial_id: ResolvableInt, + sats: ResolvableInt, + script: ResolvableStr, + ): super().__init__() self.funding = funding self.serial_id = serial_id @@ -690,11 +845,13 @@ def __init__(self, def action(self, runner: Runner) -> bool: super().action(runner) - funding = self.resolve_arg('funding', runner, self.funding) - funding.add_output(**self.resolve_args(runner, - {'serial_id': self.serial_id, - 'sats': self.sats, - 'script': self.script})) + funding = self.resolve_arg("funding", runner, self.funding) + funding.add_output( + **self.resolve_args( + runner, + {"serial_id": self.serial_id, "sats": self.sats, "script": self.script}, + ) + ) return True @@ -703,30 +860,33 @@ def __init__(self, funding: ResolvableFunding): self.funding = funding def action(self, runner: Runner) -> bool: - funding = self.resolve_arg('funding', runner, self.funding) + funding = self.resolve_arg("funding", runner, self.funding) tx = funding.build_tx() funding.sign_our_inputs() # FIXME: sanity checks? - print('finalized funding', tx) + print("finalized funding", tx) return True class AddWitnesses(Event): - def __init__(self, - funding: ResolvableFunding, - witness_stack: Union[List[Dict[str, Any]], - Callable[['Runner', 'Event', str], - List[Dict[str, Any]]]]): + def __init__( + self, + funding: ResolvableFunding, + witness_stack: Union[ + List[Dict[str, Any]], + Callable[["Runner", "Event", str], List[Dict[str, Any]]], + ], + ): self.funding = funding self.witness_stack = witness_stack def action(self, runner: Runner) -> bool: - funding = self.resolve_arg('funding', runner, self.funding) - stack = self.resolve_arg('witness_stack', runner, self.witness_stack) + funding = self.resolve_arg("funding", runner, self.funding) + stack = self.resolve_arg("witness_stack", runner, self.witness_stack) # FIXME: is there a way to resolve this more .. nicely? # Convert from string to python obj wit_stack = eval(stack) tx_hex = funding.add_witnesses(wit_stack) - runner.add_stash('FundingTx', tx_hex) + runner.add_stash("FundingTx", tx_hex) return True diff --git a/lnprototest/keyset.py b/lnprototest/keyset.py index 41d8106..d92a4ef 100644 --- a/lnprototest/keyset.py +++ b/lnprototest/keyset.py @@ -6,12 +6,14 @@ class KeySet(object): - def __init__(self, - revocation_base_secret: str, - payment_base_secret: str, - htlc_base_secret: str, - delayed_payment_base_secret: str, - shachain_seed: str): + def __init__( + self, + revocation_base_secret: str, + payment_base_secret: str, + htlc_base_secret: str, + delayed_payment_base_secret: str, + shachain_seed: str, + ): self.revocation_base_secret = privkey_expand(revocation_base_secret) self.payment_base_secret = privkey_expand(payment_base_secret) self.htlc_base_secret = privkey_expand(htlc_base_secret) @@ -68,7 +70,7 @@ def raw_per_commit_secret(self, n: int) -> coincurve.PrivateKey: P = bytearray(self.shachain_seed) for B in range(47, -1, -1): if ((1 << B) & index) != 0: - P[B // 8] ^= (1 << (B % 8)) + P[B // 8] ^= 1 << (B % 8) P = bytearray(hashlib.sha256(P).digest()) return coincurve.PrivateKey(P) @@ -91,37 +93,82 @@ def test_shachain() -> None: # seed: 0x0000000000000000000000000000000000000000000000000000000000000000 # I: 281474976710655 # output: 0x02a40c85b6f28da08dfdbe0926c53fab2de6d28c10301f8f7c4073d5e42e3148 - keyset = KeySet('01', '01', '01', '01', '0000000000000000000000000000000000000000000000000000000000000000') - assert keyset.per_commit_secret(0xFFFFFFFFFFFF - 281474976710655) == '02a40c85b6f28da08dfdbe0926c53fab2de6d28c10301f8f7c4073d5e42e3148' + keyset = KeySet( + "01", + "01", + "01", + "01", + "0000000000000000000000000000000000000000000000000000000000000000", + ) + assert ( + keyset.per_commit_secret(0xFFFFFFFFFFFF - 281474976710655) + == "02a40c85b6f28da08dfdbe0926c53fab2de6d28c10301f8f7c4073d5e42e3148" + ) # BOLT #3: # name: generate_from_seed FF final node # seed: 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF # I: 281474976710655 # output: 0x7cc854b54e3e0dcdb010d7a3fee464a9687be6e8db3be6854c475621e007a5dc - keyset = KeySet('01', '01', '01', '01', 'FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF') - assert keyset.per_commit_secret(0xFFFFFFFFFFFF - 281474976710655) == '7cc854b54e3e0dcdb010d7a3fee464a9687be6e8db3be6854c475621e007a5dc' + keyset = KeySet( + "01", + "01", + "01", + "01", + "FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF", + ) + assert ( + keyset.per_commit_secret(0xFFFFFFFFFFFF - 281474976710655) + == "7cc854b54e3e0dcdb010d7a3fee464a9687be6e8db3be6854c475621e007a5dc" + ) # BOLT #3: # name: generate_from_seed FF alternate bits 1 # seed: 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF # I: 0xaaaaaaaaaaa # output: 0x56f4008fb007ca9acf0e15b054d5c9fd12ee06cea347914ddbaed70d1c13a528 - keyset = KeySet('01', '01', '01', '01', 'FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF') - assert keyset.per_commit_secret(0xFFFFFFFFFFFF - 0xaaaaaaaaaaa) == '56f4008fb007ca9acf0e15b054d5c9fd12ee06cea347914ddbaed70d1c13a528' + keyset = KeySet( + "01", + "01", + "01", + "01", + "FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF", + ) + assert ( + keyset.per_commit_secret(0xFFFFFFFFFFFF - 0xAAAAAAAAAAA) + == "56f4008fb007ca9acf0e15b054d5c9fd12ee06cea347914ddbaed70d1c13a528" + ) # BOLT #3: # name: generate_from_seed FF alternate bits 2 # seed: 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF # I: 0x555555555555 # output: 0x9015daaeb06dba4ccc05b91b2f73bd54405f2be9f217fbacd3c5ac2e62327d31 - keyset = KeySet('01', '01', '01', '01', 'FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF') - assert keyset.per_commit_secret(0xFFFFFFFFFFFF - 0x555555555555) == '9015daaeb06dba4ccc05b91b2f73bd54405f2be9f217fbacd3c5ac2e62327d31' + keyset = KeySet( + "01", + "01", + "01", + "01", + "FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF", + ) + assert ( + keyset.per_commit_secret(0xFFFFFFFFFFFF - 0x555555555555) + == "9015daaeb06dba4ccc05b91b2f73bd54405f2be9f217fbacd3c5ac2e62327d31" + ) # BOLT #3: # name: generate_from_seed 01 last nontrivial node # seed: 0x0101010101010101010101010101010101010101010101010101010101010101 # I: 1 # output: 0x915c75942a26bb3a433a8ce2cb0427c29ec6c1775cfc78328b57f6ba7bfeaa9c - keyset = KeySet('01', '01', '01', '01', '0101010101010101010101010101010101010101010101010101010101010101') - assert keyset.per_commit_secret(0xFFFFFFFFFFFF - 1) == '915c75942a26bb3a433a8ce2cb0427c29ec6c1775cfc78328b57f6ba7bfeaa9c' + keyset = KeySet( + "01", + "01", + "01", + "01", + "0101010101010101010101010101010101010101010101010101010101010101", + ) + assert ( + keyset.per_commit_secret(0xFFFFFFFFFFFF - 1) + == "915c75942a26bb3a433a8ce2cb0427c29ec6c1775cfc78328b57f6ba7bfeaa9c" + ) diff --git a/lnprototest/namespace.py b/lnprototest/namespace.py index 42d250d..239119c 100755 --- a/lnprototest/namespace.py +++ b/lnprototest/namespace.py @@ -12,16 +12,16 @@ def make_namespace(csv: List[str]) -> MessageNamespace: ns = MessageNamespace() # We replace the fundamental signature type with our custom type, # then we load in all the csv files so they use it. - ns.fundamentaltypes['signature'] = SigType() + ns.fundamentaltypes["signature"] = SigType() ns.load_csv(csv) return ns def peer_message_namespace() -> MessageNamespace: """Namespace containing all the peer messages""" - return make_namespace(pyln.spec.bolt1.csv - + pyln.spec.bolt2.csv - + pyln.spec.bolt7.csv) + return make_namespace( + pyln.spec.bolt1.csv + pyln.spec.bolt2.csv + pyln.spec.bolt7.csv + ) def namespace() -> MessageNamespace: diff --git a/lnprototest/runner.py b/lnprototest/runner.py index e110420..b47738c 100644 --- a/lnprototest/runner.py +++ b/lnprototest/runner.py @@ -19,9 +19,10 @@ class Conn(object): """Class for connections. Details filled in by the particular runner.""" + def __init__(self, connprivkey: str): """Create a connection from a node with the given hex privkey: we use -trivial values for private keys, so we simply left-pad with zeroes""" + trivial values for private keys, so we simply left-pad with zeroes""" self.name = connprivkey self.connprivkey = privkey_expand(connprivkey) self.pubkey = coincurve.PublicKey.from_secret(self.connprivkey.secret) @@ -35,10 +36,11 @@ def __str__(self) -> str: class Runner(object): """Abstract base class for runners. -Most of the runner parameters can be extracted at runtime, but we do -require that minimum_depth be 3, just for test simplicity. + Most of the runner parameters can be extracted at runtime, but we do + require that minimum_depth be 3, just for test simplicity. """ + def __init__(self, config: Any): self.config = config # key == connprivkey, value == Conn @@ -111,7 +113,13 @@ def get_stash(self, event: Event, stashname: str, default: Any = None) -> Any: def connect(self, event: Event, connprivkey: str) -> None: raise NotImplementedError() - def check_final_error(self, event: Event, conn: Conn, expected: bool, must_not_events: List[MustNotMsg]) -> None: + def check_final_error( + self, + event: Event, + conn: Conn, + expected: bool, + must_not_events: List[MustNotMsg], + ) -> None: raise NotImplementedError() def start(self) -> None: @@ -144,26 +152,29 @@ def invoice(self, event: Event, amount: int, preimage: str) -> None: def accept_add_fund(self, event: Event) -> None: raise NotImplementedError() - def fundchannel(self, - event: Event, - conn: Conn, - amount: int, - feerate: int = 0, - expect_fail: bool = False) -> None: + def fundchannel( + self, + event: Event, + conn: Conn, + amount: int, + feerate: int = 0, + expect_fail: bool = False, + ) -> None: raise NotImplementedError() - def init_rbf(self, - event: Event, - conn: Conn, - channel_id: str, - amount: int, - utxo_txid: str, - utxo_outnum: int, - feerate: int) -> None: + def init_rbf( + self, + event: Event, + conn: Conn, + channel_id: str, + amount: int, + utxo_txid: str, + utxo_outnum: int, + feerate: int, + ) -> None: raise NotImplementedError() - def addhtlc(self, event: Event, conn: Conn, - amount: int, preimage: str) -> None: + def addhtlc(self, event: Event, conn: Conn, amount: int, preimage: str) -> None: raise NotImplementedError() def get_keyset(self) -> KeySet: @@ -184,6 +195,7 @@ def add_startup_flag(self, flag: str) -> None: def remote_revocation_basepoint() -> Callable[[Runner, Event, str], str]: """Get the remote revocation basepoint""" + def _remote_revocation_basepoint(runner: Runner, event: Event, field: str) -> str: return runner.get_keyset().revocation_basepoint() @@ -192,48 +204,72 @@ def _remote_revocation_basepoint(runner: Runner, event: Event, field: str) -> st def remote_payment_basepoint() -> Callable[[Runner, Event, str], str]: """Get the remote payment basepoint""" + def _remote_payment_basepoint(runner: Runner, event: Event, field: str) -> str: return runner.get_keyset().payment_basepoint() + return _remote_payment_basepoint def remote_delayed_payment_basepoint() -> Callable[[Runner, Event, str], str]: """Get the remote delayed_payment basepoint""" - def _remote_delayed_payment_basepoint(runner: Runner, event: Event, field: str) -> str: + + def _remote_delayed_payment_basepoint( + runner: Runner, event: Event, field: str + ) -> str: return runner.get_keyset().delayed_payment_basepoint() + return _remote_delayed_payment_basepoint def remote_htlc_basepoint() -> Callable[[Runner, Event, str], str]: """Get the remote htlc basepoint""" + def _remote_htlc_basepoint(runner: Runner, event: Event, field: str) -> str: return runner.get_keyset().htlc_basepoint() + return _remote_htlc_basepoint def remote_funding_pubkey() -> Callable[[Runner, Event, str], str]: """Get the remote funding pubkey (FIXME: we assume there's only one!)""" + def _remote_funding_pubkey(runner: Runner, event: Event, field: str) -> str: - return coincurve.PublicKey.from_secret(privkey_expand(runner.get_node_bitcoinkey()).secret).format().hex() + return ( + coincurve.PublicKey.from_secret( + privkey_expand(runner.get_node_bitcoinkey()).secret + ) + .format() + .hex() + ) + return _remote_funding_pubkey def remote_funding_privkey() -> Callable[[Runner, Event, str], str]: """Get the remote funding privkey (FIXME: we assume there's only one!)""" + def _remote_funding_privkey(runner: Runner, event: Event, field: str) -> str: return runner.get_node_bitcoinkey() + return _remote_funding_privkey def remote_per_commitment_point(n: int) -> Callable[[Runner, Event, str], str]: """Get the n'th remote per-commitment point""" - def _remote_per_commitment_point(n: int, runner: Runner, event: Event, field: str) -> str: + + def _remote_per_commitment_point( + n: int, runner: Runner, event: Event, field: str + ) -> str: return runner.get_keyset().per_commit_point(n) + return functools.partial(_remote_per_commitment_point, n) def remote_per_commitment_secret(n: int) -> Callable[[Runner, Event, str], str]: """Get the n'th remote per-commitment secret""" + def _remote_per_commitment_secret(runner: Runner, event: Event, field: str) -> str: return runner.get_keyset().per_commit_secret(n) + return _remote_per_commitment_secret diff --git a/lnprototest/signature.py b/lnprototest/signature.py index 92edbca..6af1d4d 100755 --- a/lnprototest/signature.py +++ b/lnprototest/signature.py @@ -8,20 +8,20 @@ class Sig(object): """The value of a signature, either as a privkey/hash pair or a raw -signature. This has the property that if the raw signature is a valid -signature of privkey over hash, they are considered "equal" + signature. This has the property that if the raw signature is a valid + signature of privkey over hash, they are considered "equal" + """ -""" def __init__(self, *args: Any): """Either a 64-byte hex/bytes value, or a PrivateKey and a hash""" if len(args) == 1: if type(args[0]) is bytes: if len(args[0]) != 64: - raise ValueError('Sig() with 1 arg expects 64 bytes or 128 hexstr') + raise ValueError("Sig() with 1 arg expects 64 bytes or 128 hexstr") self.sigval: Union[bytes, None] = cast(bytes, args[0]) else: if type(args[0]) is not str: - raise TypeError('Expected hexsig or Privkey, hash') + raise TypeError("Expected hexsig or Privkey, hash") if len(args[0]) != 128: self.sigval = Sig.from_der(bytes.fromhex(args[0])) else: @@ -31,7 +31,7 @@ def __init__(self, *args: Any): self.privkey = privkey_expand(args[0]) self.hashval = bytes.fromhex(check_hex(args[1], 64)) else: - raise TypeError('Expected hexsig or Privkey, hash') + raise TypeError("Expected hexsig or Privkey, hash") @staticmethod def to_der(b: bytes) -> bytes: @@ -62,12 +62,12 @@ def from_der(b: bytes) -> bytes: if b[0] != 0x30 or b[1] != len(b) - 2 or b[2] != 0x02: raise ValueError("{} is not a DER signature?".format(b.hex())) rlen = b[3] - r = b[4:4 + rlen].rjust(32, bytes(1))[-32:] - assert(len(r) == 32) + r = b[4 : 4 + rlen].rjust(32, bytes(1))[-32:] + assert len(r) == 32 if b[4 + rlen] != 0x02: raise ValueError("{} is not a DER signature?".format(b.hex())) - s = b[4 + rlen + 1 + 1:].rjust(32, bytes(1))[-32:] - assert(len(s) == 32) + s = b[4 + rlen + 1 + 1 :].rjust(32, bytes(1))[-32:] + assert len(s) == 32 return r + s def __eq__(self, other: Any) -> bool: @@ -89,7 +89,9 @@ def __eq__(self, other: Any) -> bool: # A has a privkey/hash, B has a sigval. pubkey = coincurve.PublicKey.from_secret(a.privkey.secret) assert b.sigval is not None - if coincurve.verify_signature(self.to_der(b.sigval), a.hashval, pubkey.format(), hasher=None): + if coincurve.verify_signature( + self.to_der(b.sigval), a.hashval, pubkey.format(), hasher=None + ): return True return False @@ -97,12 +99,12 @@ def to_str(self) -> str: if self.sigval: return self.sigval.hex() else: - return 'Sig({},{})'.format(self.privkey.secret.hex(), self.hashval.hex()) + return "Sig({},{})".format(self.privkey.secret.hex(), self.hashval.hex()) @staticmethod - def from_str(s: str) -> Tuple['Sig', str]: + def from_str(s: str) -> Tuple["Sig", str]: a, b = split_field(s) - if a.startswith('Sig('): + if a.startswith("Sig("): privkey = a[4:] a, b = split_field(b[1:]) # Trim ) off Sig() @@ -118,16 +120,19 @@ def to_bin(self) -> bytes: class SigType(FieldType): """A signature type which has special comparison properties""" + def __init__(self) -> None: - super().__init__('signature') + super().__init__("signature") def val_to_str(self, v: Sig, otherfields: Dict[str, Any]) -> str: return v.to_str() - def val_from_str(self, s: str) -> Tuple['Sig', str]: + def val_from_str(self, s: str) -> Tuple["Sig", str]: return Sig.from_str(s) - def write(self, io_out: BufferedIOBase, v: Sig, otherfields: Dict[str, Any]) -> None: + def write( + self, io_out: BufferedIOBase, v: Sig, otherfields: Dict[str, Any] + ) -> None: io_out.write(v.to_bin()) def read(self, io_in: BufferedIOBase, otherfields: Dict[str, Any]) -> Optional[Sig]: @@ -135,19 +140,19 @@ def read(self, io_in: BufferedIOBase, otherfields: Dict[str, Any]) -> Optional[S if len(val) == 0: return None elif len(val) != 64: - raise ValueError('{}: not enough remaining'.format(self)) + raise ValueError("{}: not enough remaining".format(self)) return Sig(val) def test_der() -> None: - der = b'0E\x02!\x00\xa0\xb3\x7f\x8f\xbah<\xc6\x8fet\xcdC\xb3\x9f\x03C\xa5\x00\x08\xbfl\xce\xa9\xd121\xd9\xe7\xe2\xe1\xe4\x02 \x11\xed\xc8\xd3\x07%B\x96&J\xeb\xfc=\xc7l\xd8\xb6h7:\x07/\xd6Fe\xb5\x00\x00\xe9\xfc\xceR' + der = b"0E\x02!\x00\xa0\xb3\x7f\x8f\xbah<\xc6\x8fet\xcdC\xb3\x9f\x03C\xa5\x00\x08\xbfl\xce\xa9\xd121\xd9\xe7\xe2\xe1\xe4\x02 \x11\xed\xc8\xd3\x07%B\x96&J\xeb\xfc=\xc7l\xd8\xb6h7:\x07/\xd6Fe\xb5\x00\x00\xe9\xfc\xceR" sig = Sig.from_der(der) der2 = Sig.to_der(sig) assert der == der2 def test_signature() -> None: - s = Sig('01', '00' * 32) + s = Sig("01", "00" * 32) assert s == s b = s.to_bin() diff --git a/lnprototest/stash/__init__.py b/lnprototest/stash/__init__.py index 6d6c533..eff3e42 100644 --- a/lnprototest/stash/__init__.py +++ b/lnprototest/stash/__init__.py @@ -9,7 +9,26 @@ simply return another function. """ -from .stash import commitsig_to_send, commitsig_to_recv, channel_id, channel_announcement, channel_update, get_member, rcvd, sent, funding_amount, funding_pubkey, funding_tx, funding_txid, funding, funding_close_tx, htlc_sigs_to_send, htlc_sigs_to_recv, locking_script, witnesses +from .stash import ( + commitsig_to_send, + commitsig_to_recv, + channel_id, + channel_announcement, + channel_update, + get_member, + rcvd, + sent, + funding_amount, + funding_pubkey, + funding_tx, + funding_txid, + funding, + funding_close_tx, + htlc_sigs_to_send, + htlc_sigs_to_recv, + locking_script, + witnesses, +) __all__ = [ diff --git a/lnprototest/stash/stash.py b/lnprototest/stash/stash.py index 279b6ff..ab87d8f 100644 --- a/lnprototest/stash/stash.py +++ b/lnprototest/stash/stash.py @@ -8,25 +8,27 @@ def commitsig_to_send() -> Callable[[Runner, Event, str], str]: """Get the appropriate signature for the local side to send to the remote""" + def _commitsig_to_send(runner: Runner, event: Event, field: str) -> str: - tx = runner.get_stash(event, 'Commit').remote_unsigned_tx() - return runner.get_stash(event, 'Commit').local_sig(tx) + tx = runner.get_stash(event, "Commit").remote_unsigned_tx() + return runner.get_stash(event, "Commit").local_sig(tx) return _commitsig_to_send def commitsig_to_recv() -> Callable[[Runner, Event, str], str]: """Get the appropriate signature for the remote side to send to the local""" + def _commitsig_to_recv(runner: Runner, event: Event, field: str) -> str: - tx = runner.get_stash(event, 'Commit').local_unsigned_tx() - return runner.get_stash(event, 'Commit').remote_sig(tx) + tx = runner.get_stash(event, "Commit").local_unsigned_tx() + return runner.get_stash(event, "Commit").remote_sig(tx) return _commitsig_to_recv def _htlc_sigs(signer: Side, runner: Runner, event: Event, field: str) -> str: - sigs = runner.get_stash(event, 'Commit').htlc_sigs(signer, not signer) - return '[' + ','.join([sig.to_str() for sig in sigs]) + ']' + sigs = runner.get_stash(event, "Commit").htlc_sigs(signer, not signer) + return "[" + ",".join([sig.to_str() for sig in sigs]) + "]" def htlc_sigs_to_send() -> Callable[[Runner, Event, str], str]: @@ -41,85 +43,129 @@ def htlc_sigs_to_recv() -> Callable[[Runner, Event, str], str]: def channel_id() -> Callable[[Runner, Event, str], str]: """Get the channel_id for the current Commit""" + def _channel_id(runner: Runner, event: Event, field: str) -> str: - return runner.get_stash(event, 'Commit').funding.channel_id() + return runner.get_stash(event, "Commit").funding.channel_id() return _channel_id def channel_id_v2() -> Callable[[Runner, Event, str], str]: - """ Get the channel_id for the current Commit for a v2 channel open""" + """Get the channel_id for the current Commit for a v2 channel open""" + def _channel_id(runner: Runner, event: Event, field: str) -> str: - return runner.get_stash(event, 'Commit').channel_id_v2() + return runner.get_stash(event, "Commit").channel_id_v2() return _channel_id -def channel_announcement(short_channel_id: str, features: bytes) -> Callable[[Runner, Event, str], str]: +def channel_announcement( + short_channel_id: str, features: bytes +) -> Callable[[Runner, Event, str], str]: """Get the channel_announcement for the current Commit""" - def _channel_announcement(short_channel_id: str, features: bytes, runner: Runner, event: Event, field: str) -> Message: - return runner.get_stash(event, 'Commit').channel_announcement(short_channel_id, features) + + def _channel_announcement( + short_channel_id: str, features: bytes, runner: Runner, event: Event, field: str + ) -> Message: + return runner.get_stash(event, "Commit").channel_announcement( + short_channel_id, features + ) + return functools.partial(_channel_announcement, short_channel_id, features) -def channel_update(short_channel_id: str, - side: Side, - disable: bool, - cltv_expiry_delta: int, - htlc_minimum_msat: int, - fee_base_msat: int, - fee_proportional_millionths: int, - htlc_maximum_msat: Optional[int], - timestamp: Optional[int] = None) -> Callable[[Runner, Event, str], str]: +def channel_update( + short_channel_id: str, + side: Side, + disable: bool, + cltv_expiry_delta: int, + htlc_minimum_msat: int, + fee_base_msat: int, + fee_proportional_millionths: int, + htlc_maximum_msat: Optional[int], + timestamp: Optional[int] = None, +) -> Callable[[Runner, Event, str], str]: """Get a channel_update for the current Commit""" - def _channel_update(short_channel_id: str, - side: Side, - disable: bool, - cltv_expiry_delta: int, - htlc_minimum_msat: int, - fee_base_msat: int, - fee_proportional_millionths: int, - timestamp: Optional[int], - htlc_maximum_msat: Optional[int], - runner: Runner, event: Event, field: str) -> Message: + + def _channel_update( + short_channel_id: str, + side: Side, + disable: bool, + cltv_expiry_delta: int, + htlc_minimum_msat: int, + fee_base_msat: int, + fee_proportional_millionths: int, + timestamp: Optional[int], + htlc_maximum_msat: Optional[int], + runner: Runner, + event: Event, + field: str, + ) -> Message: """Get the channel_update""" if timestamp is None: timestamp = int(time.time()) - return runner.get_stash(event, 'Commit').channel_update(short_channel_id, side, disable, cltv_expiry_delta, htlc_maximum_msat, fee_base_msat, fee_proportional_millionths, timestamp, htlc_maximum_msat) - return functools.partial(_channel_update, short_channel_id, side, disable, cltv_expiry_delta, htlc_minimum_msat, fee_base_msat, fee_proportional_millionths, htlc_maximum_msat, timestamp) - - -def get_member(event: Event, runner: 'Runner', stashname: str, var: str, last: bool = True) -> str: + return runner.get_stash(event, "Commit").channel_update( + short_channel_id, + side, + disable, + cltv_expiry_delta, + htlc_maximum_msat, + fee_base_msat, + fee_proportional_millionths, + timestamp, + htlc_maximum_msat, + ) + + return functools.partial( + _channel_update, + short_channel_id, + side, + disable, + cltv_expiry_delta, + htlc_minimum_msat, + fee_base_msat, + fee_proportional_millionths, + htlc_maximum_msat, + timestamp, + ) + + +def get_member( + event: Event, runner: "Runner", stashname: str, var: str, last: bool = True +) -> str: """Get member field from stash for ExpectMsg or Msg. -If var contains a '.' then we look for that message to extract the field. If last is True, we get the last message, otherwise the first. -""" + If var contains a '.' then we look for that message to extract the field. If last is True, we get the last message, otherwise the first.""" stash = runner.get_stash(event, stashname) - if '.' in var: - prevname, _, var = var.partition('.') + if "." in var: + prevname, _, var = var.partition(".") else: - prevname = '' + prevname = "" if last: seq = reversed(stash) else: seq = stash for name, d in seq: - if prevname == '' or name == prevname: + if prevname == "" or name == prevname: if var not in d: - raise SpecFileError(event, '{}: {} did not receive a {}' - .format(stashname, prevname, var)) + raise SpecFileError( + event, + "{}: {} did not receive a {}".format(stashname, prevname, var), + ) return d[var] - raise SpecFileError(event, '{}: have no prior {}'.format(stashname, prevname)) - - -def _get_member(stashname: str, - fieldname: Optional[str], - casttype: Any, - # This is the signature which Msg() expects for callable values: - runner: 'Runner', - event: Event, - field: str) -> Any: + raise SpecFileError(event, "{}: have no prior {}".format(stashname, prevname)) + + +def _get_member( + stashname: str, + fieldname: Optional[str], + casttype: Any, + # This is the signature which Msg() expects for callable values: + runner: "Runner", + event: Event, + field: str, +) -> Any: # If they don't specify fieldname, it's same as this field. if fieldname is None: fieldname = field @@ -127,79 +173,102 @@ def _get_member(stashname: str, try: return casttype(strval) except ValueError: - raise SpecFileError(event, "{}.{} is {}, not a valid {}".format(stashname, fieldname, strval, casttype)) + raise SpecFileError( + event, + "{}.{} is {}, not a valid {}".format( + stashname, fieldname, strval, casttype + ), + ) -def rcvd(fieldname: Optional[str] = None, casttype: Any = str) -> Callable[[Runner, Event, Any], Any]: +def rcvd( + fieldname: Optional[str] = None, casttype: Any = str +) -> Callable[[Runner, Event, Any], Any]: """Use previous ExpectMsg field (as string) -fieldname can be [msg].[field] or just [field] for last ExpectMsg + fieldname can be [msg].[field] or just [field] for last ExpectMsg """ - return functools.partial(_get_member, 'ExpectMsg', fieldname, casttype) + return functools.partial(_get_member, "ExpectMsg", fieldname, casttype) -def sent(fieldname: Optional[str] = None, casttype: Any = str) -> Callable[[Runner, Event, Any], Any]: +def sent( + fieldname: Optional[str] = None, casttype: Any = str +) -> Callable[[Runner, Event, Any], Any]: """Use previous Msg field (as string) -fieldname can be [msg].[field] or just [field] for last Msg + fieldname can be [msg].[field] or just [field] for last Msg """ - return functools.partial(_get_member, 'Msg', fieldname, casttype) + return functools.partial(_get_member, "Msg", fieldname, casttype) def funding_amount() -> Callable[[Runner, Event, str], int]: """Get the stashed funding amount""" + def _funding_amount(runner: Runner, event: Event, field: str) -> int: - return runner.get_stash(event, 'Funding').amount + return runner.get_stash(event, "Funding").amount return _funding_amount def funding_pubkey(side: Side) -> Callable[[Runner, Event, str], str]: """Get the stashed funding pubkey for side""" + def _funding_pubkey(side: Side, runner: Runner, event: Event, field: str) -> str: - return coincurve.PublicKey.from_secret(runner.get_stash(event, 'Funding').funding_privkeys[side].secret) + return coincurve.PublicKey.from_secret( + runner.get_stash(event, "Funding").funding_privkeys[side].secret + ) return functools.partial(_funding_pubkey, side) def funding_tx() -> Callable[[Runner, Event, str], str]: """Get the funding transaction (as stashed by CreateFunding)""" + def _funding_tx(runner: Runner, event: Event, field: str) -> str: - return runner.get_stash(event, 'FundingTx') + return runner.get_stash(event, "FundingTx") + return _funding_tx def funding_txid() -> Callable[[Runner, Event, str], str]: """Get the stashed funding transaction id""" + def _funding_txid(runner: Runner, event: Event, field: str) -> str: - return runner.get_stash(event, 'Funding').txid + return runner.get_stash(event, "Funding").txid + return _funding_txid def funding() -> Callable[[Runner, Event, str], Funding]: """Get the stashed Funding (as stashed by CreateFunding or AcceptFunding)""" + def _funding(runner: Runner, event: Event, field: str) -> Funding: - return runner.get_stash(event, 'Funding') + return runner.get_stash(event, "Funding") + return _funding def witnesses() -> Callable[[Runner, Event, str], str]: """Get the witnesses for the stashed funding tx""" + def _witnesses(runner: Runner, event: Event, field: str) -> str: - funding = runner.get_stash(event, 'Funding') + funding = runner.get_stash(event, "Funding") return funding.our_witnesses() + return _witnesses def locking_script() -> Callable[[Runner, Event, str], str]: def _locking_script(runner: Runner, event: Event, field: str) -> str: - return runner.get_stash(event, 'Funding').locking_script().hex() + return runner.get_stash(event, "Funding").locking_script().hex() + return _locking_script def funding_close_tx() -> Callable[[Runner, Event, str], str]: def _funding_close_tx(runner: Runner, event: Event, field: str) -> str: - return runner.get_stash(event, 'Funding').close_tx() + return runner.get_stash(event, "Funding").close_tx() + return _funding_close_tx diff --git a/lnprototest/structure.py b/lnprototest/structure.py index 49c696e..a082e4a 100644 --- a/lnprototest/structure.py +++ b/lnprototest/structure.py @@ -5,38 +5,42 @@ from .namespace import namespace from pyln.proto.message import Message from typing import Union, List, Optional, TYPE_CHECKING, cast + if TYPE_CHECKING: # Otherwise a circular dependency from .runner import Runner, Conn # These can all be fed to a Sequence() initializer. -SequenceUnion = Union['Sequence', List[Event], Event] +SequenceUnion = Union["Sequence", List[Event], Event] class Sequence(Event): """A sequence of ordered events""" - def __init__(self, - events: Union['Sequence', List[Event], Event], - enable: ResolvableBool = True): + + def __init__( + self, + events: Union["Sequence", List[Event], Event], + enable: ResolvableBool = True, + ): """Events can be a Sequence, a single Event, or a list of Events. If -enable is False, this turns into a noop (e.g. if runner doesn't support -it).""" + enable is False, this turns into a noop (e.g. if runner doesn't support + it).""" super().__init__() self.enable = enable if type(events) is Sequence: # mypy gets upset because Sequence isn't defined yet. self.events = events.events # type: ignore self.enable = events.enable # type: ignore - self.name = events.name # type: ignore + self.name = events.name # type: ignore elif isinstance(events, Event): self.events = [events] else: self.events = events - def enabled(self, runner: 'Runner') -> bool: - return self.resolve_arg('enable', runner, self.enable) + def enabled(self, runner: "Runner") -> bool: + return self.resolve_arg("enable", runner, self.enable) - def action(self, runner: 'Runner', skip_first: bool = False) -> bool: + def action(self, runner: "Runner", skip_first: bool = False) -> bool: super().action(runner) all_done = True for e in self.events: @@ -49,7 +53,9 @@ def action(self, runner: 'Runner', skip_first: bool = False) -> bool: return all_done @staticmethod - def ignored_by_all(msg: Message, sequences: List['Sequence']) -> Optional[List[Message]]: + def ignored_by_all( + msg: Message, sequences: List["Sequence"] + ) -> Optional[List[Message]]: # If they all say the same thing, that's the answer. rets = [cast(ExpectMsg, s.events[0]).ignore(msg) for s in sequences] if all([ignored == rets[0] for ignored in rets[1:]]): @@ -57,7 +63,9 @@ def ignored_by_all(msg: Message, sequences: List['Sequence']) -> Optional[List[M return None @staticmethod - def match_which_sequence(runner: 'Runner', msg: Message, sequences: List['Sequence']) -> Optional['Sequence']: + def match_which_sequence( + runner: "Runner", msg: Message, sequences: List["Sequence"] + ) -> Optional["Sequence"]: """Return which sequence expects this msg, or None""" for s in sequences: @@ -70,6 +78,7 @@ def match_which_sequence(runner: 'Runner', msg: Message, sequences: List['Sequen class OneOf(Event): """Event representing multiple possible sequences, one of which should happen""" + def __init__(self, *args: SequenceUnion): super().__init__() self.sequences = [] @@ -79,11 +88,11 @@ def __init__(self, *args: SequenceUnion): raise ValueError("{} is an empty sequence".format(s)) self.sequences.append(seq) - def enabled_sequences(self, runner: 'Runner') -> List[Sequence]: + def enabled_sequences(self, runner: "Runner") -> List[Sequence]: """Returns all enabled sequences""" return [s for s in self.sequences if s.enabled(runner)] - def action(self, runner: 'Runner') -> bool: + def action(self, runner: "Runner") -> bool: super().action(runner) # Check they all use the same conn! @@ -106,8 +115,7 @@ def action(self, runner: 'Runner') -> bool: except ValueError as ve: raise EventError(self, "Invalid msg {}: {}".format(binmsg.hex(), ve)) - ignored = Sequence.ignored_by_all(msg, - self.enabled_sequences(runner)) + ignored = Sequence.ignored_by_all(msg, self.enabled_sequences(runner)) # If they gave us responses, send those now. if ignored is not None: for msg in ignored: @@ -116,17 +124,24 @@ def action(self, runner: 'Runner') -> bool: runner.recv(self, conn, binm.getvalue()) continue - seq = Sequence.match_which_sequence(runner, msg, self.enabled_sequences(runner)) + seq = Sequence.match_which_sequence( + runner, msg, self.enabled_sequences(runner) + ) if seq is not None: # We found the sequence, run it return seq.action(runner, skip_first=True) - raise EventError(self, - "None of the sequences {} matched {}".format(self.enabled_sequences(runner), msg.to_str())) + raise EventError( + self, + "None of the sequences {} matched {}".format( + self.enabled_sequences(runner), msg.to_str() + ), + ) class AnyOrder(Event): """Event representing multiple sequences, all of which should happen, but not defined which order they would happen""" + def __init__(self, *args: SequenceUnion): super().__init__() self.sequences = [] @@ -136,11 +151,11 @@ def __init__(self, *args: SequenceUnion): raise ValueError("{} is an empty sequence".format(s)) self.sequences.append(seq) - def enabled_sequences(self, runner: 'Runner') -> List[Sequence]: + def enabled_sequences(self, runner: "Runner") -> List[Sequence]: """Returns all enabled sequences""" return [s for s in self.sequences if s.enabled(runner)] - def action(self, runner: 'Runner') -> bool: + def action(self, runner: "Runner") -> bool: super().action(runner) # Check they all use the same conn! @@ -159,8 +174,12 @@ def action(self, runner: 'Runner') -> bool: # Get message binmsg = runner.get_output_message(conn, sequences[0].events[0]) if binmsg is None: - raise EventError(self, "Did not receive a message from runner, still expecting {}" - .format([s.events[0] for s in sequences])) + raise EventError( + self, + "Did not receive a message from runner, still expecting {}".format( + [s.events[0] for s in sequences] + ), + ) try: msg = Message.read(namespace(), io.BytesIO(binmsg)) @@ -176,20 +195,24 @@ def action(self, runner: 'Runner') -> bool: all_done &= seq.action(runner, skip_first=True) continue - raise EventError(self, - "Message did not match any sequences {}: {}" - .format([s.events[0] for s in sequences], msg.to_str())) + raise EventError( + self, + "Message did not match any sequences {}: {}".format( + [s.events[0] for s in sequences], msg.to_str() + ), + ) return all_done class TryAll(Event): """Event representing multiple sequences, each of which should be tested""" + def __init__(self, *args: SequenceUnion): super().__init__() self.sequences = [Sequence(s) for s in args] self.done = [False] * len(self.sequences) - def action(self, runner: 'Runner') -> bool: + def action(self, runner: "Runner") -> bool: super().action(runner) # Take first undone one, or if that fails, first enabled one. diff --git a/lnprototest/utils.py b/lnprototest/utils.py index 239039e..badd8d3 100644 --- a/lnprototest/utils.py +++ b/lnprototest/utils.py @@ -2,17 +2,18 @@ import string import coincurve import time +import typing from enum import IntEnum # regtest chain hash (hash of regtest genesis block) -regtest_hash = '06226e46111a0b59caaf126043eb5bbf28c34f3a5e332a1fc7b2b73cf188910f' +regtest_hash = "06226e46111a0b59caaf126043eb5bbf28c34f3a5e332a1fc7b2b73cf188910f" class Side(IntEnum): local = 0 remote = 1 - def __not__(self) -> 'Side': + def __not__(self) -> "Side": if self == Side.local: return Side.remote return Side.local @@ -31,7 +32,7 @@ def privkey_expand(secret: str) -> coincurve.PrivateKey: return coincurve.PrivateKey(bytes.fromhex(secret).rjust(32, bytes(1))) -def wait_for(success, timeout=180): +def wait_for(success: typing.Callable, timeout: int = 180) -> None: start_time = time.time() interval = 0.25 while not success(): diff --git a/setup.py b/setup.py index ced85c6..91375cf 100755 --- a/setup.py +++ b/setup.py @@ -2,21 +2,23 @@ import io -with io.open('README.md', encoding='utf-8') as f: +with io.open("README.md", encoding="utf-8") as f: long_description = f.read() -with io.open('requirements.txt', encoding='utf-8') as f: - requirements = [r for r in f.read().split('\n') if len(r)] +with io.open("requirements.txt", encoding="utf-8") as f: + requirements = [r for r in f.read().split("\n") if len(r)] -setup(name='lnprototest', - version='0.0.1', - description='Spec protocol tests for lightning network implementations', - long_description=long_description, - long_description_content_type='text/markdown', - author='Rusty Russell', - author_email='rusty@ln.dev', - license='MIT', - packages=['lnprototest', 'lnprototest.clightning'], - scripts=[], - zip_safe=True, - install_requires=requirements) +setup( + name="lnprototest", + version="0.0.1", + description="Spec protocol tests for lightning network implementations", + long_description=long_description, + long_description_content_type="text/markdown", + author="Rusty Russell", + author_email="rusty@ln.dev", + license="MIT", + packages=["lnprototest", "lnprototest.clightning"], + scripts=[], + zip_safe=True, + install_requires=requirements, +) diff --git a/tests/conftest.py b/tests/conftest.py index 0058e9b..3c77acc 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -10,19 +10,32 @@ def pytest_addoption(parser: Any) -> None: - parser.addoption("--runner", action="store", help="runner to use", default="lnprototest.DummyRunner") - parser.addoption("--runner-args", action="append", help="parameters for runner to use", default=[]) + parser.addoption( + "--runner", + action="store", + help="runner to use", + default="lnprototest.DummyRunner", + ) + parser.addoption( + "--runner-args", + action="append", + help="parameters for runner to use", + default=[], + ) @pytest.fixture() # type: ignore def runner(pytestconfig: Any) -> Any: - parts = pytestconfig.getoption("runner").rpartition('.') + parts = pytestconfig.getoption("runner").rpartition(".") return importlib.import_module(parts[0]).__dict__[parts[2]](pytestconfig) @pytest.fixture() -def namespaceoverride(pytestconfig: Any) -> Generator[Callable[[MessageNamespace], None], None, None]: +def namespaceoverride( + pytestconfig: Any, +) -> Generator[Callable[[MessageNamespace], None], None, None]: """Use this if you want to override the message namespace""" + def _setter(newns: MessageNamespace) -> None: lnprototest.assign_namespace(newns) @@ -32,20 +45,27 @@ def _setter(newns: MessageNamespace) -> None: @pytest.fixture() -def with_proposal(pytestconfig: Any) -> Generator[Callable[[List[str]], None], None, None]: +def with_proposal( + pytestconfig: Any, +) -> Generator[Callable[[List[str]], None], None, None]: """Use this to add additional messages to the namespace - Useful for testing proposed (but not yet merged) spec mods. Noop if it seems already merged. """ + Useful for testing proposed (but not yet merged) spec mods. Noop if it seems already merged.""" + def _setter(proposal_csv: List[str]) -> None: # Testing first line is cheap, pretty effective. - if proposal_csv[0] not in (pyln.spec.bolt1.csv - + pyln.spec.bolt2.csv - + pyln.spec.bolt7.csv): + if proposal_csv[0] not in ( + pyln.spec.bolt1.csv + pyln.spec.bolt2.csv + pyln.spec.bolt7.csv + ): # We merge *csv*, because then you can add tlv entries; merging # namespaces with duplicate TLVs complains of a clash. - lnprototest.assign_namespace(lnprototest.make_namespace(pyln.spec.bolt1.csv - + pyln.spec.bolt2.csv - + pyln.spec.bolt7.csv - + proposal_csv)) + lnprototest.assign_namespace( + lnprototest.make_namespace( + pyln.spec.bolt1.csv + + pyln.spec.bolt2.csv + + pyln.spec.bolt7.csv + + proposal_csv + ) + ) yield _setter diff --git a/tests/helpers.py b/tests/helpers.py index 2491dc2..227a338 100644 --- a/tests/helpers.py +++ b/tests/helpers.py @@ -62,7 +62,7 @@ # P2WSH: bcrt1qug62lyrfd7khs7welgu28y66zzuq5nc4t9gdnyx3rjm9fud2f7gqm0ksxn # UTXO: d3fb780146954eb42e371c80cbee1725f8ae330848522f105bda24e1fb1fc010/6 (0.06BTC) # -tx_spendable = '0200000000010184591a56720aabc8023cecf71801c5e0f9d049d0c550ab42412ad12a67d89f3a0000000000feffffff0780841e0000000000160014fd9658fbd476d318f3b825b152b152aafa49bc9240420f000000000016001483440596268132e6c99d44dae2d151dabd9a2b232c180a2901000000160014d295f76da2319791f36df5759e45b15d5e105221c0c62d000000000016001454d14ae910793e930d8e33d3de0b0cbf05aa533300093d00000000001600141b42e1fc7b1cd93a469fa67ed5eabf36ce354dd620a107000000000016001406afd46bcdfd22ef94ac122aa11f241244a37ecc808d5b000000000022002000b068df6e0e0542e776cea5ebe8f5f1a9b40b531ddd8e94b1a7ff9829b5bbaa024730440220367b9bfed0565bad2137124f736373626fa3135e59b20a7b5c1d8f2b8f1b26bb02202f664de39787082a376d222487f02ef19e45696c041044a6d579eecabb68e94501210356609a904a7026c7391d3fbf71ad92a00e04b4cd2fb6a8d1e69cbc0998f6690a65000000' +tx_spendable = "0200000000010184591a56720aabc8023cecf71801c5e0f9d049d0c550ab42412ad12a67d89f3a0000000000feffffff0780841e0000000000160014fd9658fbd476d318f3b825b152b152aafa49bc9240420f000000000016001483440596268132e6c99d44dae2d151dabd9a2b232c180a2901000000160014d295f76da2319791f36df5759e45b15d5e105221c0c62d000000000016001454d14ae910793e930d8e33d3de0b0cbf05aa533300093d00000000001600141b42e1fc7b1cd93a469fa67ed5eabf36ce354dd620a107000000000016001406afd46bcdfd22ef94ac122aa11f241244a37ecc808d5b000000000022002000b068df6e0e0542e776cea5ebe8f5f1a9b40b531ddd8e94b1a7ff9829b5bbaa024730440220367b9bfed0565bad2137124f736373626fa3135e59b20a7b5c1d8f2b8f1b26bb02202f664de39787082a376d222487f02ef19e45696c041044a6d579eecabb68e94501210356609a904a7026c7391d3fbf71ad92a00e04b4cd2fb6a8d1e69cbc0998f6690a65000000" def utxo(index: int = 0) -> Tuple[str, int, int, str, int]: @@ -71,30 +71,30 @@ def utxo(index: int = 0) -> Tuple[str, int, int, str, int]: amount = (index + 1) * 1000000 if index == 0: txout = 1 - key = '76edf0c303b9e692da9cb491abedef46ca5b81d32f102eb4648461b239cb0f99' + key = "76edf0c303b9e692da9cb491abedef46ca5b81d32f102eb4648461b239cb0f99" elif index == 1: txout = 0 - key = 'bc2f48a76a6b8815940accaf01981d3b6347a68fbe844f81c50ecbadf27cd179' + key = "bc2f48a76a6b8815940accaf01981d3b6347a68fbe844f81c50ecbadf27cd179" elif index == 2: txout = 3 - key = '16c5027616e940d1e72b4c172557b3b799a93c0582f924441174ea556aadd01c' + key = "16c5027616e940d1e72b4c172557b3b799a93c0582f924441174ea556aadd01c" elif index == 3: txout = 4 - key = '53ac43309b75d9b86bef32c5bbc99c500910b64f9ae089667c870c2cc69e17a4' + key = "53ac43309b75d9b86bef32c5bbc99c500910b64f9ae089667c870c2cc69e17a4" elif index == 4: txout = 2 - key = '16be98a5d4156f6f3af99205e9bc1395397bca53db967e50427583c94271d27f' + key = "16be98a5d4156f6f3af99205e9bc1395397bca53db967e50427583c94271d27f" amount = 4983494700 elif index == 5: txout = 5 - key = '0000000000000000000000000000000000000000000000000000000000000002' + key = "0000000000000000000000000000000000000000000000000000000000000002" amount = 500000 elif index == 6: txout = 6 - key = '38204720bc4f9647fd58c6d0a4bd3a6dd2be16d8e4273c4d1bdd5774e8c51eaf' + key = "38204720bc4f9647fd58c6d0a4bd3a6dd2be16d8e4273c4d1bdd5774e8c51eaf" amount = 6000000 else: - raise ValueError('index must be 0-6 inclusive') + raise ValueError("index must be 0-6 inclusive") # Reasonable funding fee in sats reasonable_funding_fee = 200 @@ -113,7 +113,7 @@ def privkey_for_index(index: int = 0) -> str: def utxo_amount(index: int = 0) -> int: - """How much is this utxo worth """ + """How much is this utxo worth""" _, _, amt, _, _ = utxo(index) return amt @@ -131,4 +131,6 @@ def txid_raw(tx: str) -> str: def pubkey_of(privkey: str) -> str: """Return the public key corresponding to this privkey""" - return coincurve.PublicKey.from_secret(privkey_expand(privkey).secret).format().hex() + return ( + coincurve.PublicKey.from_secret(privkey_expand(privkey).secret).format().hex() + ) diff --git a/tests/test_bolt1-01-init.py b/tests/test_bolt1-01-init.py index 6f1fbc1..5df9c28 100755 --- a/tests/test_bolt1-01-init.py +++ b/tests/test_bolt1-01-init.py @@ -2,7 +2,22 @@ # Variations on init exchange. # Spec: MUST respond to known feature bits as specified in [BOLT #9](09-features.md). -from lnprototest import Runner, Event, Sequence, TryAll, Connect, Disconnect, EventError, ExpectMsg, Msg, ExpectError, has_bit, bitfield, bitfield_len, SpecFileError +from lnprototest import ( + Runner, + Event, + Sequence, + TryAll, + Connect, + Disconnect, + EventError, + ExpectMsg, + Msg, + ExpectError, + has_bit, + bitfield, + bitfield_len, + SpecFileError, +) from lnprototest.stash import rcvd import pyln.spec.bolt1 from pyln.proto.message import Message @@ -15,28 +30,38 @@ # BOLT #1: The sending node: # ... # - SHOULD NOT set features greater than 13 in `globalfeatures`. -def no_gf13(event: Event, msg: Message, runner: 'Runner') -> None: - for i in range(14, bitfield_len(msg.fields['globalfeatures'])): - if has_bit(msg.fields['globalfeatures'], i): +def no_gf13(event: Event, msg: Message, runner: "Runner") -> None: + for i in range(14, bitfield_len(msg.fields["globalfeatures"])): + if has_bit(msg.fields["globalfeatures"], i): raise EventError(event, "globalfeatures bit {} set".format(i)) -def no_feature(featurebits: List[int], event: Event, msg: Message, runner: 'Runner') -> None: +def no_feature( + featurebits: List[int], event: Event, msg: Message, runner: "Runner" +) -> None: for bit in featurebits: - if has_bit(msg.fields['features'], bit): - raise EventError(event, "features set bit {} unexpected: {}".format(bit, msg.to_str())) + if has_bit(msg.fields["features"], bit): + raise EventError( + event, "features set bit {} unexpected: {}".format(bit, msg.to_str()) + ) -def has_feature(featurebits: List[int], event: Event, msg: Message, runner: 'Runner') -> None: +def has_feature( + featurebits: List[int], event: Event, msg: Message, runner: "Runner" +) -> None: for bit in featurebits: - if not has_bit(msg.fields['features'], bit): - raise EventError(event, "features set bit {} unset: {}".format(bit, msg.to_str())) + if not has_bit(msg.fields["features"], bit): + raise EventError( + event, "features set bit {} unset: {}".format(bit, msg.to_str()) + ) -def has_one_feature(featurebits: List[int], event: Event, msg: Message, runner: 'Runner') -> None: +def has_one_feature( + featurebits: List[int], event: Event, msg: Message, runner: "Runner" +) -> None: has_any = False for bit in featurebits: - if has_bit(msg.fields['features'], bit): + if has_bit(msg.fields["features"], bit): has_any = True if not has_any: @@ -48,123 +73,142 @@ def test_namespace_override(runner: Runner, namespaceoverride: Any) -> None: namespaceoverride(pyln.spec.bolt1.namespace) # Try to send a message that's not in BOLT1 - with pytest.raises(SpecFileError, match=r'Unknown msgtype open_channel'): - Msg('open_channel') + with pytest.raises(SpecFileError, match=r"Unknown msgtype open_channel"): + Msg("open_channel") def test_init(runner: Runner, namespaceoverride: Any) -> None: # We override default namespace since we only need BOLT1 namespaceoverride(pyln.spec.bolt1.namespace) - test = [Connect(connprivkey='03'), - ExpectMsg('init'), - Msg('init', globalfeatures='', features=''), - - # optionally disconnect that first one - TryAll([], Disconnect()), - - Connect(connprivkey='02'), - TryAll( - # Even if we don't send anything, it should send init. - [ExpectMsg('init')], - - # Minimal possible init message. + test = [ + Connect(connprivkey="03"), + ExpectMsg("init"), + Msg("init", globalfeatures="", features=""), + # optionally disconnect that first one + TryAll([], Disconnect()), + Connect(connprivkey="02"), + TryAll( + # Even if we don't send anything, it should send init. + [ExpectMsg("init")], + # Minimal possible init message. + # BOLT #1: + # The sending node: + # - MUST send `init` as the first Lightning message for any connection. + [ExpectMsg("init"), Msg("init", globalfeatures="", features="")], + # BOLT #1: + # The sending node:... + # - SHOULD NOT set features greater than 13 in `globalfeatures`. + [ + ExpectMsg("init", if_match=no_gf13), # BOLT #1: - # The sending node: - # - MUST send `init` as the first Lightning message for any connection. - [ExpectMsg('init'), - Msg('init', globalfeatures='', features='')], - - # BOLT #1: - # The sending node:... - # - SHOULD NOT set features greater than 13 in `globalfeatures`. - [ExpectMsg('init', if_match=no_gf13), - # BOLT #1: - # The receiving node:... - # - upon receiving unknown _odd_ feature bits that are non-zero: - # - MUST ignore the bit. - - # init msg with unknown odd global bit (99): no error - Msg('init', globalfeatures=bitfield(99), features='')], - - # Sanity check that bits 98 and 99 are not used! - [ExpectMsg('init', if_match=functools.partial(no_feature, [98, 99])), - # BOLT #1: - # The receiving node:... - # - upon receiving unknown _odd_ feature bits that are non-zero: - # - MUST ignore the bit. - - # init msg with unknown odd local bit (99): no error - Msg('init', globalfeatures='', features=bitfield(99))], - + # The receiving node:... + # - upon receiving unknown _odd_ feature bits that are non-zero: + # - MUST ignore the bit. + # init msg with unknown odd global bit (99): no error + Msg("init", globalfeatures=bitfield(99), features=""), + ], + # Sanity check that bits 98 and 99 are not used! + [ + ExpectMsg("init", if_match=functools.partial(no_feature, [98, 99])), # BOLT #1: - # The receiving node: ... - # - upon receiving unknown _even_ feature bits that are non-zero: - # - MUST fail the connection. - [ExpectMsg('init'), - Msg('init', globalfeatures='', features=bitfield(98)), - ExpectError()], - - # init msg with unknown even global bit (98): you will error - [ExpectMsg('init'), - Msg('init', globalfeatures=bitfield(98), features=''), - ExpectError()], - - # If you don't support `option_data_loss_protect`, you will be ok if - # we ask for it. - Sequence([ExpectMsg('init', if_match=functools.partial(no_feature, [0, 1])), - Msg('init', globalfeatures='', features=bitfield(1))], - enable=not runner.has_option('option_data_loss_protect')), - - # If you don't support `option_data_loss_protect`, you will error if - # we require it. - Sequence([ExpectMsg('init', if_match=functools.partial(no_feature, [0, 1])), - Msg('init', globalfeatures='', features=bitfield(0)), - ExpectError()], - enable=not runner.has_option('option_data_loss_protect')), - - # If you support `option_data_loss_protect`, you will advertize it odd. - Sequence([ExpectMsg('init', if_match=functools.partial(has_feature, [1]))], - enable=(runner.has_option('option_data_loss_protect') == 'odd')), - - # If you require `option_data_loss_protect`, you will advertize it even. - Sequence([ExpectMsg('init', if_match=functools.partial(has_feature, [0]))], - enable=(runner.has_option('option_data_loss_protect') == 'even')), - - # If you don't support `option_anchor_outputs`, you will be ok if - # we ask for it. - Sequence([ExpectMsg('init', if_match=functools.partial(no_feature, [20, 21])), - Msg('init', globalfeatures='', features=bitfield(21))], - enable=not runner.has_option('option_anchor_outputs')), - - # If you don't support `option_anchor_outputs`, you will error if - # we require it. - Sequence([ExpectMsg('init', if_match=functools.partial(no_feature, [20, 21])), - Msg('init', globalfeatures='', features=bitfield(20)), - ExpectError()], - enable=not runner.has_option('option_anchor_outputs')), - - # If you support `option_anchor_outputs`, you will advertize it odd. - Sequence([ExpectMsg('init', if_match=functools.partial(has_feature, [21]))], - enable=(runner.has_option('option_anchor_outputs') == 'odd')), - - # If you require `option_anchor_outputs`, you will advertize it even. - Sequence([ExpectMsg('init', if_match=functools.partial(has_feature, [20]))], - enable=(runner.has_option('option_anchor_outputs') == 'even')), - - # BOLT-a12da24dd0102c170365124782b46d9710950ac1 #9: - # | Bits | Name | ... | Dependencies - # ... - # | 12/13 | `option_static_remotekey` | - # ... - # | 20/21 | `option_anchor_outputs` | ... | `option_static_remotekey` | - - # If you support `option_anchor_outputs`, you will - # advertize option_static_remotekey. - Sequence([ExpectMsg('init', if_match=functools.partial(has_one_feature, [12, 13]))], - enable=(runner.has_option('option_anchor_outputs') is not None)), - - # You should always handle us echoing your own features back! - [ExpectMsg('init'), - Msg('init', globalfeatures=rcvd(), features=rcvd())],)] + # The receiving node:... + # - upon receiving unknown _odd_ feature bits that are non-zero: + # - MUST ignore the bit. + # init msg with unknown odd local bit (99): no error + Msg("init", globalfeatures="", features=bitfield(99)), + ], + # BOLT #1: + # The receiving node: ... + # - upon receiving unknown _even_ feature bits that are non-zero: + # - MUST fail the connection. + [ + ExpectMsg("init"), + Msg("init", globalfeatures="", features=bitfield(98)), + ExpectError(), + ], + # init msg with unknown even global bit (98): you will error + [ + ExpectMsg("init"), + Msg("init", globalfeatures=bitfield(98), features=""), + ExpectError(), + ], + # If you don't support `option_data_loss_protect`, you will be ok if + # we ask for it. + Sequence( + [ + ExpectMsg("init", if_match=functools.partial(no_feature, [0, 1])), + Msg("init", globalfeatures="", features=bitfield(1)), + ], + enable=not runner.has_option("option_data_loss_protect"), + ), + # If you don't support `option_data_loss_protect`, you will error if + # we require it. + Sequence( + [ + ExpectMsg("init", if_match=functools.partial(no_feature, [0, 1])), + Msg("init", globalfeatures="", features=bitfield(0)), + ExpectError(), + ], + enable=not runner.has_option("option_data_loss_protect"), + ), + # If you support `option_data_loss_protect`, you will advertize it odd. + Sequence( + [ExpectMsg("init", if_match=functools.partial(has_feature, [1]))], + enable=(runner.has_option("option_data_loss_protect") == "odd"), + ), + # If you require `option_data_loss_protect`, you will advertize it even. + Sequence( + [ExpectMsg("init", if_match=functools.partial(has_feature, [0]))], + enable=(runner.has_option("option_data_loss_protect") == "even"), + ), + # If you don't support `option_anchor_outputs`, you will be ok if + # we ask for it. + Sequence( + [ + ExpectMsg("init", if_match=functools.partial(no_feature, [20, 21])), + Msg("init", globalfeatures="", features=bitfield(21)), + ], + enable=not runner.has_option("option_anchor_outputs"), + ), + # If you don't support `option_anchor_outputs`, you will error if + # we require it. + Sequence( + [ + ExpectMsg("init", if_match=functools.partial(no_feature, [20, 21])), + Msg("init", globalfeatures="", features=bitfield(20)), + ExpectError(), + ], + enable=not runner.has_option("option_anchor_outputs"), + ), + # If you support `option_anchor_outputs`, you will advertize it odd. + Sequence( + [ExpectMsg("init", if_match=functools.partial(has_feature, [21]))], + enable=(runner.has_option("option_anchor_outputs") == "odd"), + ), + # If you require `option_anchor_outputs`, you will advertize it even. + Sequence( + [ExpectMsg("init", if_match=functools.partial(has_feature, [20]))], + enable=(runner.has_option("option_anchor_outputs") == "even"), + ), + # BOLT-a12da24dd0102c170365124782b46d9710950ac1 #9: + # | Bits | Name | ... | Dependencies + # ... + # | 12/13 | `option_static_remotekey` | + # ... + # | 20/21 | `option_anchor_outputs` | ... | `option_static_remotekey` | + # If you support `option_anchor_outputs`, you will + # advertize option_static_remotekey. + Sequence( + [ + ExpectMsg( + "init", if_match=functools.partial(has_one_feature, [12, 13]) + ) + ], + enable=(runner.has_option("option_anchor_outputs") is not None), + ), + # You should always handle us echoing your own features back! + [ExpectMsg("init"), Msg("init", globalfeatures=rcvd(), features=rcvd())], + ), + ] runner.run(test) diff --git a/tests/test_bolt1-02-unknown-messages.py b/tests/test_bolt1-02-unknown-messages.py index 7a5e66d..234e2d3 100644 --- a/tests/test_bolt1-02-unknown-messages.py +++ b/tests/test_bolt1-02-unknown-messages.py @@ -2,7 +2,7 @@ # Init exchange, with unknown messages # -from lnprototest import (TryAll, Connect, ExpectMsg, Msg, RawMsg, ExpectError, Runner) +from lnprototest import TryAll, Connect, ExpectMsg, Msg, RawMsg, ExpectError, Runner import pyln.spec.bolt1 from typing import Any @@ -10,24 +10,24 @@ def test_unknowns(runner: Runner, namespaceoverride: Any) -> None: # We override default namespace since we only need BOLT1 namespaceoverride(pyln.spec.bolt1.namespace) - test = [Connect(connprivkey='03'), - ExpectMsg('init'), - Msg('init', globalfeatures='', features=''), - TryAll( - [], - # BOLT #1: - # A receiving node: - # - upon receiving a message of _odd_, unknown type: - # - MUST ignore the received message. - [RawMsg(bytes.fromhex('270F'))], - - - # BOLT #1: - # A receiving node:... - # - upon receiving a message of _even_, unknown type: - # - MUST close the connection. - # - MAY fail the channels. - [RawMsg(bytes.fromhex('2710')), - ExpectError()])] + test = [ + Connect(connprivkey="03"), + ExpectMsg("init"), + Msg("init", globalfeatures="", features=""), + TryAll( + [], + # BOLT #1: + # A receiving node: + # - upon receiving a message of _odd_, unknown type: + # - MUST ignore the received message. + [RawMsg(bytes.fromhex("270F"))], + # BOLT #1: + # A receiving node:... + # - upon receiving a message of _even_, unknown type: + # - MUST close the connection. + # - MAY fail the channels. + [RawMsg(bytes.fromhex("2710")), ExpectError()], + ), + ] runner.run(test) diff --git a/tests/test_bolt2-01-open_channel.py b/tests/test_bolt2-01-open_channel.py index c9c8c1e..c4db416 100644 --- a/tests/test_bolt2-01-open_channel.py +++ b/tests/test_bolt2-01-open_channel.py @@ -1,207 +1,257 @@ #! /usr/bin/env python3 # Variations on open_channel, accepter + opener perspectives -from lnprototest import TryAll, Connect, Block, FundChannel, ExpectMsg, ExpectTx, Msg, RawMsg, KeySet, 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 -from lnprototest.stash import sent, rcvd, commitsig_to_send, commitsig_to_recv, channel_id, funding_txid, funding_tx, funding +from lnprototest import ( + TryAll, + Connect, + Block, + FundChannel, + ExpectMsg, + ExpectTx, + Msg, + RawMsg, + KeySet, + 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, +) +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 def test_open_channel(runner: Runner) -> None: - local_funding_privkey = '20' - - local_keyset = KeySet(revocation_base_secret='21', - payment_base_secret='22', - htlc_base_secret='24', - delayed_payment_base_secret='23', - shachain_seed='00' * 32) - - 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'))), - ])] + local_funding_privkey = "20" + + local_keyset = KeySet( + revocation_base_secret="21", + payment_base_secret="22", + htlc_base_secret="24", + delayed_payment_base_secret="23", + shachain_seed="00" * 32, + ) + + 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"))), + ], + ), + ] runner.run(test) diff --git a/tests/test_bolt2-02-reestablish.py b/tests/test_bolt2-02-reestablish.py index 209a5c4..c4e1371 100644 --- a/tests/test_bolt2-02-reestablish.py +++ b/tests/test_bolt2-02-reestablish.py @@ -1,8 +1,43 @@ #! /usr/bin/env python3 # Variations on adding an HTLC. -from lnprototest import TryAll, Sequence, Connect, Block, ExpectMsg, Msg, RawMsg, KeySet, CreateFunding, Commit, Runner, Disconnect, 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, negotiated -from lnprototest.stash import sent, rcvd, commitsig_to_send, commitsig_to_recv, channel_id, funding_txid, funding_tx, funding +from lnprototest import ( + TryAll, + Sequence, + Connect, + Block, + ExpectMsg, + Msg, + RawMsg, + KeySet, + CreateFunding, + Commit, + Runner, + Disconnect, + 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, + negotiated, +) +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 # FIXME: bolt9.featurebits? @@ -20,146 +55,158 @@ def test_reestablish(runner: Runner) -> None: - local_funding_privkey = '20' - - local_keyset = KeySet(revocation_base_secret='21', - payment_base_secret='22', - htlc_base_secret='24', - delayed_payment_base_secret='23', - shachain_seed='00' * 32) - test = [Block(blockheight=102, txs=[tx_spendable]), - Connect(connprivkey='02'), ExpectMsg('init'), TryAll( - Msg('init', globalfeatures='', - features=bitfield(data_loss_protect)), - Msg('init', - globalfeatures='', features=bitfield(static_remotekey)), - Msg('init', - globalfeatures='', features=bitfield(static_remotekey, - anchor_outputs)), - # And nothing. - Msg('init', globalfeatures='', - features='')), - - 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, - # clightning uses to_self_delay=6; we use 5 to test differentiation - 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), - - ExpectMsg('accept_channel', - 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), - - # 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=remote_per_commitment_point(1)), - - Msg('funding_locked', - channel_id=channel_id(), - next_per_commitment_point=local_keyset.per_commit_point(1)), - - Disconnect(), - Connect(connprivkey='02'), - ExpectMsg('init'), - - # Reconnect with same features. - Msg('init', globalfeatures='', features=sent('init.features')), - - # BOLT #2: - # - if `next_revocation_number` equals 0: - # - MUST set `your_last_per_commitment_secret` to all zeroes - # - otherwise: - # - MUST set `your_last_per_commitment_secret` to the last - # `per_commitment_secret` it received - ExpectMsg('channel_reestablish', - channel_id=channel_id(), - next_commitment_number=1, - next_revocation_number=0, - your_last_per_commitment_secret='00' * 32), - - # BOLT #2: - # The sending node:... - # - if `option_static_remotekey` applies to the commitment - # transaction: - # - MUST set `my_current_per_commitment_point` to a valid point. - # - otherwise: - # - MUST set `my_current_per_commitment_point` to its commitment - # point for the last signed commitment it received from its - # channel peer (i.e. the commitment_point corresponding to the - # commitment transaction the sender would use to unilaterally - # close). - Sequence(CheckEq(rcvd('my_current_per_commitment_point'), - remote_per_commitment_point(0)), - enable=negotiated(sent('init.features'), - rcvd('init.features'), - excluded=[static_remotekey])), - - # Ignore unknown odd messages - TryAll([], RawMsg(bytes.fromhex('270F'))), - - Msg('channel_reestablish', - channel_id=channel_id(), - next_commitment_number=1, - next_revocation_number=0, - your_last_per_commitment_secret='00' * 32, - my_current_per_commitment_point=local_keyset.per_commit_point(0)), - - # FIXME: Check that they error and unilateral close if we give - # the wrong info! - ] + local_funding_privkey = "20" + + local_keyset = KeySet( + revocation_base_secret="21", + payment_base_secret="22", + htlc_base_secret="24", + delayed_payment_base_secret="23", + shachain_seed="00" * 32, + ) + test = [ + Block(blockheight=102, txs=[tx_spendable]), + Connect(connprivkey="02"), + ExpectMsg("init"), + TryAll( + Msg("init", globalfeatures="", features=bitfield(data_loss_protect)), + Msg("init", globalfeatures="", features=bitfield(static_remotekey)), + Msg( + "init", + globalfeatures="", + features=bitfield(static_remotekey, anchor_outputs), + ), + # And nothing. + Msg("init", globalfeatures="", features=""), + ), + 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, + # clightning uses to_self_delay=6; we use 5 to test differentiation + 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, + ), + ExpectMsg( + "accept_channel", + 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, + ), + # 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=remote_per_commitment_point(1), + ), + Msg( + "funding_locked", + channel_id=channel_id(), + next_per_commitment_point=local_keyset.per_commit_point(1), + ), + Disconnect(), + Connect(connprivkey="02"), + ExpectMsg("init"), + # Reconnect with same features. + Msg("init", globalfeatures="", features=sent("init.features")), + # BOLT #2: + # - if `next_revocation_number` equals 0: + # - MUST set `your_last_per_commitment_secret` to all zeroes + # - otherwise: + # - MUST set `your_last_per_commitment_secret` to the last + # `per_commitment_secret` it received + ExpectMsg( + "channel_reestablish", + channel_id=channel_id(), + next_commitment_number=1, + next_revocation_number=0, + your_last_per_commitment_secret="00" * 32, + ), + # BOLT #2: + # The sending node:... + # - if `option_static_remotekey` applies to the commitment + # transaction: + # - MUST set `my_current_per_commitment_point` to a valid point. + # - otherwise: + # - MUST set `my_current_per_commitment_point` to its commitment + # point for the last signed commitment it received from its + # channel peer (i.e. the commitment_point corresponding to the + # commitment transaction the sender would use to unilaterally + # close). + Sequence( + CheckEq( + rcvd("my_current_per_commitment_point"), remote_per_commitment_point(0) + ), + enable=negotiated( + sent("init.features"), + rcvd("init.features"), + excluded=[static_remotekey], + ), + ), + # Ignore unknown odd messages + TryAll([], RawMsg(bytes.fromhex("270F"))), + Msg( + "channel_reestablish", + channel_id=channel_id(), + next_commitment_number=1, + next_revocation_number=0, + your_last_per_commitment_secret="00" * 32, + my_current_per_commitment_point=local_keyset.per_commit_point(0), + ), + # FIXME: Check that they error and unilateral close if we give + # the wrong info! + ] runner.run(test) diff --git a/tests/test_bolt2-10-add-htlc.py b/tests/test_bolt2-10-add-htlc.py index 17fd356..92a7698 100644 --- a/tests/test_bolt2-10-add-htlc.py +++ b/tests/test_bolt2-10-add-htlc.py @@ -1,8 +1,45 @@ #! /usr/bin/env python3 # Variations on adding an HTLC. -from lnprototest import TryAll, Connect, Block, ExpectMsg, Msg, RawMsg, KeySet, CreateFunding, Commit, Runner, Disconnect, remote_funding_pubkey, remote_revocation_basepoint, remote_payment_basepoint, remote_htlc_basepoint, remote_per_commitment_point, remote_delayed_payment_basepoint, Side, msat, remote_funding_privkey, regtest_hash, bitfield, HTLC, UpdateCommit, remote_per_commitment_secret -from lnprototest.stash import sent, rcvd, commitsig_to_send, commitsig_to_recv, channel_id, funding_txid, funding_tx, funding, htlc_sigs_to_send, htlc_sigs_to_recv +from lnprototest import ( + TryAll, + Connect, + Block, + ExpectMsg, + Msg, + RawMsg, + KeySet, + CreateFunding, + Commit, + Runner, + Disconnect, + remote_funding_pubkey, + remote_revocation_basepoint, + remote_payment_basepoint, + remote_htlc_basepoint, + remote_per_commitment_point, + remote_delayed_payment_basepoint, + Side, + msat, + remote_funding_privkey, + regtest_hash, + bitfield, + HTLC, + UpdateCommit, + remote_per_commitment_secret, +) +from lnprototest.stash import ( + sent, + rcvd, + commitsig_to_send, + commitsig_to_recv, + channel_id, + funding_txid, + funding_tx, + funding, + htlc_sigs_to_send, + htlc_sigs_to_recv, +) from helpers import utxo, tx_spendable, funding_amount_for_utxo, pubkey_of # FIXME: bolt9.featurebits? @@ -20,259 +57,310 @@ def test_htlc_add(runner: Runner) -> None: - local_funding_privkey = '20' + local_funding_privkey = "20" - local_keyset = KeySet(revocation_base_secret='21', - payment_base_secret='22', - htlc_base_secret='24', - delayed_payment_base_secret='23', - shachain_seed='00' * 32) + local_keyset = KeySet( + revocation_base_secret="21", + payment_base_secret="22", + htlc_base_secret="24", + delayed_payment_base_secret="23", + shachain_seed="00" * 32, + ) # FIXME: Generate onion routing packet! - dust_htlc = HTLC(owner=Side.local, - amount_msat=1000, - payment_secret='00' * 32, - cltv_expiry=200, - # hop_data[0] = 00000000000000000000000000000003E8000000C8000000000000000000000000 - onion_routing_packet='0002eec7245d6b7d2ccb30380bfbe2a3648cd7a942653f5aa340edcea1f283686619b1153ae94698ea83a3298ab3c0ddd9f755853e4e5fbc5d4f3cb457bbb74a9b81d3b5bc9cf42d8617d1fe6966ffb66b8ec0eaa1188865957e26df123d11705395d339472bcc4920e428492f7822424eef8e6d903a768ec01959f3a1f2c1cd8725ba13329df3a932f641dee600dbb1a9f3bbe93a167410961f1777a7b48679d8a3041d57c0b8e795ed4884fbb33a2564d4cdafb528c7b63fc31cd2739e71d1d3b56f35ba7976a373b883eed8f1f263aedd540cce9b548e53e58c32ab604195f6004d8d92fe0a9a454229b9bc0795f3e4ccd54089075483afaa0ef3b32ee12cf321052f7b9e5ac1c28169e57d5628c3aee5c775d5fb33ba835fda195981b1e3a06792bdd0ecf85f8f6107fd830ca932e92c6713ea6d4d5129395f54aeabb54debccca130ad019a1f53a20c0c46dd8625ada068e2a13ea5373b60ecdf412728cc78192ae1a56bae26dfb450d2f6b4905e6bd9843fda7df63eb11fb77ce995b25d3076210eca527bb556b4ddc564fa4c6ccb43f1149163a4959ffe4178d653d35bdc052e4a46dd58b8f95fde83d114c4e35fd02e94a0dd2a9ae21594184808074a57d9de30c5105b53efe03aca192f8c518bc2b9e13211a9761c1948b31aa97f99da449968380005f96ff49a6e5fe833220a82f358eb94197584b2dfa5a1efee8918b5020f028748e5897bb694979f580ff58b8b1d865783340eaff2d1ce738409ec1c62c1bd7f632cf0730a5634a1a2d91244b865302339c1861655e11b264aeaf2feefbf2d1222bb13c6bd6b2d2379d9a548f93de4d2a044928458eafa745021e0a69796bb40f17c1ca53b895c76b53924faa886a4a19f07b50eda5f316e5f3b5422e984c59928144c275d4ae5e78634e16c6dafcfc92bb302c7d5eef1456250b0b8a41f0cabb55dd114d6b0bcaf53ef1ee2185d2383df57a0f1bc21d31f5d3ae395bab6e77370ee83ffe8995e9bfbe2f90b3ff0578720e0584e969479d40327415835579d7b8885037c02a611292c6bbffde25e86c184cc7c7481e8856ce6a3cf7109a6c001e51a2289c5ee3633936578d4dc3de82c18ebb787bf2c475e8fa0393727cbdbcd36849ee0b7411fba6fd5cb8459e63aaf3fba7a4cd4a04b266d8f416f0586e2093ea9c210140a6e6cb72759ae1dee7c24497f68389fb8d154f927cc4ab59b9137652eaf9c7cb56f0cce6c58616646c6fee836b07ce738a965b1ea725d9960c47e61086be053f3e9c48c08ce945404b060d9e699ad962c910208dda42d665f8eacf9865a64d2612ea62e0e2c0a4c731b35ae87b04e45739c34f4c972ce433a2094b10a9601e6711b95a6a226a85f4e4ed0e0417dbc9d737cd7d3513a82943de94ff8e4c9e91838506283f4878e3f41488fec47198b4a262b55d3691d275c6154d2a2ce9ee6ab97087e0f33654b01450869797c993dfca76cd732677bf1856f43d040d68022055987588f64af357bea80491b4bc42341dd6f81631d30fc28e8c5d7e3312655b30d277f10ce76c2525279ad53157b1c2c78b412107fc5f974ac7946bdc33ee54d71f3fc261530d50f20813e4e6aadf39e67573d5dc93a45023edf297b56def6b14ec5e19ca10fbfd1b807f17fa983bec363cf495c708a581db1bba1a23730ce22d0f925d764b04be014d662c3a36ac58b015317c9cf5ca6464f2ecef15e1769f2c91922968532bda66e9aaa2a7f120a9301f563fd33db8e90c940984b0a297e0c595544b7f687476325a07dbaba255c8461e98f069eea2246cfa50f1c2ef8d4c54f5fd509a9cc839548d7c252e60bb9c165d05f30bd525f6b53a4c8afc8fc31026686bcd5a48172593941b3113cbed88e6cfb566f7a693bb63c9a89925c1f5df0a115b4893128866a81c1b') - - non_dust_htlc = HTLC(owner=Side.local, - amount_msat=1000000, - payment_secret='00' * 32, - cltv_expiry=200, - onion_routing_packet='0002eec7245d6b7d2ccb30380bfbe2a3648cd7a942653f5aa340edcea1f283686619b1153ae94698ea83a3298ab3c0ddd9f755853e4e5fbc5d4f3cb457bbb74a9b81d3b5bc9cf42d8617d1fe6966ffb66b8ec0eaa1188865957e26df123d11705395d339472bcc4920e428492f7822424eef8e6d903a768ec01959f3a1f2c1cd8725ba13329df3a932f641dee600dbb1a9f3bbe93a167410961f1777a7b48679d8a3041d57c0b8e795ed4884fbb33a2564d4cdafb528c7b63fc31cd2739e71d1d3b56f35ba7976a373b883eed8f1f263aedd540cce9b548e53e58c32ab604195f6004d8d92fe0a9a454229b9bc0795f3e4ccd54089075483afaa0ef3b32ee12cf321052f7b9e5ac1c28169e57d5628c3aee5c775d5fb33ba835fda195981b1e3a06792bdd0ecf85f8f6107fd830ca932e92c6713ea6d4d5129395f54aeabb54debccca130ad019a1f53a20c0c46dd8625ada068e2a13ea5373b60ecdf412728cc78192ae1a56bae26dfb450d2f6b4905e6bd9843fda7df63eb11fb77ce995b25d3076210eca527bb556b4ddc564fa4c6ccb43f1149163a4959ffe4178d653d35bdc052e4a46dd58b8f95fde83d114c4e35fd02e94a0dd2a9ae21594184808074a57d9de30c5105b53efe03aca192f8c518bc2b9e13211a9761c1948b31aa97f99da449968380005f96ff49a6e5fe833220a82f358eb94197584b2dfa5a1efee8918b5020f028748e5897bb694979f580ff58b8b1d865783340eaff2d1ce738409ec1c62c1bd7f632cf0730a5634a1a2d91244b865302339c1861655e11b264aeaf2feefbf2d1222bb13c6bd6b2d2379d9a548f93de4d2a044928458eafa745021e0a69796bb40f17c1ca53b895c76b53924faa886a4a19f07b50eda5f316e5f3b5422e984c59928144c275d4ae5e78634e16c6dafcfc92bb302c7d5eef1456250b0b8a41f0cabb55dd114d6b0bcaf53ef1ee2185d2383df57a0f1bc21d31f5d3ae395bab6e77370ee83ffe8995e9bfbe2f90b3ff0578720e0584e969479d40327415835579d7b8885037c02a611292c6bbffde25e86c184cc7c7481e8856ce6a3cf7109a6c001e51a2289c5ee3633936578d4dc3de82c18ebb787bf2c475e8fa0393727cbdbcd36849ee0b7411fba6fd5cb8459e63aaf3fba7a4cd4a04b266d8f416f0586e2093ea9c210140a6e6cb72759ae1dee7c24497f68389fb8d154f927cc4ab59b9137652eaf9c7cb56f0cce6c58616646c6fee836b07ce738a965b1ea725d9960c47e61086be053f3e9c48c08ce945404b060d9e699ad962c910208dda42d665f8eacf9865a64d2612ea62e0e2c0a4c731b35ae87b04e45739c34f4c972ce433a2094b10a9601e6711b95a6a226a85f4e4ed0e0417dbc9d737cd7d3513a82943de94ff8e4c9e91838506283f4878e3f41488fec47198b4a262b55d3691d275c6154d2a2ce9ee6ab97087e0f33654b01450869797c993dfca76cd732677bf1856f43d040d68022055987588f64af357bea80491b4bc42341dd6f81631d30fc28e8c5d7e3312655b30d277f10ce76c2525279ad53157b1c2c78b412107fc5f974ac7946bdc33ee54d71f3fc261530d50f20813e4e6aadf39e67573d5dc93a45023edf297b56def6b14ec5e19ca10fbfd1b807f17fa983bec363cf495c708a581db1bba1a23730ce22d0f925d764b04be014d662c3a36ac58b015317c9cf5ca6464f2ecef15e1769f2c91922968532bda66e9aaa2a7f120a9301f563fd33db8e90c940984b0a297e0c595544b7f687476325a07dbaba255c8461e98f069eea2246cfa50f1c2ef8d4c54f5fd509a9cc839548d7c252e60bb9c165d05f30bd525f6b53a4c8afc8fc31026686bcd5a48172593941b3113cbed88e6cfb566f7a693bb63c9a89925c1f5df0a115b4893128866a81c1b') - - test = [Block(blockheight=102, txs=[tx_spendable]), - Connect(connprivkey='02'), ExpectMsg('init'), TryAll( - Msg('init', globalfeatures='', - features=bitfield(data_loss_protect)), - Msg('init', - globalfeatures='', features=bitfield(static_remotekey)), - Msg('init', - globalfeatures='', features=bitfield(static_remotekey, - anchor_outputs)), - # And nothing. - Msg('init', globalfeatures='', - features='')), - - 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, - # clightning uses to_self_delay=6; we use 5 to test differentiation - 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), - - ExpectMsg('accept_channel', - 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), - - # 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=remote_per_commitment_point(1)), - - Msg('funding_locked', + dust_htlc = HTLC( + owner=Side.local, + amount_msat=1000, + payment_secret="00" * 32, + cltv_expiry=200, + # hop_data[0] = 00000000000000000000000000000003E8000000C8000000000000000000000000 + onion_routing_packet="0002eec7245d6b7d2ccb30380bfbe2a3648cd7a942653f5aa340edcea1f283686619b1153ae94698ea83a3298ab3c0ddd9f755853e4e5fbc5d4f3cb457bbb74a9b81d3b5bc9cf42d8617d1fe6966ffb66b8ec0eaa1188865957e26df123d11705395d339472bcc4920e428492f7822424eef8e6d903a768ec01959f3a1f2c1cd8725ba13329df3a932f641dee600dbb1a9f3bbe93a167410961f1777a7b48679d8a3041d57c0b8e795ed4884fbb33a2564d4cdafb528c7b63fc31cd2739e71d1d3b56f35ba7976a373b883eed8f1f263aedd540cce9b548e53e58c32ab604195f6004d8d92fe0a9a454229b9bc0795f3e4ccd54089075483afaa0ef3b32ee12cf321052f7b9e5ac1c28169e57d5628c3aee5c775d5fb33ba835fda195981b1e3a06792bdd0ecf85f8f6107fd830ca932e92c6713ea6d4d5129395f54aeabb54debccca130ad019a1f53a20c0c46dd8625ada068e2a13ea5373b60ecdf412728cc78192ae1a56bae26dfb450d2f6b4905e6bd9843fda7df63eb11fb77ce995b25d3076210eca527bb556b4ddc564fa4c6ccb43f1149163a4959ffe4178d653d35bdc052e4a46dd58b8f95fde83d114c4e35fd02e94a0dd2a9ae21594184808074a57d9de30c5105b53efe03aca192f8c518bc2b9e13211a9761c1948b31aa97f99da449968380005f96ff49a6e5fe833220a82f358eb94197584b2dfa5a1efee8918b5020f028748e5897bb694979f580ff58b8b1d865783340eaff2d1ce738409ec1c62c1bd7f632cf0730a5634a1a2d91244b865302339c1861655e11b264aeaf2feefbf2d1222bb13c6bd6b2d2379d9a548f93de4d2a044928458eafa745021e0a69796bb40f17c1ca53b895c76b53924faa886a4a19f07b50eda5f316e5f3b5422e984c59928144c275d4ae5e78634e16c6dafcfc92bb302c7d5eef1456250b0b8a41f0cabb55dd114d6b0bcaf53ef1ee2185d2383df57a0f1bc21d31f5d3ae395bab6e77370ee83ffe8995e9bfbe2f90b3ff0578720e0584e969479d40327415835579d7b8885037c02a611292c6bbffde25e86c184cc7c7481e8856ce6a3cf7109a6c001e51a2289c5ee3633936578d4dc3de82c18ebb787bf2c475e8fa0393727cbdbcd36849ee0b7411fba6fd5cb8459e63aaf3fba7a4cd4a04b266d8f416f0586e2093ea9c210140a6e6cb72759ae1dee7c24497f68389fb8d154f927cc4ab59b9137652eaf9c7cb56f0cce6c58616646c6fee836b07ce738a965b1ea725d9960c47e61086be053f3e9c48c08ce945404b060d9e699ad962c910208dda42d665f8eacf9865a64d2612ea62e0e2c0a4c731b35ae87b04e45739c34f4c972ce433a2094b10a9601e6711b95a6a226a85f4e4ed0e0417dbc9d737cd7d3513a82943de94ff8e4c9e91838506283f4878e3f41488fec47198b4a262b55d3691d275c6154d2a2ce9ee6ab97087e0f33654b01450869797c993dfca76cd732677bf1856f43d040d68022055987588f64af357bea80491b4bc42341dd6f81631d30fc28e8c5d7e3312655b30d277f10ce76c2525279ad53157b1c2c78b412107fc5f974ac7946bdc33ee54d71f3fc261530d50f20813e4e6aadf39e67573d5dc93a45023edf297b56def6b14ec5e19ca10fbfd1b807f17fa983bec363cf495c708a581db1bba1a23730ce22d0f925d764b04be014d662c3a36ac58b015317c9cf5ca6464f2ecef15e1769f2c91922968532bda66e9aaa2a7f120a9301f563fd33db8e90c940984b0a297e0c595544b7f687476325a07dbaba255c8461e98f069eea2246cfa50f1c2ef8d4c54f5fd509a9cc839548d7c252e60bb9c165d05f30bd525f6b53a4c8afc8fc31026686bcd5a48172593941b3113cbed88e6cfb566f7a693bb63c9a89925c1f5df0a115b4893128866a81c1b", + ) + + non_dust_htlc = HTLC( + owner=Side.local, + amount_msat=1000000, + payment_secret="00" * 32, + cltv_expiry=200, + onion_routing_packet="0002eec7245d6b7d2ccb30380bfbe2a3648cd7a942653f5aa340edcea1f283686619b1153ae94698ea83a3298ab3c0ddd9f755853e4e5fbc5d4f3cb457bbb74a9b81d3b5bc9cf42d8617d1fe6966ffb66b8ec0eaa1188865957e26df123d11705395d339472bcc4920e428492f7822424eef8e6d903a768ec01959f3a1f2c1cd8725ba13329df3a932f641dee600dbb1a9f3bbe93a167410961f1777a7b48679d8a3041d57c0b8e795ed4884fbb33a2564d4cdafb528c7b63fc31cd2739e71d1d3b56f35ba7976a373b883eed8f1f263aedd540cce9b548e53e58c32ab604195f6004d8d92fe0a9a454229b9bc0795f3e4ccd54089075483afaa0ef3b32ee12cf321052f7b9e5ac1c28169e57d5628c3aee5c775d5fb33ba835fda195981b1e3a06792bdd0ecf85f8f6107fd830ca932e92c6713ea6d4d5129395f54aeabb54debccca130ad019a1f53a20c0c46dd8625ada068e2a13ea5373b60ecdf412728cc78192ae1a56bae26dfb450d2f6b4905e6bd9843fda7df63eb11fb77ce995b25d3076210eca527bb556b4ddc564fa4c6ccb43f1149163a4959ffe4178d653d35bdc052e4a46dd58b8f95fde83d114c4e35fd02e94a0dd2a9ae21594184808074a57d9de30c5105b53efe03aca192f8c518bc2b9e13211a9761c1948b31aa97f99da449968380005f96ff49a6e5fe833220a82f358eb94197584b2dfa5a1efee8918b5020f028748e5897bb694979f580ff58b8b1d865783340eaff2d1ce738409ec1c62c1bd7f632cf0730a5634a1a2d91244b865302339c1861655e11b264aeaf2feefbf2d1222bb13c6bd6b2d2379d9a548f93de4d2a044928458eafa745021e0a69796bb40f17c1ca53b895c76b53924faa886a4a19f07b50eda5f316e5f3b5422e984c59928144c275d4ae5e78634e16c6dafcfc92bb302c7d5eef1456250b0b8a41f0cabb55dd114d6b0bcaf53ef1ee2185d2383df57a0f1bc21d31f5d3ae395bab6e77370ee83ffe8995e9bfbe2f90b3ff0578720e0584e969479d40327415835579d7b8885037c02a611292c6bbffde25e86c184cc7c7481e8856ce6a3cf7109a6c001e51a2289c5ee3633936578d4dc3de82c18ebb787bf2c475e8fa0393727cbdbcd36849ee0b7411fba6fd5cb8459e63aaf3fba7a4cd4a04b266d8f416f0586e2093ea9c210140a6e6cb72759ae1dee7c24497f68389fb8d154f927cc4ab59b9137652eaf9c7cb56f0cce6c58616646c6fee836b07ce738a965b1ea725d9960c47e61086be053f3e9c48c08ce945404b060d9e699ad962c910208dda42d665f8eacf9865a64d2612ea62e0e2c0a4c731b35ae87b04e45739c34f4c972ce433a2094b10a9601e6711b95a6a226a85f4e4ed0e0417dbc9d737cd7d3513a82943de94ff8e4c9e91838506283f4878e3f41488fec47198b4a262b55d3691d275c6154d2a2ce9ee6ab97087e0f33654b01450869797c993dfca76cd732677bf1856f43d040d68022055987588f64af357bea80491b4bc42341dd6f81631d30fc28e8c5d7e3312655b30d277f10ce76c2525279ad53157b1c2c78b412107fc5f974ac7946bdc33ee54d71f3fc261530d50f20813e4e6aadf39e67573d5dc93a45023edf297b56def6b14ec5e19ca10fbfd1b807f17fa983bec363cf495c708a581db1bba1a23730ce22d0f925d764b04be014d662c3a36ac58b015317c9cf5ca6464f2ecef15e1769f2c91922968532bda66e9aaa2a7f120a9301f563fd33db8e90c940984b0a297e0c595544b7f687476325a07dbaba255c8461e98f069eea2246cfa50f1c2ef8d4c54f5fd509a9cc839548d7c252e60bb9c165d05f30bd525f6b53a4c8afc8fc31026686bcd5a48172593941b3113cbed88e6cfb566f7a693bb63c9a89925c1f5df0a115b4893128866a81c1b", + ) + + test = [ + Block(blockheight=102, txs=[tx_spendable]), + Connect(connprivkey="02"), + ExpectMsg("init"), + TryAll( + Msg("init", globalfeatures="", features=bitfield(data_loss_protect)), + Msg("init", globalfeatures="", features=bitfield(static_remotekey)), + Msg( + "init", + globalfeatures="", + features=bitfield(static_remotekey, anchor_outputs), + ), + # And nothing. + Msg("init", globalfeatures="", features=""), + ), + 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, + # clightning uses to_self_delay=6; we use 5 to test differentiation + 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, + ), + ExpectMsg( + "accept_channel", + 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, + ), + # 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=remote_per_commitment_point(1), + ), + Msg( + "funding_locked", + channel_id=channel_id(), + next_per_commitment_point=local_keyset.per_commit_point(1), + ), + # We try both a dust and a non-dust htlc. + TryAll( + Msg( + "update_add_htlc", channel_id=channel_id(), - next_per_commitment_point=local_keyset.per_commit_point(1)), - - # We try both a dust and a non-dust htlc. - TryAll( - Msg('update_add_htlc', + id=0, + amount_msat=dust_htlc.amount_msat, + payment_hash=dust_htlc.payment_hash(), + cltv_expiry=dust_htlc.cltv_expiry, + onion_routing_packet=dust_htlc.onion_routing_packet, + ), + Msg( + "update_add_htlc", + channel_id=channel_id(), + id=0, + amount_msat=non_dust_htlc.amount_msat, + payment_hash=non_dust_htlc.payment_hash(), + cltv_expiry=non_dust_htlc.cltv_expiry, + onion_routing_packet=non_dust_htlc.onion_routing_packet, + ), + ), + # Optional reconnect: + TryAll( + [], + [ + Disconnect(), + Connect(connprivkey="02"), + ExpectMsg("init"), + # Reconnect with same features. + Msg("init", globalfeatures="", features=sent("init.features")), + ExpectMsg( + "channel_reestablish", + channel_id=channel_id(), + next_commitment_number=1, + next_revocation_number=0, + your_last_per_commitment_secret="00" * 32, + ), + Msg( + "channel_reestablish", + channel_id=channel_id(), + next_commitment_number=1, + next_revocation_number=0, + your_last_per_commitment_secret="00" * 32, + my_current_per_commitment_point=local_keyset.per_commit_point(0), + ), + # BOLT #2: + # A node: + # - if `next_commitment_number` is 1 in both the + # `channel_reestablish` it sent and received: + # - MUST retransmit `funding_locked`. + # - otherwise: + # - MUST NOT retransmit `funding_locked`. + ExpectMsg( + "funding_locked", + channel_id=channel_id(), + next_per_commitment_point=remote_per_commitment_point(1), + ignore=ExpectMsg.ignore_all_gossip, + ), + # BOLT #2: + # A node: + # ... + # - upon disconnection: + # - MUST reverse any uncommitted updates sent by the + # other side (i.e. all messages beginning with `update_` + # for which no `commitment_signed` has been received). + # So this puts us back where we were. + Msg( + "update_add_htlc", channel_id=channel_id(), id=0, amount_msat=dust_htlc.amount_msat, payment_hash=dust_htlc.payment_hash(), cltv_expiry=dust_htlc.cltv_expiry, - onion_routing_packet=dust_htlc.onion_routing_packet), - - Msg('update_add_htlc', + onion_routing_packet=dust_htlc.onion_routing_packet, + ), + ], + ), + UpdateCommit(new_htlcs=[(dust_htlc, 0)]), + Msg( + "commitment_signed", + channel_id=channel_id(), + signature=commitsig_to_send(), + htlc_signature=htlc_sigs_to_send(), + ), + ExpectMsg( + "revoke_and_ack", + channel_id=channel_id(), + per_commitment_secret=remote_per_commitment_secret(0), + next_per_commitment_point=remote_per_commitment_point(2), + ignore=ExpectMsg.ignore_all_gossip, + ), + ExpectMsg( + "commitment_signed", + signature=commitsig_to_recv(), + htlc_signature=htlc_sigs_to_recv(), + ignore=ExpectMsg.ignore_all_gossip, + ), + # Now try optionally reconnecting. + TryAll( + [], + # Ignore unknown. + [RawMsg(bytes.fromhex("270F"))], + [ + Disconnect(), + Connect(connprivkey="02"), + ExpectMsg("init"), + # Reconnect with same features. + Msg("init", globalfeatures="", features=sent("init.features")), + ExpectMsg( + "channel_reestablish", channel_id=channel_id(), - id=0, - amount_msat=non_dust_htlc.amount_msat, - payment_hash=non_dust_htlc.payment_hash(), - cltv_expiry=non_dust_htlc.cltv_expiry, - onion_routing_packet=non_dust_htlc.onion_routing_packet)), - - # Optional reconnect: - TryAll([], - [Disconnect(), - Connect(connprivkey='02'), - ExpectMsg('init'), - - # Reconnect with same features. - Msg('init', globalfeatures='', features=sent('init.features')), - - ExpectMsg('channel_reestablish', - channel_id=channel_id(), - next_commitment_number=1, - next_revocation_number=0, - your_last_per_commitment_secret='00' * 32), - - Msg('channel_reestablish', - channel_id=channel_id(), - next_commitment_number=1, - next_revocation_number=0, - your_last_per_commitment_secret='00' * 32, - my_current_per_commitment_point=local_keyset.per_commit_point(0)), - - # BOLT #2: - # A node: - # - if `next_commitment_number` is 1 in both the - # `channel_reestablish` it sent and received: - # - MUST retransmit `funding_locked`. - # - otherwise: - # - MUST NOT retransmit `funding_locked`. - ExpectMsg('funding_locked', - channel_id=channel_id(), - next_per_commitment_point=remote_per_commitment_point(1), - ignore=ExpectMsg.ignore_all_gossip), - - # BOLT #2: - # A node: - # ... - # - upon disconnection: - # - MUST reverse any uncommitted updates sent by the - # other side (i.e. all messages beginning with `update_` - # for which no `commitment_signed` has been received). - - # So this puts us back where we were. - Msg('update_add_htlc', - channel_id=channel_id(), - id=0, - amount_msat=dust_htlc.amount_msat, - payment_hash=dust_htlc.payment_hash(), - cltv_expiry=dust_htlc.cltv_expiry, - onion_routing_packet=dust_htlc.onion_routing_packet)]), - - UpdateCommit(new_htlcs=[(dust_htlc, 0)]), - - Msg('commitment_signed', - channel_id=channel_id(), - signature=commitsig_to_send(), - htlc_signature=htlc_sigs_to_send()), - - ExpectMsg('revoke_and_ack', - channel_id=channel_id(), - per_commitment_secret=remote_per_commitment_secret(0), - next_per_commitment_point=remote_per_commitment_point(2), - ignore=ExpectMsg.ignore_all_gossip), - - ExpectMsg('commitment_signed', - signature=commitsig_to_recv(), - htlc_signature=htlc_sigs_to_recv(), - ignore=ExpectMsg.ignore_all_gossip), - - # Now try optionally reconnecting. - TryAll([], - # Ignore unknown. - [RawMsg(bytes.fromhex('270F'))], - [Disconnect(), - Connect(connprivkey='02'), - ExpectMsg('init'), - - # Reconnect with same features. - Msg('init', globalfeatures='', features=sent('init.features')), - - ExpectMsg('channel_reestablish', - channel_id=channel_id(), - next_commitment_number=2, - next_revocation_number=0, - your_last_per_commitment_secret='00' * 32, - ignore=ExpectMsg.ignore_all_gossip), - - # Depends on what we tell them we already received: - TryAll( - # We didn't receive revoke_and_ack: - [Msg('channel_reestablish', - channel_id=channel_id(), - next_commitment_number=1, - next_revocation_number=0, - your_last_per_commitment_secret='00' * 32, - my_current_per_commitment_point=local_keyset.per_commit_point(0)), - - ExpectMsg('revoke_and_ack', - channel_id=channel_id(), - per_commitment_secret=remote_per_commitment_secret(0), - next_per_commitment_point=remote_per_commitment_point(2), - ignore=ExpectMsg.ignore_all_gossip), - - ExpectMsg('commitment_signed', - signature=commitsig_to_recv(), - htlc_signature=htlc_sigs_to_recv(), - ignore=ExpectMsg.ignore_all_gossip)], - - # We did receive revoke_and_ack, but not - # commitment_signed - [Msg('channel_reestablish', - channel_id=channel_id(), - next_commitment_number=1, - next_revocation_number=1, - your_last_per_commitment_secret=remote_per_commitment_secret(0), - my_current_per_commitment_point=local_keyset.per_commit_point(0)), - - ExpectMsg('commitment_signed', - signature=commitsig_to_recv(), - htlc_signature=htlc_sigs_to_recv(), - ignore=ExpectMsg.ignore_all_gossip)], - - # We received commitment_signed: - [Msg('channel_reestablish', - channel_id=channel_id(), - next_commitment_number=2, - next_revocation_number=1, - your_last_per_commitment_secret=remote_per_commitment_secret(0), - my_current_per_commitment_point=local_keyset.per_commit_point(1))])])] + next_commitment_number=2, + next_revocation_number=0, + your_last_per_commitment_secret="00" * 32, + ignore=ExpectMsg.ignore_all_gossip, + ), + # Depends on what we tell them we already received: + TryAll( + # We didn't receive revoke_and_ack: + [ + Msg( + "channel_reestablish", + channel_id=channel_id(), + next_commitment_number=1, + next_revocation_number=0, + your_last_per_commitment_secret="00" * 32, + my_current_per_commitment_point=local_keyset.per_commit_point( + 0 + ), + ), + ExpectMsg( + "revoke_and_ack", + channel_id=channel_id(), + per_commitment_secret=remote_per_commitment_secret(0), + next_per_commitment_point=remote_per_commitment_point(2), + ignore=ExpectMsg.ignore_all_gossip, + ), + ExpectMsg( + "commitment_signed", + signature=commitsig_to_recv(), + htlc_signature=htlc_sigs_to_recv(), + ignore=ExpectMsg.ignore_all_gossip, + ), + ], + # We did receive revoke_and_ack, but not + # commitment_signed + [ + Msg( + "channel_reestablish", + channel_id=channel_id(), + next_commitment_number=1, + next_revocation_number=1, + your_last_per_commitment_secret=remote_per_commitment_secret( + 0 + ), + my_current_per_commitment_point=local_keyset.per_commit_point( + 0 + ), + ), + ExpectMsg( + "commitment_signed", + signature=commitsig_to_recv(), + htlc_signature=htlc_sigs_to_recv(), + ignore=ExpectMsg.ignore_all_gossip, + ), + ], + # We received commitment_signed: + [ + Msg( + "channel_reestablish", + channel_id=channel_id(), + next_commitment_number=2, + next_revocation_number=1, + your_last_per_commitment_secret=remote_per_commitment_secret( + 0 + ), + my_current_per_commitment_point=local_keyset.per_commit_point( + 1 + ), + ) + ], + ), + ], + ), + ] runner.run(test) diff --git a/tests/test_bolt2-20-open_channel_accepter.py b/tests/test_bolt2-20-open_channel_accepter.py index adfd60f..88b4d69 100644 --- a/tests/test_bolt2-20-open_channel_accepter.py +++ b/tests/test_bolt2-20-open_channel_accepter.py @@ -67,7 +67,6 @@ def channel_id_v2(local_keyset: KeySet) -> Callable[[Runner, Event, str], str]: - def _channel_id_v2(runner: Runner, event: Event, field: str) -> str: # BOLT-0eebb43e32a513f3b4dd9ced72ad1e915aefdd25 #2: @@ -82,10 +81,13 @@ def _channel_id_v2(runner: Runner, event: Event, field: str) -> str: return sha256(remote_key.format() + local_key.format()).digest().hex() else: return sha256(local_key.format() + remote_key.format()).digest().hex() + return _channel_id_v2 -def channel_id_tmp(local_keyset: KeySet, opener: Side) -> Callable[[Runner, Event, str], str]: +def channel_id_tmp( + local_keyset: KeySet, opener: Side +) -> Callable[[Runner, Event, str], str]: def _channel_id_tmp(runner: Runner, event: Event, field: str) -> str: # BOLT-f53ca2301232db780843e894f55d95d512f297f9 #2: # @@ -97,68 +99,97 @@ def _channel_id_tmp(runner: Runner, event: Event, field: str) -> str: else: key = runner.get_keyset().raw_revocation_basepoint() - return sha256(bytes.fromhex('00' * 33) + key.format()).digest().hex() + return sha256(bytes.fromhex("00" * 33) + key.format()).digest().hex() return _channel_id_tmp -def odd_serial(event: Event, msg: Message, runner: 'Runner') -> None: +def odd_serial(event: Event, msg: Message, runner: "Runner") -> None: """ - Test that a message's serial_id is odd. - Note that the dummy runner will fail this test, so we skip for them + Test that a message's serial_id is odd. + Note that the dummy runner will fail this test, so we skip for them """ - if msg.fields['serial_id'] % 2 == 0: + if msg.fields["serial_id"] % 2 == 0: if runner._is_dummy(): return - raise EventError(event, "Received **even** serial {}, expected odd".format(msg.fields['serial_id'])) + raise EventError( + event, + "Received **even** serial {}, expected odd".format(msg.fields["serial_id"]), + ) -def even_serial(event: Event, msg: Message, runner: 'Runner') -> None: - if msg.fields['serial_id'] % 2 == 1: - raise EventError(event, "Received **odd** serial {}, expected event".format(msg.fields['serial_id'])) +def even_serial(event: Event, msg: Message, runner: "Runner") -> None: + if msg.fields["serial_id"] % 2 == 1: + raise EventError( + event, + "Received **odd** serial {}, expected event".format( + msg.fields["serial_id"] + ), + ) -def agreed_funding(opener: Side, is_rbf: bool = False) -> Callable[[Runner, Event, str], int]: +def agreed_funding( + opener: Side, is_rbf: bool = False +) -> Callable[[Runner, Event, str], int]: def _agreed_funding(runner: Runner, event: Event, field: str) -> int: - opener_msg = 'init_rbf' if is_rbf else 'open_channel2' - accept_msg = 'ack_rbf' if is_rbf else 'accept_channel2' - - open_funding = get_member(event, - runner, - 'Msg' if opener == Side.local else 'ExpectMsg', - opener_msg + '.funding_satoshis') - accept_funding = get_member(event, - runner, - 'ExpectMsg' if opener == Side.local else 'Msg', - accept_msg + '.funding_satoshis') + opener_msg = "init_rbf" if is_rbf else "open_channel2" + accept_msg = "ack_rbf" if is_rbf else "accept_channel2" + + open_funding = get_member( + event, + runner, + "Msg" if opener == Side.local else "ExpectMsg", + opener_msg + ".funding_satoshis", + ) + accept_funding = get_member( + event, + runner, + "ExpectMsg" if opener == Side.local else "Msg", + accept_msg + ".funding_satoshis", + ) return int(open_funding) + int(accept_funding) + return _agreed_funding def funding_lockscript(our_privkey: str) -> Callable[[Runner, Event, str], str]: def _funding_lockscript(runner: Runner, event: Event, field: str) -> str: - remote_pubkey = Funding.funding_pubkey_key(privkey_expand(runner.get_node_bitcoinkey())) + remote_pubkey = Funding.funding_pubkey_key( + privkey_expand(runner.get_node_bitcoinkey()) + ) local_pubkey = Funding.funding_pubkey_key(privkey_expand(our_privkey)) return Funding.locking_script_keys(remote_pubkey, local_pubkey).hex() + return _funding_lockscript -def change_amount(opener: Side, change_for_opener: bool, script: str, input_amt: int) -> Callable[[Runner, Event, str], int]: +def change_amount( + opener: Side, change_for_opener: bool, script: str, input_amt: int +) -> Callable[[Runner, Event, str], int]: # We assume that change is input minus fees def _change_amount(runner: Runner, event: Event, field: str) -> int: if change_for_opener: - opening_amt = get_member(event, runner, - 'Msg' if opener == Side.local else 'ExpectMsg', - 'open_channel2.funding_satoshis') + opening_amt = get_member( + event, + runner, + "Msg" if opener == Side.local else "ExpectMsg", + "open_channel2.funding_satoshis", + ) else: - opening_amt = get_member(event, runner, - 'ExpectMsg' if opener == Side.local else 'Msg', - 'accept_channel2.funding_satoshis') - - feerate = get_member(event, runner, - 'Msg' if opener == Side.local else 'ExpectMsg', - 'open_channel2.funding_feerate_perkw') + opening_amt = get_member( + event, + runner, + "ExpectMsg" if opener == Side.local else "Msg", + "accept_channel2.funding_satoshis", + ) + + feerate = get_member( + event, + runner, + "Msg" if opener == Side.local else "ExpectMsg", + "open_channel2.funding_feerate_perkw", + ) # assume 1 input, with no redeemscript weight = (32 + 4 + 4 + 1 + 0) * 4 @@ -180,789 +211,839 @@ def _change_amount(runner: Runner, event: Event, field: str) -> int: def test_open_accepter_no_inputs(runner: Runner, with_proposal: Any) -> None: with_proposal(dual_fund_csv) - runner.add_startup_flag('experimental-dual-fund') - - local_funding_privkey = '20' - local_keyset = KeySet(revocation_base_secret='21', - payment_base_secret='22', - htlc_base_secret='24', - delayed_payment_base_secret='23', - shachain_seed='00' * 32) + runner.add_startup_flag("experimental-dual-fund") + + local_funding_privkey = "20" + local_keyset = KeySet( + revocation_base_secret="21", + payment_base_secret="22", + htlc_base_secret="24", + delayed_payment_base_secret="23", + shachain_seed="00" * 32, + ) input_index = 0 - test = [Block(blockheight=102, txs=[tx_spendable]), - Connect(connprivkey='02'), - ExpectMsg('init'), - - # BOLT-f53ca2301232db780843e894f55d95d512f297f9 #9: - # | 28/29 | `option_dual_fund` | Use v2 of channel open, enables dual funding | IN9 | `option_anchor_outputs`, `option_static_remotekey` | [BOLT #2](02-peer-protocol.md) | - - Msg('init', globalfeatures='', features=bitfield(12, 20, 29)), - - # Accepter side: we initiate a new channel. - Msg('open_channel2', - channel_id=channel_id_tmp(local_keyset, Side.local), - chain_hash=regtest_hash, - funding_satoshis=funding_amount_for_utxo(input_index), - dust_limit_satoshis=546, - max_htlc_value_in_flight_msat=4294967295, - htlc_minimum_msat=0, - funding_feerate_perkw=253, - commitment_feerate_perkw=253, - # We use 5, because c-lightning runner uses 6, so this is different. - to_self_delay=5, - max_accepted_htlcs=483, - locktime=0, - 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_channel2', - channel_id=channel_id_tmp(local_keyset, Side.local), - funding_satoshis=0, - 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)), - - # Ignore unknown odd messages - TryAll([], RawMsg(bytes.fromhex('270F'))), - - # Create and stash Funding object and FundingTx - CreateFunding(*utxo(input_index), - 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('accept_channel2.to_self_delay', int), - remote_to_self_delay=sent('open_channel2.to_self_delay', int), - local_amount=msat(sent('open_channel2.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('tx_add_input', - channel_id=channel_id_v2(local_keyset), - serial_id=2, - prevtx=tx_spendable, - prevtx_vout=tx_out_for_index(input_index), - sequence=0xfffffffd, - script_sig=''), - - AddInput(funding=funding(), - privkey=privkey_for_index(input_index), - serial_id=sent('tx_add_input.serial_id', int), - prevtx=sent(), - prevtx_vout=sent('tx_add_input.prevtx_vout', int), - script_sig=sent()), - - # Ignore unknown odd messages - TryAll([], RawMsg(bytes.fromhex('270F'))), - - ExpectMsg('tx_complete', - channel_id=channel_id_v2(local_keyset)), - - # Try removing and re-adding an input - TryAll([], - [Msg('tx_remove_input', - channel_id=channel_id_v2(local_keyset), - serial_id=2), - ExpectMsg('tx_complete', - channel_id=channel_id_v2(local_keyset)), - Msg('tx_add_input', - channel_id=channel_id_v2(local_keyset), - serial_id=2, - prevtx=tx_spendable, - prevtx_vout=tx_out_for_index(input_index), - sequence=0xfffffffd, - script_sig=''), - ExpectMsg('tx_complete', - channel_id=channel_id_v2(local_keyset))]), - - Msg('tx_add_output', - channel_id=channel_id_v2(local_keyset), - serial_id=2, - sats=funding_amount_for_utxo(input_index), - script=locking_script()), - - AddOutput(funding=funding(), - serial_id=sent('tx_add_output.serial_id', int), - script=sent(), - sats=sent('tx_add_output.sats', int)), - - # Ignore unknown odd messages - TryAll([], RawMsg(bytes.fromhex('270F'))), - - ExpectMsg('tx_complete', - channel_id=channel_id_v2(local_keyset)), - - # Try removing and re-adding an output - TryAll([], - [Msg('tx_remove_output', - channel_id=channel_id_v2(local_keyset), - serial_id=2), - ExpectMsg('tx_complete', - channel_id=channel_id_v2(local_keyset)), - Msg('tx_add_output', - channel_id=channel_id_v2(local_keyset), - serial_id=2, - sats=funding_amount_for_utxo(input_index), - script=locking_script()), - ExpectMsg('tx_complete', - channel_id=channel_id_v2(local_keyset))]), - - Msg('tx_complete', - channel_id=channel_id_v2(local_keyset)), - - FinalizeFunding(funding=funding()), - - # Ignore unknown odd messages - TryAll([], RawMsg(bytes.fromhex('270F'))), - - Msg('commitment_signed', - channel_id=channel_id_v2(local_keyset), - signature=commitsig_to_send(), - htlc_signature='[]'), - - # Ignore unknown odd messages - TryAll([], RawMsg(bytes.fromhex('270F'))), - - ExpectMsg('commitment_signed', - channel_id=channel_id_v2(local_keyset), - signature=commitsig_to_recv()), - - ExpectMsg('tx_signatures', - channel_id=channel_id_v2(local_keyset), - txid=funding_txid(), - witness_stack='[]'), - - Msg('tx_signatures', - channel_id=channel_id_v2(local_keyset), - txid=funding_txid(), - witness_stack=witnesses()), - - # Mine the block! - Block(blockheight=103, number=3, txs=[funding_tx()]), - - Msg('funding_locked', - channel_id=channel_id_v2(local_keyset), - next_per_commitment_point='027eed8389cf8eb715d73111b73d94d2c2d04bf96dc43dfd5b0970d80b3617009d'), - - ExpectMsg('funding_locked', - channel_id=channel_id_v2(local_keyset), - next_per_commitment_point='032405cbd0f41225d5f203fe4adac8401321a9e05767c5f8af97d51d2e81fbb206'), - - # Ignore unknown odd messages - TryAll([], RawMsg(bytes.fromhex('270F'))), - ] + test = [ + Block(blockheight=102, txs=[tx_spendable]), + Connect(connprivkey="02"), + ExpectMsg("init"), + # BOLT-f53ca2301232db780843e894f55d95d512f297f9 #9: + # | 28/29 | `option_dual_fund` | Use v2 of channel open, enables dual funding | IN9 | `option_anchor_outputs`, `option_static_remotekey` | [BOLT #2](02-peer-protocol.md) | + Msg("init", globalfeatures="", features=bitfield(12, 20, 29)), + # Accepter side: we initiate a new channel. + Msg( + "open_channel2", + channel_id=channel_id_tmp(local_keyset, Side.local), + chain_hash=regtest_hash, + funding_satoshis=funding_amount_for_utxo(input_index), + dust_limit_satoshis=546, + max_htlc_value_in_flight_msat=4294967295, + htlc_minimum_msat=0, + funding_feerate_perkw=253, + commitment_feerate_perkw=253, + # We use 5, because c-lightning runner uses 6, so this is different. + to_self_delay=5, + max_accepted_htlcs=483, + locktime=0, + 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_channel2", + channel_id=channel_id_tmp(local_keyset, Side.local), + funding_satoshis=0, + 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), + ), + # Ignore unknown odd messages + TryAll([], RawMsg(bytes.fromhex("270F"))), + # Create and stash Funding object and FundingTx + CreateFunding( + *utxo(input_index), + 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("accept_channel2.to_self_delay", int), + remote_to_self_delay=sent("open_channel2.to_self_delay", int), + local_amount=msat(sent("open_channel2.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( + "tx_add_input", + channel_id=channel_id_v2(local_keyset), + serial_id=2, + prevtx=tx_spendable, + prevtx_vout=tx_out_for_index(input_index), + sequence=0xFFFFFFFD, + script_sig="", + ), + AddInput( + funding=funding(), + privkey=privkey_for_index(input_index), + serial_id=sent("tx_add_input.serial_id", int), + prevtx=sent(), + prevtx_vout=sent("tx_add_input.prevtx_vout", int), + script_sig=sent(), + ), + # Ignore unknown odd messages + TryAll([], RawMsg(bytes.fromhex("270F"))), + ExpectMsg("tx_complete", channel_id=channel_id_v2(local_keyset)), + # Try removing and re-adding an input + TryAll( + [], + [ + Msg( + "tx_remove_input", + channel_id=channel_id_v2(local_keyset), + serial_id=2, + ), + ExpectMsg("tx_complete", channel_id=channel_id_v2(local_keyset)), + Msg( + "tx_add_input", + channel_id=channel_id_v2(local_keyset), + serial_id=2, + prevtx=tx_spendable, + prevtx_vout=tx_out_for_index(input_index), + sequence=0xFFFFFFFD, + script_sig="", + ), + ExpectMsg("tx_complete", channel_id=channel_id_v2(local_keyset)), + ], + ), + Msg( + "tx_add_output", + channel_id=channel_id_v2(local_keyset), + serial_id=2, + sats=funding_amount_for_utxo(input_index), + script=locking_script(), + ), + AddOutput( + funding=funding(), + serial_id=sent("tx_add_output.serial_id", int), + script=sent(), + sats=sent("tx_add_output.sats", int), + ), + # Ignore unknown odd messages + TryAll([], RawMsg(bytes.fromhex("270F"))), + ExpectMsg("tx_complete", channel_id=channel_id_v2(local_keyset)), + # Try removing and re-adding an output + TryAll( + [], + [ + Msg( + "tx_remove_output", + channel_id=channel_id_v2(local_keyset), + serial_id=2, + ), + ExpectMsg("tx_complete", channel_id=channel_id_v2(local_keyset)), + Msg( + "tx_add_output", + channel_id=channel_id_v2(local_keyset), + serial_id=2, + sats=funding_amount_for_utxo(input_index), + script=locking_script(), + ), + ExpectMsg("tx_complete", channel_id=channel_id_v2(local_keyset)), + ], + ), + Msg("tx_complete", channel_id=channel_id_v2(local_keyset)), + FinalizeFunding(funding=funding()), + # Ignore unknown odd messages + TryAll([], RawMsg(bytes.fromhex("270F"))), + Msg( + "commitment_signed", + channel_id=channel_id_v2(local_keyset), + signature=commitsig_to_send(), + htlc_signature="[]", + ), + # Ignore unknown odd messages + TryAll([], RawMsg(bytes.fromhex("270F"))), + ExpectMsg( + "commitment_signed", + channel_id=channel_id_v2(local_keyset), + signature=commitsig_to_recv(), + ), + ExpectMsg( + "tx_signatures", + channel_id=channel_id_v2(local_keyset), + txid=funding_txid(), + witness_stack="[]", + ), + Msg( + "tx_signatures", + channel_id=channel_id_v2(local_keyset), + txid=funding_txid(), + witness_stack=witnesses(), + ), + # Mine the block! + Block(blockheight=103, number=3, txs=[funding_tx()]), + Msg( + "funding_locked", + channel_id=channel_id_v2(local_keyset), + next_per_commitment_point="027eed8389cf8eb715d73111b73d94d2c2d04bf96dc43dfd5b0970d80b3617009d", + ), + ExpectMsg( + "funding_locked", + channel_id=channel_id_v2(local_keyset), + next_per_commitment_point="032405cbd0f41225d5f203fe4adac8401321a9e05767c5f8af97d51d2e81fbb206", + ), + # Ignore unknown odd messages + TryAll([], RawMsg(bytes.fromhex("270F"))), + ] runner.run(test) def test_open_accepter_with_inputs(runner: Runner, with_proposal: Any) -> None: with_proposal(dual_fund_csv) - runner.add_startup_flag('experimental-dual-fund') + runner.add_startup_flag("experimental-dual-fund") - local_funding_privkey = '20' + local_funding_privkey = "20" - local_keyset = KeySet(revocation_base_secret='21', - payment_base_secret='22', - htlc_base_secret='24', - delayed_payment_base_secret='23', - shachain_seed='00' * 32) + local_keyset = KeySet( + revocation_base_secret="21", + payment_base_secret="22", + htlc_base_secret="24", + delayed_payment_base_secret="23", + shachain_seed="00" * 32, + ) # Index 5+6 are special, only the test runner can spend them input_index = 5 # Since technically these can be sent in any order, # we must specify this as ok! - expected_add_input = ExpectMsg('tx_add_input', - channel_id=channel_id_v2(local_keyset), - sequence=0xfffffffd, - script_sig='', - if_match=odd_serial) - - expected_add_output = ExpectMsg('tx_add_output', - channel_id=channel_id_v2(local_keyset), - if_match=odd_serial) - - test = [Block(blockheight=102, txs=[tx_spendable]), - Connect(connprivkey='02'), - ExpectMsg('init'), - - # BOLT-f53ca2301232db780843e894f55d95d512f297f9 #9: - # | 28/29 | `option_dual_fund` | Use v2 of channel open, enables dual funding | IN9 | `option_anchor_outputs`, `option_static_remotekey` | [BOLT #2](02-peer-protocol.md) | - Msg('init', globalfeatures='', features=bitfield(12, 20, 29)), - - DualFundAccept(), - - # Accepter side: we initiate a new channel. - Msg('open_channel2', - channel_id=channel_id_tmp(local_keyset, Side.local), - chain_hash=regtest_hash, - funding_satoshis=funding_amount_for_utxo(input_index), - dust_limit_satoshis=546, - max_htlc_value_in_flight_msat=4294967295, - htlc_minimum_msat=0, - funding_feerate_perkw=253, - commitment_feerate_perkw=253, - # We use 5, because c-lightning runner uses 6, so this is different. - to_self_delay=5, - max_accepted_htlcs=483, - locktime=100, - 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_channel2', - channel_id=sent('open_channel2.channel_id'), - funding_satoshis=funding_amount_for_utxo(input_index), - 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)), - - # Create and stash Funding object and FundingTx - CreateDualFunding(fee=200, - funding_sats=agreed_funding(Side.local), - locktime=sent('open_channel2.locktime', int), - local_node_privkey='02', - local_funding_privkey=local_funding_privkey, - remote_node_privkey=runner.get_node_privkey(), - remote_funding_privkey=remote_funding_privkey()), - - # Ignore unknown odd messages - TryAll([], RawMsg(bytes.fromhex('270F'))), - - Msg('tx_add_input', - channel_id=channel_id_v2(local_keyset), - serial_id=0, - sequence=0xfffffffd, - prevtx=tx_spendable, - prevtx_vout=tx_out_for_index(input_index), - script_sig=''), - - AddInput(funding=funding(), - privkey=privkey_for_index(input_index), - serial_id=sent('tx_add_input.serial_id', int), - prevtx=sent(), - prevtx_vout=sent('tx_add_input.prevtx_vout', int), - script_sig=sent()), - - OneOf([expected_add_input, - Msg('tx_add_output', - channel_id=channel_id_v2(local_keyset), - serial_id=0, - sats=agreed_funding(Side.local), - script=funding_lockscript(local_funding_privkey)), - expected_add_output], - [expected_add_output, - Msg('tx_add_output', - channel_id=channel_id_v2(local_keyset), - serial_id=2, - sats=agreed_funding(Side.local), - script=funding_lockscript(local_funding_privkey)), - expected_add_input]), - - AddInput(funding=funding(), - serial_id=rcvd('tx_add_input.serial_id', int), - prevtx=rcvd('tx_add_input.prevtx'), - prevtx_vout=rcvd('tx_add_input.prevtx_vout', int), - script_sig=rcvd('tx_add_input.script_sig')), - - AddOutput(funding=funding(), - serial_id=rcvd('tx_add_output.serial_id', int), - sats=rcvd('tx_add_output.sats', int), - script=rcvd('tx_add_output.script')), - - AddOutput(funding=funding(), - serial_id=sent('tx_add_output.serial_id', int), - sats=sent('tx_add_output.sats', int), - script=sent('tx_add_output.script')), - - FinalizeFunding(funding=funding()), - - # Ignore unknown odd messages - TryAll([], RawMsg(bytes.fromhex('270F'))), - - Msg('tx_complete', - channel_id=channel_id_v2(local_keyset)), - - ExpectMsg('tx_complete', - channel_id=channel_id_v2(local_keyset)), - - Commit(funding=funding(), - opener=Side.local, - local_keyset=local_keyset, - local_to_self_delay=rcvd('accept_channel2.to_self_delay', int), - remote_to_self_delay=sent('open_channel2.to_self_delay', int), - local_amount=msat(sent('open_channel2.funding_satoshis', int)), - remote_amount=msat(rcvd('accept_channel2.funding_satoshis', int)), - local_dust_limit=546, - remote_dust_limit=546, - feerate=253, - local_features=sent('init.features'), - remote_features=rcvd('init.features')), - - # Ignore unknown odd messages - TryAll([], RawMsg(bytes.fromhex('270F'))), - - Msg('commitment_signed', - channel_id=channel_id_v2(local_keyset), - signature=commitsig_to_send(), - htlc_signature='[]'), - - # Ignore unknown odd messages - TryAll([], RawMsg(bytes.fromhex('270F'))), - - ExpectMsg('commitment_signed', - channel_id=channel_id_v2(local_keyset), - signature=commitsig_to_recv()), - - ExpectMsg('tx_signatures', - channel_id=channel_id_v2(local_keyset), - txid=funding_txid()), - - Msg('tx_signatures', - channel_id=channel_id_v2(local_keyset), - txid=funding_txid(), - witness_stack=witnesses()), - - AddWitnesses(funding=funding(), - witness_stack=rcvd('witness_stack')), - - # Mine the block + lock-in - Block(blockheight=103, number=3, txs=[funding_tx()]), - Msg('funding_locked', - channel_id=channel_id_v2(local_keyset), - next_per_commitment_point=local_keyset.per_commit_point(1)), - ExpectMsg('funding_locked', - channel_id=channel_id_v2(local_keyset), - next_per_commitment_point=remote_per_commitment_point(1)), - # Ignore unknown odd messages - TryAll([], RawMsg(bytes.fromhex('270F'))), - ] + expected_add_input = ExpectMsg( + "tx_add_input", + channel_id=channel_id_v2(local_keyset), + sequence=0xFFFFFFFD, + script_sig="", + if_match=odd_serial, + ) + + expected_add_output = ExpectMsg( + "tx_add_output", channel_id=channel_id_v2(local_keyset), if_match=odd_serial + ) + + test = [ + Block(blockheight=102, txs=[tx_spendable]), + Connect(connprivkey="02"), + ExpectMsg("init"), + # BOLT-f53ca2301232db780843e894f55d95d512f297f9 #9: + # | 28/29 | `option_dual_fund` | Use v2 of channel open, enables dual funding | IN9 | `option_anchor_outputs`, `option_static_remotekey` | [BOLT #2](02-peer-protocol.md) | + Msg("init", globalfeatures="", features=bitfield(12, 20, 29)), + DualFundAccept(), + # Accepter side: we initiate a new channel. + Msg( + "open_channel2", + channel_id=channel_id_tmp(local_keyset, Side.local), + chain_hash=regtest_hash, + funding_satoshis=funding_amount_for_utxo(input_index), + dust_limit_satoshis=546, + max_htlc_value_in_flight_msat=4294967295, + htlc_minimum_msat=0, + funding_feerate_perkw=253, + commitment_feerate_perkw=253, + # We use 5, because c-lightning runner uses 6, so this is different. + to_self_delay=5, + max_accepted_htlcs=483, + locktime=100, + 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_channel2", + channel_id=sent("open_channel2.channel_id"), + funding_satoshis=funding_amount_for_utxo(input_index), + 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), + ), + # Create and stash Funding object and FundingTx + CreateDualFunding( + fee=200, + funding_sats=agreed_funding(Side.local), + locktime=sent("open_channel2.locktime", int), + local_node_privkey="02", + local_funding_privkey=local_funding_privkey, + remote_node_privkey=runner.get_node_privkey(), + remote_funding_privkey=remote_funding_privkey(), + ), + # Ignore unknown odd messages + TryAll([], RawMsg(bytes.fromhex("270F"))), + Msg( + "tx_add_input", + channel_id=channel_id_v2(local_keyset), + serial_id=0, + sequence=0xFFFFFFFD, + prevtx=tx_spendable, + prevtx_vout=tx_out_for_index(input_index), + script_sig="", + ), + AddInput( + funding=funding(), + privkey=privkey_for_index(input_index), + serial_id=sent("tx_add_input.serial_id", int), + prevtx=sent(), + prevtx_vout=sent("tx_add_input.prevtx_vout", int), + script_sig=sent(), + ), + OneOf( + [ + expected_add_input, + Msg( + "tx_add_output", + channel_id=channel_id_v2(local_keyset), + serial_id=0, + sats=agreed_funding(Side.local), + script=funding_lockscript(local_funding_privkey), + ), + expected_add_output, + ], + [ + expected_add_output, + Msg( + "tx_add_output", + channel_id=channel_id_v2(local_keyset), + serial_id=2, + sats=agreed_funding(Side.local), + script=funding_lockscript(local_funding_privkey), + ), + expected_add_input, + ], + ), + AddInput( + funding=funding(), + serial_id=rcvd("tx_add_input.serial_id", int), + prevtx=rcvd("tx_add_input.prevtx"), + prevtx_vout=rcvd("tx_add_input.prevtx_vout", int), + script_sig=rcvd("tx_add_input.script_sig"), + ), + AddOutput( + funding=funding(), + serial_id=rcvd("tx_add_output.serial_id", int), + sats=rcvd("tx_add_output.sats", int), + script=rcvd("tx_add_output.script"), + ), + AddOutput( + funding=funding(), + serial_id=sent("tx_add_output.serial_id", int), + sats=sent("tx_add_output.sats", int), + script=sent("tx_add_output.script"), + ), + FinalizeFunding(funding=funding()), + # Ignore unknown odd messages + TryAll([], RawMsg(bytes.fromhex("270F"))), + Msg("tx_complete", channel_id=channel_id_v2(local_keyset)), + ExpectMsg("tx_complete", channel_id=channel_id_v2(local_keyset)), + Commit( + funding=funding(), + opener=Side.local, + local_keyset=local_keyset, + local_to_self_delay=rcvd("accept_channel2.to_self_delay", int), + remote_to_self_delay=sent("open_channel2.to_self_delay", int), + local_amount=msat(sent("open_channel2.funding_satoshis", int)), + remote_amount=msat(rcvd("accept_channel2.funding_satoshis", int)), + local_dust_limit=546, + remote_dust_limit=546, + feerate=253, + local_features=sent("init.features"), + remote_features=rcvd("init.features"), + ), + # Ignore unknown odd messages + TryAll([], RawMsg(bytes.fromhex("270F"))), + Msg( + "commitment_signed", + channel_id=channel_id_v2(local_keyset), + signature=commitsig_to_send(), + htlc_signature="[]", + ), + # Ignore unknown odd messages + TryAll([], RawMsg(bytes.fromhex("270F"))), + ExpectMsg( + "commitment_signed", + channel_id=channel_id_v2(local_keyset), + signature=commitsig_to_recv(), + ), + ExpectMsg( + "tx_signatures", channel_id=channel_id_v2(local_keyset), txid=funding_txid() + ), + Msg( + "tx_signatures", + channel_id=channel_id_v2(local_keyset), + txid=funding_txid(), + witness_stack=witnesses(), + ), + AddWitnesses(funding=funding(), witness_stack=rcvd("witness_stack")), + # Mine the block + lock-in + Block(blockheight=103, number=3, txs=[funding_tx()]), + Msg( + "funding_locked", + channel_id=channel_id_v2(local_keyset), + next_per_commitment_point=local_keyset.per_commit_point(1), + ), + ExpectMsg( + "funding_locked", + channel_id=channel_id_v2(local_keyset), + next_per_commitment_point=remote_per_commitment_point(1), + ), + # Ignore unknown odd messages + TryAll([], RawMsg(bytes.fromhex("270F"))), + ] runner.run(test) def test_open_opener_no_input(runner: Runner, with_proposal: Any) -> None: with_proposal(dual_fund_csv) - runner.add_startup_flag('experimental-dual-fund') - - local_funding_privkey = '20' - - local_keyset = KeySet(revocation_base_secret='21', - payment_base_secret='22', - htlc_base_secret='24', - delayed_payment_base_secret='23', - shachain_seed='00' * 32) - - test = [Block(blockheight=102, txs=[tx_spendable]), - Connect(connprivkey='02'), - ExpectMsg('init'), - - # BOLT-f53ca2301232db780843e894f55d95d512f297f9 #9: - # | 28/29 | `option_dual_fund` | Use v2 of channel open, enables dual funding | IN9 | `option_anchor_outputs`, `option_static_remotekey` | [BOLT #2](02-peer-protocol.md) | - Msg('init', globalfeatures='', features=bitfield(12, 20, 29)), - - FundChannel(amount=999877), - - ExpectMsg('open_channel2', - channel_id=channel_id_tmp(local_keyset, Side.remote), - chain_hash=regtest_hash, - funding_satoshis=999877, - dust_limit_satoshis=546, - htlc_minimum_msat=0, - 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), - channel_flags='01'), - - Msg('accept_channel2', - channel_id=rcvd('open_channel2.channel_id'), - dust_limit_satoshis=550, - funding_satoshis=0, - max_htlc_value_in_flight_msat=4294967295, - htlc_minimum_msat=0, - minimum_depth=3, - max_accepted_htlcs=483, - # We use 5, to be different from c-lightning runner who uses 6 - 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'))), - - # Create and stash Funding object and FundingTx - CreateDualFunding(fee=200, - funding_sats=agreed_funding(Side.remote), - locktime=rcvd('open_channel2.locktime', int), - local_node_privkey='02', - local_funding_privkey=local_funding_privkey, - remote_node_privkey=runner.get_node_privkey(), - remote_funding_privkey=remote_funding_privkey()), - - ExpectMsg('tx_add_input', - channel_id=channel_id_v2(local_keyset), - if_match=even_serial, - prevtx=tx_spendable, - sequence=0xfffffffd, - script_sig=''), - - AddInput(funding=funding(), - serial_id=rcvd('tx_add_input.serial_id', int), - prevtx=rcvd('tx_add_input.prevtx'), - prevtx_vout=rcvd('tx_add_input.prevtx_vout', int), - script_sig=rcvd('tx_add_input.script_sig')), - - # Ignore unknown odd messages - TryAll([], RawMsg(bytes.fromhex('270F'))), - - Msg('tx_complete', - channel_id=channel_id_v2(local_keyset)), - - # The funding output - ExpectMsg('tx_add_output', - channel_id=channel_id_v2(local_keyset), - sats=agreed_funding(Side.remote), - if_match=even_serial), - # FIXME: They may send us the funding output second, - # if there's also a change output - AddOutput(funding=funding(), - serial_id=rcvd('tx_add_output.serial_id', int), - sats=rcvd('tx_add_output.sats', int), - script=rcvd('tx_add_output.script')), - - Msg('tx_complete', - channel_id=channel_id_v2(local_keyset)), - - # Their change if they have one! - OneOf([ExpectMsg('tx_add_output', - if_match=even_serial, - channel_id=channel_id_v2(local_keyset)), - Msg('tx_complete', - channel_id=channel_id_v2(local_keyset)), - ExpectMsg('tx_complete', - channel_id=channel_id_v2(local_keyset)), - AddOutput(funding=funding(), - serial_id=rcvd('tx_add_output.serial_id', int), - sats=rcvd('tx_add_output.sats', int), - script=rcvd('tx_add_output.script'))], - [ExpectMsg('tx_complete', - channel_id=channel_id_v2(local_keyset))]), - - FinalizeFunding(funding=funding()), - - Commit(funding=funding(), - opener=Side.remote, - local_keyset=local_keyset, - local_to_self_delay=rcvd('open_channel2.to_self_delay', int), - remote_to_self_delay=sent('accept_channel2.to_self_delay', int), - local_amount=msat(sent('accept_channel2.funding_satoshis', int)), - remote_amount=msat(rcvd('open_channel2.funding_satoshis', int)), - local_dust_limit=550, - remote_dust_limit=546, - feerate=rcvd('open_channel2.commitment_feerate_perkw', int), - local_features=sent('init.features'), - remote_features=rcvd('init.features')), - - # Ignore unknown odd messages - TryAll([], RawMsg(bytes.fromhex('270F'))), - - ExpectMsg('commitment_signed', - channel_id=channel_id_v2(local_keyset), - signature=commitsig_to_recv()), - - # Ignore unknown odd messages - TryAll([], RawMsg(bytes.fromhex('270F'))), - - Msg('commitment_signed', - channel_id=channel_id_v2(local_keyset), - signature=commitsig_to_send(), - htlc_signature='[]'), - - Msg('tx_signatures', - channel_id=channel_id_v2(local_keyset), - txid=funding_txid(), - witness_stack=witnesses()), - - # Ignore unknown odd messages - TryAll([], RawMsg(bytes.fromhex('270F'))), - - ExpectMsg('tx_signatures', - channel_id=channel_id_v2(local_keyset), - txid=funding_txid()), - - AddWitnesses(funding=funding(), - witness_stack=rcvd('witness_stack')), - - TryAll([Msg('shutdown', - channel_id=channel_id_v2(local_keyset), - scriptpubkey='001473daa75958d5b2ddca87a6c279bb7cb307167037'), - # Ignore unknown odd messages - TryAll([], RawMsg(bytes.fromhex('270F'))), - ExpectMsg('shutdown', - channel_id=channel_id_v2(local_keyset)) - ], - [Block(blockheight=103, number=3, txs=[funding_tx()]), - ExpectMsg('funding_locked', - channel_id=channel_id_v2(local_keyset), - next_per_commitment_point=remote_per_commitment_point(1)), - # Ignore unknown odd messages - TryAll([], RawMsg(bytes.fromhex('270F'))), - ]), - ] + runner.add_startup_flag("experimental-dual-fund") + + local_funding_privkey = "20" + + local_keyset = KeySet( + revocation_base_secret="21", + payment_base_secret="22", + htlc_base_secret="24", + delayed_payment_base_secret="23", + shachain_seed="00" * 32, + ) + + test = [ + Block(blockheight=102, txs=[tx_spendable]), + Connect(connprivkey="02"), + ExpectMsg("init"), + # BOLT-f53ca2301232db780843e894f55d95d512f297f9 #9: + # | 28/29 | `option_dual_fund` | Use v2 of channel open, enables dual funding | IN9 | `option_anchor_outputs`, `option_static_remotekey` | [BOLT #2](02-peer-protocol.md) | + Msg("init", globalfeatures="", features=bitfield(12, 20, 29)), + FundChannel(amount=999877), + ExpectMsg( + "open_channel2", + channel_id=channel_id_tmp(local_keyset, Side.remote), + chain_hash=regtest_hash, + funding_satoshis=999877, + dust_limit_satoshis=546, + htlc_minimum_msat=0, + 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), + channel_flags="01", + ), + Msg( + "accept_channel2", + channel_id=rcvd("open_channel2.channel_id"), + dust_limit_satoshis=550, + funding_satoshis=0, + max_htlc_value_in_flight_msat=4294967295, + htlc_minimum_msat=0, + minimum_depth=3, + max_accepted_htlcs=483, + # We use 5, to be different from c-lightning runner who uses 6 + 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"))), + # Create and stash Funding object and FundingTx + CreateDualFunding( + fee=200, + funding_sats=agreed_funding(Side.remote), + locktime=rcvd("open_channel2.locktime", int), + local_node_privkey="02", + local_funding_privkey=local_funding_privkey, + remote_node_privkey=runner.get_node_privkey(), + remote_funding_privkey=remote_funding_privkey(), + ), + ExpectMsg( + "tx_add_input", + channel_id=channel_id_v2(local_keyset), + if_match=even_serial, + prevtx=tx_spendable, + sequence=0xFFFFFFFD, + script_sig="", + ), + AddInput( + funding=funding(), + serial_id=rcvd("tx_add_input.serial_id", int), + prevtx=rcvd("tx_add_input.prevtx"), + prevtx_vout=rcvd("tx_add_input.prevtx_vout", int), + script_sig=rcvd("tx_add_input.script_sig"), + ), + # Ignore unknown odd messages + TryAll([], RawMsg(bytes.fromhex("270F"))), + Msg("tx_complete", channel_id=channel_id_v2(local_keyset)), + # The funding output + ExpectMsg( + "tx_add_output", + channel_id=channel_id_v2(local_keyset), + sats=agreed_funding(Side.remote), + if_match=even_serial, + ), + # FIXME: They may send us the funding output second, + # if there's also a change output + AddOutput( + funding=funding(), + serial_id=rcvd("tx_add_output.serial_id", int), + sats=rcvd("tx_add_output.sats", int), + script=rcvd("tx_add_output.script"), + ), + Msg("tx_complete", channel_id=channel_id_v2(local_keyset)), + # Their change if they have one! + OneOf( + [ + ExpectMsg( + "tx_add_output", + if_match=even_serial, + channel_id=channel_id_v2(local_keyset), + ), + Msg("tx_complete", channel_id=channel_id_v2(local_keyset)), + ExpectMsg("tx_complete", channel_id=channel_id_v2(local_keyset)), + AddOutput( + funding=funding(), + serial_id=rcvd("tx_add_output.serial_id", int), + sats=rcvd("tx_add_output.sats", int), + script=rcvd("tx_add_output.script"), + ), + ], + [ExpectMsg("tx_complete", channel_id=channel_id_v2(local_keyset))], + ), + FinalizeFunding(funding=funding()), + Commit( + funding=funding(), + opener=Side.remote, + local_keyset=local_keyset, + local_to_self_delay=rcvd("open_channel2.to_self_delay", int), + remote_to_self_delay=sent("accept_channel2.to_self_delay", int), + local_amount=msat(sent("accept_channel2.funding_satoshis", int)), + remote_amount=msat(rcvd("open_channel2.funding_satoshis", int)), + local_dust_limit=550, + remote_dust_limit=546, + feerate=rcvd("open_channel2.commitment_feerate_perkw", int), + local_features=sent("init.features"), + remote_features=rcvd("init.features"), + ), + # Ignore unknown odd messages + TryAll([], RawMsg(bytes.fromhex("270F"))), + ExpectMsg( + "commitment_signed", + channel_id=channel_id_v2(local_keyset), + signature=commitsig_to_recv(), + ), + # Ignore unknown odd messages + TryAll([], RawMsg(bytes.fromhex("270F"))), + Msg( + "commitment_signed", + channel_id=channel_id_v2(local_keyset), + signature=commitsig_to_send(), + htlc_signature="[]", + ), + Msg( + "tx_signatures", + channel_id=channel_id_v2(local_keyset), + txid=funding_txid(), + witness_stack=witnesses(), + ), + # Ignore unknown odd messages + TryAll([], RawMsg(bytes.fromhex("270F"))), + ExpectMsg( + "tx_signatures", channel_id=channel_id_v2(local_keyset), txid=funding_txid() + ), + AddWitnesses(funding=funding(), witness_stack=rcvd("witness_stack")), + TryAll( + [ + Msg( + "shutdown", + channel_id=channel_id_v2(local_keyset), + scriptpubkey="001473daa75958d5b2ddca87a6c279bb7cb307167037", + ), + # Ignore unknown odd messages + TryAll([], RawMsg(bytes.fromhex("270F"))), + ExpectMsg("shutdown", channel_id=channel_id_v2(local_keyset)), + ], + [ + Block(blockheight=103, number=3, txs=[funding_tx()]), + ExpectMsg( + "funding_locked", + channel_id=channel_id_v2(local_keyset), + next_per_commitment_point=remote_per_commitment_point(1), + ), + # Ignore unknown odd messages + TryAll([], RawMsg(bytes.fromhex("270F"))), + ], + ), + ] runner.run(test) def test_open_opener_with_inputs(runner: Runner, with_proposal: Any) -> None: with_proposal(dual_fund_csv) - runner.add_startup_flag('experimental-dual-fund') + runner.add_startup_flag("experimental-dual-fund") - local_funding_privkey = '20' + local_funding_privkey = "20" - local_keyset = KeySet(revocation_base_secret='21', - payment_base_secret='22', - htlc_base_secret='24', - delayed_payment_base_secret='23', - shachain_seed='00' * 32) + local_keyset = KeySet( + revocation_base_secret="21", + payment_base_secret="22", + htlc_base_secret="24", + delayed_payment_base_secret="23", + shachain_seed="00" * 32, + ) # Index 5 is special, only the test runner can spend it ii = 5 - test = [Block(blockheight=102, txs=[tx_spendable]), - Connect(connprivkey='02'), - ExpectMsg('init'), - - # BOLT-f53ca2301232db780843e894f55d95d512f297f9 #9: - # | 28/29 | `option_dual_fund` | Use v2 of channel open, enables dual funding | IN9 | `option_anchor_outputs`, `option_static_remotekey` | [BOLT #2](02-peer-protocol.md) | - Msg('init', globalfeatures='', features=bitfield(12, 20, 29)), - - FundChannel(amount=999877), - - ExpectMsg('open_channel2', - channel_id=channel_id_tmp(local_keyset, Side.remote), - chain_hash=regtest_hash, - funding_satoshis=999877, - dust_limit_satoshis=546, - htlc_minimum_msat=0, - 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), - channel_flags='01'), - - Msg('accept_channel2', - channel_id=rcvd('open_channel2.channel_id'), - dust_limit_satoshis=550, - funding_satoshis=400000, - max_htlc_value_in_flight_msat=4294967295, - htlc_minimum_msat=0, - minimum_depth=3, - max_accepted_htlcs=483, - # We use 5, to be different from c-lightning runner who uses 6 - 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'))), - - # Create and stash Funding object and FundingTx - CreateDualFunding(fee=200, - funding_sats=agreed_funding(Side.remote), - locktime=rcvd('open_channel2.locktime', int), - local_node_privkey='02', - local_funding_privkey=local_funding_privkey, - remote_node_privkey=runner.get_node_privkey(), - remote_funding_privkey=remote_funding_privkey()), - - ExpectMsg('tx_add_input', - channel_id=channel_id_v2(local_keyset), - if_match=even_serial, - prevtx=tx_spendable, - sequence=0xfffffffd, - script_sig=''), - - AddInput(funding=funding(), - serial_id=rcvd('tx_add_input.serial_id', int), - prevtx=rcvd('tx_add_input.prevtx'), - prevtx_vout=rcvd('tx_add_input.prevtx_vout', int), - script_sig=rcvd('tx_add_input.script_sig')), - - # Ignore unknown odd messages - TryAll([], RawMsg(bytes.fromhex('270F'))), - - Msg('tx_add_input', - channel_id=channel_id_v2(local_keyset), - serial_id=1, - sequence=0xfffffffd, - prevtx=tx_spendable, - prevtx_vout=tx_out_for_index(ii), - script_sig=''), - - AddInput(funding=funding(), - privkey=privkey_for_index(ii), - serial_id=sent('tx_add_input.serial_id', int), - prevtx=sent(), - prevtx_vout=sent('tx_add_input.prevtx_vout', int), - script_sig=sent()), - - # The funding output - ExpectMsg('tx_add_output', - channel_id=channel_id_v2(local_keyset), - sats=agreed_funding(Side.remote), - if_match=even_serial), - - - Msg('tx_add_output', - channel_id=channel_id_v2(local_keyset), - serial_id=101, - sats=change_amount(Side.remote, False, - '001473daa75958d5b2ddca87a6c279bb7cb307167037', - funding_amount_for_utxo(ii)), - script='001473daa75958d5b2ddca87a6c279bb7cb307167037'), - - # FIXME: They may send us the funding output second, - # if there's also a change output - AddOutput(funding=funding(), - serial_id=rcvd('tx_add_output.serial_id', int), - sats=rcvd('tx_add_output.sats', int), - script=rcvd('tx_add_output.script')), - - AddOutput(funding=funding(), - serial_id=sent('tx_add_output.serial_id', int), - script=sent(), - sats=sent('tx_add_output.sats', int)), - - # Their change if they have one! - OneOf([ExpectMsg('tx_add_output', - if_match=even_serial, - channel_id=channel_id_v2(local_keyset)), - Msg('tx_complete', - channel_id=channel_id_v2(local_keyset)), - ExpectMsg('tx_complete', - channel_id=channel_id_v2(local_keyset)), - AddOutput(funding=funding(), - serial_id=rcvd('tx_add_output.serial_id', int), - sats=rcvd('tx_add_output.sats', int), - script=rcvd('tx_add_output.script'))], - [ExpectMsg('tx_complete', - channel_id=channel_id_v2(local_keyset)), - Msg('tx_complete', - channel_id=channel_id_v2(local_keyset)), - ]), - - FinalizeFunding(funding=funding()), - - Commit(funding=funding(), - opener=Side.remote, - local_keyset=local_keyset, - local_to_self_delay=rcvd('open_channel2.to_self_delay', int), - remote_to_self_delay=sent('accept_channel2.to_self_delay', int), - local_amount=msat(sent('accept_channel2.funding_satoshis', int)), - remote_amount=msat(rcvd('open_channel2.funding_satoshis', int)), - local_dust_limit=550, - remote_dust_limit=546, - feerate=rcvd('open_channel2.commitment_feerate_perkw', int), - local_features=sent('init.features'), - remote_features=rcvd('init.features')), - - # Ignore unknown odd messages - TryAll([], RawMsg(bytes.fromhex('270F'))), - - ExpectMsg('commitment_signed', - channel_id=channel_id_v2(local_keyset), - signature=commitsig_to_recv()), - - # Ignore unknown odd messages - TryAll([], RawMsg(bytes.fromhex('270F'))), - - Msg('commitment_signed', - channel_id=channel_id_v2(local_keyset), - signature=commitsig_to_send(), - htlc_signature='[]'), - - Msg('tx_signatures', - channel_id=channel_id_v2(local_keyset), - txid=funding_txid(), - witness_stack=witnesses()), - - # Ignore unknown odd messages - TryAll([], RawMsg(bytes.fromhex('270F'))), - - ExpectMsg('tx_signatures', - channel_id=channel_id_v2(local_keyset), - txid=funding_txid()), - - AddWitnesses(funding=funding(), - witness_stack=rcvd('witness_stack')), - - # Mine the block! - Block(blockheight=103, number=3, txs=[funding_tx()]), - ExpectMsg('funding_locked', - channel_id=channel_id_v2(local_keyset), - next_per_commitment_point=remote_per_commitment_point(1)), - # Ignore unknown odd messages - TryAll([], RawMsg(bytes.fromhex('270F'))), - ] + test = [ + Block(blockheight=102, txs=[tx_spendable]), + Connect(connprivkey="02"), + ExpectMsg("init"), + # BOLT-f53ca2301232db780843e894f55d95d512f297f9 #9: + # | 28/29 | `option_dual_fund` | Use v2 of channel open, enables dual funding | IN9 | `option_anchor_outputs`, `option_static_remotekey` | [BOLT #2](02-peer-protocol.md) | + Msg("init", globalfeatures="", features=bitfield(12, 20, 29)), + FundChannel(amount=999877), + ExpectMsg( + "open_channel2", + channel_id=channel_id_tmp(local_keyset, Side.remote), + chain_hash=regtest_hash, + funding_satoshis=999877, + dust_limit_satoshis=546, + htlc_minimum_msat=0, + 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), + channel_flags="01", + ), + Msg( + "accept_channel2", + channel_id=rcvd("open_channel2.channel_id"), + dust_limit_satoshis=550, + funding_satoshis=400000, + max_htlc_value_in_flight_msat=4294967295, + htlc_minimum_msat=0, + minimum_depth=3, + max_accepted_htlcs=483, + # We use 5, to be different from c-lightning runner who uses 6 + 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"))), + # Create and stash Funding object and FundingTx + CreateDualFunding( + fee=200, + funding_sats=agreed_funding(Side.remote), + locktime=rcvd("open_channel2.locktime", int), + local_node_privkey="02", + local_funding_privkey=local_funding_privkey, + remote_node_privkey=runner.get_node_privkey(), + remote_funding_privkey=remote_funding_privkey(), + ), + ExpectMsg( + "tx_add_input", + channel_id=channel_id_v2(local_keyset), + if_match=even_serial, + prevtx=tx_spendable, + sequence=0xFFFFFFFD, + script_sig="", + ), + AddInput( + funding=funding(), + serial_id=rcvd("tx_add_input.serial_id", int), + prevtx=rcvd("tx_add_input.prevtx"), + prevtx_vout=rcvd("tx_add_input.prevtx_vout", int), + script_sig=rcvd("tx_add_input.script_sig"), + ), + # Ignore unknown odd messages + TryAll([], RawMsg(bytes.fromhex("270F"))), + Msg( + "tx_add_input", + channel_id=channel_id_v2(local_keyset), + serial_id=1, + sequence=0xFFFFFFFD, + prevtx=tx_spendable, + prevtx_vout=tx_out_for_index(ii), + script_sig="", + ), + AddInput( + funding=funding(), + privkey=privkey_for_index(ii), + serial_id=sent("tx_add_input.serial_id", int), + prevtx=sent(), + prevtx_vout=sent("tx_add_input.prevtx_vout", int), + script_sig=sent(), + ), + # The funding output + ExpectMsg( + "tx_add_output", + channel_id=channel_id_v2(local_keyset), + sats=agreed_funding(Side.remote), + if_match=even_serial, + ), + Msg( + "tx_add_output", + channel_id=channel_id_v2(local_keyset), + serial_id=101, + sats=change_amount( + Side.remote, + False, + "001473daa75958d5b2ddca87a6c279bb7cb307167037", + funding_amount_for_utxo(ii), + ), + script="001473daa75958d5b2ddca87a6c279bb7cb307167037", + ), + # FIXME: They may send us the funding output second, + # if there's also a change output + AddOutput( + funding=funding(), + serial_id=rcvd("tx_add_output.serial_id", int), + sats=rcvd("tx_add_output.sats", int), + script=rcvd("tx_add_output.script"), + ), + AddOutput( + funding=funding(), + serial_id=sent("tx_add_output.serial_id", int), + script=sent(), + sats=sent("tx_add_output.sats", int), + ), + # Their change if they have one! + OneOf( + [ + ExpectMsg( + "tx_add_output", + if_match=even_serial, + channel_id=channel_id_v2(local_keyset), + ), + Msg("tx_complete", channel_id=channel_id_v2(local_keyset)), + ExpectMsg("tx_complete", channel_id=channel_id_v2(local_keyset)), + AddOutput( + funding=funding(), + serial_id=rcvd("tx_add_output.serial_id", int), + sats=rcvd("tx_add_output.sats", int), + script=rcvd("tx_add_output.script"), + ), + ], + [ + ExpectMsg("tx_complete", channel_id=channel_id_v2(local_keyset)), + Msg("tx_complete", channel_id=channel_id_v2(local_keyset)), + ], + ), + FinalizeFunding(funding=funding()), + Commit( + funding=funding(), + opener=Side.remote, + local_keyset=local_keyset, + local_to_self_delay=rcvd("open_channel2.to_self_delay", int), + remote_to_self_delay=sent("accept_channel2.to_self_delay", int), + local_amount=msat(sent("accept_channel2.funding_satoshis", int)), + remote_amount=msat(rcvd("open_channel2.funding_satoshis", int)), + local_dust_limit=550, + remote_dust_limit=546, + feerate=rcvd("open_channel2.commitment_feerate_perkw", int), + local_features=sent("init.features"), + remote_features=rcvd("init.features"), + ), + # Ignore unknown odd messages + TryAll([], RawMsg(bytes.fromhex("270F"))), + ExpectMsg( + "commitment_signed", + channel_id=channel_id_v2(local_keyset), + signature=commitsig_to_recv(), + ), + # Ignore unknown odd messages + TryAll([], RawMsg(bytes.fromhex("270F"))), + Msg( + "commitment_signed", + channel_id=channel_id_v2(local_keyset), + signature=commitsig_to_send(), + htlc_signature="[]", + ), + Msg( + "tx_signatures", + channel_id=channel_id_v2(local_keyset), + txid=funding_txid(), + witness_stack=witnesses(), + ), + # Ignore unknown odd messages + TryAll([], RawMsg(bytes.fromhex("270F"))), + ExpectMsg( + "tx_signatures", channel_id=channel_id_v2(local_keyset), txid=funding_txid() + ), + AddWitnesses(funding=funding(), witness_stack=rcvd("witness_stack")), + # Mine the block! + Block(blockheight=103, number=3, txs=[funding_tx()]), + ExpectMsg( + "funding_locked", + channel_id=channel_id_v2(local_keyset), + next_per_commitment_point=remote_per_commitment_point(1), + ), + # Ignore unknown odd messages + TryAll([], RawMsg(bytes.fromhex("270F"))), + ] runner.run(test) def test_df_accepter_opener_underpays_fees(runner: Runner, with_proposal: Any) -> None: with_proposal(dual_fund_csv) - runner.add_startup_flag('experimental-dual-fund') + runner.add_startup_flag("experimental-dual-fund") - local_funding_privkey = '20' + local_funding_privkey = "20" - local_keyset = KeySet(revocation_base_secret='21', - payment_base_secret='22', - htlc_base_secret='24', - delayed_payment_base_secret='23', - shachain_seed='00' * 32) + local_keyset = KeySet( + revocation_base_secret="21", + payment_base_secret="22", + htlc_base_secret="24", + delayed_payment_base_secret="23", + shachain_seed="00" * 32, + ) # Index 6 is special, it's a 1of5 multisig script that # only the test runner can spend @@ -970,744 +1051,809 @@ def test_df_accepter_opener_underpays_fees(runner: Runner, with_proposal: Any) - # Since technically these can be sent in any order, # we must specify this as ok! - expected_add_input = ExpectMsg('tx_add_input', - channel_id=channel_id_v2(local_keyset), - sequence=0xfffffffd, - script_sig='', - if_match=odd_serial) + expected_add_input = ExpectMsg( + "tx_add_input", + channel_id=channel_id_v2(local_keyset), + sequence=0xFFFFFFFD, + script_sig="", + if_match=odd_serial, + ) funding_amount = 100000 - test = [Block(blockheight=102, txs=[tx_spendable]), - Connect(connprivkey='02'), - ExpectMsg('init'), - - # BOLT-f53ca2301232db780843e894f55d95d512f297f9 #9: - # | 28/29 | `option_dual_fund` | Use v2 of channel open, enables dual funding | IN9 | `option_anchor_outputs`, `option_static_remotekey` | [BOLT #2](02-peer-protocol.md) | - Msg('init', globalfeatures='', features=bitfield(12, 20, 29)), - - DualFundAccept(), - - # Accepter side: we initiate a new channel. - Msg('open_channel2', - channel_id=channel_id_tmp(local_keyset, Side.local), - chain_hash=regtest_hash, - # Leave some room for a change output - funding_satoshis=funding_amount, - dust_limit_satoshis=546, - max_htlc_value_in_flight_msat=4294967295, - htlc_minimum_msat=0, - funding_feerate_perkw=1000, - commitment_feerate_perkw=253, - # We use 5, because c-lightning runner uses 6, so this is different. - to_self_delay=5, - max_accepted_htlcs=483, - locktime=100, - 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_channel2', - channel_id=sent('channel_id'), - funding_satoshis=funding_amount, - 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)), - - # Create and stash Funding object and FundingTx - CreateDualFunding(fee=200, - funding_sats=agreed_funding(Side.local), - locktime=sent('open_channel2.locktime', int), - local_node_privkey='02', - local_funding_privkey=local_funding_privkey, - remote_node_privkey=runner.get_node_privkey(), - remote_funding_privkey=remote_funding_privkey()), - - # Ignore unknown odd messages - TryAll([], RawMsg(bytes.fromhex('270F'))), - - Msg('tx_add_input', - channel_id=channel_id_v2(local_keyset), - serial_id=0, - sequence=0xfffffffd, - prevtx=tx_spendable, - prevtx_vout=tx_out_for_index(input_index), - script_sig=''), - - AddInput(funding=funding(), - privkey=privkey_for_index(input_index), - serial_id=sent('tx_add_input.serial_id', int), - prevtx=sent(), - prevtx_vout=sent('tx_add_input.prevtx_vout', int), - script_sig=sent()), - - expected_add_input, - - AddInput(funding=funding(), - serial_id=rcvd('tx_add_input.serial_id', int), - prevtx=rcvd('tx_add_input.prevtx'), - prevtx_vout=rcvd('tx_add_input.prevtx_vout', int), - script_sig=rcvd('tx_add_input.script_sig')), - - Msg('tx_add_output', - channel_id=channel_id_v2(local_keyset), - serial_id=0, - sats=agreed_funding(Side.local), - script=funding_lockscript(local_funding_privkey)), - - AddOutput(funding=funding(), - serial_id=sent('tx_add_output.serial_id', int), - sats=sent('tx_add_output.sats', int), - script=sent('tx_add_output.script')), - - ExpectMsg('tx_add_output', - channel_id=channel_id_v2(local_keyset), - if_match=odd_serial), - - AddOutput(funding=funding(), - serial_id=rcvd('tx_add_output.serial_id', int), - sats=rcvd('tx_add_output.sats', int), - script=rcvd('tx_add_output.script')), - - Msg('tx_add_output', - channel_id=channel_id_v2(local_keyset), - serial_id=2, - # This function is the key to this test. - # `change_amount` uses a P2WPKH weight to calculate - # the input's fees; for this test we're using the - # magic `index_input = 6`, which is a large - # P2WSH-multisig address. - sats=change_amount(Side.local, True, - '001473daa75958d5b2ddca87a6c279bb7cb307167037', - utxo_amount(input_index)), - script='001473daa75958d5b2ddca87a6c279bb7cb307167037'), - - AddOutput(funding=funding(), - serial_id=sent('tx_add_output.serial_id', int), - script=sent(), - sats=sent('tx_add_output.sats', int)), - - - FinalizeFunding(funding=funding()), - - # Ignore unknown odd messages - TryAll([], RawMsg(bytes.fromhex('270F'))), - - Msg('tx_complete', - channel_id=channel_id_v2(local_keyset)), - - ExpectMsg('tx_complete', - channel_id=channel_id_v2(local_keyset)), - - Commit(funding=funding(), - opener=Side.local, - local_keyset=local_keyset, - local_to_self_delay=rcvd('accept_channel2.to_self_delay', int), - remote_to_self_delay=sent('open_channel2.to_self_delay', int), - local_amount=msat(sent('open_channel2.funding_satoshis', int)), - remote_amount=msat(rcvd('accept_channel2.funding_satoshis', int)), - local_dust_limit=546, - remote_dust_limit=546, - feerate=253, - local_features=sent('init.features'), - remote_features=rcvd('init.features')), - - # Ignore unknown odd messages - TryAll([], RawMsg(bytes.fromhex('270F'))), - - Msg('commitment_signed', - channel_id=channel_id_v2(local_keyset), - signature=commitsig_to_send(), - htlc_signature='[]'), - - # Ignore unknown odd messages - TryAll([], RawMsg(bytes.fromhex('270F'))), - - ExpectMsg('commitment_signed', - channel_id=channel_id_v2(local_keyset), - signature=commitsig_to_recv()), - - ExpectMsg('tx_signatures', - channel_id=channel_id_v2(local_keyset), - txid=funding_txid()), - - Msg('tx_signatures', - channel_id=channel_id_v2(local_keyset), - txid=funding_txid(), - witness_stack=witnesses()), - - ExpectError() - ] + test = [ + Block(blockheight=102, txs=[tx_spendable]), + Connect(connprivkey="02"), + ExpectMsg("init"), + # BOLT-f53ca2301232db780843e894f55d95d512f297f9 #9: + # | 28/29 | `option_dual_fund` | Use v2 of channel open, enables dual funding | IN9 | `option_anchor_outputs`, `option_static_remotekey` | [BOLT #2](02-peer-protocol.md) | + Msg("init", globalfeatures="", features=bitfield(12, 20, 29)), + DualFundAccept(), + # Accepter side: we initiate a new channel. + Msg( + "open_channel2", + channel_id=channel_id_tmp(local_keyset, Side.local), + chain_hash=regtest_hash, + # Leave some room for a change output + funding_satoshis=funding_amount, + dust_limit_satoshis=546, + max_htlc_value_in_flight_msat=4294967295, + htlc_minimum_msat=0, + funding_feerate_perkw=1000, + commitment_feerate_perkw=253, + # We use 5, because c-lightning runner uses 6, so this is different. + to_self_delay=5, + max_accepted_htlcs=483, + locktime=100, + 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_channel2", + channel_id=sent("channel_id"), + funding_satoshis=funding_amount, + 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), + ), + # Create and stash Funding object and FundingTx + CreateDualFunding( + fee=200, + funding_sats=agreed_funding(Side.local), + locktime=sent("open_channel2.locktime", int), + local_node_privkey="02", + local_funding_privkey=local_funding_privkey, + remote_node_privkey=runner.get_node_privkey(), + remote_funding_privkey=remote_funding_privkey(), + ), + # Ignore unknown odd messages + TryAll([], RawMsg(bytes.fromhex("270F"))), + Msg( + "tx_add_input", + channel_id=channel_id_v2(local_keyset), + serial_id=0, + sequence=0xFFFFFFFD, + prevtx=tx_spendable, + prevtx_vout=tx_out_for_index(input_index), + script_sig="", + ), + AddInput( + funding=funding(), + privkey=privkey_for_index(input_index), + serial_id=sent("tx_add_input.serial_id", int), + prevtx=sent(), + prevtx_vout=sent("tx_add_input.prevtx_vout", int), + script_sig=sent(), + ), + expected_add_input, + AddInput( + funding=funding(), + serial_id=rcvd("tx_add_input.serial_id", int), + prevtx=rcvd("tx_add_input.prevtx"), + prevtx_vout=rcvd("tx_add_input.prevtx_vout", int), + script_sig=rcvd("tx_add_input.script_sig"), + ), + Msg( + "tx_add_output", + channel_id=channel_id_v2(local_keyset), + serial_id=0, + sats=agreed_funding(Side.local), + script=funding_lockscript(local_funding_privkey), + ), + AddOutput( + funding=funding(), + serial_id=sent("tx_add_output.serial_id", int), + sats=sent("tx_add_output.sats", int), + script=sent("tx_add_output.script"), + ), + ExpectMsg( + "tx_add_output", channel_id=channel_id_v2(local_keyset), if_match=odd_serial + ), + AddOutput( + funding=funding(), + serial_id=rcvd("tx_add_output.serial_id", int), + sats=rcvd("tx_add_output.sats", int), + script=rcvd("tx_add_output.script"), + ), + Msg( + "tx_add_output", + channel_id=channel_id_v2(local_keyset), + serial_id=2, + # This function is the key to this test. + # `change_amount` uses a P2WPKH weight to calculate + # the input's fees; for this test we're using the + # magic `index_input = 6`, which is a large + # P2WSH-multisig address. + sats=change_amount( + Side.local, + True, + "001473daa75958d5b2ddca87a6c279bb7cb307167037", + utxo_amount(input_index), + ), + script="001473daa75958d5b2ddca87a6c279bb7cb307167037", + ), + AddOutput( + funding=funding(), + serial_id=sent("tx_add_output.serial_id", int), + script=sent(), + sats=sent("tx_add_output.sats", int), + ), + FinalizeFunding(funding=funding()), + # Ignore unknown odd messages + TryAll([], RawMsg(bytes.fromhex("270F"))), + Msg("tx_complete", channel_id=channel_id_v2(local_keyset)), + ExpectMsg("tx_complete", channel_id=channel_id_v2(local_keyset)), + Commit( + funding=funding(), + opener=Side.local, + local_keyset=local_keyset, + local_to_self_delay=rcvd("accept_channel2.to_self_delay", int), + remote_to_self_delay=sent("open_channel2.to_self_delay", int), + local_amount=msat(sent("open_channel2.funding_satoshis", int)), + remote_amount=msat(rcvd("accept_channel2.funding_satoshis", int)), + local_dust_limit=546, + remote_dust_limit=546, + feerate=253, + local_features=sent("init.features"), + remote_features=rcvd("init.features"), + ), + # Ignore unknown odd messages + TryAll([], RawMsg(bytes.fromhex("270F"))), + Msg( + "commitment_signed", + channel_id=channel_id_v2(local_keyset), + signature=commitsig_to_send(), + htlc_signature="[]", + ), + # Ignore unknown odd messages + TryAll([], RawMsg(bytes.fromhex("270F"))), + ExpectMsg( + "commitment_signed", + channel_id=channel_id_v2(local_keyset), + signature=commitsig_to_recv(), + ), + ExpectMsg( + "tx_signatures", channel_id=channel_id_v2(local_keyset), txid=funding_txid() + ), + Msg( + "tx_signatures", + channel_id=channel_id_v2(local_keyset), + txid=funding_txid(), + witness_stack=witnesses(), + ), + ExpectError(), + ] runner.run(test) def test_df_opener_accepter_underpays_fees(runner: Runner, with_proposal: Any) -> None: with_proposal(dual_fund_csv) - runner.add_startup_flag('experimental-dual-fund') + runner.add_startup_flag("experimental-dual-fund") - local_funding_privkey = '20' + local_funding_privkey = "20" - local_keyset = KeySet(revocation_base_secret='21', - payment_base_secret='22', - htlc_base_secret='24', - delayed_payment_base_secret='23', - shachain_seed='00' * 32) + local_keyset = KeySet( + revocation_base_secret="21", + payment_base_secret="22", + htlc_base_secret="24", + delayed_payment_base_secret="23", + shachain_seed="00" * 32, + ) # Index 6 is special, only the test runner can spend it # 6 is a 1 of 5 multisig, meant to test the witness fee calculations input_index = 6 - test = [Block(blockheight=102, txs=[tx_spendable]), - Connect(connprivkey='02'), - ExpectMsg('init'), - - # BOLT-f53ca2301232db780843e894f55d95d512f297f9 #9: - # | 28/29 | `option_dual_fund` | Use v2 of channel open, enables dual funding | IN9 | `option_anchor_outputs`, `option_static_remotekey` | [BOLT #2](02-peer-protocol.md) | - Msg('init', globalfeatures='', features=bitfield(12, 20, 29)), - - FundChannel(amount=900000, feerate=1000, expect_fail=True), - - ExpectMsg('open_channel2', - channel_id=channel_id_tmp(local_keyset, Side.remote), - chain_hash=regtest_hash, - funding_satoshis=900000, - dust_limit_satoshis=546, - htlc_minimum_msat=0, - to_self_delay=6, - funding_feerate_perkw=1000, - 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), - channel_flags='01'), - - Msg('accept_channel2', - channel_id=rcvd('channel_id'), - dust_limit_satoshis=550, - funding_satoshis=400000, - max_htlc_value_in_flight_msat=4294967295, - htlc_minimum_msat=0, - minimum_depth=3, - max_accepted_htlcs=483, - # We use 5, to be different from c-lightning runner who uses 6 - 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)), - - # Create and stash Funding object and FundingTx - CreateDualFunding(fee=200, - funding_sats=agreed_funding(Side.remote), - locktime=rcvd('open_channel2.locktime', int), - local_node_privkey='02', - local_funding_privkey=local_funding_privkey, - remote_node_privkey=runner.get_node_privkey(), - remote_funding_privkey=remote_funding_privkey()), - - ExpectMsg('tx_add_input', - channel_id=channel_id_v2(local_keyset), - if_match=even_serial, - prevtx=tx_spendable, - sequence=0xfffffffd, - script_sig=''), - - AddInput(funding=funding(), - serial_id=rcvd('tx_add_input.serial_id', int), - prevtx=rcvd('tx_add_input.prevtx'), - prevtx_vout=rcvd('tx_add_input.prevtx_vout', int), - script_sig=rcvd('tx_add_input.script_sig')), - - Msg('tx_add_input', - channel_id=channel_id_v2(local_keyset), - serial_id=1, - sequence=0xfffffffd, - prevtx=tx_spendable, - prevtx_vout=tx_out_for_index(input_index), - script_sig=''), - - AddInput(funding=funding(), - privkey=privkey_for_index(input_index), - serial_id=sent('tx_add_input.serial_id', int), - prevtx=sent(), - prevtx_vout=sent('tx_add_input.prevtx_vout', int), - script_sig=sent()), - - # The funding output - ExpectMsg('tx_add_output', - channel_id=channel_id_v2(local_keyset), - sats=agreed_funding(Side.remote), - if_match=even_serial), - - Msg('tx_add_output', - channel_id=channel_id_v2(local_keyset), - serial_id=101, - # This function is the key to this test. - # `change_amount` uses a P2WPKH weight to calculate - # the input's fees; for this test we're using the - # magic `index_input = 6`, which is a large - # P2WSH-multisig address. - sats=change_amount(Side.remote, False, - '001473daa75958d5b2ddca87a6c279bb7cb307167037', - utxo_amount(input_index)), - script='001473daa75958d5b2ddca87a6c279bb7cb307167037'), - - # FIXME: They may send us the funding output second, - # if there's also a change output - AddOutput(funding=funding(), - serial_id=rcvd('tx_add_output.serial_id', int), - sats=rcvd('tx_add_output.sats', int), - script=rcvd('tx_add_output.script')), - - AddOutput(funding=funding(), - serial_id=sent('tx_add_output.serial_id', int), - script=sent(), - sats=sent('tx_add_output.sats', int)), - - # Their change if they have one! - OneOf([ExpectMsg('tx_add_output', - if_match=even_serial, - channel_id=channel_id_v2(local_keyset)), - Msg('tx_complete', - channel_id=channel_id_v2(local_keyset)), - ExpectMsg('tx_complete', - channel_id=channel_id_v2(local_keyset)), - AddOutput(funding=funding(), - serial_id=rcvd('tx_add_output.serial_id', int), - sats=rcvd('tx_add_output.sats', int), - script=rcvd('tx_add_output.script'))], - [ExpectMsg('tx_complete', - channel_id=channel_id_v2(local_keyset)), - Msg('tx_complete', - channel_id=channel_id_v2(local_keyset)), - ]), - - FinalizeFunding(funding=funding()), - - Commit(funding=funding(), - opener=Side.remote, - local_keyset=local_keyset, - local_to_self_delay=rcvd('open_channel2.to_self_delay', int), - remote_to_self_delay=sent('accept_channel2.to_self_delay', int), - local_amount=msat(sent('accept_channel2.funding_satoshis', int)), - remote_amount=msat(rcvd('open_channel2.funding_satoshis', int)), - local_dust_limit=550, - remote_dust_limit=546, - feerate=rcvd('open_channel2.commitment_feerate_perkw', int), - local_features=sent('init.features'), - remote_features=rcvd('init.features')), - - ExpectMsg('commitment_signed', - channel_id=channel_id_v2(local_keyset), - signature=commitsig_to_recv()), - - Msg('commitment_signed', - channel_id=channel_id_v2(local_keyset), - signature=commitsig_to_send(), - htlc_signature='[]'), - - Msg('tx_signatures', - channel_id=channel_id_v2(local_keyset), - txid=funding_txid(), - witness_stack=witnesses()), - - ExpectError() - ] + test = [ + Block(blockheight=102, txs=[tx_spendable]), + Connect(connprivkey="02"), + ExpectMsg("init"), + # BOLT-f53ca2301232db780843e894f55d95d512f297f9 #9: + # | 28/29 | `option_dual_fund` | Use v2 of channel open, enables dual funding | IN9 | `option_anchor_outputs`, `option_static_remotekey` | [BOLT #2](02-peer-protocol.md) | + Msg("init", globalfeatures="", features=bitfield(12, 20, 29)), + FundChannel(amount=900000, feerate=1000, expect_fail=True), + ExpectMsg( + "open_channel2", + channel_id=channel_id_tmp(local_keyset, Side.remote), + chain_hash=regtest_hash, + funding_satoshis=900000, + dust_limit_satoshis=546, + htlc_minimum_msat=0, + to_self_delay=6, + funding_feerate_perkw=1000, + 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), + channel_flags="01", + ), + Msg( + "accept_channel2", + channel_id=rcvd("channel_id"), + dust_limit_satoshis=550, + funding_satoshis=400000, + max_htlc_value_in_flight_msat=4294967295, + htlc_minimum_msat=0, + minimum_depth=3, + max_accepted_htlcs=483, + # We use 5, to be different from c-lightning runner who uses 6 + 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), + ), + # Create and stash Funding object and FundingTx + CreateDualFunding( + fee=200, + funding_sats=agreed_funding(Side.remote), + locktime=rcvd("open_channel2.locktime", int), + local_node_privkey="02", + local_funding_privkey=local_funding_privkey, + remote_node_privkey=runner.get_node_privkey(), + remote_funding_privkey=remote_funding_privkey(), + ), + ExpectMsg( + "tx_add_input", + channel_id=channel_id_v2(local_keyset), + if_match=even_serial, + prevtx=tx_spendable, + sequence=0xFFFFFFFD, + script_sig="", + ), + AddInput( + funding=funding(), + serial_id=rcvd("tx_add_input.serial_id", int), + prevtx=rcvd("tx_add_input.prevtx"), + prevtx_vout=rcvd("tx_add_input.prevtx_vout", int), + script_sig=rcvd("tx_add_input.script_sig"), + ), + Msg( + "tx_add_input", + channel_id=channel_id_v2(local_keyset), + serial_id=1, + sequence=0xFFFFFFFD, + prevtx=tx_spendable, + prevtx_vout=tx_out_for_index(input_index), + script_sig="", + ), + AddInput( + funding=funding(), + privkey=privkey_for_index(input_index), + serial_id=sent("tx_add_input.serial_id", int), + prevtx=sent(), + prevtx_vout=sent("tx_add_input.prevtx_vout", int), + script_sig=sent(), + ), + # The funding output + ExpectMsg( + "tx_add_output", + channel_id=channel_id_v2(local_keyset), + sats=agreed_funding(Side.remote), + if_match=even_serial, + ), + Msg( + "tx_add_output", + channel_id=channel_id_v2(local_keyset), + serial_id=101, + # This function is the key to this test. + # `change_amount` uses a P2WPKH weight to calculate + # the input's fees; for this test we're using the + # magic `index_input = 6`, which is a large + # P2WSH-multisig address. + sats=change_amount( + Side.remote, + False, + "001473daa75958d5b2ddca87a6c279bb7cb307167037", + utxo_amount(input_index), + ), + script="001473daa75958d5b2ddca87a6c279bb7cb307167037", + ), + # FIXME: They may send us the funding output second, + # if there's also a change output + AddOutput( + funding=funding(), + serial_id=rcvd("tx_add_output.serial_id", int), + sats=rcvd("tx_add_output.sats", int), + script=rcvd("tx_add_output.script"), + ), + AddOutput( + funding=funding(), + serial_id=sent("tx_add_output.serial_id", int), + script=sent(), + sats=sent("tx_add_output.sats", int), + ), + # Their change if they have one! + OneOf( + [ + ExpectMsg( + "tx_add_output", + if_match=even_serial, + channel_id=channel_id_v2(local_keyset), + ), + Msg("tx_complete", channel_id=channel_id_v2(local_keyset)), + ExpectMsg("tx_complete", channel_id=channel_id_v2(local_keyset)), + AddOutput( + funding=funding(), + serial_id=rcvd("tx_add_output.serial_id", int), + sats=rcvd("tx_add_output.sats", int), + script=rcvd("tx_add_output.script"), + ), + ], + [ + ExpectMsg("tx_complete", channel_id=channel_id_v2(local_keyset)), + Msg("tx_complete", channel_id=channel_id_v2(local_keyset)), + ], + ), + FinalizeFunding(funding=funding()), + Commit( + funding=funding(), + opener=Side.remote, + local_keyset=local_keyset, + local_to_self_delay=rcvd("open_channel2.to_self_delay", int), + remote_to_self_delay=sent("accept_channel2.to_self_delay", int), + local_amount=msat(sent("accept_channel2.funding_satoshis", int)), + remote_amount=msat(rcvd("open_channel2.funding_satoshis", int)), + local_dust_limit=550, + remote_dust_limit=546, + feerate=rcvd("open_channel2.commitment_feerate_perkw", int), + local_features=sent("init.features"), + remote_features=rcvd("init.features"), + ), + ExpectMsg( + "commitment_signed", + channel_id=channel_id_v2(local_keyset), + signature=commitsig_to_recv(), + ), + Msg( + "commitment_signed", + channel_id=channel_id_v2(local_keyset), + signature=commitsig_to_send(), + htlc_signature="[]", + ), + Msg( + "tx_signatures", + channel_id=channel_id_v2(local_keyset), + txid=funding_txid(), + witness_stack=witnesses(), + ), + ExpectError(), + ] runner.run(test) -def accepter_tx_creation(input_index: int, is_rbf: bool, funding_amt: int, - local_funding_privkey: str, - local_keyset: KeySet, - runner: Runner) -> List[Event]: - """ Repeated tx construction protocols, for accepter tests """ +def accepter_tx_creation( + input_index: int, + is_rbf: bool, + funding_amt: int, + local_funding_privkey: str, + local_keyset: KeySet, + runner: Runner, +) -> List[Event]: + """Repeated tx construction protocols, for accepter tests""" txid_in, tx_index_in, sats_in, spending_privkey, fee = utxo(input_index) fee = sats_in - funding_amt if is_rbf else fee - open_msg = 'init_rbf' if is_rbf else 'open_channel2' - accept_msg = 'ack_rbf' if is_rbf else 'accept_channel2' - - return[ - CreateFunding(txid_in=txid_in, - tx_index_in=tx_index_in, - sats_in=sats_in, - spending_privkey=spending_privkey, - fee=fee, - 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('accept_channel2.to_self_delay', int), - remote_to_self_delay=sent('open_channel2.to_self_delay', int), - local_amount=msat(sent(open_msg + '.funding_satoshis', int)), - remote_amount=msat(rcvd(accept_msg + '.funding_satoshis', int)), - local_dust_limit=546, - remote_dust_limit=546, - feerate=253, - local_features=sent('init.features'), - remote_features=rcvd('init.features')), - - Msg('tx_add_input', + open_msg = "init_rbf" if is_rbf else "open_channel2" + accept_msg = "ack_rbf" if is_rbf else "accept_channel2" + + return [ + CreateFunding( + txid_in=txid_in, + tx_index_in=tx_index_in, + sats_in=sats_in, + spending_privkey=spending_privkey, + fee=fee, + 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("accept_channel2.to_self_delay", int), + remote_to_self_delay=sent("open_channel2.to_self_delay", int), + local_amount=msat(sent(open_msg + ".funding_satoshis", int)), + remote_amount=msat(rcvd(accept_msg + ".funding_satoshis", int)), + local_dust_limit=546, + remote_dust_limit=546, + feerate=253, + local_features=sent("init.features"), + remote_features=rcvd("init.features"), + ), + Msg( + "tx_add_input", channel_id=channel_id_v2(local_keyset), serial_id=2, prevtx=tx_spendable, prevtx_vout=tx_out_for_index(input_index), - sequence=0xfffffffd, - script_sig=''), - - AddInput(funding=funding(), - privkey=privkey_for_index(input_index), - serial_id=sent('tx_add_input.serial_id', int), - prevtx=sent(), - prevtx_vout=sent('tx_add_input.prevtx_vout', int), - script_sig=sent()), - + sequence=0xFFFFFFFD, + script_sig="", + ), + AddInput( + funding=funding(), + privkey=privkey_for_index(input_index), + serial_id=sent("tx_add_input.serial_id", int), + prevtx=sent(), + prevtx_vout=sent("tx_add_input.prevtx_vout", int), + script_sig=sent(), + ), # Ignore unknown odd messages - TryAll([], RawMsg(bytes.fromhex('270F'))), - - ExpectMsg('tx_complete', - channel_id=channel_id_v2(local_keyset)), - + TryAll([], RawMsg(bytes.fromhex("270F"))), + ExpectMsg("tx_complete", channel_id=channel_id_v2(local_keyset)), # Try removing and re-adding an input - TryAll([], - [Msg('tx_remove_input', + TryAll( + [], + [ + Msg( + "tx_remove_input", channel_id=channel_id_v2(local_keyset), - serial_id=2), - ExpectMsg('tx_complete', - channel_id=channel_id_v2(local_keyset)), - Msg('tx_add_input', + serial_id=2, + ), + ExpectMsg("tx_complete", channel_id=channel_id_v2(local_keyset)), + Msg( + "tx_add_input", channel_id=channel_id_v2(local_keyset), serial_id=2, prevtx=tx_spendable, prevtx_vout=tx_out_for_index(input_index), - sequence=0xfffffffd, - script_sig=''), - ExpectMsg('tx_complete', - channel_id=channel_id_v2(local_keyset))]), - - Msg('tx_add_output', + sequence=0xFFFFFFFD, + script_sig="", + ), + ExpectMsg("tx_complete", channel_id=channel_id_v2(local_keyset)), + ], + ), + Msg( + "tx_add_output", channel_id=channel_id_v2(local_keyset), serial_id=2, sats=funding_amt, - script=locking_script()), - - AddOutput(funding=funding(), - serial_id=sent('tx_add_output.serial_id', int), - script=sent(), - sats=sent('tx_add_output.sats', int)), - + script=locking_script(), + ), + AddOutput( + funding=funding(), + serial_id=sent("tx_add_output.serial_id", int), + script=sent(), + sats=sent("tx_add_output.sats", int), + ), # Ignore unknown odd messages - TryAll([], RawMsg(bytes.fromhex('270F'))), - - ExpectMsg('tx_complete', - channel_id=channel_id_v2(local_keyset)), - + TryAll([], RawMsg(bytes.fromhex("270F"))), + ExpectMsg("tx_complete", channel_id=channel_id_v2(local_keyset)), # Try removing and re-adding an output - TryAll([], - [Msg('tx_remove_output', + TryAll( + [], + [ + Msg( + "tx_remove_output", channel_id=channel_id_v2(local_keyset), - serial_id=2), - ExpectMsg('tx_complete', - channel_id=channel_id_v2(local_keyset)), - Msg('tx_add_output', + serial_id=2, + ), + ExpectMsg("tx_complete", channel_id=channel_id_v2(local_keyset)), + Msg( + "tx_add_output", channel_id=channel_id_v2(local_keyset), serial_id=2, sats=funding_amt, - script=locking_script()), - ExpectMsg('tx_complete', - channel_id=channel_id_v2(local_keyset))]), - - Msg('tx_complete', - channel_id=channel_id_v2(local_keyset)), - + script=locking_script(), + ), + ExpectMsg("tx_complete", channel_id=channel_id_v2(local_keyset)), + ], + ), + Msg("tx_complete", channel_id=channel_id_v2(local_keyset)), FinalizeFunding(funding=funding()), - # Ignore unknown odd messages - TryAll([], RawMsg(bytes.fromhex('270F'))), - - Msg('commitment_signed', + TryAll([], RawMsg(bytes.fromhex("270F"))), + Msg( + "commitment_signed", channel_id=channel_id_v2(local_keyset), signature=commitsig_to_send(), - htlc_signature='[]'), - + htlc_signature="[]", + ), # Ignore unknown odd messages - TryAll([], RawMsg(bytes.fromhex('270F'))), - - ExpectMsg('commitment_signed', - channel_id=channel_id_v2(local_keyset), - signature=commitsig_to_recv()), - - ExpectMsg('tx_signatures', - channel_id=channel_id_v2(local_keyset), - txid=funding_txid(), - witness_stack='[]'), - - Msg('tx_signatures', + TryAll([], RawMsg(bytes.fromhex("270F"))), + ExpectMsg( + "commitment_signed", + channel_id=channel_id_v2(local_keyset), + signature=commitsig_to_recv(), + ), + ExpectMsg( + "tx_signatures", + channel_id=channel_id_v2(local_keyset), + txid=funding_txid(), + witness_stack="[]", + ), + Msg( + "tx_signatures", channel_id=channel_id_v2(local_keyset), txid=funding_txid(), - witness_stack=witnesses()), + witness_stack=witnesses(), + ), ] -def opener_tx_creation(input_index: int, is_rbf: bool, funding_amt: int, - local_funding_privkey: str, - local_keyset: KeySet, - runner: Runner) -> List[Event]: - """ Repeated tx construction protocols, for opener tests """ +def opener_tx_creation( + input_index: int, + is_rbf: bool, + funding_amt: int, + local_funding_privkey: str, + local_keyset: KeySet, + runner: Runner, +) -> List[Event]: + """Repeated tx construction protocols, for opener tests""" txid_in, tx_index_in, sats_in, spending_privkey, fee = utxo(input_index) fee = sats_in - funding_amt if is_rbf else fee - open_msg = 'init_rbf' if is_rbf else 'open_channel2' - accept_msg = 'ack_rbf' if is_rbf else 'accept_channel2' - - return[ - CreateDualFunding(fee=fee, - funding_sats=agreed_funding(Side.remote, is_rbf=is_rbf), - locktime=rcvd(open_msg + '.locktime', int), - local_node_privkey='02', - local_funding_privkey=local_funding_privkey, - remote_node_privkey=runner.get_node_privkey(), - remote_funding_privkey=remote_funding_privkey()), - - ExpectMsg('tx_add_input', - channel_id=channel_id_v2(local_keyset), - if_match=even_serial, - prevtx=tx_spendable, - sequence=0xfffffffd, - script_sig=''), - - AddInput(funding=funding(), - serial_id=rcvd('tx_add_input.serial_id', int), - prevtx=rcvd('tx_add_input.prevtx'), - prevtx_vout=rcvd('tx_add_input.prevtx_vout', int), - script_sig=rcvd('tx_add_input.script_sig')), - + open_msg = "init_rbf" if is_rbf else "open_channel2" + accept_msg = "ack_rbf" if is_rbf else "accept_channel2" + + return [ + CreateDualFunding( + fee=fee, + funding_sats=agreed_funding(Side.remote, is_rbf=is_rbf), + locktime=rcvd(open_msg + ".locktime", int), + local_node_privkey="02", + local_funding_privkey=local_funding_privkey, + remote_node_privkey=runner.get_node_privkey(), + remote_funding_privkey=remote_funding_privkey(), + ), + ExpectMsg( + "tx_add_input", + channel_id=channel_id_v2(local_keyset), + if_match=even_serial, + prevtx=tx_spendable, + sequence=0xFFFFFFFD, + script_sig="", + ), + AddInput( + funding=funding(), + serial_id=rcvd("tx_add_input.serial_id", int), + prevtx=rcvd("tx_add_input.prevtx"), + prevtx_vout=rcvd("tx_add_input.prevtx_vout", int), + script_sig=rcvd("tx_add_input.script_sig"), + ), # Ignore unknown odd messages - TryAll([], RawMsg(bytes.fromhex('270F'))), - - Msg('tx_add_input', + TryAll([], RawMsg(bytes.fromhex("270F"))), + Msg( + "tx_add_input", channel_id=channel_id_v2(local_keyset), serial_id=1, - sequence=0xfffffffd, + sequence=0xFFFFFFFD, prevtx=tx_spendable, prevtx_vout=tx_out_for_index(input_index), - script_sig=''), - - AddInput(funding=funding(), - privkey=privkey_for_index(input_index), - serial_id=sent('tx_add_input.serial_id', int), - prevtx=sent(), - prevtx_vout=sent('tx_add_input.prevtx_vout', int), - script_sig=sent()), - + script_sig="", + ), + AddInput( + funding=funding(), + privkey=privkey_for_index(input_index), + serial_id=sent("tx_add_input.serial_id", int), + prevtx=sent(), + prevtx_vout=sent("tx_add_input.prevtx_vout", int), + script_sig=sent(), + ), # The funding output - ExpectMsg('tx_add_output', - channel_id=channel_id_v2(local_keyset), - if_match=even_serial), - + ExpectMsg( + "tx_add_output", + channel_id=channel_id_v2(local_keyset), + if_match=even_serial, + ), # FIXME: They may send us the funding output second, # if there's also a change output - AddOutput(funding=funding(), - serial_id=rcvd('tx_add_output.serial_id', int), - sats=rcvd('tx_add_output.sats', int), - script=rcvd('tx_add_output.script')), - - - Msg('tx_add_output', + AddOutput( + funding=funding(), + serial_id=rcvd("tx_add_output.serial_id", int), + sats=rcvd("tx_add_output.sats", int), + script=rcvd("tx_add_output.script"), + ), + Msg( + "tx_add_output", channel_id=channel_id_v2(local_keyset), serial_id=101, - sats=change_amount(Side.remote, False, - '001473daa75958d5b2ddca87a6c279bb7cb307167037', - funding_amount_for_utxo(input_index)), - script='001473daa75958d5b2ddca87a6c279bb7cb307167037'), - - AddOutput(funding=funding(), - serial_id=sent('tx_add_output.serial_id', int), - script=sent(), - sats=sent('tx_add_output.sats', int)), - + sats=change_amount( + Side.remote, + False, + "001473daa75958d5b2ddca87a6c279bb7cb307167037", + funding_amount_for_utxo(input_index), + ), + script="001473daa75958d5b2ddca87a6c279bb7cb307167037", + ), + AddOutput( + funding=funding(), + serial_id=sent("tx_add_output.serial_id", int), + script=sent(), + sats=sent("tx_add_output.sats", int), + ), # Their change if they have one! - OneOf([ExpectMsg('tx_add_output', - if_match=even_serial, - channel_id=channel_id_v2(local_keyset)), - Msg('tx_complete', - channel_id=channel_id_v2(local_keyset)), - ExpectMsg('tx_complete', - channel_id=channel_id_v2(local_keyset)), - AddOutput(funding=funding(), - serial_id=rcvd('tx_add_output.serial_id', int), - sats=rcvd('tx_add_output.sats', int), - script=rcvd('tx_add_output.script'))], - [ExpectMsg('tx_complete', - channel_id=channel_id_v2(local_keyset)), - Msg('tx_complete', - channel_id=channel_id_v2(local_keyset)), - ]), - + OneOf( + [ + ExpectMsg( + "tx_add_output", + if_match=even_serial, + channel_id=channel_id_v2(local_keyset), + ), + Msg("tx_complete", channel_id=channel_id_v2(local_keyset)), + ExpectMsg("tx_complete", channel_id=channel_id_v2(local_keyset)), + AddOutput( + funding=funding(), + serial_id=rcvd("tx_add_output.serial_id", int), + sats=rcvd("tx_add_output.sats", int), + script=rcvd("tx_add_output.script"), + ), + ], + [ + ExpectMsg("tx_complete", channel_id=channel_id_v2(local_keyset)), + Msg("tx_complete", channel_id=channel_id_v2(local_keyset)), + ], + ), FinalizeFunding(funding=funding()), - - Commit(funding=funding(), - opener=Side.remote, - local_keyset=local_keyset, - local_to_self_delay=rcvd('open_channel2.to_self_delay', int), - remote_to_self_delay=sent('accept_channel2.to_self_delay', int), - local_amount=msat(sent(accept_msg + '.funding_satoshis', int)), - remote_amount=msat(rcvd(open_msg + '.funding_satoshis', int)), - local_dust_limit=550, - remote_dust_limit=546, - feerate=rcvd('open_channel2.commitment_feerate_perkw', int), - local_features=sent('init.features'), - remote_features=rcvd('init.features')), - + Commit( + funding=funding(), + opener=Side.remote, + local_keyset=local_keyset, + local_to_self_delay=rcvd("open_channel2.to_self_delay", int), + remote_to_self_delay=sent("accept_channel2.to_self_delay", int), + local_amount=msat(sent(accept_msg + ".funding_satoshis", int)), + remote_amount=msat(rcvd(open_msg + ".funding_satoshis", int)), + local_dust_limit=550, + remote_dust_limit=546, + feerate=rcvd("open_channel2.commitment_feerate_perkw", int), + local_features=sent("init.features"), + remote_features=rcvd("init.features"), + ), # Ignore unknown odd messages - TryAll([], RawMsg(bytes.fromhex('270F'))), - - ExpectMsg('commitment_signed', - channel_id=channel_id_v2(local_keyset), - signature=commitsig_to_recv()), - + TryAll([], RawMsg(bytes.fromhex("270F"))), + ExpectMsg( + "commitment_signed", + channel_id=channel_id_v2(local_keyset), + signature=commitsig_to_recv(), + ), # Ignore unknown odd messages - TryAll([], RawMsg(bytes.fromhex('270F'))), - - Msg('commitment_signed', + TryAll([], RawMsg(bytes.fromhex("270F"))), + Msg( + "commitment_signed", channel_id=channel_id_v2(local_keyset), signature=commitsig_to_send(), - htlc_signature='[]'), - - Msg('tx_signatures', + htlc_signature="[]", + ), + Msg( + "tx_signatures", channel_id=channel_id_v2(local_keyset), txid=funding_txid(), - witness_stack=witnesses()), - + witness_stack=witnesses(), + ), # Ignore unknown odd messages - TryAll([], RawMsg(bytes.fromhex('270F'))), - - ExpectMsg('tx_signatures', - channel_id=channel_id_v2(local_keyset), - txid=funding_txid()), - - AddWitnesses(funding=funding(), - witness_stack=rcvd('witness_stack')), - + TryAll([], RawMsg(bytes.fromhex("270F"))), + ExpectMsg( + "tx_signatures", channel_id=channel_id_v2(local_keyset), txid=funding_txid() + ), + AddWitnesses(funding=funding(), witness_stack=rcvd("witness_stack")), ] def test_rbf_accepter(runner: Runner, with_proposal: Any) -> None: with_proposal(dual_fund_csv) - runner.add_startup_flag('experimental-dual-fund') - - local_funding_privkey = '20' - local_keyset = KeySet(revocation_base_secret='21', - payment_base_secret='22', - htlc_base_secret='24', - delayed_payment_base_secret='23', - shachain_seed='00' * 32) + runner.add_startup_flag("experimental-dual-fund") + + local_funding_privkey = "20" + local_keyset = KeySet( + revocation_base_secret="21", + payment_base_secret="22", + htlc_base_secret="24", + delayed_payment_base_secret="23", + shachain_seed="00" * 32, + ) input_index = 0 funding_amount = funding_amount_for_utxo(input_index) rbf_funding_amount = funding_amount - 1000 - test = [Block(blockheight=102, txs=[tx_spendable]), - Connect(connprivkey='02'), - ExpectMsg('init'), - - # BOLT-f53ca2301232db780843e894f55d95d512f297f9 #9: - # | 28/29 | `option_dual_fund` | Use v2 of channel open, enables dual funding | IN9 | `option_anchor_outputs`, `option_static_remotekey` | [BOLT #2](02-peer-protocol.md) | - - Msg('init', globalfeatures='', features=bitfield(12, 20, 29)), - - # Accepter side: we initiate a new channel. - Msg('open_channel2', - channel_id=channel_id_tmp(local_keyset, Side.local), - chain_hash=regtest_hash, - funding_satoshis=funding_amount, - dust_limit_satoshis=546, - max_htlc_value_in_flight_msat=4294967295, - htlc_minimum_msat=0, - funding_feerate_perkw=253, - commitment_feerate_perkw=253, - # We use 5, because c-lightning runner uses 6, so this is different. - to_self_delay=5, - max_accepted_htlcs=483, - locktime=0, - 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_channel2', - channel_id=sent('channel_id'), - funding_satoshis=0, - 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)), - - # Ignore unknown odd messages - TryAll([], RawMsg(bytes.fromhex('270F'))), - ] - - test += accepter_tx_creation(input_index, False, funding_amount, - local_funding_privkey, local_keyset, - runner) - - test += [TryAll([], RawMsg(bytes.fromhex('270F'))), - # Let's RBF - Msg('init_rbf', - channel_id=channel_id_v2(local_keyset), - funding_satoshis=rbf_funding_amount, - funding_feerate_perkw=253 * 65 // 64, - locktime=0), - # Ignore unknown odd messages - TryAll([], RawMsg(bytes.fromhex('270F'))), - ExpectMsg('ack_rbf', - channel_id=channel_id_v2(local_keyset), - funding_satoshis=0), - ] - - test += accepter_tx_creation(input_index, True, rbf_funding_amount, - local_funding_privkey, local_keyset, - runner) + test = [ + Block(blockheight=102, txs=[tx_spendable]), + Connect(connprivkey="02"), + ExpectMsg("init"), + # BOLT-f53ca2301232db780843e894f55d95d512f297f9 #9: + # | 28/29 | `option_dual_fund` | Use v2 of channel open, enables dual funding | IN9 | `option_anchor_outputs`, `option_static_remotekey` | [BOLT #2](02-peer-protocol.md) | + Msg("init", globalfeatures="", features=bitfield(12, 20, 29)), + # Accepter side: we initiate a new channel. + Msg( + "open_channel2", + channel_id=channel_id_tmp(local_keyset, Side.local), + chain_hash=regtest_hash, + funding_satoshis=funding_amount, + dust_limit_satoshis=546, + max_htlc_value_in_flight_msat=4294967295, + htlc_minimum_msat=0, + funding_feerate_perkw=253, + commitment_feerate_perkw=253, + # We use 5, because c-lightning runner uses 6, so this is different. + to_self_delay=5, + max_accepted_htlcs=483, + locktime=0, + 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_channel2", + channel_id=sent("channel_id"), + funding_satoshis=0, + 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), + ), + # Ignore unknown odd messages + TryAll([], RawMsg(bytes.fromhex("270F"))), + ] + + test += accepter_tx_creation( + input_index, False, funding_amount, local_funding_privkey, local_keyset, runner + ) + + test += [ + TryAll([], RawMsg(bytes.fromhex("270F"))), + # Let's RBF + Msg( + "init_rbf", + channel_id=channel_id_v2(local_keyset), + funding_satoshis=rbf_funding_amount, + funding_feerate_perkw=253 * 65 // 64, + locktime=0, + ), + # Ignore unknown odd messages + TryAll([], RawMsg(bytes.fromhex("270F"))), + ExpectMsg( + "ack_rbf", channel_id=channel_id_v2(local_keyset), funding_satoshis=0 + ), + ] + + test += accepter_tx_creation( + input_index, + True, + rbf_funding_amount, + local_funding_privkey, + local_keyset, + runner, + ) runner.run(test) def test_rbf_opener(runner: Runner, with_proposal: Any) -> None: with_proposal(dual_fund_csv) - runner.add_startup_flag('experimental-dual-fund') + runner.add_startup_flag("experimental-dual-fund") - local_funding_privkey = '20' - local_keyset = KeySet(revocation_base_secret='21', - payment_base_secret='22', - htlc_base_secret='24', - delayed_payment_base_secret='23', - shachain_seed='00' * 32) + local_funding_privkey = "20" + local_keyset = KeySet( + revocation_base_secret="21", + payment_base_secret="22", + htlc_base_secret="24", + delayed_payment_base_secret="23", + shachain_seed="00" * 32, + ) # Index 5 is special, only the test runner can spend it input_index = 5 @@ -1718,219 +1864,232 @@ def test_rbf_opener(runner: Runner, with_proposal: Any) -> None: init_feerate = 2000 rbf_feerate = init_feerate * 65 // 64 - test = [Block(blockheight=102, txs=[tx_spendable]), - Connect(connprivkey='02'), - ExpectMsg('init'), - - # BOLT-f53ca2301232db780843e894f55d95d512f297f9 #9: - # | 28/29 | `option_dual_fund` | Use v2 of channel open, enables dual funding | IN9 | `option_anchor_outputs`, `option_static_remotekey` | [BOLT #2](02-peer-protocol.md) | - - Msg('init', globalfeatures='', features=bitfield(12, 20, 29)), - - FundChannel(amount=funding_amount, feerate=init_feerate), - - ExpectMsg('open_channel2', - channel_id=channel_id_tmp(local_keyset, Side.remote), - chain_hash=regtest_hash, - funding_satoshis=funding_amount, - dust_limit_satoshis=546, - htlc_minimum_msat=0, - 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), - channel_flags='01'), - - Msg('accept_channel2', - channel_id=rcvd('channel_id'), - dust_limit_satoshis=550, - funding_satoshis=400000, - max_htlc_value_in_flight_msat=4294967295, - htlc_minimum_msat=0, - minimum_depth=3, - max_accepted_htlcs=483, - # We use 5, to be different from c-lightning runner who uses 6 - 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'))), - ] - - test += opener_tx_creation(input_index, False, funding_amount, - local_funding_privkey, local_keyset, - runner) - - test += [TryAll([], RawMsg(bytes.fromhex('270F'))), - # Let's RBF - InitRbf(channel_id=channel_id_v2(local_keyset), - amount=rbf_funding_amount, - utxo_tx=rcvd('tx_add_input.prevtx'), - utxo_outnum=rcvd('tx_add_input.prevtx_vout', int), - feerate=rbf_feerate * 2), - - ExpectMsg('init_rbf', - channel_id=channel_id_v2(local_keyset), - funding_satoshis=rbf_funding_amount, - funding_feerate_perkw=rbf_feerate * 2), - - # Ignore unknown odd messages - TryAll([], RawMsg(bytes.fromhex('270F'))), - - Msg('ack_rbf', - channel_id=channel_id_v2(local_keyset), - funding_satoshis=380000), - ] - - test += opener_tx_creation(input_index, True, rbf_funding_amount, - local_funding_privkey, local_keyset, - runner) - - test += [Block(blockheight=103, number=3, txs=[funding_tx()]), - ExpectMsg('funding_locked', - channel_id=channel_id_v2(local_keyset), - next_per_commitment_point=remote_per_commitment_point(1)), - # Ignore unknown odd messages - TryAll([], RawMsg(bytes.fromhex('270F'))), - ] + test = [ + Block(blockheight=102, txs=[tx_spendable]), + Connect(connprivkey="02"), + ExpectMsg("init"), + # BOLT-f53ca2301232db780843e894f55d95d512f297f9 #9: + # | 28/29 | `option_dual_fund` | Use v2 of channel open, enables dual funding | IN9 | `option_anchor_outputs`, `option_static_remotekey` | [BOLT #2](02-peer-protocol.md) | + Msg("init", globalfeatures="", features=bitfield(12, 20, 29)), + FundChannel(amount=funding_amount, feerate=init_feerate), + ExpectMsg( + "open_channel2", + channel_id=channel_id_tmp(local_keyset, Side.remote), + chain_hash=regtest_hash, + funding_satoshis=funding_amount, + dust_limit_satoshis=546, + htlc_minimum_msat=0, + 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), + channel_flags="01", + ), + Msg( + "accept_channel2", + channel_id=rcvd("channel_id"), + dust_limit_satoshis=550, + funding_satoshis=400000, + max_htlc_value_in_flight_msat=4294967295, + htlc_minimum_msat=0, + minimum_depth=3, + max_accepted_htlcs=483, + # We use 5, to be different from c-lightning runner who uses 6 + 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"))), + ] + + test += opener_tx_creation( + input_index, False, funding_amount, local_funding_privkey, local_keyset, runner + ) + + test += [ + TryAll([], RawMsg(bytes.fromhex("270F"))), + # Let's RBF + InitRbf( + channel_id=channel_id_v2(local_keyset), + amount=rbf_funding_amount, + utxo_tx=rcvd("tx_add_input.prevtx"), + utxo_outnum=rcvd("tx_add_input.prevtx_vout", int), + feerate=rbf_feerate * 2, + ), + ExpectMsg( + "init_rbf", + channel_id=channel_id_v2(local_keyset), + funding_satoshis=rbf_funding_amount, + funding_feerate_perkw=rbf_feerate * 2, + ), + # Ignore unknown odd messages + TryAll([], RawMsg(bytes.fromhex("270F"))), + Msg("ack_rbf", channel_id=channel_id_v2(local_keyset), funding_satoshis=380000), + ] + + test += opener_tx_creation( + input_index, + True, + rbf_funding_amount, + local_funding_privkey, + local_keyset, + runner, + ) + + test += [ + Block(blockheight=103, number=3, txs=[funding_tx()]), + ExpectMsg( + "funding_locked", + channel_id=channel_id_v2(local_keyset), + next_per_commitment_point=remote_per_commitment_point(1), + ), + # Ignore unknown odd messages + TryAll([], RawMsg(bytes.fromhex("270F"))), + ] runner.run(test) def test_rbf_accepter_funding_locked(runner: Runner, with_proposal: Any) -> None: with_proposal(dual_fund_csv) - runner.add_startup_flag('experimental-dual-fund') - - local_funding_privkey = '20' - local_keyset = KeySet(revocation_base_secret='21', - payment_base_secret='22', - htlc_base_secret='24', - delayed_payment_base_secret='23', - shachain_seed='00' * 32) + runner.add_startup_flag("experimental-dual-fund") + + local_funding_privkey = "20" + local_keyset = KeySet( + revocation_base_secret="21", + payment_base_secret="22", + htlc_base_secret="24", + delayed_payment_base_secret="23", + shachain_seed="00" * 32, + ) input_index = 0 funding_amount = funding_amount_for_utxo(input_index) rbf_funding_amount = funding_amount - 1000 - test = [Block(blockheight=102, txs=[tx_spendable]), - Connect(connprivkey='02'), - ExpectMsg('init'), - - # BOLT-f53ca2301232db780843e894f55d95d512f297f9 #9: - # | 28/29 | `option_dual_fund` | Use v2 of channel open, enables dual funding | IN9 | `option_anchor_outputs`, `option_static_remotekey` | [BOLT #2](02-peer-protocol.md) | - Msg('init', globalfeatures='', features=bitfield(12, 20, 29)), - - # Accepter side: we initiate a new channel. - Msg('open_channel2', - channel_id=channel_id_tmp(local_keyset, Side.local), - chain_hash=regtest_hash, - funding_satoshis=funding_amount, - dust_limit_satoshis=546, - max_htlc_value_in_flight_msat=4294967295, - htlc_minimum_msat=0, - funding_feerate_perkw=253, - commitment_feerate_perkw=253, - # We use 5, because c-lightning runner uses 6, so this is different. - to_self_delay=5, - max_accepted_htlcs=483, - locktime=0, - 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_channel2', - channel_id=sent('channel_id'), - funding_satoshis=0, - 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)), - - # Ignore unknown odd messages - TryAll([], RawMsg(bytes.fromhex('270F'))), - ] - - test += accepter_tx_creation(input_index, False, funding_amount, - local_funding_privkey, local_keyset, - runner) - - test += [TryAll([], RawMsg(bytes.fromhex('270F'))), - # Let's RBF - Msg('init_rbf', - channel_id=channel_id_v2(local_keyset), - funding_satoshis=rbf_funding_amount, - funding_feerate_perkw=253 * 65 // 64, - locktime=0), - # Ignore unknown odd messages - TryAll([], RawMsg(bytes.fromhex('270F'))), - ExpectMsg('ack_rbf', - channel_id=channel_id_v2(local_keyset), - funding_satoshis=0), - ] - - test += [Msg('tx_add_input', - channel_id=channel_id_v2(local_keyset), - serial_id=2, - prevtx=tx_spendable, - prevtx_vout=tx_out_for_index(input_index), - sequence=0xfffffffd, - script_sig=''), - - # Ignore unknown odd messages - TryAll([], RawMsg(bytes.fromhex('270F'))), - - ExpectMsg('tx_complete', - channel_id=channel_id_v2(local_keyset)), - - # We're in the middle of an RBF, the original funding tx confirms - Block(blockheight=103, number=3, txs=[funding_tx()]), - - # Ignore unknown odd messages - TryAll([], RawMsg(bytes.fromhex('270F'))), - - Msg('funding_locked', - channel_id=channel_id_v2(local_keyset), - next_per_commitment_point=local_keyset.per_commit_point(1)), - - # Ignore unknown odd messages - TryAll([], RawMsg(bytes.fromhex('270F'))), - - ExpectMsg('funding_locked', - channel_id=channel_id_v2(local_keyset), - next_per_commitment_point=remote_per_commitment_point(1)), - ] + test = [ + Block(blockheight=102, txs=[tx_spendable]), + Connect(connprivkey="02"), + ExpectMsg("init"), + # BOLT-f53ca2301232db780843e894f55d95d512f297f9 #9: + # | 28/29 | `option_dual_fund` | Use v2 of channel open, enables dual funding | IN9 | `option_anchor_outputs`, `option_static_remotekey` | [BOLT #2](02-peer-protocol.md) | + Msg("init", globalfeatures="", features=bitfield(12, 20, 29)), + # Accepter side: we initiate a new channel. + Msg( + "open_channel2", + channel_id=channel_id_tmp(local_keyset, Side.local), + chain_hash=regtest_hash, + funding_satoshis=funding_amount, + dust_limit_satoshis=546, + max_htlc_value_in_flight_msat=4294967295, + htlc_minimum_msat=0, + funding_feerate_perkw=253, + commitment_feerate_perkw=253, + # We use 5, because c-lightning runner uses 6, so this is different. + to_self_delay=5, + max_accepted_htlcs=483, + locktime=0, + 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_channel2", + channel_id=sent("channel_id"), + funding_satoshis=0, + 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), + ), + # Ignore unknown odd messages + TryAll([], RawMsg(bytes.fromhex("270F"))), + ] + + test += accepter_tx_creation( + input_index, False, funding_amount, local_funding_privkey, local_keyset, runner + ) + + test += [ + TryAll([], RawMsg(bytes.fromhex("270F"))), + # Let's RBF + Msg( + "init_rbf", + channel_id=channel_id_v2(local_keyset), + funding_satoshis=rbf_funding_amount, + funding_feerate_perkw=253 * 65 // 64, + locktime=0, + ), + # Ignore unknown odd messages + TryAll([], RawMsg(bytes.fromhex("270F"))), + ExpectMsg( + "ack_rbf", channel_id=channel_id_v2(local_keyset), funding_satoshis=0 + ), + ] + + test += [ + Msg( + "tx_add_input", + channel_id=channel_id_v2(local_keyset), + serial_id=2, + prevtx=tx_spendable, + prevtx_vout=tx_out_for_index(input_index), + sequence=0xFFFFFFFD, + script_sig="", + ), + # Ignore unknown odd messages + TryAll([], RawMsg(bytes.fromhex("270F"))), + ExpectMsg("tx_complete", channel_id=channel_id_v2(local_keyset)), + # We're in the middle of an RBF, the original funding tx confirms + Block(blockheight=103, number=3, txs=[funding_tx()]), + # Ignore unknown odd messages + TryAll([], RawMsg(bytes.fromhex("270F"))), + Msg( + "funding_locked", + channel_id=channel_id_v2(local_keyset), + next_per_commitment_point=local_keyset.per_commit_point(1), + ), + # Ignore unknown odd messages + TryAll([], RawMsg(bytes.fromhex("270F"))), + ExpectMsg( + "funding_locked", + channel_id=channel_id_v2(local_keyset), + next_per_commitment_point=remote_per_commitment_point(1), + ), + ] runner.run(test) def test_rbf_opener_funding_locked(runner: Runner, with_proposal: Any) -> None: - """ Check that if the funding transaction is published while we're inflight - everything works as expected """ + """Check that if the funding transaction is published while we're inflight + everything works as expected""" with_proposal(dual_fund_csv) - runner.add_startup_flag('experimental-dual-fund') + runner.add_startup_flag("experimental-dual-fund") - local_funding_privkey = '20' - local_keyset = KeySet(revocation_base_secret='21', - payment_base_secret='22', - htlc_base_secret='24', - delayed_payment_base_secret='23', - shachain_seed='00' * 32) + local_funding_privkey = "20" + local_keyset = KeySet( + revocation_base_secret="21", + payment_base_secret="22", + htlc_base_secret="24", + delayed_payment_base_secret="23", + shachain_seed="00" * 32, + ) # Index 5 is special, only the test runner can spend it input_index = 5 @@ -1940,283 +2099,290 @@ def test_rbf_opener_funding_locked(runner: Runner, with_proposal: Any) -> None: rbf_funding_amount = funding_amount - 1000 init_feerate = 2000 - test = [Block(blockheight=102, txs=[tx_spendable]), - Connect(connprivkey='02'), - ExpectMsg('init'), - - # BOLT-f53ca2301232db780843e894f55d95d512f297f9 #9: - # | 28/29 | `option_dual_fund` | Use v2 of channel open, enables dual funding | IN9 | `option_anchor_outputs`, `option_static_remotekey` | [BOLT #2](02-peer-protocol.md) | - - Msg('init', globalfeatures='', features=bitfield(12, 20, 29)), - - FundChannel(amount=funding_amount, feerate=init_feerate), - - ExpectMsg('open_channel2', - channel_id=channel_id_tmp(local_keyset, Side.remote), - chain_hash=regtest_hash, - funding_satoshis=funding_amount, - dust_limit_satoshis=546, - htlc_minimum_msat=0, - 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), - channel_flags='01'), - - Msg('accept_channel2', - channel_id=rcvd('open_channel2.channel_id'), - dust_limit_satoshis=550, - funding_satoshis=400000, - max_htlc_value_in_flight_msat=4294967295, - htlc_minimum_msat=0, - minimum_depth=3, - max_accepted_htlcs=483, - # We use 5, to be different from c-lightning runner who uses 6 - 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'))), - ] - - test += opener_tx_creation(input_index, False, funding_amount, - local_funding_privkey, local_keyset, - runner) - - test += [TryAll([], RawMsg(bytes.fromhex('270F'))), - # Let's RBF - InitRbf(channel_id=channel_id_v2(local_keyset), - amount=rbf_funding_amount, - utxo_tx=rcvd('tx_add_input.prevtx'), - utxo_outnum=rcvd('tx_add_input.prevtx_vout', int), - feerate=init_feerate * 65 // 64), - - ExpectMsg('init_rbf', - channel_id=channel_id_v2(local_keyset), - funding_satoshis=rbf_funding_amount, - funding_feerate_perkw=init_feerate * 65 // 64), - - # Ignore unknown odd messages - TryAll([], RawMsg(bytes.fromhex('270F'))), - - Msg('ack_rbf', - channel_id=channel_id_v2(local_keyset), - funding_satoshis=400000), - ] - - test += [ExpectMsg('tx_add_input', - channel_id=channel_id_v2(local_keyset), - if_match=even_serial, - prevtx=tx_spendable, - sequence=0xfffffffd, - script_sig=''), - - # Ignore unknown odd messages - TryAll([], RawMsg(bytes.fromhex('270F'))), - - Msg('tx_add_input', - channel_id=channel_id_v2(local_keyset), - serial_id=1, - sequence=0xfffffffd, - prevtx=tx_spendable, - prevtx_vout=tx_out_for_index(input_index), - script_sig=''), - - # The funding output - ExpectMsg('tx_add_output', - channel_id=channel_id_v2(local_keyset), - if_match=even_serial), - - # Ignore unknown odd messages - TryAll([], RawMsg(bytes.fromhex('270F'))), - - Block(blockheight=103, number=3, txs=[funding_tx()]), - - Msg('funding_locked', - channel_id=channel_id_v2(local_keyset), - next_per_commitment_point=local_keyset.per_commit_point(1)), - - - ExpectMsg('funding_locked', - channel_id=channel_id_v2(local_keyset), - next_per_commitment_point=remote_per_commitment_point(1)), - # Ignore unknown odd messages - TryAll([], RawMsg(bytes.fromhex('270F'))), - ] + test = [ + Block(blockheight=102, txs=[tx_spendable]), + Connect(connprivkey="02"), + ExpectMsg("init"), + # BOLT-f53ca2301232db780843e894f55d95d512f297f9 #9: + # | 28/29 | `option_dual_fund` | Use v2 of channel open, enables dual funding | IN9 | `option_anchor_outputs`, `option_static_remotekey` | [BOLT #2](02-peer-protocol.md) | + Msg("init", globalfeatures="", features=bitfield(12, 20, 29)), + FundChannel(amount=funding_amount, feerate=init_feerate), + ExpectMsg( + "open_channel2", + channel_id=channel_id_tmp(local_keyset, Side.remote), + chain_hash=regtest_hash, + funding_satoshis=funding_amount, + dust_limit_satoshis=546, + htlc_minimum_msat=0, + 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), + channel_flags="01", + ), + Msg( + "accept_channel2", + channel_id=rcvd("open_channel2.channel_id"), + dust_limit_satoshis=550, + funding_satoshis=400000, + max_htlc_value_in_flight_msat=4294967295, + htlc_minimum_msat=0, + minimum_depth=3, + max_accepted_htlcs=483, + # We use 5, to be different from c-lightning runner who uses 6 + 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"))), + ] + + test += opener_tx_creation( + input_index, False, funding_amount, local_funding_privkey, local_keyset, runner + ) + + test += [ + TryAll([], RawMsg(bytes.fromhex("270F"))), + # Let's RBF + InitRbf( + channel_id=channel_id_v2(local_keyset), + amount=rbf_funding_amount, + utxo_tx=rcvd("tx_add_input.prevtx"), + utxo_outnum=rcvd("tx_add_input.prevtx_vout", int), + feerate=init_feerate * 65 // 64, + ), + ExpectMsg( + "init_rbf", + channel_id=channel_id_v2(local_keyset), + funding_satoshis=rbf_funding_amount, + funding_feerate_perkw=init_feerate * 65 // 64, + ), + # Ignore unknown odd messages + TryAll([], RawMsg(bytes.fromhex("270F"))), + Msg("ack_rbf", channel_id=channel_id_v2(local_keyset), funding_satoshis=400000), + ] + + test += [ + ExpectMsg( + "tx_add_input", + channel_id=channel_id_v2(local_keyset), + if_match=even_serial, + prevtx=tx_spendable, + sequence=0xFFFFFFFD, + script_sig="", + ), + # Ignore unknown odd messages + TryAll([], RawMsg(bytes.fromhex("270F"))), + Msg( + "tx_add_input", + channel_id=channel_id_v2(local_keyset), + serial_id=1, + sequence=0xFFFFFFFD, + prevtx=tx_spendable, + prevtx_vout=tx_out_for_index(input_index), + script_sig="", + ), + # The funding output + ExpectMsg( + "tx_add_output", + channel_id=channel_id_v2(local_keyset), + if_match=even_serial, + ), + # Ignore unknown odd messages + TryAll([], RawMsg(bytes.fromhex("270F"))), + Block(blockheight=103, number=3, txs=[funding_tx()]), + Msg( + "funding_locked", + channel_id=channel_id_v2(local_keyset), + next_per_commitment_point=local_keyset.per_commit_point(1), + ), + ExpectMsg( + "funding_locked", + channel_id=channel_id_v2(local_keyset), + next_per_commitment_point=remote_per_commitment_point(1), + ), + # Ignore unknown odd messages + TryAll([], RawMsg(bytes.fromhex("270F"))), + ] runner.run(test) def test_rbf_accepter_forgets(runner: Runner, with_proposal: Any) -> None: - """ The runner forgets we're in the middle of an RBF. - Peer should reconnect and allow another RBF to be initialized. + """The runner forgets we're in the middle of an RBF. + Peer should reconnect and allow another RBF to be initialized. """ with_proposal(dual_fund_csv) - runner.add_startup_flag('experimental-dual-fund') - - local_funding_privkey = '20' - local_keyset = KeySet(revocation_base_secret='21', - payment_base_secret='22', - htlc_base_secret='24', - delayed_payment_base_secret='23', - shachain_seed='00' * 32) + runner.add_startup_flag("experimental-dual-fund") + + local_funding_privkey = "20" + local_keyset = KeySet( + revocation_base_secret="21", + payment_base_secret="22", + htlc_base_secret="24", + delayed_payment_base_secret="23", + shachain_seed="00" * 32, + ) input_index = 0 funding_amount = funding_amount_for_utxo(input_index) rbf_funding_amount = funding_amount - 1000 - test = [Block(blockheight=102, txs=[tx_spendable]), - Connect(connprivkey='02'), - ExpectMsg('init'), - - # BOLT-f53ca2301232db780843e894f55d95d512f297f9 #9: - # | 28/29 | `option_dual_fund` | Use v2 of channel open, enables dual funding | IN9 | `option_anchor_outputs`, `option_static_remotekey` | [BOLT #2](02-peer-protocol.md) | - - Msg('init', globalfeatures='', features=bitfield(12, 20, 29)), - - # Accepter side: we initiate a new channel. - Msg('open_channel2', - channel_id=channel_id_tmp(local_keyset, Side.local), - chain_hash=regtest_hash, - funding_satoshis=funding_amount, - dust_limit_satoshis=546, - max_htlc_value_in_flight_msat=4294967295, - htlc_minimum_msat=0, - funding_feerate_perkw=253, - commitment_feerate_perkw=253, - # We use 5, because c-lightning runner uses 6, so this is different. - to_self_delay=5, - max_accepted_htlcs=483, - locktime=0, - 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_channel2', - channel_id=sent('open_channel2.channel_id'), - funding_satoshis=0, - 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)), - - # Ignore unknown odd messages - TryAll([], RawMsg(bytes.fromhex('270F'))), - ] - - test += accepter_tx_creation(input_index, False, funding_amount, - local_funding_privkey, local_keyset, - runner) - - test += [TryAll([], RawMsg(bytes.fromhex('270F'))), - # Let's RBF - Msg('init_rbf', - channel_id=channel_id_v2(local_keyset), - funding_satoshis=rbf_funding_amount, - funding_feerate_perkw=253 * 65 // 64, - locktime=0), - # Ignore unknown odd messages - TryAll([], RawMsg(bytes.fromhex('270F'))), - ExpectMsg('ack_rbf', - channel_id=channel_id_v2(local_keyset), - funding_satoshis=0), - ] + test = [ + Block(blockheight=102, txs=[tx_spendable]), + Connect(connprivkey="02"), + ExpectMsg("init"), + # BOLT-f53ca2301232db780843e894f55d95d512f297f9 #9: + # | 28/29 | `option_dual_fund` | Use v2 of channel open, enables dual funding | IN9 | `option_anchor_outputs`, `option_static_remotekey` | [BOLT #2](02-peer-protocol.md) | + Msg("init", globalfeatures="", features=bitfield(12, 20, 29)), + # Accepter side: we initiate a new channel. + Msg( + "open_channel2", + channel_id=channel_id_tmp(local_keyset, Side.local), + chain_hash=regtest_hash, + funding_satoshis=funding_amount, + dust_limit_satoshis=546, + max_htlc_value_in_flight_msat=4294967295, + htlc_minimum_msat=0, + funding_feerate_perkw=253, + commitment_feerate_perkw=253, + # We use 5, because c-lightning runner uses 6, so this is different. + to_self_delay=5, + max_accepted_htlcs=483, + locktime=0, + 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_channel2", + channel_id=sent("open_channel2.channel_id"), + funding_satoshis=0, + 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), + ), + # Ignore unknown odd messages + TryAll([], RawMsg(bytes.fromhex("270F"))), + ] + + test += accepter_tx_creation( + input_index, False, funding_amount, local_funding_privkey, local_keyset, runner + ) + + test += [ + TryAll([], RawMsg(bytes.fromhex("270F"))), + # Let's RBF + Msg( + "init_rbf", + channel_id=channel_id_v2(local_keyset), + funding_satoshis=rbf_funding_amount, + funding_feerate_perkw=253 * 65 // 64, + locktime=0, + ), + # Ignore unknown odd messages + TryAll([], RawMsg(bytes.fromhex("270F"))), + ExpectMsg( + "ack_rbf", channel_id=channel_id_v2(local_keyset), funding_satoshis=0 + ), + ] # Now we forget that we're in the middle of an RBF - test += [Msg('tx_add_input', - channel_id=channel_id_v2(local_keyset), - serial_id=2, - prevtx=tx_spendable, - prevtx_vout=tx_out_for_index(input_index), - sequence=0xfffffffd, - script_sig=''), - - # Ignore unknown odd messages - TryAll([], RawMsg(bytes.fromhex('270F'))), - - ExpectMsg('tx_complete', - channel_id=channel_id_v2(local_keyset)), - - # Ignore unknown odd messages - TryAll([], RawMsg(bytes.fromhex('270F'))), - - # Let's RBF, again? - Msg('init_rbf', - channel_id=channel_id_v2(local_keyset), - funding_satoshis=rbf_funding_amount, - funding_feerate_perkw=253 * 65 // 64, - locktime=0), - - ExpectError(), - - # We reconnect! - Disconnect(), - Connect(connprivkey='02'), - ExpectMsg('init'), - Msg('init', globalfeatures='', features=bitfield(12, 20, 29)), - - # We expect them to send channel reestablish - ExpectMsg('channel_reestablish', - channel_id=channel_id_v2(local_keyset), - next_commitment_number=1, - next_revocation_number=0, - your_last_per_commitment_secret='00' * 32, - ignore=ExpectMsg.ignore_all_gossip), - - Msg('channel_reestablish', - channel_id=channel_id_v2(local_keyset), - next_commitment_number=1, - next_revocation_number=0, - your_last_per_commitment_secret='00' * 32, - my_current_per_commitment_point=local_keyset.per_commit_point(0)), - - ExpectMsg('tx_signatures', - channel_id=channel_id_v2(local_keyset), - txid=funding_txid()), - - # Let's RBF, again? - Msg('init_rbf', - channel_id=channel_id_v2(local_keyset), - funding_satoshis=rbf_funding_amount, - funding_feerate_perkw=253 * 65 // 64, - locktime=0), - - # Ignore unknown odd messages - TryAll([], RawMsg(bytes.fromhex('270F'))), - ExpectMsg('ack_rbf', - channel_id=channel_id_v2(local_keyset), - funding_satoshis=0), - ] + test += [ + Msg( + "tx_add_input", + channel_id=channel_id_v2(local_keyset), + serial_id=2, + prevtx=tx_spendable, + prevtx_vout=tx_out_for_index(input_index), + sequence=0xFFFFFFFD, + script_sig="", + ), + # Ignore unknown odd messages + TryAll([], RawMsg(bytes.fromhex("270F"))), + ExpectMsg("tx_complete", channel_id=channel_id_v2(local_keyset)), + # Ignore unknown odd messages + TryAll([], RawMsg(bytes.fromhex("270F"))), + # Let's RBF, again? + Msg( + "init_rbf", + channel_id=channel_id_v2(local_keyset), + funding_satoshis=rbf_funding_amount, + funding_feerate_perkw=253 * 65 // 64, + locktime=0, + ), + ExpectError(), + # We reconnect! + Disconnect(), + Connect(connprivkey="02"), + ExpectMsg("init"), + Msg("init", globalfeatures="", features=bitfield(12, 20, 29)), + # We expect them to send channel reestablish + ExpectMsg( + "channel_reestablish", + channel_id=channel_id_v2(local_keyset), + next_commitment_number=1, + next_revocation_number=0, + your_last_per_commitment_secret="00" * 32, + ignore=ExpectMsg.ignore_all_gossip, + ), + Msg( + "channel_reestablish", + channel_id=channel_id_v2(local_keyset), + next_commitment_number=1, + next_revocation_number=0, + your_last_per_commitment_secret="00" * 32, + my_current_per_commitment_point=local_keyset.per_commit_point(0), + ), + ExpectMsg( + "tx_signatures", channel_id=channel_id_v2(local_keyset), txid=funding_txid() + ), + # Let's RBF, again? + Msg( + "init_rbf", + channel_id=channel_id_v2(local_keyset), + funding_satoshis=rbf_funding_amount, + funding_feerate_perkw=253 * 65 // 64, + locktime=0, + ), + # Ignore unknown odd messages + TryAll([], RawMsg(bytes.fromhex("270F"))), + ExpectMsg( + "ack_rbf", channel_id=channel_id_v2(local_keyset), funding_satoshis=0 + ), + ] runner.run(test) def test_rbf_opener_forgets(runner: Runner, with_proposal: Any) -> None: with_proposal(dual_fund_csv) - runner.add_startup_flag('experimental-dual-fund') + runner.add_startup_flag("experimental-dual-fund") - local_funding_privkey = '20' - local_keyset = KeySet(revocation_base_secret='21', - payment_base_secret='22', - htlc_base_secret='24', - delayed_payment_base_secret='23', - shachain_seed='00' * 32) + local_funding_privkey = "20" + local_keyset = KeySet( + revocation_base_secret="21", + payment_base_secret="22", + htlc_base_secret="24", + delayed_payment_base_secret="23", + shachain_seed="00" * 32, + ) # Index 5 is special, only the test runner can spend it input_index = 5 @@ -2227,318 +2393,325 @@ def test_rbf_opener_forgets(runner: Runner, with_proposal: Any) -> None: initial_feerate = 2000 rbf_feerate = initial_feerate * 65 // 64 - test = [Block(blockheight=102, txs=[tx_spendable]), - Connect(connprivkey='02'), - ExpectMsg('init'), - - # BOLT-f53ca2301232db780843e894f55d95d512f297f9 #9: - # | 28/29 | `option_dual_fund` | Use v2 of channel open, enables dual funding | IN9 | `option_anchor_outputs`, `option_static_remotekey` | [BOLT #2](02-peer-protocol.md) | - - Msg('init', globalfeatures='', features=bitfield(12, 20, 29)), - - FundChannel(amount=funding_amount, feerate=initial_feerate), - - ExpectMsg('open_channel2', - channel_id=channel_id_tmp(local_keyset, Side.remote), - chain_hash=regtest_hash, - funding_satoshis=funding_amount, - dust_limit_satoshis=546, - htlc_minimum_msat=0, - 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), - channel_flags='01'), - - Msg('accept_channel2', - channel_id=rcvd('open_channel2.channel_id'), - dust_limit_satoshis=550, - funding_satoshis=400000, - max_htlc_value_in_flight_msat=4294967295, - htlc_minimum_msat=0, - minimum_depth=3, - max_accepted_htlcs=483, - # We use 5, to be different from c-lightning runner who uses 6 - 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'))), - ] - - test += opener_tx_creation(input_index, False, funding_amount, - local_funding_privkey, local_keyset, - runner) - - test += [TryAll([], RawMsg(bytes.fromhex('270F'))), - # Let's RBF - InitRbf(channel_id=channel_id_v2(local_keyset), - amount=rbf_funding_amount, - utxo_tx=rcvd('tx_add_input.prevtx'), - utxo_outnum=rcvd('tx_add_input.prevtx_vout', int), - feerate=rbf_feerate), - - ExpectMsg('init_rbf', - channel_id=channel_id_v2(local_keyset), - funding_satoshis=rbf_funding_amount, - funding_feerate_perkw=rbf_feerate), - - # Ignore unknown odd messages - TryAll([], RawMsg(bytes.fromhex('270F'))), - - Msg('ack_rbf', - channel_id=channel_id_v2(local_keyset), - funding_satoshis=400000), - ] + test = [ + Block(blockheight=102, txs=[tx_spendable]), + Connect(connprivkey="02"), + ExpectMsg("init"), + # BOLT-f53ca2301232db780843e894f55d95d512f297f9 #9: + # | 28/29 | `option_dual_fund` | Use v2 of channel open, enables dual funding | IN9 | `option_anchor_outputs`, `option_static_remotekey` | [BOLT #2](02-peer-protocol.md) | + Msg("init", globalfeatures="", features=bitfield(12, 20, 29)), + FundChannel(amount=funding_amount, feerate=initial_feerate), + ExpectMsg( + "open_channel2", + channel_id=channel_id_tmp(local_keyset, Side.remote), + chain_hash=regtest_hash, + funding_satoshis=funding_amount, + dust_limit_satoshis=546, + htlc_minimum_msat=0, + 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), + channel_flags="01", + ), + Msg( + "accept_channel2", + channel_id=rcvd("open_channel2.channel_id"), + dust_limit_satoshis=550, + funding_satoshis=400000, + max_htlc_value_in_flight_msat=4294967295, + htlc_minimum_msat=0, + minimum_depth=3, + max_accepted_htlcs=483, + # We use 5, to be different from c-lightning runner who uses 6 + 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"))), + ] + + test += opener_tx_creation( + input_index, False, funding_amount, local_funding_privkey, local_keyset, runner + ) + + test += [ + TryAll([], RawMsg(bytes.fromhex("270F"))), + # Let's RBF + InitRbf( + channel_id=channel_id_v2(local_keyset), + amount=rbf_funding_amount, + utxo_tx=rcvd("tx_add_input.prevtx"), + utxo_outnum=rcvd("tx_add_input.prevtx_vout", int), + feerate=rbf_feerate, + ), + ExpectMsg( + "init_rbf", + channel_id=channel_id_v2(local_keyset), + funding_satoshis=rbf_funding_amount, + funding_feerate_perkw=rbf_feerate, + ), + # Ignore unknown odd messages + TryAll([], RawMsg(bytes.fromhex("270F"))), + Msg("ack_rbf", channel_id=channel_id_v2(local_keyset), funding_satoshis=400000), + ] # Now we forget that we're in the middle of an RBF - test += [ExpectMsg('tx_add_input', - channel_id=channel_id_v2(local_keyset), - if_match=even_serial, - prevtx=tx_spendable, - sequence=0xfffffffd, - script_sig=''), - - # Ignore unknown odd messages - TryAll([], RawMsg(bytes.fromhex('270F'))), - - Msg('tx_add_input', - channel_id=channel_id_v2(local_keyset), - serial_id=1, - sequence=0xfffffffd, - prevtx=tx_spendable, - prevtx_vout=tx_out_for_index(input_index), - script_sig=''), - - # The funding output - ExpectMsg('tx_add_output', - channel_id=channel_id_v2(local_keyset), - if_match=even_serial), - - # Ignore unknown odd messages - TryAll([], RawMsg(bytes.fromhex('270F'))), - - # Let's RBF, again? - Msg('init_rbf', - channel_id=channel_id_v2(local_keyset), - funding_satoshis=rbf_funding_amount, - funding_feerate_perkw=rbf_feerate, - locktime=100), - - ExpectError(), - - # We reconnect! - Disconnect(), - Connect(connprivkey='02'), - ExpectMsg('init'), - Msg('init', globalfeatures='', features=bitfield(12, 20, 29)), - - # We expect them to send channel reestablish - ExpectMsg('channel_reestablish', - channel_id=channel_id_v2(local_keyset), - next_commitment_number=1, - next_revocation_number=0, - your_last_per_commitment_secret='00' * 32, - ignore=ExpectMsg.ignore_all_gossip), - - Msg('channel_reestablish', - channel_id=channel_id_v2(local_keyset), - next_commitment_number=1, - next_revocation_number=0, - your_last_per_commitment_secret='00' * 32, - my_current_per_commitment_point=local_keyset.per_commit_point(0)), - - ExpectMsg('tx_signatures', - channel_id=channel_id_v2(local_keyset), - txid=funding_txid()), - - # Let's RBF again? - InitRbf(channel_id=channel_id_v2(local_keyset), - amount=rbf_funding_amount, - utxo_tx=rcvd('tx_add_input.prevtx'), - utxo_outnum=rcvd('tx_add_input.prevtx_vout', int), - feerate=rbf_feerate), - - ExpectMsg('init_rbf', - channel_id=channel_id_v2(local_keyset), - funding_satoshis=rbf_funding_amount, - funding_feerate_perkw=rbf_feerate), - - # Ignore unknown odd messages - TryAll([], RawMsg(bytes.fromhex('270F'))), - - Msg('ack_rbf', - channel_id=channel_id_v2(local_keyset), - funding_satoshis=400000), - ] + test += [ + ExpectMsg( + "tx_add_input", + channel_id=channel_id_v2(local_keyset), + if_match=even_serial, + prevtx=tx_spendable, + sequence=0xFFFFFFFD, + script_sig="", + ), + # Ignore unknown odd messages + TryAll([], RawMsg(bytes.fromhex("270F"))), + Msg( + "tx_add_input", + channel_id=channel_id_v2(local_keyset), + serial_id=1, + sequence=0xFFFFFFFD, + prevtx=tx_spendable, + prevtx_vout=tx_out_for_index(input_index), + script_sig="", + ), + # The funding output + ExpectMsg( + "tx_add_output", + channel_id=channel_id_v2(local_keyset), + if_match=even_serial, + ), + # Ignore unknown odd messages + TryAll([], RawMsg(bytes.fromhex("270F"))), + # Let's RBF, again? + Msg( + "init_rbf", + channel_id=channel_id_v2(local_keyset), + funding_satoshis=rbf_funding_amount, + funding_feerate_perkw=rbf_feerate, + locktime=100, + ), + ExpectError(), + # We reconnect! + Disconnect(), + Connect(connprivkey="02"), + ExpectMsg("init"), + Msg("init", globalfeatures="", features=bitfield(12, 20, 29)), + # We expect them to send channel reestablish + ExpectMsg( + "channel_reestablish", + channel_id=channel_id_v2(local_keyset), + next_commitment_number=1, + next_revocation_number=0, + your_last_per_commitment_secret="00" * 32, + ignore=ExpectMsg.ignore_all_gossip, + ), + Msg( + "channel_reestablish", + channel_id=channel_id_v2(local_keyset), + next_commitment_number=1, + next_revocation_number=0, + your_last_per_commitment_secret="00" * 32, + my_current_per_commitment_point=local_keyset.per_commit_point(0), + ), + ExpectMsg( + "tx_signatures", channel_id=channel_id_v2(local_keyset), txid=funding_txid() + ), + # Let's RBF again? + InitRbf( + channel_id=channel_id_v2(local_keyset), + amount=rbf_funding_amount, + utxo_tx=rcvd("tx_add_input.prevtx"), + utxo_outnum=rcvd("tx_add_input.prevtx_vout", int), + feerate=rbf_feerate, + ), + ExpectMsg( + "init_rbf", + channel_id=channel_id_v2(local_keyset), + funding_satoshis=rbf_funding_amount, + funding_feerate_perkw=rbf_feerate, + ), + # Ignore unknown odd messages + TryAll([], RawMsg(bytes.fromhex("270F"))), + Msg("ack_rbf", channel_id=channel_id_v2(local_keyset), funding_satoshis=400000), + ] runner.run(test) def test_rbf_not_valid_rbf(runner: Runner, with_proposal: Any) -> None: - """ The proposed rbf doesn't have any overlapping inputs """ + """The proposed rbf doesn't have any overlapping inputs""" with_proposal(dual_fund_csv) - runner.add_startup_flag('experimental-dual-fund') - - local_funding_privkey = '20' - local_keyset = KeySet(revocation_base_secret='21', - payment_base_secret='22', - htlc_base_secret='24', - delayed_payment_base_secret='23', - shachain_seed='00' * 32) + runner.add_startup_flag("experimental-dual-fund") + + local_funding_privkey = "20" + local_keyset = KeySet( + revocation_base_secret="21", + payment_base_secret="22", + htlc_base_secret="24", + delayed_payment_base_secret="23", + shachain_seed="00" * 32, + ) orig_input_index = 0 other_input_index = 2 funding_amount = funding_amount_for_utxo(orig_input_index) rbf_funding_amount = funding_amount - 1000 - test = [Block(blockheight=102, txs=[tx_spendable]), - Connect(connprivkey='02'), - ExpectMsg('init'), - - # BOLT-f53ca2301232db780843e894f55d95d512f297f9 #9: - # | 28/29 | `option_dual_fund` | Use v2 of channel open, enables dual funding | IN9 | `option_anchor_outputs`, `option_static_remotekey` | [BOLT #2](02-peer-protocol.md) | - - Msg('init', globalfeatures='', features=bitfield(12, 20, 29)), - - # Accepter side: we initiate a new channel. - Msg('open_channel2', - channel_id=channel_id_tmp(local_keyset, Side.local), - chain_hash=regtest_hash, - funding_satoshis=funding_amount, - dust_limit_satoshis=546, - max_htlc_value_in_flight_msat=4294967295, - htlc_minimum_msat=0, - funding_feerate_perkw=253, - commitment_feerate_perkw=253, - # We use 5, because c-lightning runner uses 6, so this is different. - to_self_delay=5, - max_accepted_htlcs=483, - locktime=0, - 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_channel2', - channel_id=sent('open_channel2.channel_id'), - funding_satoshis=0, - 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)), - - # Ignore unknown odd messages - TryAll([], RawMsg(bytes.fromhex('270F'))), - ] - - test += accepter_tx_creation(orig_input_index, False, funding_amount, - local_funding_privkey, local_keyset, - runner) - - test += [TryAll([], RawMsg(bytes.fromhex('270F'))), - # Let's RBF - Msg('init_rbf', - channel_id=channel_id_v2(local_keyset), - funding_satoshis=rbf_funding_amount, - funding_feerate_perkw=253 * 65 // 64, - locktime=0), - # Ignore unknown odd messages - TryAll([], RawMsg(bytes.fromhex('270F'))), - ExpectMsg('ack_rbf', - channel_id=channel_id_v2(local_keyset), - funding_satoshis=0), - ] + test = [ + Block(blockheight=102, txs=[tx_spendable]), + Connect(connprivkey="02"), + ExpectMsg("init"), + # BOLT-f53ca2301232db780843e894f55d95d512f297f9 #9: + # | 28/29 | `option_dual_fund` | Use v2 of channel open, enables dual funding | IN9 | `option_anchor_outputs`, `option_static_remotekey` | [BOLT #2](02-peer-protocol.md) | + Msg("init", globalfeatures="", features=bitfield(12, 20, 29)), + # Accepter side: we initiate a new channel. + Msg( + "open_channel2", + channel_id=channel_id_tmp(local_keyset, Side.local), + chain_hash=regtest_hash, + funding_satoshis=funding_amount, + dust_limit_satoshis=546, + max_htlc_value_in_flight_msat=4294967295, + htlc_minimum_msat=0, + funding_feerate_perkw=253, + commitment_feerate_perkw=253, + # We use 5, because c-lightning runner uses 6, so this is different. + to_self_delay=5, + max_accepted_htlcs=483, + locktime=0, + 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_channel2", + channel_id=sent("open_channel2.channel_id"), + funding_satoshis=0, + 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), + ), + # Ignore unknown odd messages + TryAll([], RawMsg(bytes.fromhex("270F"))), + ] + + test += accepter_tx_creation( + orig_input_index, + False, + funding_amount, + local_funding_privkey, + local_keyset, + runner, + ) + + test += [ + TryAll([], RawMsg(bytes.fromhex("270F"))), + # Let's RBF + Msg( + "init_rbf", + channel_id=channel_id_v2(local_keyset), + funding_satoshis=rbf_funding_amount, + funding_feerate_perkw=253 * 65 // 64, + locktime=0, + ), + # Ignore unknown odd messages + TryAll([], RawMsg(bytes.fromhex("270F"))), + ExpectMsg( + "ack_rbf", channel_id=channel_id_v2(local_keyset), funding_satoshis=0 + ), + ] txid_in, tx_index_in, sats_in, spending_privkey, fee = utxo(other_input_index) fee = sats_in - rbf_funding_amount - test += [CreateFunding(txid_in=txid_in, - tx_index_in=tx_index_in, - sats_in=sats_in, - spending_privkey=spending_privkey, - fee=fee, - 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('accept_channel2.to_self_delay', int), - remote_to_self_delay=sent('open_channel2.to_self_delay', int), - local_amount=msat(sent('init_rbf.funding_satoshis', int)), - remote_amount=msat(rcvd('ack_rbf.funding_satoshis', int)), - local_dust_limit=546, - remote_dust_limit=546, - feerate=253, - local_features=sent('init.features'), - remote_features=rcvd('init.features')), - - Msg('tx_add_input', - channel_id=channel_id_v2(local_keyset), - serial_id=2, - prevtx=tx_spendable, - prevtx_vout=tx_out_for_index(other_input_index), - sequence=0xfffffffd, - script_sig=''), - - AddInput(funding=funding(), - privkey=privkey_for_index(other_input_index), - serial_id=sent('tx_add_input.serial_id', int), - prevtx=sent(), - prevtx_vout=sent('tx_add_input.prevtx_vout', int), - script_sig=sent()), - - # Ignore unknown odd messages - TryAll([], RawMsg(bytes.fromhex('270F'))), - - ExpectMsg('tx_complete', - channel_id=channel_id_v2(local_keyset)), - - Msg('tx_add_output', - channel_id=channel_id_v2(local_keyset), - serial_id=2, - sats=rbf_funding_amount, - script=locking_script()), - - AddOutput(funding=funding(), - serial_id=sent('tx_add_output.serial_id', int), - script=sent(), - sats=sent('tx_add_output.sats', int)), - - # Ignore unknown odd messages - TryAll([], RawMsg(bytes.fromhex('270F'))), - - ExpectMsg('tx_complete', - channel_id=channel_id_v2(local_keyset)), - - Msg('tx_complete', - channel_id=channel_id_v2(local_keyset)), - - FinalizeFunding(funding=funding()), - - # Ignore unknown odd messages - TryAll([], RawMsg(bytes.fromhex('270F'))), - - # We fail, because there's no overlapping input - ExpectError(), - ] + test += [ + CreateFunding( + txid_in=txid_in, + tx_index_in=tx_index_in, + sats_in=sats_in, + spending_privkey=spending_privkey, + fee=fee, + 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("accept_channel2.to_self_delay", int), + remote_to_self_delay=sent("open_channel2.to_self_delay", int), + local_amount=msat(sent("init_rbf.funding_satoshis", int)), + remote_amount=msat(rcvd("ack_rbf.funding_satoshis", int)), + local_dust_limit=546, + remote_dust_limit=546, + feerate=253, + local_features=sent("init.features"), + remote_features=rcvd("init.features"), + ), + Msg( + "tx_add_input", + channel_id=channel_id_v2(local_keyset), + serial_id=2, + prevtx=tx_spendable, + prevtx_vout=tx_out_for_index(other_input_index), + sequence=0xFFFFFFFD, + script_sig="", + ), + AddInput( + funding=funding(), + privkey=privkey_for_index(other_input_index), + serial_id=sent("tx_add_input.serial_id", int), + prevtx=sent(), + prevtx_vout=sent("tx_add_input.prevtx_vout", int), + script_sig=sent(), + ), + # Ignore unknown odd messages + TryAll([], RawMsg(bytes.fromhex("270F"))), + ExpectMsg("tx_complete", channel_id=channel_id_v2(local_keyset)), + Msg( + "tx_add_output", + channel_id=channel_id_v2(local_keyset), + serial_id=2, + sats=rbf_funding_amount, + script=locking_script(), + ), + AddOutput( + funding=funding(), + serial_id=sent("tx_add_output.serial_id", int), + script=sent(), + sats=sent("tx_add_output.sats", int), + ), + # Ignore unknown odd messages + TryAll([], RawMsg(bytes.fromhex("270F"))), + ExpectMsg("tx_complete", channel_id=channel_id_v2(local_keyset)), + Msg("tx_complete", channel_id=channel_id_v2(local_keyset)), + FinalizeFunding(funding=funding()), + # Ignore unknown odd messages + TryAll([], RawMsg(bytes.fromhex("270F"))), + # We fail, because there's no overlapping input + ExpectError(), + ] runner.run(test) diff --git a/tests/test_bolt2-30-channel_type-open-accept-tlvs.py b/tests/test_bolt2-30-channel_type-open-accept-tlvs.py index 7f0b572..b01c9fd 100644 --- a/tests/test_bolt2-30-channel_type-open-accept-tlvs.py +++ b/tests/test_bolt2-30-channel_type-open-accept-tlvs.py @@ -1,6 +1,18 @@ #! /usr/bin/env python3 -from lnprototest import TryAll, Connect, Block, ExpectMsg, Msg, Runner, KeySet, regtest_hash, bitfield, channel_type_csv, ExpectError +from lnprototest import ( + TryAll, + Connect, + Block, + ExpectMsg, + Msg, + Runner, + KeySet, + regtest_hash, + bitfield, + channel_type_csv, + ExpectError, +) from helpers import tx_spendable, funding_amount_for_utxo, pubkey_of from typing import Any import pytest @@ -11,57 +23,60 @@ def test_open_channel(runner: Runner, with_proposal: Any) -> None: with_proposal(channel_type_csv) # This is not a feature bit, so use support_ to mark it. - if runner.has_option('supports_open_accept_channel_type') is None: - pytest.skip('Needs supports_open_accept_channel_type') - - local_funding_privkey = '20' - - local_keyset = KeySet(revocation_base_secret='21', - payment_base_secret='22', - htlc_base_secret='24', - delayed_payment_base_secret='23', - shachain_seed='00' * 32) - - 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))), - - 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, - # We negotiate *down* to a simple static channel - tlvs='{channel_type={type=' + bitfield(12) + '}}'), - - # BOLT #2 - # - if it sets `channel_type`: - # - MUST set it to the `channel_type` from `open_channel` - ExpectMsg('accept_channel', - tlvs='{channel_type={type=' + bitfield(12) + '}}')] + if runner.has_option("supports_open_accept_channel_type") is None: + pytest.skip("Needs supports_open_accept_channel_type") + + local_funding_privkey = "20" + + local_keyset = KeySet( + revocation_base_secret="21", + payment_base_secret="22", + htlc_base_secret="24", + delayed_payment_base_secret="23", + shachain_seed="00" * 32, + ) + + 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)), + ), + 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, + # We negotiate *down* to a simple static channel + tlvs="{channel_type={type=" + bitfield(12) + "}}", + ), + # BOLT #2 + # - if it sets `channel_type`: + # - MUST set it to the `channel_type` from `open_channel` + ExpectMsg("accept_channel", tlvs="{channel_type={type=" + bitfield(12) + "}}"), + ] runner.run(test) @@ -71,53 +86,56 @@ def test_open_channel_empty_type(runner: Runner, with_proposal: Any) -> None: with_proposal(channel_type_csv) # This is not a feature bit, so use support_ to mark it. - if runner.has_option('supports_open_accept_channel_type') is None: - pytest.skip('Needs supports_open_accept_channel_type') - - local_funding_privkey = '20' - - local_keyset = KeySet(revocation_base_secret='21', - payment_base_secret='22', - htlc_base_secret='24', - delayed_payment_base_secret='23', - shachain_seed='00' * 32) - - test = [Block(blockheight=102, txs=[tx_spendable]), - Connect(connprivkey='02'), - ExpectMsg('init'), - - TryAll( - # | 12/13 | `option_static_remotekey` | Static key for remote output - Msg('init', globalfeatures='', features=bitfield(13))), - - 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, - # We negotiate *down* to a simple non-static channel - tlvs='{channel_type={type=}}'), - - # BOLT #2 - # - if it sets `channel_type`: - # - MUST set it to the `channel_type` from `open_channel` - ExpectMsg('accept_channel', - tlvs='{channel_type={type=}}')] + if runner.has_option("supports_open_accept_channel_type") is None: + pytest.skip("Needs supports_open_accept_channel_type") + + local_funding_privkey = "20" + + local_keyset = KeySet( + revocation_base_secret="21", + payment_base_secret="22", + htlc_base_secret="24", + delayed_payment_base_secret="23", + shachain_seed="00" * 32, + ) + + test = [ + Block(blockheight=102, txs=[tx_spendable]), + Connect(connprivkey="02"), + ExpectMsg("init"), + TryAll( + # | 12/13 | `option_static_remotekey` | Static key for remote output + Msg("init", globalfeatures="", features=bitfield(13)) + ), + 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, + # We negotiate *down* to a simple non-static channel + tlvs="{channel_type={type=}}", + ), + # BOLT #2 + # - if it sets `channel_type`: + # - MUST set it to the `channel_type` from `open_channel` + ExpectMsg("accept_channel", tlvs="{channel_type={type=}}"), + ] runner.run(test) @@ -127,57 +145,61 @@ def test_open_channel_bad_type(runner: Runner, with_proposal: Any) -> None: with_proposal(channel_type_csv) # This is not a feature bit, so use support_ to mark it. - if runner.has_option('supports_open_accept_channel_type') is None: - pytest.skip('Needs supports_open_accept_channel_type') - - local_funding_privkey = '20' - - local_keyset = KeySet(revocation_base_secret='21', - payment_base_secret='22', - htlc_base_secret='24', - delayed_payment_base_secret='23', - shachain_seed='00' * 32) - - 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(12, 20)), - # BOLT #9: - # | 12/13 | `option_static_remotekey` | Static key for remote output - Msg('init', globalfeatures='', features=bitfield(12)), - # And not. - Msg('init', globalfeatures='', features='')), - - 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, - tlvs='{channel_type={type=' + bitfield(100) + '}}'), - - # BOLT #2 - # The receiving node MUST fail the channel if: - # - It supports `channel_types` and none of the `channel_types` - # are suitable. - ExpectError()] + if runner.has_option("supports_open_accept_channel_type") is None: + pytest.skip("Needs supports_open_accept_channel_type") + + local_funding_privkey = "20" + + local_keyset = KeySet( + revocation_base_secret="21", + payment_base_secret="22", + htlc_base_secret="24", + delayed_payment_base_secret="23", + shachain_seed="00" * 32, + ) + + 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(12, 20)), + # BOLT #9: + # | 12/13 | `option_static_remotekey` | Static key for remote output + Msg("init", globalfeatures="", features=bitfield(12)), + # And not. + Msg("init", globalfeatures="", features=""), + ), + 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, + tlvs="{channel_type={type=" + bitfield(100) + "}}", + ), + # BOLT #2 + # The receiving node MUST fail the channel if: + # - It supports `channel_types` and none of the `channel_types` + # are suitable. + ExpectError(), + ] runner.run(test) diff --git a/tests/test_bolt7-01-channel_announcement-success.py b/tests/test_bolt7-01-channel_announcement-success.py index 37b7273..d8efa2c 100644 --- a/tests/test_bolt7-01-channel_announcement-success.py +++ b/tests/test_bolt7-01-channel_announcement-success.py @@ -1,71 +1,84 @@ #! /usr/bin/env python3 # Simple gossip tests. -from lnprototest import Connect, Block, ExpectMsg, Msg, RawMsg, Funding, Side, MustNotMsg, Disconnect, Runner +from lnprototest import ( + Connect, + Block, + ExpectMsg, + Msg, + RawMsg, + Funding, + Side, + MustNotMsg, + Disconnect, + Runner, +) from helpers import tx_spendable, utxo import time def test_gossip(runner: Runner) -> None: # Make up a channel between nodes 02 and 03, using bitcoin privkeys 10 and 20 - funding, funding_tx = Funding.from_utxo(*utxo(0), - local_node_privkey='02', - local_funding_privkey='10', - remote_node_privkey='03', - remote_funding_privkey='20') + funding, funding_tx = Funding.from_utxo( + *utxo(0), + local_node_privkey="02", + local_funding_privkey="10", + remote_node_privkey="03", + remote_funding_privkey="20" + ) - test = [Block(blockheight=102, txs=[tx_spendable]), - Connect(connprivkey='03'), - ExpectMsg('init'), - Msg('init', globalfeatures='', features=''), - - Block(blockheight=103, number=6, txs=[funding_tx]), - - RawMsg(funding.channel_announcement('103x1x0', '')), - - # New peer connects, asking for initial_routing_sync. We *won't* relay channel_announcement, as there is no channel_update. - Connect(connprivkey='05'), - ExpectMsg('init'), - Msg('init', globalfeatures='', features='08'), - MustNotMsg('channel_announcement'), - Disconnect(), - - RawMsg(funding.channel_update('103x1x0', - Side.local, - disable=False, - cltv_expiry_delta=144, - htlc_minimum_msat=0, - fee_base_msat=1000, - fee_proportional_millionths=10, - timestamp=int(time.time()), - htlc_maximum_msat=None), - connprivkey='03'), - - # Now we'll relay to a new peer. - Connect(connprivkey='05'), - ExpectMsg('init'), - Msg('init', globalfeatures='', features='08'), - ExpectMsg('channel_announcement', - short_channel_id='103x1x0'), - ExpectMsg('channel_update', - short_channel_id='103x1x0', - message_flags=0, - channel_flags=0), - Disconnect(), - - # BOLT #7: - # A node: - # - SHOULD monitor the funding transactions in the blockchain, to - # identify channels that are being closed. - # - if the funding output of a channel is being spent: - # - SHOULD be removed from the local network view AND be - # considered closed. - Block(blockheight=109, txs=[funding.close_tx(200, '99')]), - - Connect(connprivkey='05'), - ExpectMsg('init'), - Msg('init', globalfeatures='', features='08'), - MustNotMsg('channel_announcement'), - MustNotMsg('channel_update')] + test = [ + Block(blockheight=102, txs=[tx_spendable]), + Connect(connprivkey="03"), + ExpectMsg("init"), + Msg("init", globalfeatures="", features=""), + Block(blockheight=103, number=6, txs=[funding_tx]), + RawMsg(funding.channel_announcement("103x1x0", "")), + # New peer connects, asking for initial_routing_sync. We *won't* relay channel_announcement, as there is no channel_update. + Connect(connprivkey="05"), + ExpectMsg("init"), + Msg("init", globalfeatures="", features="08"), + MustNotMsg("channel_announcement"), + Disconnect(), + RawMsg( + funding.channel_update( + "103x1x0", + Side.local, + disable=False, + cltv_expiry_delta=144, + htlc_minimum_msat=0, + fee_base_msat=1000, + fee_proportional_millionths=10, + timestamp=int(time.time()), + htlc_maximum_msat=None, + ), + connprivkey="03", + ), + # Now we'll relay to a new peer. + Connect(connprivkey="05"), + ExpectMsg("init"), + Msg("init", globalfeatures="", features="08"), + ExpectMsg("channel_announcement", short_channel_id="103x1x0"), + ExpectMsg( + "channel_update", + short_channel_id="103x1x0", + message_flags=0, + channel_flags=0, + ), + Disconnect(), + # BOLT #7: + # A node: + # - SHOULD monitor the funding transactions in the blockchain, to + # identify channels that are being closed. + # - if the funding output of a channel is being spent: + # - SHOULD be removed from the local network view AND be + # considered closed. + Block(blockheight=109, txs=[funding.close_tx(200, "99")]), + Connect(connprivkey="05"), + ExpectMsg("init"), + Msg("init", globalfeatures="", features="08"), + MustNotMsg("channel_announcement"), + MustNotMsg("channel_update"), + ] runner.run(test) diff --git a/tests/test_bolt7-02-channel_announcement-failure.py b/tests/test_bolt7-02-channel_announcement-failure.py index 14d688a..2cea4b5 100644 --- a/tests/test_bolt7-02-channel_announcement-failure.py +++ b/tests/test_bolt7-02-channel_announcement-failure.py @@ -1,7 +1,20 @@ #! /usr/bin/env python3 # Tests for malformed/bad channel_announcement -from lnprototest import Connect, Block, ExpectMsg, Msg, RawMsg, ExpectError, Funding, Side, MustNotMsg, Runner, TryAll, Sig +from lnprototest import ( + Connect, + Block, + ExpectMsg, + Msg, + RawMsg, + ExpectError, + Funding, + Side, + MustNotMsg, + Runner, + TryAll, + Sig, +) import time from typing import cast from helpers import utxo, tx_spendable @@ -18,103 +31,129 @@ def test_premature_channel_announcement(runner: Runner) -> None: # It's allowed (even encouraged!) to cache premature # channel_announcements, so we separate this from the other tests. - funding, funding_tx = Funding.from_utxo(*utxo(0), - local_node_privkey='02', - local_funding_privkey='10', - remote_node_privkey='03', - remote_funding_privkey='20') - - test = [Block(blockheight=102, txs=[tx_spendable]), - Connect(connprivkey='03'), - ExpectMsg('init'), - Msg('init', globalfeatures='', features=''), - - # txid 189c40b0728f382fe91c87270926584e48e0af3a6789f37454afee6c7560311d - Block(blockheight=103, txs=[funding_tx]), - - TryAll( - # Invalid `channel_announcement`: short_channel_id too young. - [RawMsg(funding.channel_announcement('103x1x0', ''))], - - # Invalid `channel_announcement`: short_channel_id *still* too young. - [Block(blockheight=104, number=4), - RawMsg(funding.channel_announcement('103x1x0', ''))]), - - # Needs a channel_update if it were to relay. - RawMsg(funding.channel_update('103x1x0', - Side.local, - disable=False, - cltv_expiry_delta=144, - htlc_minimum_msat=0, - fee_base_msat=1000, - fee_proportional_millionths=10, - timestamp=int(time.time()), - htlc_maximum_msat=None)), - - # New peer connects, asking for initial_routing_sync. We *won't* relay channel_announcement. - Connect(connprivkey='05'), - ExpectMsg('init'), - Msg('init', globalfeatures='', features='08'), - MustNotMsg('channel_announcement'), - MustNotMsg('channel_update')] + funding, funding_tx = Funding.from_utxo( + *utxo(0), + local_node_privkey="02", + local_funding_privkey="10", + remote_node_privkey="03", + remote_funding_privkey="20" + ) + + test = [ + Block(blockheight=102, txs=[tx_spendable]), + Connect(connprivkey="03"), + ExpectMsg("init"), + Msg("init", globalfeatures="", features=""), + # txid 189c40b0728f382fe91c87270926584e48e0af3a6789f37454afee6c7560311d + Block(blockheight=103, txs=[funding_tx]), + TryAll( + # Invalid `channel_announcement`: short_channel_id too young. + [RawMsg(funding.channel_announcement("103x1x0", ""))], + # Invalid `channel_announcement`: short_channel_id *still* too young. + [ + Block(blockheight=104, number=4), + RawMsg(funding.channel_announcement("103x1x0", "")), + ], + ), + # Needs a channel_update if it were to relay. + RawMsg( + funding.channel_update( + "103x1x0", + Side.local, + disable=False, + cltv_expiry_delta=144, + htlc_minimum_msat=0, + fee_base_msat=1000, + fee_proportional_millionths=10, + timestamp=int(time.time()), + htlc_maximum_msat=None, + ) + ), + # New peer connects, asking for initial_routing_sync. We *won't* relay channel_announcement. + Connect(connprivkey="05"), + ExpectMsg("init"), + Msg("init", globalfeatures="", features="08"), + MustNotMsg("channel_announcement"), + MustNotMsg("channel_update"), + ] runner.run(test) def test_bad_announcement(runner: Runner) -> None: - funding, funding_tx = Funding.from_utxo(*utxo(0), - local_node_privkey='02', - local_funding_privkey='10', - remote_node_privkey='03', - remote_funding_privkey='20') + funding, funding_tx = Funding.from_utxo( + *utxo(0), + local_node_privkey="02", + local_funding_privkey="10", + remote_node_privkey="03", + remote_funding_privkey="20" + ) # ### Ignored: - ann_bad_chainhash = funding.channel_announcement('103x1x0', '') - ann_bad_chainhash.fields['chain_hash'] = bytes.fromhex('6fe28c0ab6f1b372c1a6a246ae63f74f931e8365e15a089c68d6190000000000') - - ann_bad_scid_dne = funding.channel_announcement('103x2x0', '') - - ann_bad_scid_out_dne = funding.channel_announcement('103x1x1', '') - - ann_bad_bitcoin_key1 = Funding(funding.txid, funding.output_index, funding.amount, - local_node_privkey='02', - local_funding_privkey='10', - remote_node_privkey='03', - remote_funding_privkey='21').channel_announcement('103x1x0', '') - - ann_bad_bitcoin_key2 = Funding(funding.txid, funding.output_index, funding.amount, - local_node_privkey='02', - local_funding_privkey='11', - remote_node_privkey='03', - remote_funding_privkey='20').channel_announcement('103x1x0', '') + ann_bad_chainhash = funding.channel_announcement("103x1x0", "") + ann_bad_chainhash.fields["chain_hash"] = bytes.fromhex( + "6fe28c0ab6f1b372c1a6a246ae63f74f931e8365e15a089c68d6190000000000" + ) + + ann_bad_scid_dne = funding.channel_announcement("103x2x0", "") + + ann_bad_scid_out_dne = funding.channel_announcement("103x1x1", "") + + ann_bad_bitcoin_key1 = Funding( + funding.txid, + funding.output_index, + funding.amount, + local_node_privkey="02", + local_funding_privkey="10", + remote_node_privkey="03", + remote_funding_privkey="21", + ).channel_announcement("103x1x0", "") + + ann_bad_bitcoin_key2 = Funding( + funding.txid, + funding.output_index, + funding.amount, + local_node_privkey="02", + local_funding_privkey="11", + remote_node_privkey="03", + remote_funding_privkey="20", + ).channel_announcement("103x1x0", "") # ### These should cause an error - ann_bad_nodesig1 = funding.channel_announcement('103x1x0', '') - ann_bad_nodesig1.fields['node_signature_1'] = corrupt_sig(ann_bad_nodesig1.fields['node_signature_1']) - - ann_bad_nodesig2 = funding.channel_announcement('103x1x0', '') - ann_bad_nodesig2.fields['node_signature_2'] = corrupt_sig(ann_bad_nodesig2.fields['node_signature_2']) - - ann_bad_bitcoinsig1 = funding.channel_announcement('103x1x0', '') - ann_bad_bitcoinsig1.fields['bitcoin_signature_1'] = corrupt_sig(ann_bad_bitcoinsig1.fields['bitcoin_signature_1']) - - ann_bad_bitcoinsig2 = funding.channel_announcement('103x1x0', '') - ann_bad_bitcoinsig2.fields['bitcoin_signature_2'] = corrupt_sig(ann_bad_bitcoinsig2.fields['bitcoin_signature_2']) - - test = [Block(blockheight=102, txs=[tx_spendable]), - Connect(connprivkey='03'), - ExpectMsg('init'), - Msg('init', globalfeatures='', features=''), - - # txid 189c40b0728f382fe91c87270926584e48e0af3a6789f37454afee6c7560311d - Block(blockheight=103, number=6, txs=[funding_tx]), - - TryAll( - # These are all ignored - # BOLT #7: - # - if the specified `chain_hash` is unknown to the receiver: - # - MUST ignore the message. - [TryAll( + ann_bad_nodesig1 = funding.channel_announcement("103x1x0", "") + ann_bad_nodesig1.fields["node_signature_1"] = corrupt_sig( + ann_bad_nodesig1.fields["node_signature_1"] + ) + + ann_bad_nodesig2 = funding.channel_announcement("103x1x0", "") + ann_bad_nodesig2.fields["node_signature_2"] = corrupt_sig( + ann_bad_nodesig2.fields["node_signature_2"] + ) + + ann_bad_bitcoinsig1 = funding.channel_announcement("103x1x0", "") + ann_bad_bitcoinsig1.fields["bitcoin_signature_1"] = corrupt_sig( + ann_bad_bitcoinsig1.fields["bitcoin_signature_1"] + ) + + ann_bad_bitcoinsig2 = funding.channel_announcement("103x1x0", "") + ann_bad_bitcoinsig2.fields["bitcoin_signature_2"] = corrupt_sig( + ann_bad_bitcoinsig2.fields["bitcoin_signature_2"] + ) + + test = [ + Block(blockheight=102, txs=[tx_spendable]), + Connect(connprivkey="03"), + ExpectMsg("init"), + Msg("init", globalfeatures="", features=""), + # txid 189c40b0728f382fe91c87270926584e48e0af3a6789f37454afee6c7560311d + Block(blockheight=103, number=6, txs=[funding_tx]), + TryAll( + # These are all ignored + # BOLT #7: + # - if the specified `chain_hash` is unknown to the receiver: + # - MUST ignore the message. + [ + TryAll( [RawMsg(ann_bad_chainhash)], # BOLT #7: # - if the `short_channel_id`'s output does NOT correspond to a P2WSH (using @@ -122,58 +161,79 @@ def test_bad_announcement(runner: Runner) -> None: # [BOLT #3](03-transactions.md#funding-transaction-output)) OR the output is # spent: # - MUST ignore the message. - [RawMsg(ann_bad_scid_dne), - # Needs a channel_update if it were to relay. - RawMsg(funding.channel_update('103x2x0', - Side.local, - disable=False, - cltv_expiry_delta=144, - htlc_minimum_msat=0, - fee_base_msat=1000, - fee_proportional_millionths=10, - timestamp=int(time.time()), - htlc_maximum_msat=None))], - [RawMsg(ann_bad_scid_out_dne), - # Needs a channel_update if it were to relay. - RawMsg(funding.channel_update('103x1x1', - Side.local, - disable=False, - cltv_expiry_delta=144, - htlc_minimum_msat=0, - fee_base_msat=1000, - fee_proportional_millionths=10, - timestamp=int(time.time()), - htlc_maximum_msat=None))], + [ + RawMsg(ann_bad_scid_dne), + # Needs a channel_update if it were to relay. + RawMsg( + funding.channel_update( + "103x2x0", + Side.local, + disable=False, + cltv_expiry_delta=144, + htlc_minimum_msat=0, + fee_base_msat=1000, + fee_proportional_millionths=10, + timestamp=int(time.time()), + htlc_maximum_msat=None, + ) + ), + ], + [ + RawMsg(ann_bad_scid_out_dne), + # Needs a channel_update if it were to relay. + RawMsg( + funding.channel_update( + "103x1x1", + Side.local, + disable=False, + cltv_expiry_delta=144, + htlc_minimum_msat=0, + fee_base_msat=1000, + fee_proportional_millionths=10, + timestamp=int(time.time()), + htlc_maximum_msat=None, + ) + ), + ], [RawMsg(ann_bad_bitcoin_key1)], - [RawMsg(ann_bad_bitcoin_key2)]), - - # Needs a channel_update if it were to relay. - RawMsg(funding.channel_update('103x1x0', - Side.local, - disable=False, - cltv_expiry_delta=144, - htlc_minimum_msat=0, - fee_base_msat=1000, - fee_proportional_millionths=10, - timestamp=int(time.time()), - htlc_maximum_msat=None)), - - # New peer connects, asking for initial_routing_sync. We *won't* relay channel_announcement. - Connect(connprivkey='05'), - ExpectMsg('init'), - Msg('init', globalfeatures='', features='08'), - MustNotMsg('channel_announcement'), - MustNotMsg('channel_update')], - - # BOLT #7: - # - otherwise: - # - if `bitcoin_signature_1`, `bitcoin_signature_2`, `node_signature_1` OR - # `node_signature_2` are invalid OR NOT correct: - # - SHOULD fail the connection. - [TryAll([RawMsg(ann_bad_nodesig1)], - [RawMsg(ann_bad_nodesig2)], - [RawMsg(ann_bad_bitcoinsig1)], - [RawMsg(ann_bad_bitcoinsig2)]), - ExpectError()])] + [RawMsg(ann_bad_bitcoin_key2)], + ), + # Needs a channel_update if it were to relay. + RawMsg( + funding.channel_update( + "103x1x0", + Side.local, + disable=False, + cltv_expiry_delta=144, + htlc_minimum_msat=0, + fee_base_msat=1000, + fee_proportional_millionths=10, + timestamp=int(time.time()), + htlc_maximum_msat=None, + ) + ), + # New peer connects, asking for initial_routing_sync. We *won't* relay channel_announcement. + Connect(connprivkey="05"), + ExpectMsg("init"), + Msg("init", globalfeatures="", features="08"), + MustNotMsg("channel_announcement"), + MustNotMsg("channel_update"), + ], + # BOLT #7: + # - otherwise: + # - if `bitcoin_signature_1`, `bitcoin_signature_2`, `node_signature_1` OR + # `node_signature_2` are invalid OR NOT correct: + # - SHOULD fail the connection. + [ + TryAll( + [RawMsg(ann_bad_nodesig1)], + [RawMsg(ann_bad_nodesig2)], + [RawMsg(ann_bad_bitcoinsig1)], + [RawMsg(ann_bad_bitcoinsig2)], + ), + ExpectError(), + ], + ), + ] runner.run(test) diff --git a/tests/test_bolt7-10-gossip-filter.py b/tests/test_bolt7-10-gossip-filter.py index cd20a7e..8088ebd 100644 --- a/tests/test_bolt7-10-gossip-filter.py +++ b/tests/test_bolt7-10-gossip-filter.py @@ -1,164 +1,214 @@ #! /usr/bin/env python3 # Tests for gossip_timestamp_filter -from lnprototest import Connect, Block, ExpectMsg, Msg, RawMsg, Side, MustNotMsg, Disconnect, AnyOrder, Runner, Funding, bitfield +from lnprototest import ( + Connect, + Block, + ExpectMsg, + Msg, + RawMsg, + Side, + MustNotMsg, + Disconnect, + AnyOrder, + Runner, + Funding, + bitfield, +) import unittest import time from helpers import utxo, tx_spendable def test_gossip_timestamp_filter(runner: Runner) -> None: - if runner.has_option('option_gossip_queries') is None: - unittest.SkipTest('Needs option_gossip_queries') - - funding1, funding1_tx = Funding.from_utxo(*utxo(0), - local_node_privkey='02', - local_funding_privkey='10', - remote_node_privkey='03', - remote_funding_privkey='20') - - funding2, funding2_tx = Funding.from_utxo(*utxo(1), - local_node_privkey='04', - local_funding_privkey='30', - remote_node_privkey='05', - remote_funding_privkey='40') + if runner.has_option("option_gossip_queries") is None: + unittest.SkipTest("Needs option_gossip_queries") + + funding1, funding1_tx = Funding.from_utxo( + *utxo(0), + local_node_privkey="02", + local_funding_privkey="10", + remote_node_privkey="03", + remote_funding_privkey="20" + ) + + funding2, funding2_tx = Funding.from_utxo( + *utxo(1), + local_node_privkey="04", + local_funding_privkey="30", + remote_node_privkey="05", + remote_funding_privkey="40" + ) timestamp1 = int(time.time()) timestamp2 = timestamp1 + 1 - test = [Block(blockheight=102, txs=[tx_spendable]), - - Connect(connprivkey='03'), - ExpectMsg('init'), - Msg('init', globalfeatures='', features=''), - - # txid 189c40b0728f382fe91c87270926584e48e0af3a6789f37454afee6c7560311d - Block(blockheight=103, number=6, txs=[funding1_tx]), - - RawMsg(funding1.channel_announcement('103x1x0', '')), - RawMsg(funding1.node_announcement(Side.local, '', (1, 2, 3), 'foobar', b'', timestamp1)), - - # New peer connects, asks for gossip_timestamp_filter=all. We *won't* relay channel_announcement, as there is no channel_update. - Connect(connprivkey='05'), - ExpectMsg('init'), - # BOLT #9: - # | 6/7 | `gossip_queries` | More sophisticated gossip control - Msg('init', globalfeatures='', features=bitfield(6)), - Msg('gossip_timestamp_filter', chain_hash='06226e46111a0b59caaf126043eb5bbf28c34f3a5e332a1fc7b2b73cf188910f', - first_timestamp=0, timestamp_range=4294967295), - MustNotMsg('channel_announcement'), - MustNotMsg('channel_update'), - MustNotMsg('node_announcement'), - Disconnect(), - - # Now, with channel update - RawMsg(funding1.channel_update(side=Side.local, - short_channel_id='103x1x0', - disable=False, - cltv_expiry_delta=144, - htlc_minimum_msat=0, - fee_base_msat=1000, - fee_proportional_millionths=10, - timestamp=timestamp1, - htlc_maximum_msat=None), - connprivkey='03'), - - # New peer connects, asks for gossip_timestamp_filter=all. update and node announcement will be relayed. - Connect(connprivkey='05'), - ExpectMsg('init'), - Msg('init', globalfeatures='', features=bitfield(6)), - Msg('gossip_timestamp_filter', chain_hash='06226e46111a0b59caaf126043eb5bbf28c34f3a5e332a1fc7b2b73cf188910f', - first_timestamp=0, timestamp_range=4294967295), - - ExpectMsg('channel_announcement', short_channel_id='103x1x0'), - AnyOrder(ExpectMsg('channel_update', short_channel_id='103x1x0'), - ExpectMsg('node_announcement')), - Disconnect(), - - # BOLT #7: - # The receiver: - # - SHOULD send all gossip messages whose `timestamp` is greater or - # equal to `first_timestamp`, and less than `first_timestamp` plus - # `timestamp_range`. - Connect(connprivkey='05'), - ExpectMsg('init'), - Msg('init', globalfeatures='', features=bitfield(6)), - Msg('gossip_timestamp_filter', chain_hash='06226e46111a0b59caaf126043eb5bbf28c34f3a5e332a1fc7b2b73cf188910f', - first_timestamp=1000, timestamp_range=timestamp1 - 1000), - - MustNotMsg('channel_announcement'), - MustNotMsg('channel_update'), - MustNotMsg('node_announcement'), - Disconnect(), - - Connect(connprivkey='05'), - ExpectMsg('init'), - Msg('init', globalfeatures='', features=bitfield(6)), - Msg('gossip_timestamp_filter', chain_hash='06226e46111a0b59caaf126043eb5bbf28c34f3a5e332a1fc7b2b73cf188910f', - first_timestamp=timestamp1 + 1, timestamp_range=4294967295), - - MustNotMsg('channel_announcement'), - MustNotMsg('channel_update'), - MustNotMsg('node_announcement'), - Disconnect(), - - # These two succeed in getting the gossip, then stay connected for next test. - Connect(connprivkey='05'), - ExpectMsg('init'), - Msg('init', globalfeatures='', features=bitfield(6)), - Msg('gossip_timestamp_filter', chain_hash='06226e46111a0b59caaf126043eb5bbf28c34f3a5e332a1fc7b2b73cf188910f', - first_timestamp=timestamp1, timestamp_range=4294967295), - - ExpectMsg('channel_announcement', short_channel_id='103x1x0'), - AnyOrder(ExpectMsg('channel_update', short_channel_id='103x1x0'), - ExpectMsg('node_announcement')), - - Connect(connprivkey='06'), - ExpectMsg('init'), - Msg('init', globalfeatures='', features=bitfield(6)), - Msg('gossip_timestamp_filter', chain_hash='06226e46111a0b59caaf126043eb5bbf28c34f3a5e332a1fc7b2b73cf188910f', - first_timestamp=1000, timestamp_range=timestamp1 - 1000 + 1), - - ExpectMsg('channel_announcement', short_channel_id='103x1x0'), - AnyOrder(ExpectMsg('channel_update', short_channel_id='103x1x0'), - ExpectMsg('node_announcement')), - - - # BOLT #7: - # - SHOULD restrict future gossip messages to those whose `timestamp` - # is greater or equal to `first_timestamp`, and less than - # `first_timestamp` plus `timestamp_range`. - Block(blockheight=109, number=6, txs=[funding2_tx]), - - RawMsg(funding2.channel_announcement('109x1x0', ''), - connprivkey='03'), - RawMsg(funding2.channel_update(side=Side.local, - short_channel_id='109x1x0', - disable=False, - cltv_expiry_delta=144, - htlc_minimum_msat=0, - fee_base_msat=1000, - fee_proportional_millionths=10, - timestamp=timestamp2, - htlc_maximum_msat=None)), - RawMsg(funding2.channel_update(side=Side.remote, - short_channel_id='109x1x0', - disable=False, - cltv_expiry_delta=144, - htlc_minimum_msat=0, - fee_base_msat=1000, - fee_proportional_millionths=10, - timestamp=timestamp2, - htlc_maximum_msat=None)), - RawMsg(funding2.node_announcement(Side.local, '', (1, 2, 3), 'foobar2', b'', timestamp2)), - - # 005's filter covers this, 006's doesn't. - ExpectMsg('channel_announcement', short_channel_id='109x1x0', connprivkey='05'), - AnyOrder(ExpectMsg('channel_update', short_channel_id='109x1x0', channel_flags=0), - ExpectMsg('channel_update', short_channel_id='109x1x0', channel_flags=1), - ExpectMsg('node_announcement')), - - MustNotMsg('channel_announcement', connprivkey='06'), - MustNotMsg('channel_update'), - MustNotMsg('node_announcement')] + test = [ + Block(blockheight=102, txs=[tx_spendable]), + Connect(connprivkey="03"), + ExpectMsg("init"), + Msg("init", globalfeatures="", features=""), + # txid 189c40b0728f382fe91c87270926584e48e0af3a6789f37454afee6c7560311d + Block(blockheight=103, number=6, txs=[funding1_tx]), + RawMsg(funding1.channel_announcement("103x1x0", "")), + RawMsg( + funding1.node_announcement( + Side.local, "", (1, 2, 3), "foobar", b"", timestamp1 + ) + ), + # New peer connects, asks for gossip_timestamp_filter=all. We *won't* relay channel_announcement, as there is no channel_update. + Connect(connprivkey="05"), + ExpectMsg("init"), + # BOLT #9: + # | 6/7 | `gossip_queries` | More sophisticated gossip control + Msg("init", globalfeatures="", features=bitfield(6)), + Msg( + "gossip_timestamp_filter", + chain_hash="06226e46111a0b59caaf126043eb5bbf28c34f3a5e332a1fc7b2b73cf188910f", + first_timestamp=0, + timestamp_range=4294967295, + ), + MustNotMsg("channel_announcement"), + MustNotMsg("channel_update"), + MustNotMsg("node_announcement"), + Disconnect(), + # Now, with channel update + RawMsg( + funding1.channel_update( + side=Side.local, + short_channel_id="103x1x0", + disable=False, + cltv_expiry_delta=144, + htlc_minimum_msat=0, + fee_base_msat=1000, + fee_proportional_millionths=10, + timestamp=timestamp1, + htlc_maximum_msat=None, + ), + connprivkey="03", + ), + # New peer connects, asks for gossip_timestamp_filter=all. update and node announcement will be relayed. + Connect(connprivkey="05"), + ExpectMsg("init"), + Msg("init", globalfeatures="", features=bitfield(6)), + Msg( + "gossip_timestamp_filter", + chain_hash="06226e46111a0b59caaf126043eb5bbf28c34f3a5e332a1fc7b2b73cf188910f", + first_timestamp=0, + timestamp_range=4294967295, + ), + ExpectMsg("channel_announcement", short_channel_id="103x1x0"), + AnyOrder( + ExpectMsg("channel_update", short_channel_id="103x1x0"), + ExpectMsg("node_announcement"), + ), + Disconnect(), + # BOLT #7: + # The receiver: + # - SHOULD send all gossip messages whose `timestamp` is greater or + # equal to `first_timestamp`, and less than `first_timestamp` plus + # `timestamp_range`. + Connect(connprivkey="05"), + ExpectMsg("init"), + Msg("init", globalfeatures="", features=bitfield(6)), + Msg( + "gossip_timestamp_filter", + chain_hash="06226e46111a0b59caaf126043eb5bbf28c34f3a5e332a1fc7b2b73cf188910f", + first_timestamp=1000, + timestamp_range=timestamp1 - 1000, + ), + MustNotMsg("channel_announcement"), + MustNotMsg("channel_update"), + MustNotMsg("node_announcement"), + Disconnect(), + Connect(connprivkey="05"), + ExpectMsg("init"), + Msg("init", globalfeatures="", features=bitfield(6)), + Msg( + "gossip_timestamp_filter", + chain_hash="06226e46111a0b59caaf126043eb5bbf28c34f3a5e332a1fc7b2b73cf188910f", + first_timestamp=timestamp1 + 1, + timestamp_range=4294967295, + ), + MustNotMsg("channel_announcement"), + MustNotMsg("channel_update"), + MustNotMsg("node_announcement"), + Disconnect(), + # These two succeed in getting the gossip, then stay connected for next test. + Connect(connprivkey="05"), + ExpectMsg("init"), + Msg("init", globalfeatures="", features=bitfield(6)), + Msg( + "gossip_timestamp_filter", + chain_hash="06226e46111a0b59caaf126043eb5bbf28c34f3a5e332a1fc7b2b73cf188910f", + first_timestamp=timestamp1, + timestamp_range=4294967295, + ), + ExpectMsg("channel_announcement", short_channel_id="103x1x0"), + AnyOrder( + ExpectMsg("channel_update", short_channel_id="103x1x0"), + ExpectMsg("node_announcement"), + ), + Connect(connprivkey="06"), + ExpectMsg("init"), + Msg("init", globalfeatures="", features=bitfield(6)), + Msg( + "gossip_timestamp_filter", + chain_hash="06226e46111a0b59caaf126043eb5bbf28c34f3a5e332a1fc7b2b73cf188910f", + first_timestamp=1000, + timestamp_range=timestamp1 - 1000 + 1, + ), + ExpectMsg("channel_announcement", short_channel_id="103x1x0"), + AnyOrder( + ExpectMsg("channel_update", short_channel_id="103x1x0"), + ExpectMsg("node_announcement"), + ), + # BOLT #7: + # - SHOULD restrict future gossip messages to those whose `timestamp` + # is greater or equal to `first_timestamp`, and less than + # `first_timestamp` plus `timestamp_range`. + Block(blockheight=109, number=6, txs=[funding2_tx]), + RawMsg(funding2.channel_announcement("109x1x0", ""), connprivkey="03"), + RawMsg( + funding2.channel_update( + side=Side.local, + short_channel_id="109x1x0", + disable=False, + cltv_expiry_delta=144, + htlc_minimum_msat=0, + fee_base_msat=1000, + fee_proportional_millionths=10, + timestamp=timestamp2, + htlc_maximum_msat=None, + ) + ), + RawMsg( + funding2.channel_update( + side=Side.remote, + short_channel_id="109x1x0", + disable=False, + cltv_expiry_delta=144, + htlc_minimum_msat=0, + fee_base_msat=1000, + fee_proportional_millionths=10, + timestamp=timestamp2, + htlc_maximum_msat=None, + ) + ), + RawMsg( + funding2.node_announcement( + Side.local, "", (1, 2, 3), "foobar2", b"", timestamp2 + ) + ), + # 005's filter covers this, 006's doesn't. + ExpectMsg("channel_announcement", short_channel_id="109x1x0", connprivkey="05"), + AnyOrder( + ExpectMsg("channel_update", short_channel_id="109x1x0", channel_flags=0), + ExpectMsg("channel_update", short_channel_id="109x1x0", channel_flags=1), + ExpectMsg("node_announcement"), + ), + MustNotMsg("channel_announcement", connprivkey="06"), + MustNotMsg("channel_update"), + MustNotMsg("node_announcement"), + ] runner.run(test) diff --git a/tests/test_bolt7-20-query_channel_range.py b/tests/test_bolt7-20-query_channel_range.py index ea3814e..f9bc7d6 100644 --- a/tests/test_bolt7-20-query_channel_range.py +++ b/tests/test_bolt7-20-query_channel_range.py @@ -1,6 +1,26 @@ #! /usr/bin/env python3 # Tests for gossip_timestamp_filter -from lnprototest import Connect, Block, ExpectMsg, Msg, RawMsg, Funding, Event, Side, MustNotMsg, OneOf, Runner, bitfield, TryAll, Sequence, regtest_hash, CheckEq, EventError, namespace, Wait +from lnprototest import ( + Connect, + Block, + ExpectMsg, + Msg, + RawMsg, + Funding, + Event, + Side, + MustNotMsg, + OneOf, + Runner, + bitfield, + TryAll, + Sequence, + regtest_hash, + CheckEq, + EventError, + namespace, + Wait, +) from helpers import tx_spendable, utxo from typing import Optional import unittest @@ -32,16 +52,18 @@ def encode_timestamps(t1: int = 0, t2: int = 0) -> str: # `node_id_1`, or 0 if there was no `channel_update` from that node. # * `timestamp_node_id_2` is the timestamp of the `channel_update` for # `node_id_2`, or 0 if there was no `channel_update` from that node. - v, _ = channel_update_timestamps.val_from_str('{{timestamp_node_id_1={},timestamp_node_id_2={}}}'.format(t1, t2)) + v, _ = channel_update_timestamps.val_from_str( + "{{timestamp_node_id_1={},timestamp_node_id_2={}}}".format(t1, t2) + ) buf = io.BytesIO() channel_update_timestamps.write(buf, v, {}) return buf.getvalue().hex() -def decode_timestamps(runner: 'Runner', event: Event, field: str) -> str: +def decode_timestamps(runner: "Runner", event: Event, field: str) -> str: # Get timestamps from last reply_channel_range msg - timestamps = runner.get_stash(event, 'ExpectMsg')[-1][1]['tlvs']['timestamps_tlv'] + timestamps = runner.get_stash(event, "ExpectMsg")[-1][1]["tlvs"]["timestamps_tlv"] # BOLT #7: # Encoding types: @@ -49,23 +71,25 @@ def decode_timestamps(runner: 'Runner', event: Event, field: str) -> str: # order. # * `1`: array of `short_channel_id` types, in ascending order, compressed # with zlib deflate[1](#reference-1) - if timestamps['encoding_type'] == 0: - b = bytes.fromhex(timestamps['encoded_timestamps']) - elif timestamps['encoding_type'] == 1: - b = zlib.decompress(bytes.fromhex(timestamps['encoded_timestamps'])) + if timestamps["encoding_type"] == 0: + b = bytes.fromhex(timestamps["encoded_timestamps"]) + elif timestamps["encoding_type"] == 1: + b = zlib.decompress(bytes.fromhex(timestamps["encoded_timestamps"])) else: raise EventError(event, "Unknown encoding type: {}".format(timestamps)) return b.hex() -def decode_scids(runner: 'Runner', event: Event, field: str) -> str: +def decode_scids(runner: "Runner", event: Event, field: str) -> str: # Nothing to decode if dummy runner. if runner._is_dummy(): - return '' + return "" # Get encoded_short_ids from last msg. - encoded = bytes.fromhex(runner.get_stash(event, 'ExpectMsg')[-1][1]['encoded_short_ids']) + encoded = bytes.fromhex( + runner.get_stash(event, "ExpectMsg")[-1][1]["encoded_short_ids"] + ) # BOLT #7: # Encoding types: # * `0`: uncompressed array of `short_channel_id` types, in ascending @@ -77,9 +101,11 @@ def decode_scids(runner: 'Runner', event: Event, field: str) -> str: elif encoded[0] == 1: b = zlib.decompress(encoded[1:]) else: - raise EventError(event, "Unknown encoding type {}: {}".format(encoded[0], encoded.hex())) + raise EventError( + event, "Unknown encoding type {}: {}".format(encoded[0], encoded.hex()) + ) - scidtype = namespace().get_fundamentaltype('short_channel_id') + scidtype = namespace().get_fundamentaltype("short_channel_id") arr = [] buf = io.BytesIO(b) while True: @@ -88,7 +114,7 @@ def decode_scids(runner: 'Runner', event: Event, field: str) -> str: break arr.append(scid) - return ','.join([scidtype.val_to_str(a, {}) for a in arr]) + return ",".join([scidtype.val_to_str(a, {}) for a in arr]) def calc_checksum(update: Message) -> int: @@ -109,7 +135,7 @@ def calc_checksum(update: Message) -> int: # * [`byte`:`message_flags`] # Note: 2 bytes for `type` field - return crc32c.crc32c(buf[2 + 64:2 + 64 + 32 + 8] + buf[2 + 64 + 32 + 8 + 4:]) + return crc32c.crc32c(buf[2 + 64 : 2 + 64 + 32 + 8] + buf[2 + 64 + 32 + 8 + 4 :]) def update_checksums(update1: Optional[Message], update2: Optional[Message]) -> str: @@ -136,191 +162,254 @@ def update_checksums(update1: Optional[Message], update2: Optional[Message]) -> else: csum2 = 0 - return '{{checksum_node_id_1={},checksum_node_id_2={}}}'.format(csum1, csum2) + return "{{checksum_node_id_1={},checksum_node_id_2={}}}".format(csum1, csum2) def test_query_channel_range(runner: Runner) -> None: - if runner.has_option('option_gossip_queries') is None: - unittest.SkipTest('Needs option_gossip_queries') - - funding1, funding1_tx = Funding.from_utxo(*utxo(0), - local_node_privkey='02', - local_funding_privkey='10', - remote_node_privkey='03', - remote_funding_privkey='20') - - funding2, funding2_tx = Funding.from_utxo(*utxo(1), - local_node_privkey='04', - local_funding_privkey='30', - remote_node_privkey='05', - remote_funding_privkey='40') + if runner.has_option("option_gossip_queries") is None: + unittest.SkipTest("Needs option_gossip_queries") + + funding1, funding1_tx = Funding.from_utxo( + *utxo(0), + local_node_privkey="02", + local_funding_privkey="10", + remote_node_privkey="03", + remote_funding_privkey="20" + ) + + funding2, funding2_tx = Funding.from_utxo( + *utxo(1), + local_node_privkey="04", + local_funding_privkey="30", + remote_node_privkey="05", + remote_funding_privkey="40" + ) timestamp_103x1x0_LOCAL = int(time.time()) timestamp_109x1x0_LOCAL = timestamp_103x1x0_LOCAL - 1 timestamp_109x1x0_REMOTE = timestamp_109x1x0_LOCAL - 1 - ts_103x1x0 = encode_timestamps(*funding1.node_id_sort(timestamp_103x1x0_LOCAL, - 0)) - ts_109x1x0 = encode_timestamps(*funding2.node_id_sort(timestamp_109x1x0_LOCAL, - timestamp_109x1x0_REMOTE)) - - update_103x1x0_LOCAL = funding1.channel_update(side=Side.local, - short_channel_id='103x1x0', - disable=False, - cltv_expiry_delta=144, - htlc_minimum_msat=0, - fee_base_msat=1000, - fee_proportional_millionths=10, - timestamp=timestamp_103x1x0_LOCAL, - htlc_maximum_msat=None) - update_109x1x0_LOCAL = funding2.channel_update(side=Side.local, - short_channel_id='109x1x0', - disable=False, - cltv_expiry_delta=144, - htlc_minimum_msat=0, - fee_base_msat=1000, - fee_proportional_millionths=10, - timestamp=timestamp_109x1x0_LOCAL, - htlc_maximum_msat=None) - update_109x1x0_REMOTE = funding2.channel_update(side=Side.remote, - short_channel_id='109x1x0', - disable=False, - cltv_expiry_delta=144, - htlc_minimum_msat=0, - fee_base_msat=1000, - fee_proportional_millionths=10, - timestamp=timestamp_109x1x0_REMOTE, - htlc_maximum_msat=None) - - csums_103x1x0 = update_checksums(*funding1.node_id_sort(update_103x1x0_LOCAL, - None)) - csums_109x1x0 = update_checksums(*funding2.node_id_sort(update_109x1x0_LOCAL, - update_109x1x0_REMOTE)) - - test = [Block(blockheight=102, txs=[tx_spendable]), - # Channel 103x1x0 (between 002 and 003) - Block(blockheight=103, number=6, txs=[funding1_tx]), - # Channel 109x1x0 (between 004 and 005) - Block(blockheight=109, number=6, txs=[funding2_tx]), - - Connect(connprivkey='03'), - ExpectMsg('init'), - Msg('init', globalfeatures='', features=''), - - RawMsg(funding1.channel_announcement('103x1x0', '')), - RawMsg(update_103x1x0_LOCAL), - RawMsg(funding2.channel_announcement('109x1x0', '')), - RawMsg(update_109x1x0_LOCAL), - RawMsg(update_109x1x0_REMOTE), - - # c-lightning gets a race condition if we dont wait for - # these updates to be added to the gossip store - # FIXME: convert to explicit signal - Wait(1), - - # New peer connects, with gossip_query option. - Connect(connprivkey='05'), - ExpectMsg('init'), - # BOLT #9: - # | 6/7 | `gossip_queries` | More sophisticated gossip control - Msg('init', globalfeatures='', features=bitfield(7)), - - TryAll( - # No queries? Must not get anything. - [MustNotMsg('channel_announcement'), - MustNotMsg('channel_update'), - MustNotMsg('node_announcement')], - - # This should elicit an empty response - [Msg('query_channel_range', - chain_hash=regtest_hash, - first_blocknum=0, - number_of_blocks=103), - ExpectMsg('reply_channel_range', - chain_hash=regtest_hash, - first_blocknum=0, - number_of_blocks=103), - CheckEq(decode_scids, '')], - - # This should get the first one, not the second. - [Msg('query_channel_range', - chain_hash=regtest_hash, - first_blocknum=103, - number_of_blocks=1), - ExpectMsg('reply_channel_range', - chain_hash=regtest_hash, - first_blocknum=103, - number_of_blocks=1), - CheckEq(decode_scids, '103x1x0')], - - # This should get the second one, not the first. - [Msg('query_channel_range', - chain_hash=regtest_hash, - first_blocknum=109, - number_of_blocks=4294967295), - OneOf(ExpectMsg('reply_channel_range', - chain_hash=regtest_hash, - first_blocknum=109, - number_of_blocks=4294967186), - # Could truncate number_of_blocks. - ExpectMsg('reply_channel_range', - chain_hash=regtest_hash, - first_blocknum=109, - number_of_blocks=1)), - CheckEq(decode_scids, '109x1x0')], - - # This should get both. - [Msg('query_channel_range', - chain_hash=regtest_hash, - first_blocknum=103, - number_of_blocks=7), - ExpectMsg('reply_channel_range', - chain_hash=regtest_hash, - first_blocknum=103, - number_of_blocks=7), - CheckEq(decode_scids, '103x1x0,109x1x0')], - - # This should get appended timestamp fields with option_gossip_queries_ex - Sequence(enable=runner.has_option('option_gossip_queries_ex') is not None, - events=[Msg('query_channel_range', - chain_hash=regtest_hash, - first_blocknum=103, - number_of_blocks=7, - tlvs='{query_option={query_option_flags=1}}'), - ExpectMsg('reply_channel_range', - chain_hash=regtest_hash, - first_blocknum=103, - number_of_blocks=7), - CheckEq(decode_timestamps, ts_103x1x0 + ts_109x1x0), - CheckEq(decode_scids, '103x1x0,109x1x0')]), - - # This should get appended checksum fields with option_gossip_queries_ex - Sequence(enable=runner.has_option('option_gossip_queries_ex') is not None, - events=[Msg('query_channel_range', - chain_hash=regtest_hash, - first_blocknum=103, - number_of_blocks=7, - tlvs='{query_option={query_option_flags=2}}'), - ExpectMsg('reply_channel_range', - chain_hash=regtest_hash, - first_blocknum=103, - number_of_blocks=7, - tlvs='{checksums_tlv={checksums=[' + csums_103x1x0 + ',' + csums_109x1x0 + ']}}'), - CheckEq(decode_scids, '103x1x0,109x1x0')]), - - # This should append timestamps and checksums with option_gossip_queries_ex - Sequence(enable=runner.has_option('option_gossip_queries_ex') is not None, - events=[Msg('query_channel_range', - chain_hash=regtest_hash, - first_blocknum=103, - number_of_blocks=7, - tlvs='{query_option={query_option_flags=3}}'), - ExpectMsg('reply_channel_range', - chain_hash=regtest_hash, - first_blocknum=103, - number_of_blocks=7, - tlvs='{checksums_tlv={checksums=[' + csums_103x1x0 + ',' + csums_109x1x0 + ']}}'), - CheckEq(decode_timestamps, ts_103x1x0 + ts_109x1x0), - CheckEq(decode_scids, '103x1x0,109x1x0')]))] + ts_103x1x0 = encode_timestamps(*funding1.node_id_sort(timestamp_103x1x0_LOCAL, 0)) + ts_109x1x0 = encode_timestamps( + *funding2.node_id_sort(timestamp_109x1x0_LOCAL, timestamp_109x1x0_REMOTE) + ) + + update_103x1x0_LOCAL = funding1.channel_update( + side=Side.local, + short_channel_id="103x1x0", + disable=False, + cltv_expiry_delta=144, + htlc_minimum_msat=0, + fee_base_msat=1000, + fee_proportional_millionths=10, + timestamp=timestamp_103x1x0_LOCAL, + htlc_maximum_msat=None, + ) + update_109x1x0_LOCAL = funding2.channel_update( + side=Side.local, + short_channel_id="109x1x0", + disable=False, + cltv_expiry_delta=144, + htlc_minimum_msat=0, + fee_base_msat=1000, + fee_proportional_millionths=10, + timestamp=timestamp_109x1x0_LOCAL, + htlc_maximum_msat=None, + ) + update_109x1x0_REMOTE = funding2.channel_update( + side=Side.remote, + short_channel_id="109x1x0", + disable=False, + cltv_expiry_delta=144, + htlc_minimum_msat=0, + fee_base_msat=1000, + fee_proportional_millionths=10, + timestamp=timestamp_109x1x0_REMOTE, + htlc_maximum_msat=None, + ) + + csums_103x1x0 = update_checksums(*funding1.node_id_sort(update_103x1x0_LOCAL, None)) + csums_109x1x0 = update_checksums( + *funding2.node_id_sort(update_109x1x0_LOCAL, update_109x1x0_REMOTE) + ) + + test = [ + Block(blockheight=102, txs=[tx_spendable]), + # Channel 103x1x0 (between 002 and 003) + Block(blockheight=103, number=6, txs=[funding1_tx]), + # Channel 109x1x0 (between 004 and 005) + Block(blockheight=109, number=6, txs=[funding2_tx]), + Connect(connprivkey="03"), + ExpectMsg("init"), + Msg("init", globalfeatures="", features=""), + RawMsg(funding1.channel_announcement("103x1x0", "")), + RawMsg(update_103x1x0_LOCAL), + RawMsg(funding2.channel_announcement("109x1x0", "")), + RawMsg(update_109x1x0_LOCAL), + RawMsg(update_109x1x0_REMOTE), + # c-lightning gets a race condition if we dont wait for + # these updates to be added to the gossip store + # FIXME: convert to explicit signal + Wait(1), + # New peer connects, with gossip_query option. + Connect(connprivkey="05"), + ExpectMsg("init"), + # BOLT #9: + # | 6/7 | `gossip_queries` | More sophisticated gossip control + Msg("init", globalfeatures="", features=bitfield(7)), + TryAll( + # No queries? Must not get anything. + [ + MustNotMsg("channel_announcement"), + MustNotMsg("channel_update"), + MustNotMsg("node_announcement"), + ], + # This should elicit an empty response + [ + Msg( + "query_channel_range", + chain_hash=regtest_hash, + first_blocknum=0, + number_of_blocks=103, + ), + ExpectMsg( + "reply_channel_range", + chain_hash=regtest_hash, + first_blocknum=0, + number_of_blocks=103, + ), + CheckEq(decode_scids, ""), + ], + # This should get the first one, not the second. + [ + Msg( + "query_channel_range", + chain_hash=regtest_hash, + first_blocknum=103, + number_of_blocks=1, + ), + ExpectMsg( + "reply_channel_range", + chain_hash=regtest_hash, + first_blocknum=103, + number_of_blocks=1, + ), + CheckEq(decode_scids, "103x1x0"), + ], + # This should get the second one, not the first. + [ + Msg( + "query_channel_range", + chain_hash=regtest_hash, + first_blocknum=109, + number_of_blocks=4294967295, + ), + OneOf( + ExpectMsg( + "reply_channel_range", + chain_hash=regtest_hash, + first_blocknum=109, + number_of_blocks=4294967186, + ), + # Could truncate number_of_blocks. + ExpectMsg( + "reply_channel_range", + chain_hash=regtest_hash, + first_blocknum=109, + number_of_blocks=1, + ), + ), + CheckEq(decode_scids, "109x1x0"), + ], + # This should get both. + [ + Msg( + "query_channel_range", + chain_hash=regtest_hash, + first_blocknum=103, + number_of_blocks=7, + ), + ExpectMsg( + "reply_channel_range", + chain_hash=regtest_hash, + first_blocknum=103, + number_of_blocks=7, + ), + CheckEq(decode_scids, "103x1x0,109x1x0"), + ], + # This should get appended timestamp fields with option_gossip_queries_ex + Sequence( + enable=runner.has_option("option_gossip_queries_ex") is not None, + events=[ + Msg( + "query_channel_range", + chain_hash=regtest_hash, + first_blocknum=103, + number_of_blocks=7, + tlvs="{query_option={query_option_flags=1}}", + ), + ExpectMsg( + "reply_channel_range", + chain_hash=regtest_hash, + first_blocknum=103, + number_of_blocks=7, + ), + CheckEq(decode_timestamps, ts_103x1x0 + ts_109x1x0), + CheckEq(decode_scids, "103x1x0,109x1x0"), + ], + ), + # This should get appended checksum fields with option_gossip_queries_ex + Sequence( + enable=runner.has_option("option_gossip_queries_ex") is not None, + events=[ + Msg( + "query_channel_range", + chain_hash=regtest_hash, + first_blocknum=103, + number_of_blocks=7, + tlvs="{query_option={query_option_flags=2}}", + ), + ExpectMsg( + "reply_channel_range", + chain_hash=regtest_hash, + first_blocknum=103, + number_of_blocks=7, + tlvs="{checksums_tlv={checksums=[" + + csums_103x1x0 + + "," + + csums_109x1x0 + + "]}}", + ), + CheckEq(decode_scids, "103x1x0,109x1x0"), + ], + ), + # This should append timestamps and checksums with option_gossip_queries_ex + Sequence( + enable=runner.has_option("option_gossip_queries_ex") is not None, + events=[ + Msg( + "query_channel_range", + chain_hash=regtest_hash, + first_blocknum=103, + number_of_blocks=7, + tlvs="{query_option={query_option_flags=3}}", + ), + ExpectMsg( + "reply_channel_range", + chain_hash=regtest_hash, + first_blocknum=103, + number_of_blocks=7, + tlvs="{checksums_tlv={checksums=[" + + csums_103x1x0 + + "," + + csums_109x1x0 + + "]}}", + ), + CheckEq(decode_timestamps, ts_103x1x0 + ts_109x1x0), + CheckEq(decode_scids, "103x1x0,109x1x0"), + ], + ), + ), + ] runner.run(test) diff --git a/tools/check_quotes.py b/tools/check_quotes.py index a36b2a8..663c038 100755 --- a/tools/check_quotes.py +++ b/tools/check_quotes.py @@ -7,23 +7,26 @@ from collections import namedtuple from typing import Dict, List, Tuple, Optional -Quote = namedtuple('Quote', ['filename', 'line', 'text']) -whitespace_re = re.compile(r'\s+') +Quote = namedtuple("Quote", ["filename", "line", "text"]) +whitespace_re = re.compile(r"\s+") def collapse_whitespace(string: str) -> str: - return whitespace_re.sub(' ', string) + return whitespace_re.sub(" ", string) -def add_quote(boltquotes: Dict[int, List[Quote]], - boltnum: int, - filename: str, - line: int, - quote: str) -> None: +def add_quote( + boltquotes: Dict[int, List[Quote]], + boltnum: int, + filename: str, + line: int, + quote: str, +) -> None: if boltnum not in boltquotes: boltquotes[boltnum] = [] - boltquotes[boltnum].append(Quote(filename, line, - collapse_whitespace(quote.strip()))) + boltquotes[boltnum].append( + Quote(filename, line, collapse_whitespace(quote.strip())) + ) def included_commit(args: Namespace, boltprefix: str) -> bool: @@ -35,34 +38,34 @@ def included_commit(args: Namespace, boltprefix: str) -> bool: # This looks like a BOLT line; return the bolt number and start of # quote if we shouldn't ignore it. -def get_boltstart(args: Namespace, - line: str, - filename: str, - linenum: int) -> Tuple[Optional[int], Optional[str]]: - if not line.startswith(args.comment_start + 'BOLT'): +def get_boltstart( + args: Namespace, line: str, filename: str, linenum: int +) -> Tuple[Optional[int], Optional[str]]: + if not line.startswith(args.comment_start + "BOLT"): return None, None - parts = line[len(args.comment_start + 'BOLT'):].partition(':') + parts = line[len(args.comment_start + "BOLT") :].partition(":") boltnum = parts[0].strip() # e.g. BOLT-50143e388e16a449a92ed574fc16eb35b51426b9 #11:" - if boltnum.startswith('-'): + if boltnum.startswith("-"): if not included_commit(args, boltnum[1:]): return None, None - boltnum = boltnum.partition(' ')[2] + boltnum = boltnum.partition(" ")[2] - if not boltnum.startswith('#'): - print('{}:{}:expected # after BOLT in {}' - .format(filename, linenum, line), - file=sys.stderr) + if not boltnum.startswith("#"): + print( + "{}:{}:expected # after BOLT in {}".format(filename, linenum, line), + file=sys.stderr, + ) sys.exit(1) try: boltint = int(boltnum[1:].strip()) except ValueError: - print('{}:{}:bad bolt number {}'.format(filename, linenum, - line), - file=sys.stderr) + print( + "{}:{}:bad bolt number {}".format(filename, linenum, line), file=sys.stderr + ) sys.exit(1) return boltint, parts[2] @@ -74,11 +77,13 @@ def gather_quotes(args: Namespace) -> Dict[int, List[Quote]]: curquote = None # These initializations simply keep flake8 happy curbolt = 0 - filestart = '' + filestart = "" linestart = 0 for file_line in fileinput.input(args.files): line = file_line.strip() - boltnum, quote = get_boltstart(args, line, fileinput.filename(), fileinput.filelineno()) + boltnum, quote = get_boltstart( + args, line, fileinput.filename(), fileinput.filelineno() + ) if boltnum is not None: if curquote is not None: add_quote(boltquotes, curbolt, filestart, linestart, curquote) @@ -89,14 +94,18 @@ def gather_quotes(args: Namespace) -> Dict[int, List[Quote]]: curquote = quote elif curquote is not None: # If this is a continuation (and not an end!), add it. - if (args.comment_end is None or not line.startswith(args.comment_end)) and line.startswith(args.comment_continue): + if ( + args.comment_end is None or not line.startswith(args.comment_end) + ) and line.startswith(args.comment_continue): # Special case where end marker is on same line. if args.comment_end is not None and line.endswith(args.comment_end): - curquote += ' ' + line[len(args.comment_continue):-len(args.comment_end)] + curquote += ( + " " + line[len(args.comment_continue) : -len(args.comment_end)] + ) add_quote(boltquotes, curbolt, filestart, linestart, curquote) curquote = None else: - curquote += ' ' + line[len(args.comment_continue):] + curquote += " " + line[len(args.comment_continue) :] else: add_quote(boltquotes, curbolt, filestart, linestart, curquote) curquote = None @@ -110,17 +119,18 @@ def gather_quotes(args: Namespace) -> Dict[int, List[Quote]]: def load_bolt(boltdir: str, num: int) -> List[str]: """Return a list, divided into one-string-per-bolt-section, with -whitespace collapsed into single spaces. + whitespace collapsed into single spaces. """ boltfile = glob.glob("{}/{}-*md".format(boltdir, str(num).zfill(2))) if len(boltfile) == 0: - print("Cannot find bolt {} in {}".format(num, boltdir), - file=sys.stderr) + print("Cannot find bolt {} in {}".format(num, boltdir), file=sys.stderr) sys.exit(1) elif len(boltfile) > 1: - print("More than one bolt {} in {}? {}".format(num, boltdir, boltfile), - file=sys.stderr) + print( + "More than one bolt {} in {}? {}".format(num, boltdir, boltfile), + file=sys.stderr, + ) sys.exit(1) # We divide it into sections, and collapse whitespace. @@ -128,7 +138,7 @@ def load_bolt(boltdir: str, num: int) -> List[str]: with open(boltfile[0]) as f: sect = "" for line in f.readlines(): - if line.startswith('#'): + if line.startswith("#"): # Append with whitespace collapsed. boltsections.append(collapse_whitespace(sect)) sect = "" @@ -138,9 +148,11 @@ def load_bolt(boltdir: str, num: int) -> List[str]: return boltsections -def find_quote(text: str, boltsections: List[str]) -> Tuple[Optional[str], Optional[int]]: +def find_quote( + text: str, boltsections: List[str] +) -> Tuple[Optional[str], Optional[int]]: # '...' means "match anything". - textparts = text.split('...') + textparts = text.split("...") for b in boltsections: off = 0 for part in textparts: @@ -159,37 +171,58 @@ def main(args: Namespace) -> None: for quote in boltquotes[bolt]: sect, end = find_quote(quote.text, boltsections) if not sect: - print("{}:{}:cannot find match".format(quote.filename, quote.line), - file=sys.stderr) + print( + "{}:{}:cannot find match".format(quote.filename, quote.line), + file=sys.stderr, + ) # Reduce the text until we find a match. for n in range(len(quote.text), -1, -1): sect, end = find_quote(quote.text[:n], boltsections) if sect: - print(" common prefix: {}...".format(quote.text[:n]), - file=sys.stderr) - print(" expected ...{:.45}".format(sect[end:]), - file=sys.stderr) - print(" but have ...{:.45}".format(quote.text[n:]), - file=sys.stderr) + print( + " common prefix: {}...".format(quote.text[:n]), + file=sys.stderr, + ) + print( + " expected ...{:.45}".format(sect[end:]), file=sys.stderr + ) + print( + " but have ...{:.45}".format(quote.text[n:]), + file=sys.stderr, + ) break sys.exit(1) elif args.verbose: - print("{}:{}:Matched {} in {}".format(quote.filename, quote.line, quote.text, - sect)) + print( + "{}:{}:Matched {} in {}".format( + quote.filename, quote.line, quote.text, sect + ) + ) if __name__ == "__main__": - parser = ArgumentParser(description='Check BOLT quotes in the given files are correct') - parser.add_argument('-v', '--verbose', action='store_true') + parser = ArgumentParser( + description="Check BOLT quotes in the given files are correct" + ) + parser.add_argument("-v", "--verbose", action="store_true") # e.g. for C code these are '/* ', '*' and '*/' - parser.add_argument('--comment-start', help='marker for start of "BOLT #N" quote', default='# ') - parser.add_argument('--comment-continue', help='marker for continued "BOLT #N" quote', default='#') - parser.add_argument('--comment-end', help='marker for end of "BOLT #N" quote') - parser.add_argument('--include-commit', action='append', help='Also parse BOLT- quotes', default=[]) - parser.add_argument('--boltdir', - help='Directory to look for BOLT tests', - default="../lightning-rfc") - parser.add_argument("files", help='Files to read in (or stdin)', nargs=REMAINDER) + parser.add_argument( + "--comment-start", help='marker for start of "BOLT #N" quote', default="# " + ) + parser.add_argument( + "--comment-continue", help='marker for continued "BOLT #N" quote', default="#" + ) + parser.add_argument("--comment-end", help='marker for end of "BOLT #N" quote') + parser.add_argument( + "--include-commit", + action="append", + help="Also parse BOLT- quotes", + default=[], + ) + parser.add_argument( + "--boltdir", help="Directory to look for BOLT tests", default="../lightning-rfc" + ) + parser.add_argument("files", help="Files to read in (or stdin)", nargs=REMAINDER) args = parser.parse_args() main(args)