lnbase: save channel details in wallet, enable running online test with reestablishment_mode

This commit is contained in:
Janus 2018-05-03 17:45:14 +02:00 committed by SomberNight
parent 6c7f04fdca
commit 7210b2d0f8
No known key found for this signature in database
GPG key ID: B33B5F232C6271E9
2 changed files with 104 additions and 28 deletions

View file

@ -558,6 +558,7 @@ class Peer(PrintError):
"local_funding_locked", "local_funding_locked",
"remote_funding_locked", "remote_funding_locked",
"revoke_and_ack", "revoke_and_ack",
"channel_reestablish",
"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)
@ -565,6 +566,7 @@ class Peer(PrintError):
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.commitment_signed = defaultdict(asyncio.Future)
self.channel_reestablish = 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
@ -685,6 +687,9 @@ class Peer(PrintError):
l = int.from_bytes(payload['num_pong_bytes'], 'big') l = int.from_bytes(payload['num_pong_bytes'], 'big')
self.send_message(gen_msg('pong', byteslen=l)) self.send_message(gen_msg('pong', byteslen=l))
def on_channel_reestablish(self, payload):
self.channel_reestablish[payload["channel_id"]].set_result(payload)
def on_accept_channel(self, payload): def on_accept_channel(self, payload):
self.channel_accepted[payload["temporary_channel_id"]].set_result(payload) self.channel_accepted[payload["temporary_channel_id"]].set_result(payload)
@ -826,7 +831,7 @@ class Peer(PrintError):
to_self_delay=int.from_bytes(payload['to_self_delay'], byteorder='big'), to_self_delay=int.from_bytes(payload['to_self_delay'], byteorder='big'),
dust_limit_sat=int.from_bytes(payload['dust_limit_satoshis'], byteorder='big'), dust_limit_sat=int.from_bytes(payload['dust_limit_satoshis'], byteorder='big'),
max_htlc_value_in_flight_msat=int.from_bytes(payload['max_htlc_value_in_flight_msat'], 'big'), max_htlc_value_in_flight_msat=int.from_bytes(payload['max_htlc_value_in_flight_msat'], 'big'),
max_accepted_htlcs=payload["max_accepted_htlcs"] max_accepted_htlcs=int.from_bytes(payload["max_accepted_htlcs"], 'big')
) )
funding_txn_minimum_depth = int.from_bytes(payload['minimum_depth'], 'big') funding_txn_minimum_depth = int.from_bytes(payload['minimum_depth'], 'big')
print('remote dust limit', remote_config.dust_limit_sat) print('remote dust limit', remote_config.dust_limit_sat)
@ -912,27 +917,36 @@ class Peer(PrintError):
) )
return chan return chan
async def reestablish_channel(self, chan, m, n):
await self.initialized
self.send_message(gen_msg("channel_reestablish", channel_id=chan.channel_id, next_local_commitment_number=m, next_remote_revocation_number=n))
channel_reestablish_msg = await self.channel_reestablish[chan.channel_id]
print(channel_reestablish_msg)
async def wait_for_funding_locked(self, chan, wallet): async def wait_for_funding_locked(self, chan, wallet):
channel_id = chan.channel_id channel_id = chan.channel_id
# wait until we see confirmations conf = wallet.get_tx_height(chan.funding_outpoint.txid)[1]
def on_network_update(event, *args): if conf < chan.constraints.funding_txn_minimum_depth:
conf = wallet.get_tx_height(chan.funding_outpoint.txid)[1] # wait until we see confirmations
if conf >= chan.constraints.funding_txn_minimum_depth: def on_network_update(event, *args):
async def set_local_funding_locked_result(): conf = wallet.get_tx_height(chan.funding_outpoint.txid)[1]
try: if conf >= chan.constraints.funding_txn_minimum_depth:
self.local_funding_locked[channel_id].set_result(1) async def set_local_funding_locked_result():
except (asyncio.InvalidStateError, KeyError) as e: try:
# FIXME race condition if updates come in quickly, set_result might be called multiple times self.local_funding_locked[channel_id].set_result(1)
# or self.local_funding_locked[channel_id] might be deleted already except (asyncio.InvalidStateError, KeyError) as e:
self.print_error('local_funding_locked.set_result error for channel {}: {}'.format(channel_id, e)) # FIXME race condition if updates come in quickly, set_result might be called multiple times
asyncio.run_coroutine_threadsafe(set_local_funding_locked_result(), asyncio.get_event_loop()) # or self.local_funding_locked[channel_id] might be deleted already
self.network.unregister_callback(on_network_update) self.print_error('local_funding_locked.set_result error for channel {}: {}'.format(channel_id, e))
self.network.register_callback(on_network_update, ['updated']) # thread safe asyncio.run_coroutine_threadsafe(set_local_funding_locked_result(), asyncio.get_event_loop())
self.network.unregister_callback(on_network_update)
self.network.register_callback(on_network_update, ['updated']) # thread safe
try:
await self.local_funding_locked[channel_id]
finally:
del self.local_funding_locked[channel_id]
try:
await self.local_funding_locked[channel_id]
finally:
del self.local_funding_locked[channel_id]
per_commitment_secret_index = 2**48 - (chan.local_state.ctn + 1) - 1 per_commitment_secret_index = 2**48 - (chan.local_state.ctn + 1) - 1
per_commitment_point_second = secret_to_pubkey(int.from_bytes( per_commitment_point_second = secret_to_pubkey(int.from_bytes(
get_per_commitment_secret_from_seed(chan.local_state.per_commitment_secret_seed, per_commitment_secret_index), 'big')) get_per_commitment_secret_from_seed(chan.local_state.per_commitment_secret_seed, per_commitment_secret_index), 'big'))
@ -1094,7 +1108,7 @@ class Peer(PrintError):
class LNWorker: class LNWorker:
def __init__(self, wallet, network): def __init__(self, wallet, network):
self.privkey = H256(str(time.time()).encode("ascii")) self.privkey = H256(b"0123456789")
self.wallet = wallet self.wallet = wallet
self.network = network self.network = network
self.config = network.config self.config = network.config

View file

@ -13,20 +13,71 @@ from lib.simple_config import SimpleConfig
from lib.network import Network 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 from lib.lnbase import Peer, node_list, Outpoint, ChannelConfig, LocalState, RemoteState, Keypair, OnlyPubkeyKeypair, OpenChannel, ChannelConstraints
from lib.lightning_payencode.lnaddr import lnencode, LnAddr from lib.lightning_payencode.lnaddr import lnencode, LnAddr
import lib.constants as constants import lib.constants as constants
is_key = lambda k: k.endswith("_basepoint") or k.endswith("_key")
def maybeDecode(k, v):
if k in ["pubkey", "privkey", "next_per_commitment_point", "per_commitment_secret_seed"] and v is not None:
return binascii.unhexlify(v)
return v
def decodeAll(v):
return {i: maybeDecode(i, j) for i, j in v.items()} if isinstance(v, dict) else v
def typeWrap(k, v, local):
if is_key(k):
if local:
return Keypair(**v)
else:
return OnlyPubkeyKeypair(**v)
return v
def reconstruct_namedtuples(openingchannel):
openingchannel=OpenChannel(**openingchannel)
openingchannel = openingchannel._replace(funding_outpoint=Outpoint(**openingchannel.funding_outpoint))
new_local_config = {k: typeWrap(k, decodeAll(v), True) for k, v in openingchannel.local_config.items()}
openingchannel = openingchannel._replace(local_config=ChannelConfig(**new_local_config))
new_remote_config = {k: typeWrap(k, decodeAll(v), False) for k, v in openingchannel.remote_config.items()}
openingchannel = openingchannel._replace(remote_config=ChannelConfig(**new_remote_config))
new_local_state = decodeAll(openingchannel.local_state)
openingchannel = openingchannel._replace(local_state=LocalState(**new_local_state))
new_remote_state = decodeAll(openingchannel.remote_state)
openingchannel = openingchannel._replace(remote_state=RemoteState(**new_remote_state))
openingchannel = openingchannel._replace(constraints=ChannelConstraints(**openingchannel.constraints))
return openingchannel
def serialize_channels(channels):
serialized_channels = []
for chan in channels:
namedtuples_to_dict = lambda v: {i: j._asdict() if isinstance(j, tuple) else j for i, j in v._asdict().items()}
serialized_channels.append({k: namedtuples_to_dict(v) if isinstance(v, tuple) else v for k, v in chan._asdict().items()})
class MyJsonEncoder(json.JSONEncoder):
def default(self, o):
if isinstance(o, bytes):
return binascii.hexlify(o).decode("ascii")
return super(MyJsonEncoder, self)
dumped = MyJsonEncoder().encode(serialized_channels)
roundtripped = json.loads(dumped)
reconstructed = [reconstruct_namedtuples(x) for x in roundtripped]
if reconstructed != channels:
raise Exception("Channels did not roundtrip serialization without changes:\n" + repr(reconstructed) + "\n" + repr(channels))
return dumped
if __name__ == "__main__": if __name__ == "__main__":
if len(sys.argv) > 2: if len(sys.argv) > 3:
host, port, pubkey = sys.argv[2:5] host, port, pubkey = sys.argv[3:6]
else: else:
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 ["simnet", "testnet"]: if sys.argv[1] not in ["new_channel", "reestablish_channel"]:
raise Exception("first argument must be simnet or testnet") raise Exception("first argument must be new_channel or reestablish_channel")
if sys.argv[1] == "simnet": if sys.argv[2] not in ["simnet", "testnet"]:
raise Exception("second argument must be simnet or testnet")
if sys.argv[2] == "simnet":
set_simnet() set_simnet()
config = SimpleConfig({'lnbase':True, 'simnet':True}) config = SimpleConfig({'lnbase':True, 'simnet':True})
else: else:
@ -41,7 +92,7 @@ if __name__ == "__main__":
wallet = Wallet(storage) wallet = Wallet(storage)
wallet.start_threads(network) wallet.start_threads(network)
# start peer # start peer
privkey = sha256(str(time.time())) privkey = sha256("0123456789")
peer = Peer(host, port, pubkey, privkey, request_initial_sync=False, network=network) peer = Peer(host, port, pubkey, privkey, request_initial_sync=False, network=network)
network.futures.append(asyncio.run_coroutine_threadsafe(peer.main_loop(), network.asyncio_loop)) network.futures.append(asyncio.run_coroutine_threadsafe(peer.main_loop(), network.asyncio_loop))
@ -52,7 +103,18 @@ if __name__ == "__main__":
async def async_test(): async def async_test():
payment_preimage = bytes.fromhex("01"*32) payment_preimage = bytes.fromhex("01"*32)
RHASH = sha256(payment_preimage) RHASH = sha256(payment_preimage)
openingchannel = await peer.channel_establishment_flow(wallet, config, None, funding_satoshis, push_msat, temp_channel_id=os.urandom(32)) channels = wallet.storage.get("channels", None)
if sys.argv[1] == "new_channel":
openingchannel = await peer.channel_establishment_flow(wallet, config, None, funding_satoshis, push_msat, temp_channel_id=os.urandom(32))
dumped = serialize_channels([openingchannel])
wallet.storage.put("channels", dumped)
return
else:
openingchannel = json.loads(channels)[0]
openingchannel = reconstruct_namedtuples(openingchannel)
next_local_commitment_number, next_remote_revocation_number = 1, 1
await peer.reestablish_channel(openingchannel, next_local_commitment_number, next_remote_revocation_number)
openchannel = await peer.wait_for_funding_locked(openingchannel, wallet) openchannel = await peer.wait_for_funding_locked(openingchannel, wallet)
expected_received_sat = 400000 expected_received_sat = 400000
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=Decimal("0.00000001")*expected_received_sat, tags=[('d', 'one cup of coffee')]), peer.privkey[:32])