pickhardtpayments library

Attempt module

class Attempt.Attempt(path: List[Channel.Channel], amount: int = 0)

Bases: object

When sending an amount of sats from sender to receiver, a payment is usually split up and sent across several paths, to increase the probability of being successfully delivered. Each of these paths is referred to as an Attempt. Attempts thus belong to Payments. Central elements of an Attempt are the list of UncertaintyChannels and amount in sats to be sent along this path.

When an Attempt is instantiated, the given amount is allocated to the in_flight amount in the channels of the path in the UncertaintyNetwork and the AttemptStatus is set to PLANNED.

Each attempt is then probed with send_onion on the oracle network to find out, if the amount can be sent successfully from sender to receiver. If successful, the AttemptStatus is set to INFLIGHT. The in_flight amounts remain on the UncertaintyNetwork. Nothing is done by the Attempt instance. If successful, the AttemptStatus is set to FAILED. Then the in_flight amounts are removed from the channels on the UncertaintyNetwork.

property amount: int

Returns the amount of the attempt.


the amount that was tried to send in this Attempt

Return type


property path: List[Channel.Channel]

Returns the path of the attempt.


the list of Channels that the path consists of

Return type


property probability: float

Returns estimated success probability before the attempt


estimated success probability before the attempt

Return type


property routing_fee: int

Returns the accrued routing fee in msat requested for this path.


accrued routing fees for this attempt in msat

Return type


property status: Attempt.AttemptStatus

Returns the status of the attempt.


returns the state of the attempt

Return type


class Attempt.AttemptStatus(value)

Bases: enum.Enum

An enumeration.


Channel module

class Channel.Channel(cln_jsn)

Bases: object

Stores the public available information of a channel.

The Channel Class is intended to be read only and internally stores the data from core lightning’s lightning-cli listchannels command as a json. If you retrieve data from a different implementation I suggest to overload the constructor and transform the information into the given json format

property base_fee
property capacity
property cln_jsn
property cltv_delta
property dest
property flags
property htlc_max_msat
property htlc_min_msat
property is_active
property is_announced
property ppm
property short_channel_id
property src
class Channel.ChannelFields

Bases: object

These are the values describing public data about channels that is either available via gossip or via the Bitcoin Blockchain. Their format is taken from the core lighting API. If you use a different implementation I suggest to write a wrapper around the ChannelFields and Channel class

ACTIVE = 'active'
ANNOUNCED = 'public'
BASE_FEE_MSAT = 'base_fee_millisatoshi'
CAP = 'satoshis'
CLTV = 'delay'
DEST = 'destination'
FEATURES = 'features'
FEE_RATE = 'fee_per_millionth'
FLAGS = 'channel_flags'
HTLC_MAXIMUM_MSAT = 'htlc_maximum_msat'
HTLC_MINIMUM_MSAT = 'htlc_minimum_msat'
LAST_UPDATE = 'last_update'
SHORT_CHANNEL_ID = 'short_channel_id'
SRC = 'source'

ChannelGraph module

class ChannelGraph.ChannelGraph(lightning_cli_listchannels_json_file: str)

Bases: object

Represents the public information about the Lightning Network that we see from Gossip and the Bitcoin Blockchain.

The channels of the Channel Graph are directed and identified uniquely by a triple consisting of (source_node_id, destination_node_id, short_channel_id). This allows the ChannelGraph to also contain parallel channels.

get_channel(src: str, dest: str, short_channel_id: str)

returns a specific channel object identified by source, destination and short_channel_id from the ChannelGraph

property network

OracleChannel module

class OracleChannel.OracleChannel(channel: Channel.Channel, actual_liquidity: Optional[int] = None)

Bases: Channel.Channel

An OracleChannel us used in experiments and Simulations to form the (Oracle)LightningNetwork.

It contains a ground truth about the Liquidity of a channel

property actual_liquidity

Tells us the actual liquidity according to the oracle.

This is useful for experiments but must of course not be used in routing and is also not available if mainnet remote channels are being used.

can_forward(amt: int)

check if the oracle channel can forward a certain amount

property in_flight

Tells us the actual liquidity according to the oracle.

This is useful for experiments but must of course not be used in routing and is also not available if mainnet remote channels are being used.

OracleLightningNetwork module

class OracleLightningNetwork.OracleLightningNetwork(channel_graph: ChannelGraph.ChannelGraph)

Bases: ChannelGraph.ChannelGraph

allocate_amount_as_inflight_on_path(attempt: Attempt.Attempt)

allocates amt as in_flights to all channels of the path

property network
send_onion(attempt: Attempt.Attempt)

Probes the oracle network if the amount of satoshis for this attempt can be sent through the given path in the attempt. If successful, then inflight amounts are placed on the respective oracle channels as well as uncertainty channels, and the Attempt is set to INFLIGHT. If not successful, then the status of the Attempt is set to FAILED and the failing channel is returned


attempt – the attempt that is probed




did sending the onion succeed?

Return type



if sending the onion failed, at which channel did it fail

Return type

UncertaintyChannel or None

settle_attempt(attempt: Attempt.Attempt)

receives a payment attempt and adjusts the balances of the OracleChannels and its reverse channels along the path.

settle_attempt should only be called after all send_onions for a payment terminated successfully!

theoretical_maximum_payable_amount(source: str, destination: str, base_fee: int = 0)

Uses the information from the oracle to compute the min-cut between source and destination

This is only useful for experiments and simulations if one wants to know what would be possible to actually send before starting the payment loop

Payment module

SyncSimulatedPaymentSession module

UncertaintyChannel module

class UncertaintyChannel.UncertaintyChannel(channel: Channel.Channel)

Bases: Channel.Channel

The channel class contains basic information of a channel that will be used to create the UncertaintyNetwork.

Since we optimize for reliability via a probability estimate for liquidity that is based on the capacity of the channel, the class contains the capacity as seen in the funding tx output.

As we also optimize for fees and want to be able to compute the fees of a flow ,the class contains information for the fee rate (ppm) and the base_fee (base).

Most importantly the class stores our belief about the liquidity information of a channel. This is done by reducing the uncertainty interval from [0,`capacity`] to [min_liquidity, max_liquidity]. Additionally, we need to know how many sats we currently have allocated via outstanding onions to the channel which is stored in in_flight.

The most important API call is the get_piecewise_linearized_costs function that computes the piecewise linearized cost for a channel rising from uncertainty as well as routing fees.

MAX_CHANNEL_SIZE = 15000000000
TOTAL_NUMBER_OF_SATS = 2100000000000000
allocate_inflights(amt: int)

assign or remove amount that is assigned to be in_flight.

combined_linearized_unit_cost(mu: int = 1)

Builds the weighted sum between our two unit costs.

Not being used in the code. Just here to describe the theory.

property conditional_capacity

Conditional Capacity is the capacity in the channel after we applied our knowledge about minimum liquidity and maximum liquidity. This is done by reducing the uncertainty interval from [0,`capacity`] to [min_liquidity, max_liquidity].


respect_inflight – are in_flight amounts considered when calculating the conditional capacity?


returns the uncertainty that we have about the channel

this respects our belief about the channel’s liquidity and thus is just the log of the conditional capacity.

FIXME: This respects inflight information? I assume it shouldn’t.


resets the information that we believe to have about the channel.

get_piecewise_linearized_costs(number_of_pieces: int = 5, mu: int = 1)
property in_flight

The amount currently allocated for potential payments through this channel. This resembles the concept of an HTLC.


in_flight amount on the UncertaintyChannel

learn_n_bits(oracle: OracleLightningNetwork.OracleLightningNetwork, n: int = 1)

conducts n probes of channel via binary search starting from our belief

This of course only learns n bits if we use a uniform success probability as a prior thus this method will not work if a different prior success probability is assumed


Note that the ppm is natively an integer and can just be taken as a unit cost for the solver


estimates the linearized integer uncertainty cost

FIXME: Instead of using the maximum capacity on the network it just assumes 150BTC to be max

linearized_routing_cost_msat(amt: int)

Linearizing the routing cost by ignoring the base fee.

Note that one can still include channels with small base fees to the computation the base will just be excluded in the computation and has to be paid later anyway. If as developers we go down this road this will allow routing node operators to game us with the base fee thus it seems reasonable in routing computations to just ignore channels that charge a base fee.

There are other ways of achieving this by overestimating the fee as ZmnSCPxj suggested at: https://lists.linuxfoundation.org/pipermail/lightning-dev/2021-August/003206.html

linearized_uncertainty_cost(amt: int)

the linearized uncertainty cost is just amt/(capacity+1). Using this is most likely not what one wants as this tends to saturate channels. The API is included to explain the theory.

Warning: This API does not respect our belief about the channels liquidity or allocated in_flight HTLCs

property max_liquidity
property min_liquidity
routing_cost_msat(amt: int)

Routing cost a routing node will earn to forward a payment along this channel in msats

success_probability(amt: Optional[int] = None)

returns the estimated success probability for a payment based on our belief about the channel using a uniform distribution.

While this is the core of the theory it is only used for evaluation and not for the actual min cost flow computation as we linearize this to an integer unit cost

In particular the conditional probability P(X>=a | min_liquidity < X < max_liquidity) is computed based on our belief also respecting how many satoshis we have currently outstanding and allocated. Thus it is possible that testing for the amt=0 that the success probability is zero and in particular not 1.

It also accounts for the number of satoshis we have already outstanding but have not received information about

FIXME: Potentially test other prior distributions like mixedmodels where most funds are on one side of the channel

uncertainty_cost(amt: int)

Returns the uncertainty cost associated to sending the amount amt respecting our current belief about the channel’s liquidity and the in_flight HTLC’s that we have allocated and outstanding.

update_knowledge(amount: int, return_channel, probing_successful: bool)

Updates our knowledge about the channel after probing with ‘send_onion’.

‘send_onion’ already placed the amount as inflight on the UncertaintyChannel. Because we currently don’t konw if the payment will be settled successfully, we can only incorporate current knowledge. Any change because of settlement will have to be considered at another location.

What can we learn from a successfully sent onion? * If we knew that there was a minimum liquidity greater than the in_flights on the channel, than this is fine.

Nothing done.

  • If there was no knowledge about the minimum liquidity, then we now know that it is at least the in_flights.

  • We learnt nothing about the maximum liquidity in this channel.

  • Regarding the return channel: We now know that the confirmed inflight amount of this channel cannot be in the return channel. Thus, maximum liquidity needs to be lower than capacity minus in_flight amount.

What can we learn from a failing onion? * In the channel, the maximum liquidity needs to be lower than the in_flights.

We have no further information about the minimum amount that can be sent.

  • If this amount fails, then we know that at least this amount of liquidity is in the return channel.

# This API works only if we have an Oracle that allows to ask the actual liquidity of a channel # In mainnet Lightning our oracle will not work on a per_channel level. This will change the data # flow. Here for simplicity of the simulation we make use of the Oracle on a per channel level

  • amount – The amount that went through when probing the channel, usually in_flight amount plus attempt amount!

  • return_channel – The UncertaintyChannel in the return direction, needed to adjust knowledge there.

  • probing_successful – Could the amount be sent?







UncertaintyNetwork module