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.
- Returns
the amount that was tried to send in this Attempt
- Return type
int
- property path: List[Channel.Channel]
Returns the path of the attempt.
- Returns
the list of Channels that the path consists of
- Return type
list[Channel]
- property probability: float
Returns estimated success probability before the attempt
- Returns
estimated success probability before the attempt
- Return type
float
- property routing_fee: int
Returns the accrued routing fee in msat requested for this path.
- Returns
accrued routing fees for this attempt in msat
- Return type
int
- property status: Attempt.AttemptStatus
Returns the status of the attempt.
- Returns
returns the state of the attempt
- Return type
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
- remove_channels_with_no_return_channels()
- 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
- Parameters
attempt – the attempt that is probed
- Type
- Returns
did sending the onion succeed?
- Return type
Boolean
- Returns
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].
- Parameters
respect_inflight – are in_flight amounts considered when calculating the conditional capacity?
- entropy()
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.
- forget_information()
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.
- Returns
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
- linearized_integer_routing_unit_cost()
Note that the ppm is natively an integer and can just be taken as a unit cost for the solver
- linearized_integer_uncertainty_unit_cost(use_conditional_capacity=True)
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
- Parameters
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?
- Type
int
- Type
- Type
bool