Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .gitmodules
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
[submodule "subprojects/MinCostFlow"]
path = subprojects/MinCostFlow
url = https://github.com/Lagrang3/mincostflow.git
73 changes: 73 additions & 0 deletions benchmark/analize.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
import json
import matplotlib.pyplot as plt
import numpy

def open_json_data(filename):
with open(filename,'r') as f:
return json.load(f)

ortool_data = open_json_data('ortools.json')
apath_data = open_json_data('augmenting_path.json')
cscaling_data = open_json_data('cost_scaling.json')

def plot_success_rate(data):
x = [ int(i) for i in data.keys() ]
y = [ data[i]['nsuccess']/(data[i]['nsuccess']+data[i]['nfails']) for i in data.keys() ]
fig = plt.figure()
ax = fig.subplots()
ax.grid()
ax.set_title('Success rate')
ax.set_xlabel('amount (sats)')
ax.set_ylabel('success rate')
ax.loglog(x,y)
fig.savefig('plot_success.png')
plt.show()

def plot_time(data):
fig=plt.figure()
ax=fig.subplots()
ax.grid()
ax.set_title('MCF timing')
ax.set_xlabel('amount (sats)')
ax.set_ylabel('time (ms)')
for k in data:
D = data[k]
xser = [ int(i) for i in D.keys() ]
tser = [ numpy.mean(D[i]['time_mcf'])*1000 for i in D.keys() ]
tmin = [ numpy.min(D[i]['time_mcf'])*1000 for i in D.keys() ]
tmax = [ numpy.max(D[i]['time_mcf'])*1000 for i in D.keys() ]
ax.fill_between(xser,tmin,tmax,alpha = 0.2)
ax.loglog(xser,tser,label=k)
ax.legend()
fig.savefig('plot_time.png')
plt.show()
#x = [ int(i) for i in data['ortools'].keys() ]

def plot_totaltime(data):
fig=plt.figure()
ax=fig.subplots()
ax.grid()
ax.set_title('Total Payment Time')
ax.set_xlabel('amount (sats)')
ax.set_ylabel('time (ms)')
for k in data:
D = data[k]
xser = [ int(i) for i in D.keys() ]
tser = [ numpy.mean(D[i]['time_total'])*1000 for i in D.keys() ]
tmin = [ numpy.min(D[i]['time_total'])*1000 for i in D.keys() ]
tmax = [ numpy.max(D[i]['time_total'])*1000 for i in D.keys() ]
ax.fill_between(xser,tmin,tmax,alpha = 0.2)
ax.loglog(xser,tser,label=k)
ax.legend()
fig.savefig('plot_totaltime.png')
plt.show()


#print(ortool_data)
#print(apath_data)
#print(cscaling_data)

plot_success_rate(ortool_data)
in_data = {"ortools" : ortool_data, "cost_scaling" : cscaling_data, "augmenting_path" : apath_data}
plot_time(in_data)
plot_totaltime(in_data)
83 changes: 83 additions & 0 deletions benchmark/benchmark.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
import random
import json

#prefix='cost_scaling'
prefix='augmenting_path'
#prefix='ortools'
random.seed(64)

from pickhardtpayments.ChannelGraph import ChannelGraph
from pickhardtpayments.UncertaintyNetwork import UncertaintyNetwork
from pickhardtpayments.OracleLightningNetwork import OracleLightningNetwork
from pickhardtpayments.SyncSimulatedPaymentSession import SyncSimulatedPaymentSession


#we first need to import the chanenl graph from c-lightning jsondump
#you can get your own data set via:
# $: lightning-cli listchannels > listchannels20220412.json
# alternatively you can go to https://ln.rene-pickhardt.de to find a data dump
channel_graph = ChannelGraph("listchannels20220412.json")

uncertainty_network = UncertaintyNetwork(channel_graph)
oracle_lightning_network = OracleLightningNetwork(channel_graph)
#we create ourselves a payment session which in this case operates by sending out the onions
#sequentially
payment_session = SyncSimulatedPaymentSession(oracle_lightning_network,
uncertainty_network,
mu = 0,
base = 0,
prune_network=False)

#we need to make sure we forget all learnt information on the Uncertainty Nework

net = uncertainty_network._channel_graph
nodes = net.nodes()
funds = {}
for n in nodes:
funds[n] = 0

for a,b,d in net.edges(data=True):
cap = d['channel'].capacity
funds[a] += cap

# exit(0)

def random_node_pairs(n_pairs,amt):
list_of_pairs = []
for i in range(n_pairs):
while(True):
a,b = random.sample(nodes,2)
if min(funds[a],funds[b])>=amt:
if oracle_lightning_network.theoretical_maximum_payable_amount(a,b,base_fee=0)>=amt:
list_of_pairs.append((a,b))
break
return list_of_pairs

#we run the simulation of pickhardt payments and track all the results

n_pairs = 10
amount_list = [ 2**i for i in range(24,25)]
stat = {}

for a in amount_list:
stat[a] = { 'time_mcf': [], 'time_total': [], 'nfails': 0 , 'nsuccess': 0 }

# random_node_pairs(n_pairs)

with open(prefix+'.log','w') as flog:
for amt in amount_list:
for (A,B) in random_node_pairs(n_pairs,amt):
print("trying a payment of",amt,"sats from",A,"to",B)
print("trying a payment of",amt,"sats from",A,"to",B, file=flog)
payment_session.forget_information()
try:
time_mcf,time_total = payment_session.pickhardt_pay(A,B, amt,log_out=flog)
stat[amt]['time_mcf'].append( time_mcf )
stat[amt]['time_total'].append( time_total )
stat[amt]['nsuccess'] += 1
except:
stat[amt]['nfails'] +=1
continue

with open(prefix+'.json','w') as f:
json.dump(stat,f)
182 changes: 182 additions & 0 deletions c-documentation.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,182 @@
# C-API for Pickhardt Payments

## Overview

This document describes *Pickhardt payments* C library API.

## Primitive data types

Nodes ID are encoded as 33 bytes numbers.
```
typedef char[33] nodeID_t;
```

Channels ID are encoded as 8 bytes numbers.
```
typedef uint64_t channelID_t;
```

## Payment Session

A `paymentSession` is an object that contains all the information known about the Lightning Network
from the initial knowledge of the public channels and the accumulated information
resulting from successful and failed attempts to forward payments while the session is in use.
```
typedef struct paymentSession paymentSession;
```

To create and initialize a `paymentSession` the function `paymentSession_new` must be called.
This function returns a pointer to a newly allocated `paymentSession`.
This function returns `NULL` if the necessary memory couldn't be allocated.
```
paymentSession* paymentSession_new();
```

To cleanup and release the memory one needs to call `paymentSession_delete`.
This function does not fail.
```
void paymentSession_delete(paymentSession* s);
```

Internally a Session represents the Lightning-Network graph of nodes and channels.
Initially a newly created Session has no knowled of any channel nor nodes, so one has to provide
this information by means of the function `paymentSession_addChannel`.
This function returns an error code that could have the values:
`PAYMENTSESSION_SUCCESS` if no errors occurred or
`PAYMENTSESSION_BADALLOC` if this function failed to allocate the memory for the new channel.
```
int paymentSession_addChannel(paymentSession* s,
channelID_t short_channel_id,
nodeID_t Source,
nodeID_t Dest,
int64_t capacity,
int64_t fee_rate,
int64_t base_fee);
```

A channel can be removed by calling `paymentSession_rmChannel`.
This function returns an error code.
```
int paymentSession_rmChannel(paymentSession* s,
channelID_t short_channel_id,
nodeID_t Source,
nodeID_t Dest);
```

A Session can be used to compute a multipart-payment with maximum probability of success based on
the knowledge of the Lightning-Network topology, channels capacity and previous knowledge of the
state of the liquidity in the channels.
By calling `paymentSession_optimizedPayment` one obtains a pointer to a `multiPartPayment` object
that contains a multipart-payment canditate based on the `amount` requested to be forwarded between
nodes `Source` and `Dest`.
This function returns `NULL` if the `paymentSession_optimizedPayment` fails.
The variable `status` contains an error code to log the cause of the failure.
```
multiPartPayment* paymentSession_optimizedPayment(paymentSession* s,
nodeID_t Source,
nodeID_t Dest,
int64_t amount,
char* status);
```

Once an onion is sent to the Lightning-Network from the success or failure of this attempt we can
update our knowledge of the liquidity of the channels involved in that onion. That information can
be used by the Session to compute future payments attempts.
To update the knowledge of the Network one must call `paymentSession_updateKnowledge`.
This function returns an error code.
```
int paymentSession_updateKnowledge(paymentSession* s,
int64_t short_channel_id,
nodeID_t Source,
nodeID_t Dest,
int64_t amount,
char status);
```

The accumulated knowledge in the Session can be erased by calling
`paymentSession_forgetInformation`.
This function returns an error code.
```
int paymentSession_forgetInformation(paymentSession* s);
```

## Multipart Payments

The function `paymentSession_optimizedPayment` produces a multipart payment (MPP) based on the
knowledge accumulated in the Session in use. The object that implements the concept of that MPP is
the `multiPartPayment`. We do not expose a constructor for this object in the public interface,
because it will always represent the result of an `paymentSession_optimizedPayment` and nothing
else. The `multiPartPayment` exposes an interface to query information about the MPP that the
Session has computed, therefore all of the `multiPartPayment_*` functions, except for the
destructor, do not modify the state of the MPP, the `multiPartPayment` is read-only.
```
typedef struct multiPartPayment multiPartPayment;
```

Once a `multiPartPayment` object is no longer used, it can be freed by calling
`multiPartPayment_delete`. This function never fails.
```
void multiPartPayment_delete(multiPartPayment* mpp);
```

`multiPartPayment` can be queried to get the information about the MPP information they encode.
To query the amount of satoshis this payment sends one calls `multiPartPayment_amount`.
This function never fails.
```
int64_t multiPartPayment_amount(const multiPartPayment* mpp);
```

To query the source and the destination of the payment the functions
`multiPartPayment_source` and `multiPartPayment_destination` are called respectively.
These functions never fail.
```
nodeID_t multiPartPayment_source(const multiPartPayment* mpp);
```
```
nodeID_t multiPartPayment_destination(const multiPartPayment* mpp);
```

Each MPP consist of a list of paths in the channels/nodes graph.
By calling `multiPartPayment_numParts` one can query for the number of paths that constitute the
MPP.
This functions never fails.
```
uint64_t multiPartPayment_numParts(const multiPartPayment* mpp);
```

By calling `multiPartPayment_pathLengths` one can query the length of the individual paths that make
the MPP. The pointer `lengths` must be previously allocated to hold at least as many paths as the
MPP.
This function never fails.
```
void multiPartPayment_pathLengths(const multiPartPayment* mpp,
uint64_t* lengths);
```

Similar to the `multiPartPayment_pathLengths`, the function `multiPartPayment_pathValues` queries
the MPP to obtain the amount of satoshis commited to each individual path.
The pointer `values` must be previously allocated to hold at least as many paths as the
MPP.
This function never fails.
```
void multiPartPayment_pathValues(const multiPartPayment* mpp,
int64_t* values);
```

The function `multiPartPayment_pathChannels` writes the list of channels for every path in the MPP
to the input array `short_channel_id`, and for each of these channels a value of the `orientation`
is set either to `CHANNEL_FORWARD` or `CHANNEL_BACKWARD` to indicate the direction the liquidity is
transfered. If the satoshis are transfered from a node with lowest lexicographical order to the
other then the `orientation` is set to `CHANNEL_FORWARD`, otherwise it is `CHANNEL_BACKWARD`.
This function never fails.
```
void multiPartPayment_pathChannels(const multiPartPayment* mpp,
int64_t* short_channel_id,
char* orientation);
```

## Example

```
// TODO
```
3 changes: 2 additions & 1 deletion examples/basicexample.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
#sequentially
payment_session = SyncSimulatedPaymentSession(oracle_lightning_network,
uncertainty_network,
mu=0,base=0,
prune_network=False)

#we need to make sure we forget all learnt information on the Uncertainty Nework
Expand All @@ -29,4 +30,4 @@
C_OTTO = "027ce055380348d7812d2ae7745701c9f93e70c1adeb2657f053f91df4f2843c71"
tested_amount = 10_000_000 #10 million sats

payment_session.pickhardt_pay(RENE, C_OTTO, tested_amount, mu=0, base=0)
payment_session.pickhardt_pay(RENE, C_OTTO, tested_amount)
11 changes: 11 additions & 0 deletions meson.build
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
project('pickhardtpayments','cpp','c',
default_options : ['cpp_std=c++17',
'warning_level=3',
'optimization=3'],
version: '0.0.0')


libMCF_proj = subproject('MinCostFlow')
libMCF_dep = libMCF_proj.get_variable('dep_mincostflow')
libMCF = libMCF_proj.get_variable('libmincostflow')

Loading