Browse Source

lnbase: initial 'payment to remote' attempt

regtest_lnd
Janus 7 years ago
committed by SomberNight
parent
commit
7ad08acc59
No known key found for this signature in database GPG Key ID: B33B5F232C6271E9
  1. 124
      lib/lnbase.py
  2. 20
      lib/tests/test_lnbase_online.py

124
lib/lnbase.py

@ -357,7 +357,7 @@ def make_htlc_tx_output(amount_msat, local_feerate, revocationpubkey, local_dela
weight = HTLC_SUCCESS_WEIGHT if success else HTLC_TIMEOUT_WEIGHT weight = HTLC_SUCCESS_WEIGHT if success else HTLC_TIMEOUT_WEIGHT
fee = local_feerate * weight fee = local_feerate * weight
final_amount_sat = (amount_msat - fee) // 1000 final_amount_sat = (amount_msat - fee) // 1000
assert final_amount_sat > 0 assert final_amount_sat > 0, final_amount_sat
output = (bitcoin.TYPE_ADDRESS, p2wsh, final_amount_sat) output = (bitcoin.TYPE_ADDRESS, p2wsh, final_amount_sat)
return output return output
@ -519,8 +519,13 @@ def make_commitment(ctn, local_funding_pubkey, remote_funding_pubkey, remote_pay
remote_address = bitcoin.pubkey_to_address('p2wpkh', bh2u(remote_payment_pubkey)) remote_address = bitcoin.pubkey_to_address('p2wpkh', bh2u(remote_payment_pubkey))
# TODO trim htlc outputs here while also considering 2nd stage htlc transactions # TODO trim htlc outputs here while also considering 2nd stage htlc transactions
fee = local_feerate * overall_weight(len(htlcs)) // 1000 # TODO incorrect if anything is trimmed fee = local_feerate * overall_weight(len(htlcs)) // 1000 # TODO incorrect if anything is trimmed
to_local = (bitcoin.TYPE_ADDRESS, local_address, local_amount - (fee if for_us else 0)) assert type(fee) is int
to_remote = (bitcoin.TYPE_ADDRESS, remote_address, remote_amount - (fee if not for_us else 0)) to_local_amt = local_amount - (fee if for_us else 0)
assert type(to_local_amt) is int
to_local = (bitcoin.TYPE_ADDRESS, local_address, to_local_amt)
to_remote_amt = remote_amount - (fee if not for_us else 0)
assert type(to_remote_amt) is int
to_remote = (bitcoin.TYPE_ADDRESS, remote_address, to_remote_amt)
c_outputs = [to_local, to_remote] c_outputs = [to_local, to_remote]
for script, msat_amount in htlcs: for script, msat_amount in htlcs:
c_outputs += [(bitcoin.TYPE_ADDRESS, bitcoin.redeem_script_to_address('p2wsh', bh2u(script)), msat_amount // 1000)] c_outputs += [(bitcoin.TYPE_ADDRESS, bitcoin.redeem_script_to_address('p2wsh', bh2u(script)), msat_amount // 1000)]
@ -565,14 +570,16 @@ class Peer(PrintError):
"remote_funding_locked", "remote_funding_locked",
"revoke_and_ack", "revoke_and_ack",
"channel_reestablish", "channel_reestablish",
"update_fulfill_htlc",
"commitment_signed"] "commitment_signed"]
self.channel_accepted = defaultdict(asyncio.Future) self.channel_accepted = defaultdict(asyncio.Future)
self.funding_signed = defaultdict(asyncio.Future) self.funding_signed = defaultdict(asyncio.Future)
self.local_funding_locked = defaultdict(asyncio.Future) self.local_funding_locked = defaultdict(asyncio.Future)
self.remote_funding_locked = defaultdict(asyncio.Future) self.remote_funding_locked = defaultdict(asyncio.Future)
self.revoke_and_ack = defaultdict(asyncio.Future) self.revoke_and_ack = defaultdict(asyncio.Future)
self.commitment_signed = defaultdict(asyncio.Future)
self.channel_reestablish = defaultdict(asyncio.Future) self.channel_reestablish = defaultdict(asyncio.Future)
self.update_fulfill_htlc = defaultdict(asyncio.Future)
self.commitment_signed = defaultdict(asyncio.Future)
self.initialized = asyncio.Future() self.initialized = asyncio.Future()
self.localfeatures = (0x08 if request_initial_sync else 0) self.localfeatures = (0x08 if request_initial_sync else 0)
# view of the network # view of the network
@ -710,7 +717,7 @@ class Peer(PrintError):
def on_funding_locked(self, payload): def on_funding_locked(self, payload):
channel_id = int.from_bytes(payload['channel_id'], 'big') channel_id = int.from_bytes(payload['channel_id'], 'big')
#if channel_id not in self.funding_signed: raise Exception("Got unknown funding_locked") if channel_id not in self.funding_signed: print("Got unknown funding_locked", payload)
self.remote_funding_locked[channel_id].set_result(payload) self.remote_funding_locked[channel_id].set_result(payload)
def on_node_announcement(self, payload): def on_node_announcement(self, payload):
@ -999,6 +1006,103 @@ class Peer(PrintError):
return chan._replace(remote_state=chan.remote_state._replace(next_per_commitment_point=remote_funding_locked_msg["next_per_commitment_point"])) return chan._replace(remote_state=chan.remote_state._replace(next_per_commitment_point=remote_funding_locked_msg["next_per_commitment_point"]))
async def pay(self, wallet, chan, sat, payment_hash):
def derive_and_incr():
nonlocal chan
last_small_num = chan.local_state.ctn
next_small_num = last_small_num + 2
this_small_num = last_small_num + 1
last_secret = get_per_commitment_secret_from_seed(chan.local_state.per_commitment_secret_seed, 2**48-last_small_num-1)
this_secret = get_per_commitment_secret_from_seed(chan.local_state.per_commitment_secret_seed, 2**48-this_small_num-1)
this_point = secret_to_pubkey(int.from_bytes(this_secret, 'big'))
next_secret = get_per_commitment_secret_from_seed(chan.local_state.per_commitment_secret_seed, 2**48-next_small_num-1)
next_point = secret_to_pubkey(int.from_bytes(next_secret, 'big'))
chan = chan._replace(
local_state=chan.local_state._replace(
ctn=chan.local_state.ctn + 1
)
)
return last_secret, this_point, next_point
sat = int(sat)
cltv_expiry = wallet.get_local_height() + 9
assert sat > 0, "sat is not positive"
amount_msat = sat * 1000
#def new_onion_packet(payment_path_pubkeys: Sequence[bytes], session_key: bytes,
# hops_data: Sequence[OnionHopsDataSingle], associated_data: bytes) -> OnionPacket:
#assert type(peer.pubkey) is bytes
#hops_data = [OnionHopsDataSingle(OnionPerHop(derive_short_channel_id(chan).to_bytes(8, "big"), amount_msat.to_bytes(8, "big"), cltv_expiry.to_bytes(4, "big")))]
#associated_data = b""
#onion = new_onion_packet([peer.pubkey], peer.privkey, hops_data, associated_data)
class onion:
to_bytes = lambda: b"\x00" * 1366
self.send_message(gen_msg("update_add_htlc", channel_id=chan.channel_id, id=0, cltv_expiry=cltv_expiry, amount_msat=amount_msat, payment_hash=payment_hash, onion_routing_packet=onion.to_bytes()))
their_local_htlc_pubkey = derive_pubkey(chan.remote_config.htlc_basepoint.pubkey, chan.remote_state.next_per_commitment_point)
their_remote_htlc_pubkey = derive_pubkey(chan.local_config.htlc_basepoint.pubkey, chan.remote_state.next_per_commitment_point)
their_remote_htlc_privkey_number = derive_privkey(
int.from_bytes(chan.local_config.htlc_basepoint.privkey, 'big'),
chan.remote_state.next_per_commitment_point)
their_remote_htlc_privkey = their_remote_htlc_privkey_number.to_bytes(32, 'big')
# TODO check payment_hash
revocation_pubkey = derive_blinded_pubkey(chan.local_config.revocation_basepoint.pubkey, chan.remote_state.next_per_commitment_point)
htlcs_in_remote = [(make_received_htlc(revocation_pubkey, their_remote_htlc_pubkey, their_local_htlc_pubkey, payment_hash, cltv_expiry), amount_msat)]
new_local = chan.local_state.amount_sat - sat
remote_ctx = make_commitment_using_open_channel(chan, chan.remote_state.ctn + 1, False, chan.remote_state.next_per_commitment_point,
chan.remote_state.amount_sat, new_local, htlcs_in_remote)
sig_64 = sign_and_get_sig_string(remote_ctx, chan.local_config, chan.remote_config)
htlc_tx = make_htlc_tx_with_open_channel(chan, chan.remote_state.next_per_commitment_point, False, False, amount_msat, cltv_expiry, payment_hash, remote_ctx, 0)
# htlc_sig signs the HTLC transaction that spends from THEIR commitment transaction's offered_htlc output
sig = bfh(htlc_tx.sign_txin(0, their_remote_htlc_privkey))
r, s = sigdecode_der(sig[:-1], SECP256k1.generator.order())
htlc_sig = sigencode_string_canonize(r, s, SECP256k1.generator.order())
self.send_message(gen_msg("commitment_signed", channel_id=chan.channel_id, signature=sig_64, num_htlcs=1, htlc_signature=htlc_sig))
last_secret, _, next_point = derive_and_incr()
self.send_message(gen_msg("revoke_and_ack",
channel_id=chan.channel_id,
per_commitment_secret=last_secret,
next_per_commitment_point=next_point))
try:
revoke_and_ack_msg = await self.revoke_and_ack[chan.channel_id]
finally:
del self.revoke_and_ack[chan.channel_id]
# TODO check revoke_and_ack results
next_per_commitment_point = revoke_and_ack_msg["next_per_commitment_point"]
try:
commitment_signed_msg = await self.commitment_signed[chan.channel_id]
finally:
del self.commitment_signed[chan.channel_id]
# TODO check commitment_signed results
last_secret, _, next_point = derive_and_incr()
self.send_message(gen_msg("revoke_and_ack",
channel_id=chan.channel_id,
per_commitment_secret=last_secret,
next_per_commitment_point=next_point))
try:
update_fulfill_htlc_msg = await self.update_fulfill_htlc[chan.channel_id]
finally:
del self.update_fulfill_htlc[chan.channel_id]
print("update_fulfill_htlc_msg", update_fulfill_htlc_msg)
try:
commitment_signed_msg = await self.commitment_signed[chan.channel_id]
finally:
del self.commitment_signed[chan.channel_id]
# TODO send revoke_and_ack
# TODO send commitment_signed
async def receive_commitment_revoke_ack(self, chan, expected_received_sat, payment_preimage): async def receive_commitment_revoke_ack(self, chan, expected_received_sat, payment_preimage):
def derive_and_incr(): def derive_and_incr():
nonlocal chan nonlocal chan
@ -1067,8 +1171,6 @@ class Peer(PrintError):
if not bitcoin.verify_signature(remote_htlc_pubkey, commitment_signed_msg["htlc_signature"], pre_hash): if not bitcoin.verify_signature(remote_htlc_pubkey, commitment_signed_msg["htlc_signature"], pre_hash):
raise Exception("failed verifying signature an HTLC tx spending from one of our commit tx'es HTLC outputs") raise Exception("failed verifying signature an HTLC tx spending from one of our commit tx'es HTLC outputs")
print("SENDING FIRST REVOKE AND ACK")
their_revstore.add_next_entry(last_secret) their_revstore.add_next_entry(last_secret)
self.send_message(gen_msg("revoke_and_ack", self.send_message(gen_msg("revoke_and_ack",
@ -1134,7 +1236,6 @@ class Peer(PrintError):
their_revstore.add_next_entry(last_secret) their_revstore.add_next_entry(last_secret)
print("SENDING SECOND REVOKE AND ACK")
self.send_message(gen_msg("revoke_and_ack", self.send_message(gen_msg("revoke_and_ack",
channel_id=channel_id, channel_id=channel_id,
per_commitment_secret=last_secret, per_commitment_secret=last_secret,
@ -1158,6 +1259,13 @@ class Peer(PrintError):
channel_id = int.from_bytes(payload['channel_id'], 'big') channel_id = int.from_bytes(payload['channel_id'], 'big')
self.commitment_signed[channel_id].set_result(payload) self.commitment_signed[channel_id].set_result(payload)
def on_update_fulfill_htlc(self, payload):
channel_id = int.from_bytes(payload["channel_id"], 'big')
self.update_fulfill_htlc[channel_id].set_reuslt(payload)
def on_update_fail_malformed_htlc(self, payload):
self.on_error(payload)
def on_update_add_htlc(self, payload): def on_update_add_htlc(self, payload):
# no onion routing for the moment: we assume we are the end node # no onion routing for the moment: we assume we are the end node
self.print_error('on_update_add_htlc', payload) self.print_error('on_update_add_htlc', payload)

20
lib/tests/test_lnbase_online.py

@ -6,7 +6,7 @@ import asyncio
import time import time
import os import os
from lib.bitcoin import sha256 from lib.bitcoin import sha256, COIN
from decimal import Decimal from decimal import Decimal
from lib.constants import set_testnet, set_simnet from lib.constants import set_testnet, set_simnet
from lib.simple_config import SimpleConfig from lib.simple_config import SimpleConfig
@ -14,7 +14,7 @@ from lib.network import Network
from lib.storage import WalletStorage from lib.storage import WalletStorage
from lib.wallet import Wallet from lib.wallet import Wallet
from lib.lnbase import Peer, node_list, Outpoint, ChannelConfig, LocalState, RemoteState, Keypair, OnlyPubkeyKeypair, OpenChannel, ChannelConstraints, RevocationStore from lib.lnbase import Peer, node_list, Outpoint, ChannelConfig, LocalState, RemoteState, Keypair, OnlyPubkeyKeypair, OpenChannel, ChannelConstraints, RevocationStore
from lib.lightning_payencode.lnaddr import lnencode, LnAddr from lib.lightning_payencode.lnaddr import lnencode, LnAddr, lndecode
import lib.constants as constants import lib.constants as constants
is_key = lambda k: k.endswith("_basepoint") or k.endswith("_key") is_key = lambda k: k.endswith("_basepoint") or k.endswith("_key")
@ -76,8 +76,8 @@ if __name__ == "__main__":
host, port, pubkey = node_list[0] host, port, pubkey = node_list[0]
pubkey = binascii.unhexlify(pubkey) pubkey = binascii.unhexlify(pubkey)
port = int(port) port = int(port)
if sys.argv[1] not in ["new_channel", "reestablish_channel"]: if sys.argv[1] not in ["new_channel", "reestablish_channel", "pay"]:
raise Exception("first argument must be new_channel or reestablish_channel") raise Exception("first argument must be new_channel or reestablish_channel or pay")
if sys.argv[2] not in ["simnet", "testnet"]: if sys.argv[2] not in ["simnet", "testnet"]:
raise Exception("second argument must be simnet or testnet") raise Exception("second argument must be simnet or testnet")
if sys.argv[2] == "simnet": if sys.argv[2] == "simnet":
@ -116,13 +116,23 @@ if __name__ == "__main__":
wallet.storage.put("channels", dumped) wallet.storage.put("channels", dumped)
wallet.storage.write() wallet.storage.write()
return openchannel.channel_id return openchannel.channel_id
if channels is None or len(channels) < 1: if channels is None or len(channels) < 1:
raise Exception("Can't reestablish: No channel saved") raise Exception("Can't reestablish: No channel saved")
openchannel = channels[0] openchannel = channels[0]
openchannel = reconstruct_namedtuples(openchannel) openchannel = reconstruct_namedtuples(openchannel)
openchannel = await peer.reestablish_channel(openchannel) openchannel = await peer.reestablish_channel(openchannel)
if sys.argv[1] == "pay":
addr = lndecode(sys.argv[6], expected_hrp="sb" if sys.argv[2] == "simnet" else "tb")
payment_hash = addr.paymenthash
amt = int(addr.amount * COIN)
print("amt", amt)
await peer.pay(wallet, openchannel, amt, payment_hash)
return
expected_received_sat = 200000 expected_received_sat = 200000
pay_req = lnencode(LnAddr(RHASH, amount=Decimal("0.00000001")*expected_received_sat, tags=[('d', 'one cup of coffee')]), peer.privkey[:32]) pay_req = lnencode(LnAddr(RHASH, amount=1/Decimal(COIN)*expected_received_sat, tags=[('d', 'one cup of coffee')]), peer.privkey[:32])
print("payment request", pay_req) print("payment request", pay_req)
advanced_channel = await peer.receive_commitment_revoke_ack(openchannel, expected_received_sat, payment_preimage) advanced_channel = await peer.receive_commitment_revoke_ack(openchannel, expected_received_sat, payment_preimage)
dumped = serialize_channels([advanced_channel]) dumped = serialize_channels([advanced_channel])

Loading…
Cancel
Save