Пример #1
0
 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
Пример #2
0
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
Пример #3
0
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)
Пример #5
0
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'}
Пример #7
0
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']
Пример #8
0
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()}
Пример #9
0
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
Пример #10
0
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)})
Пример #11
0
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']
    ]
Пример #12
0
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
Пример #13
0
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
Пример #14
0
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
Пример #15
0
#!/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 )
Пример #16
0
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'])}")
Пример #18
0
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]
Пример #19
0
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'
Пример #20
0
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)
Пример #21
0
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))
Пример #22
0
    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)
Пример #23
0
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
Пример #24
0
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'
Пример #25
0
def test_sum_radd():
    result = sum([Millisatoshi(1), Millisatoshi(2), Millisatoshi(3)])
    assert int(result) == 6
Пример #26
0
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)
Пример #27
0
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)