Browse Source

lightning channel reserves

dependabot/pip/contrib/deterministic-build/ecdsa-0.13.3
Janus 6 years ago
committed by ThomasV
parent
commit
15b0720f5e
  1. 4
      electrum/lnbase.py
  2. 69
      electrum/lnchan.py
  3. 2
      electrum/lnutil.py
  4. 134
      electrum/tests/test_lnchan.py

4
electrum/lnbase.py

@ -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)

69
electrum/lnchan.py

@ -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)

2
electrum/lnutil.py

@ -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?

134
electrum/tests/test_lnchan.py

@ -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…
Cancel
Save