def _coerce_arguments(func, ba): args = OrderedDict() for key, val in ba.arguments.items(): annotation = func.__annotations__.get(key) if annotation == Millisatoshi: args[key] = Millisatoshi(val) else: args[key] = val ba.arguments = args return ba
def find_worst_channel(route, nodeid): worst = None worst_val = Millisatoshi(0) for i in range(1, len(route)): if route[i - 1]['id'] == nodeid: continue val = route[i - 1]['msatoshi'] - route[i]['msatoshi'] if val > worst_val: worst = route[i]['channel'] + '/' + str(route[i]['direction']) worst_val = val return worst
def setup_routing_fees(plugin, route, msatoshi): delay = int(plugin.get_option('cltv-final')) for r in reversed(route): r['msatoshi'] = r['amount_msat'] = msatoshi r['delay'] = delay channels = plugin.rpc.listchannels(r['channel']) for ch in channels.get('channels'): if ch['destination'] == r['id']: fee = Millisatoshi(ch['base_fee_millisatoshi']) fee += msatoshi * ch['fee_per_millionth'] // 1000000 msatoshi += fee delay += ch['delay']
def test_millisatoshi_passthrough(node_factory): """ Ensure that Millisatoshi arguments and return work. """ plugin_path = 'tests/plugins/millisatoshis.py' n = node_factory.get_node(options={ 'plugin': plugin_path, 'log-level': 'io' }) # By keyword ret = n.rpc.call('echo', { 'msat': Millisatoshi(17), 'not_an_msat': '22msat' })['echo_msat'] assert type(ret) == Millisatoshi assert ret == Millisatoshi(17) # By position ret = n.rpc.call('echo', [Millisatoshi(18), '22msat'])['echo_msat'] assert type(ret) == Millisatoshi assert ret == Millisatoshi(18)
def on_openchannel(openchannel, plugin, **kwargs): # - a multiple of 11: we send back a valid address (regtest) if Millisatoshi(openchannel['funding_satoshis']).to_satoshi() % 11 == 0: return { 'result': 'continue', 'close_to': 'bcrt1q7gtnxmlaly9vklvmfj06amfdef3rtnrdazdsvw' } # - a multiple of 7: we send back an empty address if Millisatoshi(openchannel['funding_satoshis']).to_satoshi() % 7 == 0: return {'result': 'continue', 'close_to': ''} # - a multiple of 5: we send back an address for the wrong chain (mainnet) if Millisatoshi(openchannel['funding_satoshis']).to_satoshi() % 5 == 0: return { 'result': 'continue', 'close_to': 'bc1qlq8srqnz64wgklmqvurv7qnr4rvtq2u96hhfg2' } # - otherwise: we don't include the close_to return {'result': 'continue'}
def on_openchannel(openchannel, plugin, **kwargs): print("{} VARS".format(len(openchannel.keys()))) for k in sorted(openchannel.keys()): print("{}={}".format(k, openchannel[k])) if Millisatoshi(openchannel['funding_satoshis']).to_satoshi() % 2 == 1: return { 'result': 'reject', 'error_message': "I don't like odd amounts" } return {'result': 'continue'}
def setup_routing_fees(plugin, route, msatoshi): delay = int(plugin.get_option('cltv-final')) for r in reversed(route): r['msatoshi'] = msatoshi.millisatoshis r['amount_msat'] = msatoshi r['delay'] = delay channels = plugin.rpc.listchannels(r['channel']) ch = next(c for c in channels.get('channels') if c['destination'] == r['id']) fee = Millisatoshi(ch['base_fee_millisatoshi']) # BOLT #7 requires fee >= fee_base_msat + ( amount_to_forward * fee_proportional_millionths / 1000000 ) fee += (msatoshi * ch['fee_per_millionth'] + 10**6 - 1) // 10**6 # integer math trick to round up msatoshi += fee delay += ch['delay']
def getword(plugin, url="http://127.0.0.1:5000", max: Millisatoshi = Millisatoshi(24)): """Pay for a 24-byte word from a lightning word server. """ r = requests.get(url) inv = r.content.decode() # Check amount! decode = plugin.rpc.decodepay(inv) amount = decode['amount_msat'] if amount > max: raise ValueError("Amount {} is too large".format(amount)) preimage = plugin.rpc.pay(inv)['payment_preimage'] b = bytes.fromhex(preimage) return {'description': decode['description'], 'words': b[8:].decode()}
def setup_routing_fees(plugin, route, msatoshi, payload): delay = int(plugin.get_option('cltv-final')) for r in reversed(route): r['msatoshi'] = msatoshi.millisatoshis r['amount_msat'] = msatoshi r['delay'] = delay channels = plugin.rpc.listchannels(r['channel']) ch = next(c for c in channels.get('channels') if c['destination'] == r['id']) fee = Millisatoshi(ch['base_fee_millisatoshi']) fee += msatoshi * ch['fee_per_millionth'] // 10**6 if ch['source'] == payload['nodeid']: fee += payload['msatoshi'] msatoshi += fee delay += ch['delay'] r['direction'] = int(ch['channel_flags']) % 2
def test_deprecated_txprepare(node_factory, bitcoind): """Test the deprecated old-style: txprepare {destination} {satoshi} {feerate} {minconf} """ amount = 10**4 l1 = node_factory.get_node(options={'allow-deprecated-apis': True}) addr = l1.rpc.newaddr()['bech32'] for i in range(7): l1.fundwallet(10**8) bitcoind.generate_block(1) sync_blockheight(bitcoind, [l1]) wait_for(lambda: len(l1.rpc.listfunds()['outputs']) == 7) # Array type with pytest.raises(RpcError, match=r'.* should be an amount in satoshis or all, not .*'): l1.rpc.call('txprepare', [addr, 'slow']) with pytest.raises(RpcError, match=r'Need set \'satoshi\' field.'): l1.rpc.call('txprepare', [addr]) with pytest.raises(RpcError, match=r'Could not parse destination address.*'): l1.rpc.call('txprepare', [Millisatoshi(amount * 100), 'slow', 1]) l1.rpc.call('txprepare', [addr, Millisatoshi(amount * 100), 'slow', 1]) l1.rpc.call('txprepare', [addr, Millisatoshi(amount * 100), 'normal']) l1.rpc.call('txprepare', [addr, Millisatoshi(amount * 100), None, 1]) l1.rpc.call('txprepare', [addr, Millisatoshi(amount * 100)]) # Object type with pytest.raises(RpcError, match=r'Need set \'outputs\' field.'): l1.rpc.call('txprepare', {'destination': addr, 'feerate': 'slow'}) with pytest.raises(RpcError, match=r'Need set \'outputs\' field.'): l1.rpc.call('txprepare', {'satoshi': Millisatoshi(amount * 100), 'feerate': '10perkw', 'minconf': 2}) l1.rpc.call('txprepare', {'destination': addr, 'satoshi': Millisatoshi(amount * 100), 'feerate': '2000perkw', 'minconf': 1}) l1.rpc.call('txprepare', {'destination': addr, 'satoshi': Millisatoshi(amount * 100), 'feerate': '2000perkw'}) l1.rpc.call('txprepare', {'destination': addr, 'satoshi': Millisatoshi(amount * 100)})
def test_txsend(node_factory, bitcoind): amount = 1000000 l1 = node_factory.get_node(random_hsm=True) # Add some funds to withdraw later: both bech32 and p2sh for i in range(5): bitcoind.rpc.sendtoaddress(l1.rpc.newaddr()['bech32'], amount / 10**8) bitcoind.rpc.sendtoaddress( l1.rpc.newaddr('p2sh-segwit')['p2sh-segwit'], amount / 10**8) bitcoind.generate_block(1) wait_for(lambda: len(l1.rpc.listfunds()['outputs']) == 10) prep = l1.rpc.txprepare([{ 'bcrt1qeyyk6sl5pr49ycpqyckvmttus5ttj25pd0zpvg': Millisatoshi(amount * 3 * 1000) }]) out = l1.rpc.txsend(prep['txid']) # Cannot discard after send! with pytest.raises(RpcError, match=r'not an unreleased txid'): l1.rpc.txdiscard(prep['txid']) wait_for(lambda: prep['txid'] in bitcoind.rpc.getrawmempool()) # Signed tx should have same txid decode = bitcoind.rpc.decoderawtransaction(out['tx']) assert decode['txid'] == prep['txid'] bitcoind.generate_block(1) # Change output should appear. if decode['vout'][0]['value'] == Decimal(amount * 3) / 10**8: changenum = 1 elif decode['vout'][1]['value'] == Decimal(amount * 3) / 10**8: changenum = 0 else: assert False # Those spent outputs are gone, but change output has arrived. wait_for(lambda: len(l1.rpc.listfunds()['outputs']) == 10 - len(decode[ 'vin']) + 1) # Change address should appear in listfunds() assert decode['vout'][changenum]['scriptPubKey']['addresses'][0] in [ f['address'] for f in l1.rpc.listfunds()['outputs'] ]
def setup_routing_fees(plugin, route, msatoshi, payload): delay = int(plugin.get_option('cltv-final')) for r in reversed(route): r['msatoshi'] = r['amount_msat'] = msatoshi r['delay'] = delay channels = plugin.rpc.listchannels(r['channel']) for ch in channels.get('channels'): if ch['destination'] == r['id']: fee = Millisatoshi(ch['base_fee_millisatoshi']) fee += msatoshi * ch['fee_per_millionth'] // 1000000 if ch['source'] == payload['nodeid']: if fee <= payload['msatoshi']: fee = payload['msatoshi'] else: raise RpcError( "sendinvoiceless", payload, {'message': 'Insufficient sending amount'}) msatoshi += fee delay += ch['delay'] r['direction'] = int(ch['channel_flags']) % 2
def amounts_from_scid(plugin, scid): channels = plugin.rpc.listfunds().get('channels') channel = next(c for c in channels if 'short_channel_id' in c and c['short_channel_id'] == scid) our_msat = Millisatoshi(channel['our_amount_msat']) total_msat = Millisatoshi(channel['amount_msat']) return our_msat, total_msat
def summary(plugin): """Gets summary information about this node.""" reply = {} info = plugin.rpc.getinfo() funds = plugin.rpc.listfunds() peers = plugin.rpc.listpeers() # Make it stand out if we're not on mainnet. if info['network'] != 'bitcoin': reply['network'] = info['network'].upper() if not plugin.my_address: reply['warning_no_address'] = "NO PUBLIC ADDRESSES" else: reply['my_address'] = plugin.my_address utxos = [int(f['amount_msat']) for f in funds['outputs'] if f['status'] == 'confirmed'] reply['num_utxos'] = len(utxos) utxo_amount = Millisatoshi(sum(utxos)) reply['utxo_amount'] = utxo_amount.to_btc_str() avail_out = Millisatoshi(0) avail_in = Millisatoshi(0) chans = [] reply['num_channels'] = 0 reply['num_connected'] = 0 reply['num_gossipers'] = 0 for p in peers['peers']: active_channel = False for c in p['channels']: if c['state'] != 'CHANNELD_NORMAL': continue active_channel = True if p['connected']: reply['num_connected'] += 1 if c['our_reserve_msat'] < c['to_us_msat']: to_us = c['to_us_msat'] - c['our_reserve_msat'] else: to_us = Millisatoshi(0) avail_out += to_us # We have to derive amount to them to_them = c['total_msat'] - c['to_us_msat'] if c['their_reserve_msat'] < to_them: to_them = to_them - c['their_reserve_msat'] else: to_them = Millisatoshi(0) avail_in += to_them reply['num_channels'] += 1 chans.append((c['total_msat'], to_us, to_them, p['id'], c['private'], p['connected'])) if not active_channel and p['connected']: reply['num_gossipers'] += 1 reply['avail_out'] = avail_out.to_btc_str() reply['avail_in'] = avail_in.to_btc_str() if plugin.fiat_per_btc: reply['utxo_amount'] += ' ({})'.format(to_fiatstr(utxo_amount)) reply['avail_out'] += ' ({})'.format(to_fiatstr(avail_out)) reply['avail_in'] += ' ({})'.format(to_fiatstr(avail_in)) if chans != []: reply['channels_key'] = 'P=private O=offline' reply['channels'] = [] biggest = max(max(int(c[1]), int(c[2])) for c in chans) for c in chans: # Create simple line graph, 47 chars wide. our_len = int((int(c[1]) / biggest * 23)) their_len = int((int(c[2]) / biggest * 23)) divided = False # We put midpoint in the middle. mid = draw.mid if our_len == 0: left = "{:>23}".format('') mid = draw.double_left else: left = "{:>23}".format(draw.left + draw.bar * (our_len - 1)) if their_len == 0: right = "{:23}".format('') # Both 0 is a special case. if our_len == 0: mid = draw.empty else: mid = draw.double_right else: right = "{:23}".format(draw.bar * (their_len - 1) + draw.right) s = left + mid + right extra = '' if c[4]: extra += 'P' if not c[5]: extra += 'O' if extra != '': s += '({})'.format(extra) node = plugin.rpc.listnodes(c[3])['nodes'] if len(node) != 0: s += ':' + node[0]['alias'] else: s += ':' + c[3][0:32] reply['channels'].append(s) return reply
#!/usr/bin/env python3 from lightning import Plugin, Millisatoshi, RpcError import re import time import uuid plugin = Plugin() # When draining 100% we must account (not pay) for an additional HTLC fee. # Currently there is no way of getting the exact number before the fact, # so we try and error until it is high enough, or take the exception text. HTLC_FEE_NUL = Millisatoshi('0sat') HTLC_FEE_STP = Millisatoshi('10sat') HTLC_FEE_MIN = Millisatoshi('100sat') HTLC_FEE_MAX = Millisatoshi('100000sat') HTLC_FEE_PAT = re.compile("^.* HTLC fee: ([0-9]+sat).*$") def setup_routing_fees(plugin, payload, route, amount, substractfees: bool=False): delay = int(plugin.get_option('cltv-final')) amount_iter = amount for r in reversed(route): r['msatoshi'] = amount_iter.millisatoshis r['amount_msat'] = amount_iter r['delay'] = delay channels = plugin.rpc.listchannels(r['channel']) ch = next(c for c in channels.get('channels') if c['destination'] == r['id']) fee = Millisatoshi(ch['base_fee_millisatoshi']) # BOLT #7 requires fee >= fee_base_msat + ( amount_to_forward * fee_proportional_millionths / 1000000 )
def test_txprepare(node_factory, bitcoind): amount = 1000000 l1 = node_factory.get_node(random_hsm=True) # Add some funds to withdraw later: both bech32 and p2sh for i in range(5): bitcoind.rpc.sendtoaddress(l1.rpc.newaddr()['bech32'], amount / 10**8) bitcoind.rpc.sendtoaddress( l1.rpc.newaddr('p2sh-segwit')['p2sh-segwit'], amount / 10**8) bitcoind.generate_block(1) wait_for(lambda: len(l1.rpc.listfunds()['outputs']) == 10) prep = l1.rpc.txprepare('bcrt1qeyyk6sl5pr49ycpqyckvmttus5ttj25pd0zpvg', Millisatoshi(amount * 3 * 1000)) decode = bitcoind.rpc.decoderawtransaction(prep['unsigned_tx']) assert decode['txid'] == prep['txid'] # 4 inputs, 2 outputs. assert len(decode['vin']) == 4 assert len(decode['vout']) == 2 # One output will be correct. if decode['vout'][0]['value'] == Decimal(amount * 3) / 10**8: outnum = 0 changenum = 1 elif decode['vout'][1]['value'] == Decimal(amount * 3) / 10**8: outnum = 1 changenum = 0 else: assert False assert decode['vout'][outnum]['scriptPubKey'][ 'type'] == 'witness_v0_keyhash' assert decode['vout'][outnum]['scriptPubKey']['addresses'] == [ 'bcrt1qeyyk6sl5pr49ycpqyckvmttus5ttj25pd0zpvg' ] assert decode['vout'][changenum]['scriptPubKey'][ 'type'] == 'witness_v0_keyhash' # Now prepare one with no change. prep2 = l1.rpc.txprepare('bcrt1qeyyk6sl5pr49ycpqyckvmttus5ttj25pd0zpvg', 'all') decode = bitcoind.rpc.decoderawtransaction(prep2['unsigned_tx']) assert decode['txid'] == prep2['txid'] # 6 inputs, 1 outputs. assert len(decode['vin']) == 6 assert len(decode['vout']) == 1 # Some fees will be paid. assert decode['vout'][0]['value'] < Decimal(amount * 6) / 10**8 assert decode['vout'][0]['value'] > Decimal( amount * 6) / 10**8 - Decimal(0.0002) assert decode['vout'][0]['scriptPubKey']['type'] == 'witness_v0_keyhash' assert decode['vout'][0]['scriptPubKey']['addresses'] == [ 'bcrt1qeyyk6sl5pr49ycpqyckvmttus5ttj25pd0zpvg' ] # If I cancel the first one, I can get those first 4 outputs. discard = l1.rpc.txdiscard(prep['txid']) assert discard['txid'] == prep['txid'] assert discard['unsigned_tx'] == prep['unsigned_tx'] prep3 = l1.rpc.txprepare('bcrt1qeyyk6sl5pr49ycpqyckvmttus5ttj25pd0zpvg', 'all') decode = bitcoind.rpc.decoderawtransaction(prep3['unsigned_tx']) assert decode['txid'] == prep3['txid'] # 4 inputs, 1 outputs. assert len(decode['vin']) == 4 assert len(decode['vout']) == 1 # Some fees will be taken assert decode['vout'][0]['value'] < Decimal(amount * 4) / 10**8 assert decode['vout'][0]['value'] > Decimal( amount * 4) / 10**8 - Decimal(0.0002) assert decode['vout'][0]['scriptPubKey']['type'] == 'witness_v0_keyhash' assert decode['vout'][0]['scriptPubKey']['addresses'] == [ 'bcrt1qeyyk6sl5pr49ycpqyckvmttus5ttj25pd0zpvg' ] # Cannot discard twice. with pytest.raises(RpcError, match=r'not an unreleased txid'): l1.rpc.txdiscard(prep['txid']) # Discard everything, we should now spend all inputs. l1.rpc.txdiscard(prep2['txid']) l1.rpc.txdiscard(prep3['txid']) prep4 = l1.rpc.txprepare('bcrt1qeyyk6sl5pr49ycpqyckvmttus5ttj25pd0zpvg', 'all') decode = bitcoind.rpc.decoderawtransaction(prep4['unsigned_tx']) assert decode['txid'] == prep4['txid'] # 10 inputs, 1 outputs. assert len(decode['vin']) == 10 assert len(decode['vout']) == 1 # Some fees will be taken assert decode['vout'][0]['value'] < Decimal(amount * 10) / 10**8 assert decode['vout'][0]['value'] > Decimal( amount * 10) / 10**8 - Decimal(0.0003) assert decode['vout'][0]['scriptPubKey']['type'] == 'witness_v0_keyhash' assert decode['vout'][0]['scriptPubKey']['addresses'] == [ 'bcrt1qeyyk6sl5pr49ycpqyckvmttus5ttj25pd0zpvg' ]
import os import time from lightning import LightningRpc, Millisatoshi # pip3 install pylightning from bitcoin_cli import mine from lightning_cli import make_many_payments, wait_to_route ln = os.path.expandvars("$LN") n1 = LightningRpc(os.path.join(ln, "lightning-dirs/1/regtest/lightning-rpc")) n2 = LightningRpc(os.path.join(ln, "lightning-dirs/2/regtest/lightning-rpc")) n3 = LightningRpc(os.path.join(ln, "lightning-dirs/3/regtest/lightning-rpc")) # we assume the channels are already set-up amount = Millisatoshi("0.0001btc") wait_to_route(src=n1, dest=n3, msatoshi=amount.millisatoshis) # send many payments to Charlie, which would result in unresolved HTLCs (assuming charlie is evil) make_many_payments( sender=n1, receiver=n3, num_payments=480, msatoshi_per_payment=amount.millisatoshis, ) # see the number of HTLCs that node 3 have with each peer for i, peer in enumerate(n3.listpeers()["peers"]): print(f"htlcs with peers {i}: {len(peer['channels'][0]['htlcs'])}")
def test_txprepare(node_factory, bitcoind, chainparams): amount = 1000000 l1 = node_factory.get_node(random_hsm=True) addr = chainparams['example_addr'] # Add some funds to withdraw later: both bech32 and p2sh for i in range(5): bitcoind.rpc.sendtoaddress(l1.rpc.newaddr()['bech32'], amount / 10**8) bitcoind.rpc.sendtoaddress(l1.rpc.newaddr('p2sh-segwit')['p2sh-segwit'], amount / 10**8) bitcoind.generate_block(1) wait_for(lambda: len(l1.rpc.listfunds()['outputs']) == 10) prep = l1.rpc.txprepare(addr, Millisatoshi(amount * 3 * 1000)) decode = bitcoind.rpc.decoderawtransaction(prep['unsigned_tx']) assert decode['txid'] == prep['txid'] # 4 inputs, 2 outputs (3 if we have a fee output). assert len(decode['vin']) == 4 assert len(decode['vout']) == 2 if not chainparams['feeoutput'] else 3 # One output will be correct. outnum = [i for i, o in enumerate(decode['vout']) if o['value'] == Decimal(amount * 3) / 10**8][0] for i, o in enumerate(decode['vout']): if i == outnum: assert o['scriptPubKey']['type'] == 'witness_v0_keyhash' assert o['scriptPubKey']['addresses'] == [addr] else: assert o['scriptPubKey']['type'] in ['witness_v0_keyhash', 'fee'] # Now prepare one with no change. prep2 = l1.rpc.txprepare(addr, 'all') decode = bitcoind.rpc.decoderawtransaction(prep2['unsigned_tx']) assert decode['txid'] == prep2['txid'] # 6 inputs, 1 outputs. assert len(decode['vin']) == 6 assert len(decode['vout']) == 1 if not chainparams['feeoutput'] else 2 # Some fees will be paid. assert decode['vout'][0]['value'] < Decimal(amount * 6) / 10**8 assert decode['vout'][0]['value'] > Decimal(amount * 6) / 10**8 - Decimal(0.0002) assert decode['vout'][0]['scriptPubKey']['type'] == 'witness_v0_keyhash' assert decode['vout'][0]['scriptPubKey']['addresses'] == [addr] # If I cancel the first one, I can get those first 4 outputs. discard = l1.rpc.txdiscard(prep['txid']) assert discard['txid'] == prep['txid'] assert discard['unsigned_tx'] == prep['unsigned_tx'] prep3 = l1.rpc.txprepare(addr, 'all') decode = bitcoind.rpc.decoderawtransaction(prep3['unsigned_tx']) assert decode['txid'] == prep3['txid'] # 4 inputs, 1 outputs. assert len(decode['vin']) == 4 assert len(decode['vout']) == 1 if not chainparams['feeoutput'] else 2 # Some fees will be taken assert decode['vout'][0]['value'] < Decimal(amount * 4) / 10**8 assert decode['vout'][0]['value'] > Decimal(amount * 4) / 10**8 - Decimal(0.0002) assert decode['vout'][0]['scriptPubKey']['type'] == 'witness_v0_keyhash' assert decode['vout'][0]['scriptPubKey']['addresses'] == [addr] # Cannot discard twice. with pytest.raises(RpcError, match=r'not an unreleased txid'): l1.rpc.txdiscard(prep['txid']) # Discard everything, we should now spend all inputs. l1.rpc.txdiscard(prep2['txid']) l1.rpc.txdiscard(prep3['txid']) prep4 = l1.rpc.txprepare(addr, 'all') decode = bitcoind.rpc.decoderawtransaction(prep4['unsigned_tx']) assert decode['txid'] == prep4['txid'] # 10 inputs, 1 outputs. assert len(decode['vin']) == 10 assert len(decode['vout']) == 1 if not chainparams['feeoutput'] else 2 # Some fees will be taken assert decode['vout'][0]['value'] < Decimal(amount * 10) / 10**8 assert decode['vout'][0]['value'] > Decimal(amount * 10) / 10**8 - Decimal(0.0003) assert decode['vout'][0]['scriptPubKey']['type'] == 'witness_v0_keyhash' assert decode['vout'][0]['scriptPubKey']['addresses'] == [addr]
def test_txprepare(node_factory, bitcoind): amount = 1000000 l1 = node_factory.get_node(random_hsm=True) # Add some funds to withdraw later: both bech32 and p2sh for i in range(5): bitcoind.rpc.sendtoaddress(l1.rpc.newaddr()['bech32'], amount / 10**8) bitcoind.rpc.sendtoaddress( l1.rpc.newaddr('p2sh-segwit')['p2sh-segwit'], amount / 10**8) bitcoind.generate_block(1) wait_for(lambda: len(l1.rpc.listfunds()['outputs']) == 10) prep = l1.rpc.txprepare([{ 'bcrt1qeyyk6sl5pr49ycpqyckvmttus5ttj25pd0zpvg': Millisatoshi(amount * 3 * 1000) }]) decode = bitcoind.rpc.decoderawtransaction(prep['unsigned_tx']) assert decode['txid'] == prep['txid'] # 4 inputs, 2 outputs. assert len(decode['vin']) == 4 assert len(decode['vout']) == 2 # One output will be correct. if decode['vout'][0]['value'] == Decimal(amount * 3) / 10**8: outnum = 0 changenum = 1 elif decode['vout'][1]['value'] == Decimal(amount * 3) / 10**8: outnum = 1 changenum = 0 else: assert False assert decode['vout'][outnum]['scriptPubKey'][ 'type'] == 'witness_v0_keyhash' assert decode['vout'][outnum]['scriptPubKey']['addresses'] == [ 'bcrt1qeyyk6sl5pr49ycpqyckvmttus5ttj25pd0zpvg' ] assert decode['vout'][changenum]['scriptPubKey'][ 'type'] == 'witness_v0_keyhash' # Now prepare one with no change. prep2 = l1.rpc.txprepare([{ 'bcrt1qeyyk6sl5pr49ycpqyckvmttus5ttj25pd0zpvg': 'all' }]) decode = bitcoind.rpc.decoderawtransaction(prep2['unsigned_tx']) assert decode['txid'] == prep2['txid'] # 6 inputs, 1 outputs. assert len(decode['vin']) == 6 assert len(decode['vout']) == 1 # Some fees will be paid. assert decode['vout'][0]['value'] < Decimal(amount * 6) / 10**8 assert decode['vout'][0]['value'] > Decimal( amount * 6) / 10**8 - Decimal(0.0002) assert decode['vout'][0]['scriptPubKey']['type'] == 'witness_v0_keyhash' assert decode['vout'][0]['scriptPubKey']['addresses'] == [ 'bcrt1qeyyk6sl5pr49ycpqyckvmttus5ttj25pd0zpvg' ] # If I cancel the first one, I can get those first 4 outputs. discard = l1.rpc.txdiscard(prep['txid']) assert discard['txid'] == prep['txid'] assert discard['unsigned_tx'] == prep['unsigned_tx'] prep3 = l1.rpc.txprepare([{ 'bcrt1qeyyk6sl5pr49ycpqyckvmttus5ttj25pd0zpvg': 'all' }]) decode = bitcoind.rpc.decoderawtransaction(prep3['unsigned_tx']) assert decode['txid'] == prep3['txid'] # 4 inputs, 1 outputs. assert len(decode['vin']) == 4 assert len(decode['vout']) == 1 # Some fees will be taken assert decode['vout'][0]['value'] < Decimal(amount * 4) / 10**8 assert decode['vout'][0]['value'] > Decimal( amount * 4) / 10**8 - Decimal(0.0002) assert decode['vout'][0]['scriptPubKey']['type'] == 'witness_v0_keyhash' assert decode['vout'][0]['scriptPubKey']['addresses'] == [ 'bcrt1qeyyk6sl5pr49ycpqyckvmttus5ttj25pd0zpvg' ] # Cannot discard twice. with pytest.raises(RpcError, match=r'not an unreleased txid'): l1.rpc.txdiscard(prep['txid']) # Discard everything, we should now spend all inputs. l1.rpc.txdiscard(prep2['txid']) l1.rpc.txdiscard(prep3['txid']) prep4 = l1.rpc.txprepare([{ 'bcrt1qeyyk6sl5pr49ycpqyckvmttus5ttj25pd0zpvg': 'all' }]) decode = bitcoind.rpc.decoderawtransaction(prep4['unsigned_tx']) assert decode['txid'] == prep4['txid'] # 10 inputs, 1 outputs. assert len(decode['vin']) == 10 assert len(decode['vout']) == 1 # Some fees will be taken assert decode['vout'][0]['value'] < Decimal(amount * 10) / 10**8 assert decode['vout'][0]['value'] > Decimal( amount * 10) / 10**8 - Decimal(0.0003) assert decode['vout'][0]['scriptPubKey']['type'] == 'witness_v0_keyhash' assert decode['vout'][0]['scriptPubKey']['addresses'] == [ 'bcrt1qeyyk6sl5pr49ycpqyckvmttus5ttj25pd0zpvg' ] # Discard prep4 and get all funds again l1.rpc.txdiscard(prep4['txid']) with pytest.raises( RpcError, match= r'this destination wants all satoshi. The count of outputs can\'t be more than 1' ): prep5 = l1.rpc.txprepare([{ 'bcrt1qeyyk6sl5pr49ycpqyckvmttus5ttj25pd0zpvg': Millisatoshi(amount * 3 * 1000) }, { 'bcrt1qw508d6qejxtdg4y5r3zarvary0c5xw7kygt080': 'all' }]) prep5 = l1.rpc.txprepare([{ 'bcrt1qeyyk6sl5pr49ycpqyckvmttus5ttj25pd0zpvg': Millisatoshi(amount * 3 * 500 + 100000) }, { 'bcrt1qw508d6qejxtdg4y5r3zarvary0c5xw7kygt080': Millisatoshi(amount * 3 * 500 - 100000) }]) decode = bitcoind.rpc.decoderawtransaction(prep5['unsigned_tx']) assert decode['txid'] == prep5['txid'] # 4 inputs, 3 outputs(include change). assert len(decode['vin']) == 4 assert len(decode['vout']) == 3 # One output will be correct. for i in range(3): if decode['vout'][i - 1]['value'] == Decimal('0.01500100'): outnum1 = i - 1 elif decode['vout'][i - 1]['value'] == Decimal('0.01499900'): outnum2 = i - 1 else: changenum = i - 1 assert decode['vout'][outnum1]['scriptPubKey'][ 'type'] == 'witness_v0_keyhash' assert decode['vout'][outnum1]['scriptPubKey']['addresses'] == [ 'bcrt1qeyyk6sl5pr49ycpqyckvmttus5ttj25pd0zpvg' ] assert decode['vout'][outnum2]['scriptPubKey'][ 'type'] == 'witness_v0_keyhash' assert decode['vout'][outnum2]['scriptPubKey']['addresses'] == [ 'bcrt1qw508d6qejxtdg4y5r3zarvary0c5xw7kygt080' ] assert decode['vout'][changenum]['scriptPubKey'][ 'type'] == 'witness_v0_keyhash'
def rebalance(plugin, outgoing_scid, incoming_scid, msatoshi: Millisatoshi = None, maxfeepercent: float = 0.5, retry_for: int = 60, exemptfee: Millisatoshi = Millisatoshi(5000)): """Rebalancing channel liquidity with circular payments. This tool helps to move some msatoshis between your channels. """ if msatoshi: msatoshi = Millisatoshi(msatoshi) maxfeepercent = float(maxfeepercent) retry_for = int(retry_for) exemptfee = Millisatoshi(exemptfee) payload = { "outgoing_scid": outgoing_scid, "incoming_scid": incoming_scid, "msatoshi": msatoshi, "maxfeepercent": maxfeepercent, "retry_for": retry_for, "exemptfee": exemptfee } my_node_id = plugin.rpc.getinfo().get('id') outgoing_node_id = peer_from_scid(plugin, outgoing_scid, my_node_id, payload) incoming_node_id = peer_from_scid(plugin, incoming_scid, my_node_id, payload) get_channel(plugin, payload, outgoing_node_id, outgoing_scid, True) get_channel(plugin, payload, incoming_node_id, incoming_scid, True) out_ours, out_total = amounts_from_scid(plugin, outgoing_scid) in_ours, in_total = amounts_from_scid(plugin, incoming_scid) plugin.log("Outgoing node: %s, channel: %s" % (outgoing_node_id, outgoing_scid)) plugin.log("Incoming node: %s, channel: %s" % (incoming_node_id, incoming_scid)) # If amount was not given, calculate a suitable 50/50 rebalance amount if msatoshi is None: msatoshi = calc_optimal_amount(out_ours, out_total, in_ours, in_total, payload) plugin.log("Estimating optimal amount %s" % msatoshi) # Check requested amounts are selected channels if msatoshi > out_ours or msatoshi > in_total - in_ours: raise RpcError("rebalance", payload, {'message': 'Channel capacities too low'}) route_out = {'id': outgoing_node_id, 'channel': outgoing_scid} route_in = {'id': my_node_id, 'channel': incoming_scid} start_ts = int(time.time()) label = "Rebalance-" + str(uuid.uuid4()) description = "%s to %s" % (outgoing_scid, incoming_scid) invoice = plugin.rpc.invoice(msatoshi, label, description, retry_for + 60) payment_hash = invoice['payment_hash'] plugin.log("Invoice payment_hash: %s" % payment_hash) success_msg = "" try: excludes = [] # excude all own channels to prevent unwanted shortcuts [out,mid,in] mychannels = plugin.rpc.listchannels(source=my_node_id)['channels'] for channel in mychannels: excludes += [ channel['short_channel_id'] + '/0', channel['short_channel_id'] + '/1' ] while int(time.time()) - start_ts < retry_for: r = plugin.rpc.getroute(incoming_node_id, msatoshi, riskfactor=1, cltv=9, fromid=outgoing_node_id, exclude=excludes) route_mid = r['route'] route = [route_out] + route_mid + [route_in] setup_routing_fees(plugin, route, msatoshi) fees = route[0]['amount_msat'] - msatoshi # check fee and exclude worst channel the next time # NOTE: the int(msat) casts are just a workaround for outdated pylightning versions if fees > exemptfee and int( fees) > int(msatoshi) * maxfeepercent / 100: worst_channel_id = find_worst_channel(route) if worst_channel_id is None: raise RpcError("rebalance", payload, {'message': 'Insufficient fee'}) excludes += [worst_channel_id + '/0', worst_channel_id + '/1'] continue success_msg = "%d msat sent over %d hops to rebalance %d msat" % ( msatoshi + fees, len(route), msatoshi) plugin.log("Sending %s over %d hops to rebalance %s" % (msatoshi + fees, len(route), msatoshi)) for r in route: plugin.log(" - %s %14s %s" % (r['id'], r['channel'], r['amount_msat'])) try: plugin.rpc.sendpay(route, payment_hash) plugin.rpc.waitsendpay(payment_hash, retry_for + start_ts - int(time.time())) return success_msg except RpcError as e: plugin.log("RpcError: " + str(e)) erring_channel = e.error.get('data', {}).get('erring_channel') if erring_channel == incoming_scid: raise RpcError("rebalance", payload, {'message': 'Error with incoming channel'}) if erring_channel == outgoing_scid: raise RpcError("rebalance", payload, {'message': 'Error with outgoing channel'}) erring_direction = e.error.get('data', {}).get('erring_direction') if erring_channel is not None and erring_direction is not None: excludes.append(erring_channel + '/' + str(erring_direction)) except Exception as e: plugin.log("Exception: " + str(e)) return cleanup(plugin, label, payload, success_msg, e) return cleanup(plugin, label, payload, success_msg)
def append_header(table, max_msat): short_str = msat_to_approx_str(Millisatoshi(max_msat)) table.append( "%c%-13sOUT/OURS %c IN/THEIRS%12s%c SCID FLAG ALIAS" % (draw.left, short_str, draw.mid, short_str, draw.right))
def _exec_func(self, func, request): params = request.params sig = inspect.signature(func) arguments = OrderedDict() for name, value in sig.parameters.items(): arguments[name] = inspect._empty # Fill in any injected parameters if 'plugin' in arguments: arguments['plugin'] = self if 'request' in arguments: arguments['request'] = request args = [] kwargs = {} # Now zip the provided arguments and the prefilled a together if isinstance(params, dict): for k, v in params.items(): if k in arguments: # Explicitly (try to) interpret as Millisatoshi if annotated if func.__annotations__.get(k) == Millisatoshi: arguments[k] = Millisatoshi(v) else: arguments[k] = v else: kwargs[k] = v else: pos = 0 for k, v in arguments.items(): # Skip already assigned args and special catch-all args if v is not inspect._empty or k in ['args', 'kwargs']: continue if pos < len(params): # Apply positional args if we have them if func.__annotations__.get(k) == Millisatoshi: arguments[k] = Millisatoshi(params[pos]) else: arguments[k] = params[pos] elif sig.parameters[k].default is inspect.Signature.empty: # This is a positional arg with no value passed raise TypeError("Missing required parameter: %s" % sig.parameters[k]) else: # For the remainder apply default args arguments[k] = sig.parameters[k].default pos += 1 if len(arguments) < len(params): args = params[len(arguments):] if 'kwargs' in arguments: arguments['kwargs'] = kwargs elif len(kwargs) > 0: raise TypeError( "Extra arguments given: {kwargs}".format(kwargs=kwargs)) if 'args' in arguments: arguments['args'] = args elif len(args) > 0: raise TypeError("Extra arguments given: {args}".format(args=args)) missing = [k for k, v in arguments.items() if v is inspect._empty] if missing: raise TypeError("Missing positional arguments ({given} given, " "expected {expected}): {missing}".format( missing=", ".join(missing), given=len(arguments) - len(missing), expected=len(arguments))) ba = sig.bind(**arguments) ba.apply_defaults() return func(*ba.args, **ba.kwargs)
def test_to_approx_str(): amount = Millisatoshi('10000000sat') assert amount.to_approx_str() == "0.1btc" amount = Millisatoshi('1000000sat') assert amount.to_approx_str() == "0.01btc" amount = Millisatoshi('100000sat') assert amount.to_approx_str() == "0.001btc" amount = Millisatoshi('10000sat') assert amount.to_approx_str() == "10000sat" amount = Millisatoshi('1000sat') assert amount.to_approx_str() == "1000sat" amount = Millisatoshi('100msat') assert amount.to_approx_str() == "0.1sat" # also test significant rounding amount = Millisatoshi('10001234sat') assert amount.to_approx_str() == "0.1btc" amount = Millisatoshi('1234sat') assert amount.to_approx_str(3) == "1234sat" # note: no rounding amount = Millisatoshi('1234sat') assert amount.to_approx_str(2) == "1234sat" # note: no rounding amount = Millisatoshi('1230sat') assert amount.to_approx_str(2) == "1230sat" # note: no rounding amount = Millisatoshi('12345678sat') assert amount.to_approx_str() == "0.123btc" amount = Millisatoshi('12345678sat') assert amount.to_approx_str(1) == "0.1btc" amount = Millisatoshi('15345678sat') assert amount.to_approx_str(1) == "0.2btc" amount = Millisatoshi('1200000000sat') assert amount.to_approx_str() == "12btc" amount = Millisatoshi('1200000000sat') assert amount.to_approx_str(1) == "12btc" # note: no rounding
def test_txprepare(node_factory, bitcoind, chainparams): amount = 1000000 l1 = node_factory.get_node(random_hsm=True) addr = chainparams['example_addr'] # Add some funds to withdraw later: both bech32 and p2sh for i in range(5): bitcoind.rpc.sendtoaddress(l1.rpc.newaddr()['bech32'], amount / 10**8) bitcoind.rpc.sendtoaddress( l1.rpc.newaddr('p2sh-segwit')['p2sh-segwit'], amount / 10**8) bitcoind.generate_block(1) wait_for(lambda: len(l1.rpc.listfunds()['outputs']) == 10) prep = l1.rpc.txprepare([{addr: Millisatoshi(amount * 3 * 1000)}]) decode = bitcoind.rpc.decoderawtransaction(prep['unsigned_tx']) assert decode['txid'] == prep['txid'] # 4 inputs, 2 outputs (3 if we have a fee output). assert len(decode['vin']) == 4 assert len(decode['vout']) == 2 if not chainparams['feeoutput'] else 3 # One output will be correct. outnum = [ i for i, o in enumerate(decode['vout']) if o['value'] == Decimal(amount * 3) / 10**8 ][0] for i, o in enumerate(decode['vout']): if i == outnum: assert o['scriptPubKey']['type'] == 'witness_v0_keyhash' assert o['scriptPubKey']['addresses'] == [addr] else: assert o['scriptPubKey']['type'] in ['witness_v0_keyhash', 'fee'] # Now prepare one with no change. prep2 = l1.rpc.txprepare([{addr: 'all'}]) decode = bitcoind.rpc.decoderawtransaction(prep2['unsigned_tx']) assert decode['txid'] == prep2['txid'] # 6 inputs, 1 outputs. assert len(decode['vin']) == 6 assert len(decode['vout']) == 1 if not chainparams['feeoutput'] else 2 # Some fees will be paid. assert decode['vout'][0]['value'] < Decimal(amount * 6) / 10**8 assert decode['vout'][0]['value'] > Decimal( amount * 6) / 10**8 - Decimal(0.0002) assert decode['vout'][0]['scriptPubKey']['type'] == 'witness_v0_keyhash' assert decode['vout'][0]['scriptPubKey']['addresses'] == [addr] # If I cancel the first one, I can get those first 4 outputs. discard = l1.rpc.txdiscard(prep['txid']) assert discard['txid'] == prep['txid'] assert discard['unsigned_tx'] == prep['unsigned_tx'] prep3 = l1.rpc.txprepare([{addr: 'all'}]) decode = bitcoind.rpc.decoderawtransaction(prep3['unsigned_tx']) assert decode['txid'] == prep3['txid'] # 4 inputs, 1 outputs. assert len(decode['vin']) == 4 assert len(decode['vout']) == 1 if not chainparams['feeoutput'] else 2 # Some fees will be taken assert decode['vout'][0]['value'] < Decimal(amount * 4) / 10**8 assert decode['vout'][0]['value'] > Decimal( amount * 4) / 10**8 - Decimal(0.0002) assert decode['vout'][0]['scriptPubKey']['type'] == 'witness_v0_keyhash' assert decode['vout'][0]['scriptPubKey']['addresses'] == [addr] # Cannot discard twice. with pytest.raises(RpcError, match=r'not an unreleased txid'): l1.rpc.txdiscard(prep['txid']) # Discard everything, we should now spend all inputs. l1.rpc.txdiscard(prep2['txid']) l1.rpc.txdiscard(prep3['txid']) prep4 = l1.rpc.txprepare([{addr: 'all'}]) decode = bitcoind.rpc.decoderawtransaction(prep4['unsigned_tx']) assert decode['txid'] == prep4['txid'] # 10 inputs, 1 outputs. assert len(decode['vin']) == 10 assert len(decode['vout']) == 1 if not chainparams['feeoutput'] else 2 # Some fees will be taken assert decode['vout'][0]['value'] < Decimal(amount * 10) / 10**8 assert decode['vout'][0]['value'] > Decimal( amount * 10) / 10**8 - Decimal(0.0003) assert decode['vout'][0]['scriptPubKey']['type'] == 'witness_v0_keyhash' assert decode['vout'][0]['scriptPubKey']['addresses'] == [addr] l1.rpc.txdiscard(prep4['txid']) # Try passing in a utxo set utxos = [ utxo["txid"] + ":" + str(utxo["output"]) for utxo in l1.rpc.listfunds()["outputs"] ][:4] prep5 = l1.rpc.txprepare([{ addr: Millisatoshi(amount * 3.5 * 1000) }], utxos=utxos) decode = bitcoind.rpc.decoderawtransaction(prep5['unsigned_tx']) assert decode['txid'] == prep5['txid'] # Check that correct utxos are included assert len(decode['vin']) == 4 vins = ["{}:{}".format(v['txid'], v['vout']) for v in decode['vin']] for utxo in utxos: assert utxo in vins # We should have a change output, so this is exact assert len(decode['vout']) == 3 if chainparams['feeoutput'] else 2 assert decode['vout'][1]['value'] == Decimal(amount * 3.5) / 10**8 assert decode['vout'][1]['scriptPubKey']['type'] == 'witness_v0_keyhash' assert decode['vout'][1]['scriptPubKey']['addresses'] == [addr] # Discard prep4 and get all funds again l1.rpc.txdiscard(prep5['txid']) with pytest.raises( RpcError, match= r'this destination wants all satoshi. The count of outputs can\'t be more than 1' ): prep5 = l1.rpc.txprepare([{ addr: Millisatoshi(amount * 3 * 1000) }, { addr: 'all' }]) prep5 = l1.rpc.txprepare([{ addr: Millisatoshi(amount * 3 * 500 + 100000) }, { addr: Millisatoshi(amount * 3 * 500 - 100000) }]) decode = bitcoind.rpc.decoderawtransaction(prep5['unsigned_tx']) assert decode['txid'] == prep5['txid'] # 4 inputs, 3 outputs(include change). assert len(decode['vin']) == 4 assert len(decode['vout']) == 4 if chainparams['feeoutput'] else 3 # One output will be correct. for i in range(3 + chainparams['feeoutput']): if decode['vout'][i - 1]['value'] == Decimal('0.01500100'): outnum1 = i - 1 elif decode['vout'][i - 1]['value'] == Decimal('0.01499900'): outnum2 = i - 1 else: changenum = i - 1 assert decode['vout'][outnum1]['scriptPubKey'][ 'type'] == 'witness_v0_keyhash' assert decode['vout'][outnum1]['scriptPubKey']['addresses'] == [addr] assert decode['vout'][outnum2]['scriptPubKey'][ 'type'] == 'witness_v0_keyhash' assert decode['vout'][outnum2]['scriptPubKey']['addresses'] == [addr] assert decode['vout'][changenum]['scriptPubKey'][ 'type'] == 'witness_v0_keyhash'
def test_sum_radd(): result = sum([Millisatoshi(1), Millisatoshi(2), Millisatoshi(3)]) assert int(result) == 6
def sendinvoiceless(plugin, nodeid, msatoshi: Millisatoshi, maxfeepercent: float = 0.5, retry_for: int = 60, exemptfee: Millisatoshi = Millisatoshi(5000)): """Send invoiceless payments with circular routes. This tool sends some msatoshis without needing to have an invoice from the receiving node. """ msatoshi = Millisatoshi(msatoshi) maxfeepercent = float(maxfeepercent) retry_for = int(retry_for) exemptfee = Millisatoshi(exemptfee) payload = { "nodeid": nodeid, "msatoshi": msatoshi, "maxfeepercent": maxfeepercent, "retry_for": retry_for, "exemptfee": exemptfee } myid = plugin.rpc.getinfo().get('id') label = "InvoicelessChange-" + str(uuid.uuid4()) description = "Sending %s to %s" % (msatoshi, nodeid) change = Millisatoshi(1000) invoice = plugin.rpc.invoice(change, label, description, retry_for + 60) payment_hash = invoice['payment_hash'] plugin.log("Invoice payment_hash: %s" % payment_hash) success_msg = "" try: excludes = [] start_ts = int(time.time()) while int(time.time()) - start_ts < retry_for: forth = plugin.rpc.getroute(nodeid, msatoshi + change, riskfactor=10, exclude=excludes) back = plugin.rpc.getroute(myid, change, riskfactor=10, fromid=nodeid, exclude=excludes) route = forth['route'] + back['route'] setup_routing_fees(plugin, route, change, payload) fees = route[0]['amount_msat'] - route[-1]['amount_msat'] - msatoshi # check fee and exclude worst channel the next time # NOTE: the int(msat) casts are just a workaround for outdated pylightning versions if fees > exemptfee and int( fees) > int(msatoshi) * maxfeepercent / 100: worst_channel = find_worst_channel(route, nodeid) if worst_channel is None: raise RpcError("sendinvoiceless", payload, {'message': 'Insufficient fee'}) excludes.append(worst_channel) continue success_msg = "%d msat delivered with %d msat fee over %d hops" % ( msatoshi, fees, len(route)) plugin.log("Sending %s over %d hops to send %s and return %s" % (route[0]['msatoshi'], len(route), msatoshi, change)) for r in route: plugin.log(" - %s %14s %s" % (r['id'], r['channel'], r['amount_msat'])) try: plugin.rpc.sendpay(route, payment_hash) plugin.rpc.waitsendpay(payment_hash, retry_for + start_ts - int(time.time())) return success_msg except RpcError as e: plugin.log("RpcError: " + str(e)) erring_channel = e.error.get('data', {}).get('erring_channel') erring_direction = e.error.get('data', {}).get('erring_direction') if erring_channel is not None and erring_direction is not None: excludes.append(erring_channel + '/' + str(erring_direction)) except Exception as e: plugin.log("Exception: " + str(e)) return cleanup(plugin, label, payload, success_msg, e) return cleanup(plugin, label, payload, success_msg)
def sendinvoiceless(plugin, nodeid, msatoshi: Millisatoshi, maxfeepercent="0.5", retry_for=60, exemptfee: Millisatoshi = Millisatoshi(5000)): """Invoiceless payment with circular routes. This tool sends some msatoshis without needing to have an invoice from the receiving node. """ payload = { "nodeid": nodeid, "msatoshi": msatoshi, "maxfeepercent": maxfeepercent, "retry_for": retry_for, "exemptfee": exemptfee } myid = plugin.rpc.getinfo().get('id') label = "InvoicelessChange-" + str(uuid.uuid4()) description = "Sending %s to %s" % (msatoshi, nodeid) change = Millisatoshi(1000) invoice = plugin.rpc.invoice(change, label, description, int(retry_for) + 60) payment_hash = invoice['payment_hash'] plugin.log("Invoice payment_hash: %s" % payment_hash) success_msg = "" try: excludes = [] start_ts = int(time.time()) while int(time.time()) - start_ts < int(retry_for): forth = plugin.rpc.getroute(nodeid, msatoshi + change, riskfactor=10, exclude=excludes) back = plugin.rpc.getroute(myid, change, riskfactor=10, fromid=nodeid, exclude=excludes) route = forth['route'] + back['route'] setup_routing_fees(plugin, route, change, payload) fees = route[0]['msatoshi'] - route[-1]['msatoshi'] - msatoshi # Next line would be correct, but must be fixed to work around #2601 - cleanup when merged # if fees > exemptfee and fees > msatoshi * float(maxfeepercent) / 100: if fees > exemptfee and int( fees) > int(msatoshi) * float(maxfeepercent) / 100: worst_channel = find_worst_channel(route, nodeid) if worst_channel is None: raise RpcError("sendinvoiceless", payload, {'message': 'Insufficient fee'}) excludes.append(worst_channel) continue try: plugin.log( "Sending %s over %d hops to deliver %s and bring back %s" % (route[0]['msatoshi'], len(route), msatoshi, change)) for r in route: plugin.log("Node: %s, channel: %13s, %s" % (r['id'], r['channel'], r['msatoshi'])) success_msg = "%d msat delivered with %d msat fee over %d hops" % ( msatoshi, fees, len(route)) plugin.rpc.sendpay(route, payment_hash) plugin.rpc.waitsendpay( payment_hash, int(retry_for) + start_ts - int(time.time())) return success_msg except RpcError as e: plugin.log("RpcError: " + str(e)) erring_channel = e.error.get('data', {}).get('erring_channel') erring_direction = e.error.get('data', {}).get('erring_direction') if erring_channel is not None and erring_direction is not None: excludes.append(erring_channel + '/' + str(erring_direction)) except Exception as e: plugin.log("Exception: " + str(e)) return sendinvoiceless_fail(plugin, label, payload, success_msg, e) return sendinvoiceless_fail(plugin, label, payload, success_msg)