mirror of
https://github.com/LBRYFoundation/LBRY-Vault.git
synced 2025-08-23 17:47:31 +00:00
lightning channel reserves
This commit is contained in:
parent
53027d6b2f
commit
36d03f4762
4 changed files with 176 additions and 33 deletions
|
@ -390,6 +390,7 @@ class Peer(PrintError):
|
||||||
ctn=-1,
|
ctn=-1,
|
||||||
next_htlc_id=0,
|
next_htlc_id=0,
|
||||||
amount_msat=initial_msat,
|
amount_msat=initial_msat,
|
||||||
|
reserve_sat=546,
|
||||||
)
|
)
|
||||||
per_commitment_secret_seed = keypair_generator(LnKeyFamily.REVOCATION_ROOT).privkey
|
per_commitment_secret_seed = keypair_generator(LnKeyFamily.REVOCATION_ROOT).privkey
|
||||||
return local_config, per_commitment_secret_seed
|
return local_config, per_commitment_secret_seed
|
||||||
|
@ -451,6 +452,7 @@ class Peer(PrintError):
|
||||||
ctn = -1,
|
ctn = -1,
|
||||||
amount_msat=push_msat,
|
amount_msat=push_msat,
|
||||||
next_htlc_id = 0,
|
next_htlc_id = 0,
|
||||||
|
reserve_sat = int.from_bytes(payload["channel_reserve_satoshis"], 'big'),
|
||||||
|
|
||||||
next_per_commitment_point=remote_per_commitment_point,
|
next_per_commitment_point=remote_per_commitment_point,
|
||||||
current_per_commitment_point=None,
|
current_per_commitment_point=None,
|
||||||
|
@ -561,6 +563,7 @@ class Peer(PrintError):
|
||||||
ctn = -1,
|
ctn = -1,
|
||||||
amount_msat=remote_balance_sat,
|
amount_msat=remote_balance_sat,
|
||||||
next_htlc_id = 0,
|
next_htlc_id = 0,
|
||||||
|
reserve_sat = int.from_bytes(payload['channel_reserve_satoshis'], 'big'),
|
||||||
|
|
||||||
next_per_commitment_point=payload['first_per_commitment_point'],
|
next_per_commitment_point=payload['first_per_commitment_point'],
|
||||||
current_per_commitment_point=None,
|
current_per_commitment_point=None,
|
||||||
|
@ -941,7 +944,6 @@ class Peer(PrintError):
|
||||||
assert final_cltv <= cltv, (final_cltv, cltv)
|
assert final_cltv <= cltv, (final_cltv, cltv)
|
||||||
secret_key = os.urandom(32)
|
secret_key = os.urandom(32)
|
||||||
onion = new_onion_packet([x.node_id for x in route], secret_key, hops_data, associated_data=payment_hash)
|
onion = new_onion_packet([x.node_id for x in route], secret_key, hops_data, associated_data=payment_hash)
|
||||||
chan.check_can_pay(amount_msat)
|
|
||||||
# create htlc
|
# create htlc
|
||||||
htlc = {'amount_msat':amount_msat, 'payment_hash':payment_hash, 'cltv_expiry':cltv}
|
htlc = {'amount_msat':amount_msat, 'payment_hash':payment_hash, 'cltv_expiry':cltv}
|
||||||
htlc_id = chan.add_htlc(htlc)
|
htlc_id = chan.add_htlc(htlc)
|
||||||
|
|
|
@ -18,7 +18,7 @@ from .lnutil import sign_and_get_sig_string, privkey_to_pubkey, make_htlc_tx_wit
|
||||||
from .lnutil import make_htlc_tx_with_open_channel, make_commitment, make_received_htlc, make_offered_htlc
|
from .lnutil import make_htlc_tx_with_open_channel, make_commitment, make_received_htlc, make_offered_htlc
|
||||||
from .lnutil import HTLC_TIMEOUT_WEIGHT, HTLC_SUCCESS_WEIGHT
|
from .lnutil import HTLC_TIMEOUT_WEIGHT, HTLC_SUCCESS_WEIGHT
|
||||||
from .lnutil import funding_output_script, LOCAL, REMOTE, HTLCOwner, make_closing_tx, make_commitment_outputs
|
from .lnutil import funding_output_script, LOCAL, REMOTE, HTLCOwner, make_closing_tx, make_commitment_outputs
|
||||||
from .lnutil import ScriptHtlc, SENT, RECEIVED, PaymentFailure, calc_onchain_fees
|
from .lnutil import ScriptHtlc, SENT, RECEIVED, PaymentFailure, calc_onchain_fees, RemoteMisbehaving
|
||||||
from .transaction import Transaction, TxOutput, construct_witness
|
from .transaction import Transaction, TxOutput, construct_witness
|
||||||
from .simple_config import SimpleConfig, FEERATE_FALLBACK_STATIC_FEE
|
from .simple_config import SimpleConfig, FEERATE_FALLBACK_STATIC_FEE
|
||||||
|
|
||||||
|
@ -131,6 +131,7 @@ class Channel(PrintError):
|
||||||
template = lambda: {
|
template = lambda: {
|
||||||
'adds': {}, # Dict[HTLC_ID, UpdateAddHtlc]
|
'adds': {}, # Dict[HTLC_ID, UpdateAddHtlc]
|
||||||
'settles': [], # List[HTLC_ID]
|
'settles': [], # List[HTLC_ID]
|
||||||
|
'fails': [], # List[HTLC_ID]
|
||||||
}
|
}
|
||||||
self.log = {LOCAL: template(), REMOTE: template()}
|
self.log = {LOCAL: template(), REMOTE: template()}
|
||||||
for strname, subject in [('remote_log', REMOTE), ('local_log', LOCAL)]:
|
for strname, subject in [('remote_log', REMOTE), ('local_log', LOCAL)]:
|
||||||
|
@ -159,23 +160,23 @@ class Channel(PrintError):
|
||||||
def get_state(self):
|
def get_state(self):
|
||||||
return self._state
|
return self._state
|
||||||
|
|
||||||
def check_can_pay(self, amount_msat: int) -> None:
|
def _check_can_pay(self, amount_msat: int) -> None:
|
||||||
# FIXME channel reserve
|
|
||||||
if self.get_state() != 'OPEN':
|
if self.get_state() != 'OPEN':
|
||||||
raise PaymentFailure('Channel not open')
|
raise PaymentFailure('Channel not open')
|
||||||
if self.available_to_spend(LOCAL) < amount_msat:
|
if self.available_to_spend(LOCAL) < amount_msat:
|
||||||
raise PaymentFailure('Not enough local balance')
|
raise PaymentFailure('Not enough local balance')
|
||||||
if len(self.htlcs(LOCAL, only_pending=True)) + 1 > self.config[REMOTE].max_accepted_htlcs:
|
if len(self.htlcs(LOCAL, only_pending=True)) + 1 > self.config[REMOTE].max_accepted_htlcs:
|
||||||
raise PaymentFailure('Too many HTLCs already in channel')
|
raise PaymentFailure('Too many HTLCs already in channel')
|
||||||
if htlcsum(self.htlcs(LOCAL, only_pending=True)) + amount_msat > self.config[REMOTE].max_htlc_value_in_flight_msat:
|
current_htlc_sum = htlcsum(self.htlcs(LOCAL, only_pending=True))
|
||||||
raise PaymentFailure('HTLC value sum would exceed max allowed: {} msat'.format(self.config[REMOTE].max_htlc_value_in_flight_msat))
|
if current_htlc_sum + amount_msat > self.config[REMOTE].max_htlc_value_in_flight_msat:
|
||||||
|
raise PaymentFailure(f'HTLC value sum (sum of pending htlcs: {current_htlc_sum/1000} sat plus new htlc: {amount_msat/1000} sat) would exceed max allowed: {self.config[REMOTE].max_htlc_value_in_flight_msat/1000} sat')
|
||||||
if amount_msat <= 0: # FIXME htlc_minimum_msat
|
if amount_msat <= 0: # FIXME htlc_minimum_msat
|
||||||
raise PaymentFailure(f'HTLC value too small: {amount_msat} msat')
|
raise PaymentFailure(f'HTLC value too small: {amount_msat} msat')
|
||||||
|
|
||||||
def can_pay(self, amount_msat):
|
def can_pay(self, amount_msat):
|
||||||
try:
|
try:
|
||||||
self.check_can_pay(amount_msat)
|
self._check_can_pay(amount_msat)
|
||||||
except:
|
except PaymentFailure:
|
||||||
return False
|
return False
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
@ -196,6 +197,7 @@ class Channel(PrintError):
|
||||||
should be called when preparing to send an outgoing HTLC.
|
should be called when preparing to send an outgoing HTLC.
|
||||||
"""
|
"""
|
||||||
assert type(htlc) is dict
|
assert type(htlc) is dict
|
||||||
|
self._check_can_pay(htlc['amount_msat'])
|
||||||
htlc = UpdateAddHtlc(**htlc, htlc_id=self.config[LOCAL].next_htlc_id)
|
htlc = UpdateAddHtlc(**htlc, htlc_id=self.config[LOCAL].next_htlc_id)
|
||||||
self.log[LOCAL]['adds'][htlc.htlc_id] = htlc
|
self.log[LOCAL]['adds'][htlc.htlc_id] = htlc
|
||||||
self.print_error("add_htlc")
|
self.print_error("add_htlc")
|
||||||
|
@ -210,7 +212,10 @@ class Channel(PrintError):
|
||||||
"""
|
"""
|
||||||
assert type(htlc) is dict
|
assert type(htlc) is dict
|
||||||
htlc = UpdateAddHtlc(**htlc, htlc_id = self.config[REMOTE].next_htlc_id)
|
htlc = UpdateAddHtlc(**htlc, htlc_id = self.config[REMOTE].next_htlc_id)
|
||||||
self.log[REMOTE]['adds'][htlc.htlc_id] = htlc
|
if self.available_to_spend(REMOTE) < htlc.amount_msat:
|
||||||
|
raise RemoteMisbehaving('Remote dipped below channel reserve')
|
||||||
|
adds = self.log[REMOTE]['adds']
|
||||||
|
adds[htlc.htlc_id] = htlc
|
||||||
self.print_error("receive_htlc")
|
self.print_error("receive_htlc")
|
||||||
self.config[REMOTE]=self.config[REMOTE]._replace(next_htlc_id=htlc.htlc_id + 1)
|
self.config[REMOTE]=self.config[REMOTE]._replace(next_htlc_id=htlc.htlc_id + 1)
|
||||||
return htlc.htlc_id
|
return htlc.htlc_id
|
||||||
|
@ -228,10 +233,8 @@ class Channel(PrintError):
|
||||||
any). The HTLC signatures are sorted according to the BIP 69 order of the
|
any). The HTLC signatures are sorted according to the BIP 69 order of the
|
||||||
HTLC's on the commitment transaction.
|
HTLC's on the commitment transaction.
|
||||||
"""
|
"""
|
||||||
for htlc in self.log[LOCAL]['adds'].values():
|
|
||||||
if htlc.locked_in[LOCAL] is None:
|
|
||||||
htlc.locked_in[LOCAL] = self.config[LOCAL].ctn
|
|
||||||
self.print_error("sign_next_commitment")
|
self.print_error("sign_next_commitment")
|
||||||
|
self.lock_in_htlc_changes(LOCAL)
|
||||||
|
|
||||||
pending_remote_commitment = self.pending_remote_commitment
|
pending_remote_commitment = self.pending_remote_commitment
|
||||||
sig_64 = sign_and_get_sig_string(pending_remote_commitment, self.config[LOCAL], self.config[REMOTE])
|
sig_64 = sign_and_get_sig_string(pending_remote_commitment, self.config[LOCAL], self.config[REMOTE])
|
||||||
|
@ -265,6 +268,17 @@ class Channel(PrintError):
|
||||||
|
|
||||||
return sig_64, htlcsigs
|
return sig_64, htlcsigs
|
||||||
|
|
||||||
|
def lock_in_htlc_changes(self, subject):
|
||||||
|
for sub in (LOCAL, REMOTE):
|
||||||
|
for htlc_id in self.log[-sub]['fails']:
|
||||||
|
adds = self.log[sub]['adds']
|
||||||
|
htlc = adds.pop(htlc_id)
|
||||||
|
self.log[-sub]['fails'].clear()
|
||||||
|
|
||||||
|
for htlc in self.log[subject]['adds'].values():
|
||||||
|
if htlc.locked_in[subject] is None:
|
||||||
|
htlc.locked_in[subject] = self.config[subject].ctn
|
||||||
|
|
||||||
def receive_new_commitment(self, sig, htlc_sigs):
|
def receive_new_commitment(self, sig, htlc_sigs):
|
||||||
"""
|
"""
|
||||||
ReceiveNewCommitment process a signature for a new commitment state sent by
|
ReceiveNewCommitment process a signature for a new commitment state sent by
|
||||||
|
@ -276,11 +290,8 @@ class Channel(PrintError):
|
||||||
state, then this newly added commitment becomes our current accepted channel
|
state, then this newly added commitment becomes our current accepted channel
|
||||||
state.
|
state.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
self.print_error("receive_new_commitment")
|
self.print_error("receive_new_commitment")
|
||||||
for htlc in self.log[REMOTE]['adds'].values():
|
self.lock_in_htlc_changes(REMOTE)
|
||||||
if htlc.locked_in[REMOTE] is None:
|
|
||||||
htlc.locked_in[REMOTE] = self.config[REMOTE].ctn
|
|
||||||
assert len(htlc_sigs) == 0 or type(htlc_sigs[0]) is bytes
|
assert len(htlc_sigs) == 0 or type(htlc_sigs[0]) is bytes
|
||||||
|
|
||||||
pending_local_commitment = self.pending_local_commitment
|
pending_local_commitment = self.pending_local_commitment
|
||||||
|
@ -479,9 +490,16 @@ class Channel(PrintError):
|
||||||
return initial
|
return initial
|
||||||
|
|
||||||
def available_to_spend(self, subject):
|
def available_to_spend(self, subject):
|
||||||
# FIXME what about channel_reserve_satoshis? will the remote fail the channel if we go below? test.
|
return self.balance(subject)\
|
||||||
# FIXME what about tx fees
|
- htlcsum(self.log[subject]['adds'].values())\
|
||||||
return self.balance(subject) - htlcsum(self.htlcs(subject, only_pending=True))
|
- self.config[subject].reserve_sat * 1000\
|
||||||
|
- calc_onchain_fees(
|
||||||
|
# TODO should we include a potential new htlc, when we are called from receive_htlc?
|
||||||
|
len(list(self.included_htlcs(subject, LOCAL)) + list(self.included_htlcs(subject, REMOTE))),
|
||||||
|
self.pending_feerate(subject),
|
||||||
|
subject == LOCAL,
|
||||||
|
self.constraints.is_initiator,
|
||||||
|
)[subject]
|
||||||
|
|
||||||
def amounts(self):
|
def amounts(self):
|
||||||
remote_settled= htlcsum(self.htlcs(REMOTE, False))
|
remote_settled= htlcsum(self.htlcs(REMOTE, False))
|
||||||
|
@ -536,8 +554,11 @@ class Channel(PrintError):
|
||||||
res = []
|
res = []
|
||||||
for htlc in update_log['adds'].values():
|
for htlc in update_log['adds'].values():
|
||||||
locked_in = htlc.locked_in[subject]
|
locked_in = htlc.locked_in[subject]
|
||||||
|
settled = htlc.htlc_id in other_log['settles']
|
||||||
if locked_in is None or only_pending == (htlc.htlc_id in other_log['settles']):
|
failed = htlc.htlc_id in other_log['fails']
|
||||||
|
if locked_in is None:
|
||||||
|
continue
|
||||||
|
if only_pending == (settled or failed):
|
||||||
continue
|
continue
|
||||||
res.append(htlc)
|
res.append(htlc)
|
||||||
return res
|
return res
|
||||||
|
@ -561,11 +582,11 @@ class Channel(PrintError):
|
||||||
|
|
||||||
def fail_htlc(self, htlc_id):
|
def fail_htlc(self, htlc_id):
|
||||||
self.print_error("fail_htlc")
|
self.print_error("fail_htlc")
|
||||||
self.log[REMOTE]['adds'].pop(htlc_id)
|
self.log[LOCAL]['fails'].append(htlc_id)
|
||||||
|
|
||||||
def receive_fail_htlc(self, htlc_id):
|
def receive_fail_htlc(self, htlc_id):
|
||||||
self.print_error("receive_fail_htlc")
|
self.print_error("receive_fail_htlc")
|
||||||
self.log[LOCAL]['adds'].pop(htlc_id)
|
self.log[REMOTE]['fails'].append(htlc_id)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def current_height(self):
|
def current_height(self):
|
||||||
|
@ -665,8 +686,8 @@ class Channel(PrintError):
|
||||||
|
|
||||||
def make_commitment(self, subject, this_point) -> Transaction:
|
def make_commitment(self, subject, this_point) -> Transaction:
|
||||||
remote_msat, local_msat = self.amounts()
|
remote_msat, local_msat = self.amounts()
|
||||||
assert local_msat >= 0
|
assert local_msat >= 0, local_msat
|
||||||
assert remote_msat >= 0
|
assert remote_msat >= 0, remote_msat
|
||||||
this_config = self.config[subject]
|
this_config = self.config[subject]
|
||||||
other_config = self.config[-subject]
|
other_config = self.config[-subject]
|
||||||
other_htlc_pubkey = derive_pubkey(other_config.htlc_basepoint.pubkey, this_point)
|
other_htlc_pubkey = derive_pubkey(other_config.htlc_basepoint.pubkey, this_point)
|
||||||
|
|
|
@ -37,6 +37,7 @@ common = [
|
||||||
('max_htlc_value_in_flight_msat' , int),
|
('max_htlc_value_in_flight_msat' , int),
|
||||||
('max_accepted_htlcs' , int),
|
('max_accepted_htlcs' , int),
|
||||||
('initial_msat' , int),
|
('initial_msat' , int),
|
||||||
|
('reserve_sat', int),
|
||||||
]
|
]
|
||||||
|
|
||||||
ChannelConfig = NamedTuple('ChannelConfig', common)
|
ChannelConfig = NamedTuple('ChannelConfig', common)
|
||||||
|
@ -65,6 +66,7 @@ class HandshakeFailed(LightningError): pass
|
||||||
class PaymentFailure(LightningError): pass
|
class PaymentFailure(LightningError): pass
|
||||||
class ConnStringFormatError(LightningError): pass
|
class ConnStringFormatError(LightningError): pass
|
||||||
class UnknownPaymentHash(LightningError): pass
|
class UnknownPaymentHash(LightningError): pass
|
||||||
|
class RemoteMisbehaving(LightningError): pass
|
||||||
|
|
||||||
|
|
||||||
# TODO make configurable?
|
# TODO make configurable?
|
||||||
|
|
|
@ -11,6 +11,8 @@ import binascii
|
||||||
|
|
||||||
from electrum.lnutil import SENT, LOCAL, REMOTE, RECEIVED
|
from electrum.lnutil import SENT, LOCAL, REMOTE, RECEIVED
|
||||||
|
|
||||||
|
one_bitcoin_in_msat = bitcoin.COIN * 1000
|
||||||
|
|
||||||
def create_channel_state(funding_txid, funding_index, funding_sat, local_feerate, is_initiator, local_amount, remote_amount, privkeys, other_pubkeys, seed, cur, nex, other_node_id, l_dust, r_dust, l_csv, r_csv):
|
def create_channel_state(funding_txid, funding_index, funding_sat, local_feerate, is_initiator, local_amount, remote_amount, privkeys, other_pubkeys, seed, cur, nex, other_node_id, l_dust, r_dust, l_csv, r_csv):
|
||||||
assert local_amount > 0
|
assert local_amount > 0
|
||||||
assert remote_amount > 0
|
assert remote_amount > 0
|
||||||
|
@ -29,12 +31,13 @@ def create_channel_state(funding_txid, funding_index, funding_sat, local_feerate
|
||||||
revocation_basepoint=other_pubkeys[4],
|
revocation_basepoint=other_pubkeys[4],
|
||||||
to_self_delay=r_csv,
|
to_self_delay=r_csv,
|
||||||
dust_limit_sat=r_dust,
|
dust_limit_sat=r_dust,
|
||||||
max_htlc_value_in_flight_msat=500000 * 1000,
|
max_htlc_value_in_flight_msat=one_bitcoin_in_msat * 5,
|
||||||
max_accepted_htlcs=5,
|
max_accepted_htlcs=5,
|
||||||
initial_msat=remote_amount,
|
initial_msat=remote_amount,
|
||||||
ctn = 0,
|
ctn = 0,
|
||||||
next_htlc_id = 0,
|
next_htlc_id = 0,
|
||||||
amount_msat=remote_amount,
|
amount_msat=remote_amount,
|
||||||
|
reserve_sat=0,
|
||||||
|
|
||||||
next_per_commitment_point=nex,
|
next_per_commitment_point=nex,
|
||||||
current_per_commitment_point=cur,
|
current_per_commitment_point=cur,
|
||||||
|
@ -48,12 +51,13 @@ def create_channel_state(funding_txid, funding_index, funding_sat, local_feerate
|
||||||
revocation_basepoint=privkeys[4],
|
revocation_basepoint=privkeys[4],
|
||||||
to_self_delay=l_csv,
|
to_self_delay=l_csv,
|
||||||
dust_limit_sat=l_dust,
|
dust_limit_sat=l_dust,
|
||||||
max_htlc_value_in_flight_msat=500000 * 1000,
|
max_htlc_value_in_flight_msat=one_bitcoin_in_msat * 5,
|
||||||
max_accepted_htlcs=5,
|
max_accepted_htlcs=5,
|
||||||
initial_msat=local_amount,
|
initial_msat=local_amount,
|
||||||
ctn = 0,
|
ctn = 0,
|
||||||
next_htlc_id = 0,
|
next_htlc_id = 0,
|
||||||
amount_msat=local_amount,
|
amount_msat=local_amount,
|
||||||
|
reserve_sat=0,
|
||||||
|
|
||||||
per_commitment_secret_seed=seed,
|
per_commitment_secret_seed=seed,
|
||||||
funding_locked_received=True,
|
funding_locked_received=True,
|
||||||
|
@ -101,13 +105,15 @@ def create_test_channels(feerate=6000, local=None, remote=None):
|
||||||
bob_cur = lnutil.secret_to_pubkey(int.from_bytes(lnutil.get_per_commitment_secret_from_seed(bob_seed, lnutil.RevocationStore.START_INDEX), "big"))
|
bob_cur = lnutil.secret_to_pubkey(int.from_bytes(lnutil.get_per_commitment_secret_from_seed(bob_seed, lnutil.RevocationStore.START_INDEX), "big"))
|
||||||
bob_next = lnutil.secret_to_pubkey(int.from_bytes(lnutil.get_per_commitment_secret_from_seed(bob_seed, lnutil.RevocationStore.START_INDEX - 1), "big"))
|
bob_next = lnutil.secret_to_pubkey(int.from_bytes(lnutil.get_per_commitment_secret_from_seed(bob_seed, lnutil.RevocationStore.START_INDEX - 1), "big"))
|
||||||
|
|
||||||
return \
|
alice, bob = \
|
||||||
lnchan.Channel(
|
lnchan.Channel(
|
||||||
create_channel_state(funding_txid, funding_index, funding_sat, feerate, True, local_amount, remote_amount, alice_privkeys, bob_pubkeys, alice_seed, bob_cur, bob_next, b"\x02"*33, l_dust=200, r_dust=1300, l_csv=5, r_csv=4), "alice"), \
|
create_channel_state(funding_txid, funding_index, funding_sat, feerate, True, local_amount, remote_amount, alice_privkeys, bob_pubkeys, alice_seed, bob_cur, bob_next, b"\x02"*33, l_dust=200, r_dust=1300, l_csv=5, r_csv=4), "alice"), \
|
||||||
lnchan.Channel(
|
lnchan.Channel(
|
||||||
create_channel_state(funding_txid, funding_index, funding_sat, feerate, False, remote_amount, local_amount, bob_privkeys, alice_pubkeys, bob_seed, alice_cur, alice_next, b"\x01"*33, l_dust=1300, r_dust=200, l_csv=4, r_csv=5), "bob")
|
create_channel_state(funding_txid, funding_index, funding_sat, feerate, False, remote_amount, local_amount, bob_privkeys, alice_pubkeys, bob_seed, alice_cur, alice_next, b"\x01"*33, l_dust=1300, r_dust=200, l_csv=4, r_csv=5), "bob")
|
||||||
|
|
||||||
one_bitcoin_in_msat = bitcoin.COIN * 1000
|
alice.set_state('OPEN')
|
||||||
|
bob.set_state('OPEN')
|
||||||
|
return alice, bob
|
||||||
|
|
||||||
class TestFee(unittest.TestCase):
|
class TestFee(unittest.TestCase):
|
||||||
"""
|
"""
|
||||||
|
@ -134,7 +140,7 @@ class TestChannel(unittest.TestCase):
|
||||||
|
|
||||||
self.paymentPreimage = b"\x01" * 32
|
self.paymentPreimage = b"\x01" * 32
|
||||||
paymentHash = bitcoin.sha256(self.paymentPreimage)
|
paymentHash = bitcoin.sha256(self.paymentPreimage)
|
||||||
self.htlc = {
|
self.htlc_dict = {
|
||||||
'payment_hash' : paymentHash,
|
'payment_hash' : paymentHash,
|
||||||
'amount_msat' : one_bitcoin_in_msat,
|
'amount_msat' : one_bitcoin_in_msat,
|
||||||
'cltv_expiry' : 5,
|
'cltv_expiry' : 5,
|
||||||
|
@ -143,9 +149,9 @@ class TestChannel(unittest.TestCase):
|
||||||
# First Alice adds the outgoing HTLC to her local channel's state
|
# First Alice adds the outgoing HTLC to her local channel's state
|
||||||
# update log. Then Alice sends this wire message over to Bob who adds
|
# update log. Then Alice sends this wire message over to Bob who adds
|
||||||
# this htlc to his remote state update log.
|
# this htlc to his remote state update log.
|
||||||
self.aliceHtlcIndex = self.alice_channel.add_htlc(self.htlc)
|
self.aliceHtlcIndex = self.alice_channel.add_htlc(self.htlc_dict)
|
||||||
|
|
||||||
self.bobHtlcIndex = self.bob_channel.receive_htlc(self.htlc)
|
self.bobHtlcIndex = self.bob_channel.receive_htlc(self.htlc_dict)
|
||||||
self.htlc = self.bob_channel.log[lnutil.REMOTE]['adds'][0]
|
self.htlc = self.bob_channel.log[lnutil.REMOTE]['adds'][0]
|
||||||
|
|
||||||
def test_SimpleAddSettleWorkflow(self):
|
def test_SimpleAddSettleWorkflow(self):
|
||||||
|
@ -318,7 +324,120 @@ class TestChannel(unittest.TestCase):
|
||||||
bob_channel.receive_revocation(alice_revocation)
|
bob_channel.receive_revocation(alice_revocation)
|
||||||
self.assertEqual(fee, bob_channel.constraints.feerate)
|
self.assertEqual(fee, bob_channel.constraints.feerate)
|
||||||
|
|
||||||
|
def test_AddHTLCNegativeBalance(self):
|
||||||
|
self.htlc_dict['payment_hash'] = bitcoin.sha256(32 * b'\x02')
|
||||||
|
self.alice_channel.add_htlc(self.htlc_dict)
|
||||||
|
self.htlc_dict['payment_hash'] = bitcoin.sha256(32 * b'\x03')
|
||||||
|
self.alice_channel.add_htlc(self.htlc_dict)
|
||||||
|
# now there are three htlcs (one was in setUp)
|
||||||
|
|
||||||
|
# Alice now has an available balance of 2 BTC. We'll add a new HTLC of
|
||||||
|
# value 2 BTC, which should make Alice's balance negative (since she
|
||||||
|
# has to pay a commitment fee).
|
||||||
|
new = dict(self.htlc_dict)
|
||||||
|
new['amount_msat'] *= 2
|
||||||
|
new['payment_hash'] = bitcoin.sha256(32 * b'\x04')
|
||||||
|
with self.assertRaises(lnutil.PaymentFailure) as cm:
|
||||||
|
self.alice_channel.add_htlc(new)
|
||||||
|
self.assertEqual(('Not enough local balance',), cm.exception.args)
|
||||||
|
|
||||||
|
class TestAvailableToSpend(unittest.TestCase):
|
||||||
|
def test_DesyncHTLCs(self):
|
||||||
|
alice_channel, bob_channel = create_test_channels()
|
||||||
|
|
||||||
|
paymentPreimage = b"\x01" * 32
|
||||||
|
paymentHash = bitcoin.sha256(paymentPreimage)
|
||||||
|
htlc_dict = {
|
||||||
|
'payment_hash' : paymentHash,
|
||||||
|
'amount_msat' : int(4.1 * one_bitcoin_in_msat),
|
||||||
|
'cltv_expiry' : 5,
|
||||||
|
}
|
||||||
|
|
||||||
|
alice_idx = alice_channel.add_htlc(htlc_dict)
|
||||||
|
bob_idx = bob_channel.receive_htlc(htlc_dict)
|
||||||
|
force_state_transition(alice_channel, bob_channel)
|
||||||
|
bob_channel.fail_htlc(bob_idx)
|
||||||
|
alice_channel.receive_fail_htlc(alice_idx)
|
||||||
|
# Alice now has gotten all her original balance (5 BTC) back, however,
|
||||||
|
# adding a new HTLC at this point SHOULD fail, since if she adds the
|
||||||
|
# HTLC and signs the next state, Bob cannot assume she received the
|
||||||
|
# FailHTLC, and must assume she doesn't have the necessary balance
|
||||||
|
# available.
|
||||||
|
# We try adding an HTLC of value 1 BTC, which should fail because the
|
||||||
|
# balance is unavailable.
|
||||||
|
htlc_dict = {
|
||||||
|
'payment_hash' : paymentHash,
|
||||||
|
'amount_msat' : one_bitcoin_in_msat,
|
||||||
|
'cltv_expiry' : 5,
|
||||||
|
}
|
||||||
|
with self.assertRaises(lnutil.PaymentFailure):
|
||||||
|
alice_channel.add_htlc(htlc_dict)
|
||||||
|
# Now do a state transition, which will ACK the FailHTLC, making Alice
|
||||||
|
# able to add the new HTLC.
|
||||||
|
force_state_transition(alice_channel, bob_channel)
|
||||||
|
alice_channel.add_htlc(htlc_dict)
|
||||||
|
|
||||||
|
class TestChanReserve(unittest.TestCase):
|
||||||
|
def setUp(self):
|
||||||
|
alice_channel, bob_channel = create_test_channels()
|
||||||
|
alice_min_reserve = int(.5 * one_bitcoin_in_msat // 1000)
|
||||||
|
alice_channel.config[LOCAL] =\
|
||||||
|
alice_channel.config[LOCAL]._replace(reserve_sat=alice_min_reserve)
|
||||||
|
bob_channel.config[REMOTE] =\
|
||||||
|
bob_channel.config[REMOTE]._replace(reserve_sat=alice_min_reserve)
|
||||||
|
# We set Bob's channel reserve to a value that is larger than
|
||||||
|
# his current balance in the channel. This will ensure that
|
||||||
|
# after a channel is first opened, Bob can still receive HTLCs
|
||||||
|
# even though his balance is less than his channel reserve.
|
||||||
|
bob_min_reserve = 6 * one_bitcoin_in_msat // 1000
|
||||||
|
bob_channel.config[LOCAL] =\
|
||||||
|
bob_channel.config[LOCAL]._replace(reserve_sat=bob_min_reserve)
|
||||||
|
alice_channel.config[REMOTE] =\
|
||||||
|
alice_channel.config[REMOTE]._replace(reserve_sat=bob_min_reserve)
|
||||||
|
|
||||||
|
self.bob_min = bob_min_reserve
|
||||||
|
self.alice_min = bob_min_reserve
|
||||||
|
self.alice_channel = alice_channel
|
||||||
|
self.bob_channel = bob_channel
|
||||||
|
|
||||||
|
def test_part1(self):
|
||||||
|
# Add an HTLC that will increase Bob's balance. This should succeed,
|
||||||
|
# since Alice stays above her channel reserve, and Bob increases his
|
||||||
|
# balance (while still being below his channel reserve).
|
||||||
|
#
|
||||||
|
# Resulting balances:
|
||||||
|
# Alice: 4.5
|
||||||
|
# Bob: 5.0
|
||||||
|
paymentPreimage = b"\x01" * 32
|
||||||
|
paymentHash = bitcoin.sha256(paymentPreimage)
|
||||||
|
htlc_dict = {
|
||||||
|
'payment_hash' : paymentHash,
|
||||||
|
'amount_msat' : int(.5 * one_bitcoin_in_msat),
|
||||||
|
'cltv_expiry' : 5,
|
||||||
|
}
|
||||||
|
self.alice_channel.add_htlc(htlc_dict)
|
||||||
|
self.bob_channel.receive_htlc(htlc_dict)
|
||||||
|
# Force a state transition, making sure this HTLC is considered valid
|
||||||
|
# even though the channel reserves are not met.
|
||||||
|
force_state_transition(self.alice_channel, self.bob_channel)
|
||||||
|
|
||||||
|
aliceSelfBalance = self.alice_channel.balance(LOCAL)\
|
||||||
|
- lnchan.htlcsum(self.alice_channel.htlcs(LOCAL, True))
|
||||||
|
bobBalance = self.bob_channel.balance(REMOTE)\
|
||||||
|
- lnchan.htlcsum(self.alice_channel.htlcs(REMOTE, True))
|
||||||
|
self.assertEqual(aliceSelfBalance, one_bitcoin_in_msat*4.5)
|
||||||
|
self.assertEqual(bobBalance, one_bitcoin_in_msat*5)
|
||||||
|
# Now let Bob try to add an HTLC. This should fail, since it will
|
||||||
|
# decrease his balance, which is already below the channel reserve.
|
||||||
|
#
|
||||||
|
# Resulting balances:
|
||||||
|
# Alice: 4.5
|
||||||
|
# Bob: 5.0
|
||||||
|
with self.assertRaises(lnutil.PaymentFailure):
|
||||||
|
htlc_dict['payment_hash'] = bitcoin.sha256(32 * b'\x02')
|
||||||
|
self.bob_channel.add_htlc(htlc_dict)
|
||||||
|
with self.assertRaises(lnutil.RemoteMisbehaving):
|
||||||
|
self.alice_channel.receive_htlc(htlc_dict)
|
||||||
|
|
||||||
class TestDust(unittest.TestCase):
|
class TestDust(unittest.TestCase):
|
||||||
def test_DustLimit(self):
|
def test_DustLimit(self):
|
||||||
|
@ -339,7 +458,6 @@ class TestDust(unittest.TestCase):
|
||||||
aliceHtlcIndex = alice_channel.add_htlc(htlc)
|
aliceHtlcIndex = alice_channel.add_htlc(htlc)
|
||||||
bobHtlcIndex = bob_channel.receive_htlc(htlc)
|
bobHtlcIndex = bob_channel.receive_htlc(htlc)
|
||||||
force_state_transition(alice_channel, bob_channel)
|
force_state_transition(alice_channel, bob_channel)
|
||||||
self.assertEqual(alice_channel.available_to_spend(LOCAL), alice_channel.balance(LOCAL) - htlc['amount_msat'])
|
|
||||||
self.assertEqual(len(alice_channel.local_commitment.outputs()), 3)
|
self.assertEqual(len(alice_channel.local_commitment.outputs()), 3)
|
||||||
self.assertEqual(len(bob_channel.local_commitment.outputs()), 2)
|
self.assertEqual(len(bob_channel.local_commitment.outputs()), 2)
|
||||||
default_fee = calc_static_fee(0)
|
default_fee = calc_static_fee(0)
|
||||||
|
|
Loading…
Add table
Reference in a new issue