Skip to content

Commit

Permalink
Mono-hop unidirectional payments
Browse files Browse the repository at this point in the history
  • Loading branch information
canndrew authored and knocte committed Jun 15, 2021
1 parent bf1d1b2 commit 965aad9
Show file tree
Hide file tree
Showing 7 changed files with 213 additions and 7 deletions.
91 changes: 91 additions & 0 deletions src/DotNetLightning.Core/Channel/Channel.fs
Original file line number Diff line number Diff line change
Expand Up @@ -583,6 +583,58 @@ and Channel = {
return! Error <| OnceConfirmedFundingTxHasBecomeUnconfirmed(height, depth)
}

member self.MonoHopUnidirectionalPayment (amount: LNMoney)
: Result<Channel * MonoHopUnidirectionalPaymentMsg, ChannelError> = result {
if self.NegotiatingState.HasEnteredShutdown() then
return!
sprintf
"Could not send mono-hop unidirectional payment of amount %A since shutdown is already \
in progress." amount
|> apiMisuse
else
let payment: MonoHopUnidirectionalPaymentMsg = {
ChannelId = self.SavedChannelState.StaticChannelConfig.ChannelId()
Amount = amount
}
let commitments1 = self.Commitments.AddLocalProposal(payment)

let! remoteNextCommitInfo =
self.RemoteNextCommitInfoIfFundingLockedNormal "MonoHopUniDirectionalPayment"
let remoteCommit1 =
match remoteNextCommitInfo with
| Waiting nextRemoteCommit -> nextRemoteCommit
| Revoked _info -> self.SavedChannelState.RemoteCommit
let! reduced = remoteCommit1.Spec.Reduce(self.SavedChannelState.RemoteChanges.ACKed, commitments1.ProposedLocalChanges) |> expectTransactionError
do!
Validation.checkOurMonoHopUnidirectionalPaymentIsAcceptableWithCurrentSpec
reduced
self.SavedChannelState.StaticChannelConfig
payment
let channel = {
self with
Commitments = commitments1
}
return channel, payment
}

member self.ApplyMonoHopUnidirectionalPayment (msg: MonoHopUnidirectionalPaymentMsg)
: Result<Channel, ChannelError> = result {
let commitments1 = self.Commitments.AddRemoteProposal(msg)
let! reduced =
self.SavedChannelState.LocalCommit.Spec.Reduce
(self.SavedChannelState.LocalChanges.ACKed, commitments1.ProposedRemoteChanges)
|> expectTransactionError
do!
Validation.checkTheirMonoHopUnidirectionalPaymentIsAcceptableWithCurrentSpec
reduced
self.SavedChannelState.StaticChannelConfig
msg
return {
self with
Commitments = commitments1
}
}

member self.AddHTLC (op: OperationAddHTLC)
: Result<Channel * UpdateAddHTLCMsg, ChannelError> = result {
if self.NegotiatingState.HasEnteredShutdown() then
Expand Down Expand Up @@ -1116,6 +1168,45 @@ and Channel = {
(not self.SavedChannelState.LocalChanges.ACKed.IsEmpty)
|| (not self.Commitments.ProposedRemoteChanges.IsEmpty)

member self.SpendableBalance (): LNMoney =
let remoteParams = self.SavedChannelState.StaticChannelConfig.RemoteParams
let remoteCommit =
match self.RemoteNextCommitInfo with
| Some (RemoteNextCommitInfo.Waiting nextRemoteCommit) -> nextRemoteCommit
| Some (RemoteNextCommitInfo.Revoked _info) -> self.SavedChannelState.RemoteCommit
// TODO: This could return a proper error, or report the full balance
| None -> failwith "funding is not locked"
let reducedRes =
remoteCommit.Spec.Reduce(
self.SavedChannelState.RemoteChanges.ACKed,
self.Commitments.ProposedLocalChanges
)
let reduced =
match reducedRes with
| Error err ->
failwithf
"reducing commit failed even though we have not proposed any changes\
error: %A"
err
| Ok reduced -> reduced
let fees =
if self.SavedChannelState.StaticChannelConfig.IsFunder then
Transactions.commitTxFee remoteParams.DustLimitSatoshis reduced
|> LNMoney.FromMoney
else
LNMoney.Zero
let channelReserve =
remoteParams.ChannelReserveSatoshis
|> LNMoney.FromMoney
let totalBalance = reduced.ToRemote
let untrimmedSpendableBalance = totalBalance - channelReserve - fees
let dustLimit =
remoteParams.DustLimitSatoshis
|> LNMoney.FromMoney
let untrimmedMax = LNMoney.Min(untrimmedSpendableBalance, dustLimit)
let spendableBalance = LNMoney.Max(untrimmedMax, untrimmedSpendableBalance)
spendableBalance

member private self.sendCommit (remoteNextCommitInfo: RemoteNextCommitInfo)
: Result<CommitmentSignedMsg * Channel, ChannelError> =
let channelPrivKeys = self.ChannelPrivKeys
Expand Down
53 changes: 50 additions & 3 deletions src/DotNetLightning.Core/Channel/ChannelError.fs
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ type ChannelError =
| InvalidFundingLocked of InvalidFundingLockedError
| InvalidOpenChannel of InvalidOpenChannelError
| InvalidAcceptChannel of InvalidAcceptChannelError
| InvalidMonoHopUnidirectionalPayment of InvalidMonoHopUnidirectionalPaymentError
| InvalidUpdateAddHTLC of InvalidUpdateAddHTLCError
| InvalidRevokeAndACK of InvalidRevokeAndACKError
| InvalidUpdateFee of InvalidUpdateFeeError
Expand Down Expand Up @@ -76,6 +77,7 @@ type ChannelError =
| InvalidFundingLocked _ -> DistrustPeer
| InvalidOpenChannel _ -> DistrustPeer
| InvalidAcceptChannel _ -> DistrustPeer
| InvalidMonoHopUnidirectionalPayment _ -> Close
| InvalidUpdateAddHTLC _ -> Close
| InvalidRevokeAndACK _ -> Close
| InvalidUpdateFee _ -> Close
Expand Down Expand Up @@ -143,6 +145,8 @@ type ChannelError =
"Received commitment signed when we have not pending changes"
| InvalidAcceptChannel invalidAcceptChannelError ->
sprintf "Invalid accept_channel msg: %s" invalidAcceptChannelError.Message
| InvalidMonoHopUnidirectionalPayment invalidMonohopUnidirectionalPaymentError ->
sprintf "Invalid mono hop unidrectional payment: %s" invalidMonohopUnidirectionalPaymentError.Message
| InvalidUpdateAddHTLC invalidUpdateAddHTLCError ->
sprintf "Invalid udpate_add_htlc msg: %s" invalidUpdateAddHTLCError.Message
| InvalidRevokeAndACK invalidRevokeAndACKError ->
Expand Down Expand Up @@ -207,7 +211,18 @@ and InvalidAcceptChannelError = {
}
member this.Message =
String.concat "; " this.Errors

and InvalidMonoHopUnidirectionalPaymentError = {
NetworkMsg: MonoHopUnidirectionalPaymentMsg
Errors: string list
}
with
static member Create msg e = {
NetworkMsg = msg
Errors = e
}
member this.Message =
String.concat "; " this.Errors

and InvalidUpdateAddHTLCError = {
NetworkMsg: UpdateAddHTLCMsg
Errors: string list
Expand Down Expand Up @@ -554,7 +569,23 @@ module internal AcceptChannelMsgValidation =
let check7 = check (msg.MinimumDepth.Value) (>) (config.MaxMinimumDepth.Value |> uint32) "We consider the minimum depth (%A) to be unreasonably large. Our max minimum depth is (%A)"

(check1 |> Validation.ofResult) *^> check2 *^> check3 *^> check4 *^> check5 *^> check6 *^> check7


module UpdateMonoHopUnidirectionalPaymentWithContext =
let internal checkWeHaveSufficientFunds (staticChannelConfig: StaticChannelConfig) (currentSpec) =
let fees =
if staticChannelConfig.IsFunder then
Transactions.commitTxFee staticChannelConfig.RemoteParams.DustLimitSatoshis currentSpec
else
Money.Zero
let missing = currentSpec.ToRemote.ToMoney() - staticChannelConfig.RemoteParams.ChannelReserveSatoshis - fees
if missing < Money.Zero then
sprintf "We don't have sufficient funds to send mono-hop unidirectional payment. current to_remote amount is: %A. Remote Channel Reserve is: %A. and fee is %A"
(currentSpec.ToRemote.ToMoney())
staticChannelConfig.RemoteParams.ChannelReserveSatoshis
fees
|> Error
else
Ok()

module UpdateAddHTLCValidation =
let internal checkExpiryIsNotPast (current: BlockHeight) (expiry) =
Expand All @@ -569,7 +600,23 @@ module UpdateAddHTLCValidation =
let internal checkAmountIsLargerThanMinimum (htlcMinimum: LNMoney) (amount) =
check (amount) (<) (htlcMinimum) "htlc value (%A) is too small. must be greater or equal to %A"


module internal MonoHopUnidirectionalPaymentValidationWithContext =
let checkWeHaveSufficientFunds (staticChannelConfig: StaticChannelConfig) (currentSpec) =
let fees =
if staticChannelConfig.IsFunder then
Transactions.commitTxFee staticChannelConfig.RemoteParams.DustLimitSatoshis currentSpec
else
Money.Zero
let missing = currentSpec.ToRemote.ToMoney() - staticChannelConfig.RemoteParams.ChannelReserveSatoshis - fees
if missing < Money.Zero then
sprintf "We don't have sufficient funds to send mono-hop unidirectional payment. current to_remote amount is: %A. Remote Channel Reserve is: %A. and fee is %A"
(currentSpec.ToRemote.ToMoney())
staticChannelConfig.RemoteParams.ChannelReserveSatoshis
fees
|> Error
else
Ok()

module internal UpdateAddHTLCValidationWithContext =
let checkLessThanHTLCValueInFlightLimit (currentSpec: CommitmentSpec) (limit) (add: UpdateAddHTLCMsg) =
let outgoingValue =
Expand Down
17 changes: 17 additions & 0 deletions src/DotNetLightning.Core/Channel/ChannelValidation.fs
Original file line number Diff line number Diff line change
Expand Up @@ -214,6 +214,23 @@ module internal Validation =
*> AcceptChannelMsgValidation.checkConfigPermits channelHandshakeLimits acceptChannelMsg
|> Result.mapError(InvalidAcceptChannelError.Create acceptChannelMsg >> InvalidAcceptChannel)

let checkOurMonoHopUnidirectionalPaymentIsAcceptableWithCurrentSpec (currentSpec)
(staticChannelConfig: StaticChannelConfig)
(payment: MonoHopUnidirectionalPaymentMsg) =
MonoHopUnidirectionalPaymentValidationWithContext.checkWeHaveSufficientFunds
staticChannelConfig
currentSpec
|> Validation.ofResult
|> Result.mapError(fun errs -> InvalidMonoHopUnidirectionalPayment { NetworkMsg = payment; Errors = errs })

let checkTheirMonoHopUnidirectionalPaymentIsAcceptableWithCurrentSpec (currentSpec)
(staticChannelConfig: StaticChannelConfig)
(payment: MonoHopUnidirectionalPaymentMsg) =
MonoHopUnidirectionalPaymentValidationWithContext.checkWeHaveSufficientFunds
staticChannelConfig
currentSpec
|> Validation.ofResult
|> Result.mapError(fun errs -> InvalidMonoHopUnidirectionalPayment { NetworkMsg = payment; Errors = errs })

let checkOperationAddHTLC (remoteParams: RemoteParams) (op: OperationAddHTLC) =
Validation.ofResult(UpdateAddHTLCValidation.checkExpiryIsNotPast op.CurrentHeight op.Expiry)
Expand Down
7 changes: 5 additions & 2 deletions src/DotNetLightning.Core/Crypto/ShaChain.fs
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
namespace DotNetLightning.Crypto

open System

type Node = {
Value: byte[]
Height: int32
Expand All @@ -16,8 +18,9 @@ module ShaChain =
let flip (_input: byte[]) (_index: uint64): byte[] =
failwith "Not implemented: ShaChain::flip"

let addHash (_receiver: ShaChain) (_hash: byte[]) (_index: uint64) =
failwith "Not implemented: ShaChain::addHash"
let addHash (receiver: ShaChain) (_hash: byte[]) (_index: uint64) =
Console.WriteLine("WARNING: Not implemented: ShaChain::addHash")
receiver

let getHash (_receiver: ShaChain)(_index: uint64) =
failwith "Not implemented: ShaChain::getHash"
Expand Down
24 changes: 23 additions & 1 deletion src/DotNetLightning.Core/Serialization/Msgs/Msgs.fs
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,8 @@ module internal TypeFlag =
let ReplyChannelRange = 264us
[<Literal>]
let GossipTimestampFilter = 265us
[<Literal>]
let MonoHopUnidirectionalPayment = 42198us

type ILightningMsg = interface end
type ISetupMsg = inherit ILightningMsg
Expand Down Expand Up @@ -195,6 +197,8 @@ module ILightningSerializable =
deserialize<ReplyChannelRangeMsg>(ls) :> ILightningMsg
| TypeFlag.GossipTimestampFilter ->
deserialize<GossipTimestampFilterMsg>(ls) :> ILightningMsg
| TypeFlag.MonoHopUnidirectionalPayment ->
deserialize<MonoHopUnidirectionalPaymentMsg>(ls) :> ILightningMsg
| x ->
raise <| FormatException(sprintf "Unknown message type %d" x)
let serializeWithFlags (ls: LightningWriterStream) (data: ILightningMsg) =
Expand Down Expand Up @@ -283,6 +287,9 @@ module ILightningSerializable =
| :? GossipTimestampFilterMsg as d ->
ls.Write(TypeFlag.GossipTimestampFilter, false)
(d :> ILightningSerializable<GossipTimestampFilterMsg>).Serialize(ls)
| :? MonoHopUnidirectionalPaymentMsg as d ->
ls.Write(TypeFlag.MonoHopUnidirectionalPayment, false)
(d :> ILightningSerializable<MonoHopUnidirectionalPaymentMsg>).Serialize(ls)
| x -> failwithf "%A is not known lightning message. This should never happen" x

module LightningMsg =
Expand Down Expand Up @@ -1592,4 +1599,19 @@ type GossipTimestampFilterMsg = {
ls.Write(this.ChainHash, true)
ls.Write(this.FirstTimestamp, false)
ls.Write(this.TimestampRange, false)


[<CLIMutable>]
type MonoHopUnidirectionalPaymentMsg = {
mutable ChannelId: ChannelId
mutable Amount: LNMoney
}
with
interface IHTLCMsg
interface IUpdateMsg
interface ILightningSerializable<MonoHopUnidirectionalPaymentMsg> with
member this.Deserialize(ls) =
this.ChannelId <- ls.ReadUInt256(true) |> ChannelId
this.Amount <- ls.ReadUInt64(false) |> LNMoney.MilliSatoshis
member this.Serialize(ls) =
ls.Write(this.ChannelId.Value.ToBytes())
ls.Write(this.Amount.MilliSatoshi, false)
26 changes: 25 additions & 1 deletion src/DotNetLightning.Core/Transactions/CommitmentSpec.fs
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,12 @@ type CommitmentSpec = {
(fun cs -> cs.ToRemote),
(fun v cs -> { cs with ToRemote = v })

member internal this.AddOutgoingMonoHopUnidirectionalPayment(update: MonoHopUnidirectionalPaymentMsg) =
{ this with ToLocal = this.ToLocal - update.Amount; ToRemote = this.ToRemote + update.Amount }

member internal this.AddIncomingMonoHopUnidirectionalPayment(update: MonoHopUnidirectionalPaymentMsg) =
{ this with ToLocal = this.ToLocal + update.Amount; ToRemote = this.ToRemote - update.Amount }

member internal this.AddOutgoingHTLC(update: UpdateAddHTLCMsg) =
{ this with ToLocal = (this.ToLocal - update.Amount); OutgoingHTLCs = this.OutgoingHTLCs.Add(update.HTLCId, update)}

Expand Down Expand Up @@ -73,14 +79,32 @@ type CommitmentSpec = {
UnknownHTLC htlcId |> Error

member internal this.Reduce(localChanges: #IUpdateMsg list, remoteChanges: #IUpdateMsg list) =
let specMonoHopUnidirectionalPaymentLocal =
localChanges
|> List.fold(fun (acc: CommitmentSpec) updateMsg ->
match box updateMsg with
| :? MonoHopUnidirectionalPaymentMsg as u -> acc.AddOutgoingMonoHopUnidirectionalPayment u
| _ -> acc
)
this

let specMonoHopUnidirectionalPaymentRemote =
remoteChanges
|> List.fold(fun (acc: CommitmentSpec) updateMsg ->
match box updateMsg with
| :? MonoHopUnidirectionalPaymentMsg as u -> acc.AddIncomingMonoHopUnidirectionalPayment u
| _ -> acc
)
specMonoHopUnidirectionalPaymentLocal

let spec1 =
localChanges
|> List.fold(fun (acc: CommitmentSpec) updateMsg ->
match box updateMsg with
| :? UpdateAddHTLCMsg as u -> acc.AddOutgoingHTLC u
| _ -> acc
)
this
specMonoHopUnidirectionalPaymentRemote

let spec2 =
remoteChanges
Expand Down
2 changes: 2 additions & 0 deletions src/DotNetLightning.Core/Utils/LNMoney.fs
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,8 @@ type LNMoney = | LNMoney of int64 with
let satoshi = Checked.op_Multiply (amount) (decimal lnUnit)
LNMoney(Checked.int64 satoshi)

static member FromMoney (money: Money) =
LNMoney.Satoshis money.Satoshi

static member Coins(coins: decimal) =
LNMoney.FromUnit(coins * (decimal LNMoneyUnit.BTC), LNMoneyUnit.MilliSatoshi)
Expand Down

0 comments on commit 965aad9

Please sign in to comment.