Skip to content

Commit 965aad9

Browse files
canndrewknocte
authored andcommitted
Mono-hop unidirectional payments
1 parent bf1d1b2 commit 965aad9

File tree

7 files changed

+213
-7
lines changed

7 files changed

+213
-7
lines changed

src/DotNetLightning.Core/Channel/Channel.fs

Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -583,6 +583,58 @@ and Channel = {
583583
return! Error <| OnceConfirmedFundingTxHasBecomeUnconfirmed(height, depth)
584584
}
585585

586+
member self.MonoHopUnidirectionalPayment (amount: LNMoney)
587+
: Result<Channel * MonoHopUnidirectionalPaymentMsg, ChannelError> = result {
588+
if self.NegotiatingState.HasEnteredShutdown() then
589+
return!
590+
sprintf
591+
"Could not send mono-hop unidirectional payment of amount %A since shutdown is already \
592+
in progress." amount
593+
|> apiMisuse
594+
else
595+
let payment: MonoHopUnidirectionalPaymentMsg = {
596+
ChannelId = self.SavedChannelState.StaticChannelConfig.ChannelId()
597+
Amount = amount
598+
}
599+
let commitments1 = self.Commitments.AddLocalProposal(payment)
600+
601+
let! remoteNextCommitInfo =
602+
self.RemoteNextCommitInfoIfFundingLockedNormal "MonoHopUniDirectionalPayment"
603+
let remoteCommit1 =
604+
match remoteNextCommitInfo with
605+
| Waiting nextRemoteCommit -> nextRemoteCommit
606+
| Revoked _info -> self.SavedChannelState.RemoteCommit
607+
let! reduced = remoteCommit1.Spec.Reduce(self.SavedChannelState.RemoteChanges.ACKed, commitments1.ProposedLocalChanges) |> expectTransactionError
608+
do!
609+
Validation.checkOurMonoHopUnidirectionalPaymentIsAcceptableWithCurrentSpec
610+
reduced
611+
self.SavedChannelState.StaticChannelConfig
612+
payment
613+
let channel = {
614+
self with
615+
Commitments = commitments1
616+
}
617+
return channel, payment
618+
}
619+
620+
member self.ApplyMonoHopUnidirectionalPayment (msg: MonoHopUnidirectionalPaymentMsg)
621+
: Result<Channel, ChannelError> = result {
622+
let commitments1 = self.Commitments.AddRemoteProposal(msg)
623+
let! reduced =
624+
self.SavedChannelState.LocalCommit.Spec.Reduce
625+
(self.SavedChannelState.LocalChanges.ACKed, commitments1.ProposedRemoteChanges)
626+
|> expectTransactionError
627+
do!
628+
Validation.checkTheirMonoHopUnidirectionalPaymentIsAcceptableWithCurrentSpec
629+
reduced
630+
self.SavedChannelState.StaticChannelConfig
631+
msg
632+
return {
633+
self with
634+
Commitments = commitments1
635+
}
636+
}
637+
586638
member self.AddHTLC (op: OperationAddHTLC)
587639
: Result<Channel * UpdateAddHTLCMsg, ChannelError> = result {
588640
if self.NegotiatingState.HasEnteredShutdown() then
@@ -1116,6 +1168,45 @@ and Channel = {
11161168
(not self.SavedChannelState.LocalChanges.ACKed.IsEmpty)
11171169
|| (not self.Commitments.ProposedRemoteChanges.IsEmpty)
11181170

1171+
member self.SpendableBalance (): LNMoney =
1172+
let remoteParams = self.SavedChannelState.StaticChannelConfig.RemoteParams
1173+
let remoteCommit =
1174+
match self.RemoteNextCommitInfo with
1175+
| Some (RemoteNextCommitInfo.Waiting nextRemoteCommit) -> nextRemoteCommit
1176+
| Some (RemoteNextCommitInfo.Revoked _info) -> self.SavedChannelState.RemoteCommit
1177+
// TODO: This could return a proper error, or report the full balance
1178+
| None -> failwith "funding is not locked"
1179+
let reducedRes =
1180+
remoteCommit.Spec.Reduce(
1181+
self.SavedChannelState.RemoteChanges.ACKed,
1182+
self.Commitments.ProposedLocalChanges
1183+
)
1184+
let reduced =
1185+
match reducedRes with
1186+
| Error err ->
1187+
failwithf
1188+
"reducing commit failed even though we have not proposed any changes\
1189+
error: %A"
1190+
err
1191+
| Ok reduced -> reduced
1192+
let fees =
1193+
if self.SavedChannelState.StaticChannelConfig.IsFunder then
1194+
Transactions.commitTxFee remoteParams.DustLimitSatoshis reduced
1195+
|> LNMoney.FromMoney
1196+
else
1197+
LNMoney.Zero
1198+
let channelReserve =
1199+
remoteParams.ChannelReserveSatoshis
1200+
|> LNMoney.FromMoney
1201+
let totalBalance = reduced.ToRemote
1202+
let untrimmedSpendableBalance = totalBalance - channelReserve - fees
1203+
let dustLimit =
1204+
remoteParams.DustLimitSatoshis
1205+
|> LNMoney.FromMoney
1206+
let untrimmedMax = LNMoney.Min(untrimmedSpendableBalance, dustLimit)
1207+
let spendableBalance = LNMoney.Max(untrimmedMax, untrimmedSpendableBalance)
1208+
spendableBalance
1209+
11191210
member private self.sendCommit (remoteNextCommitInfo: RemoteNextCommitInfo)
11201211
: Result<CommitmentSignedMsg * Channel, ChannelError> =
11211212
let channelPrivKeys = self.ChannelPrivKeys

src/DotNetLightning.Core/Channel/ChannelError.fs

Lines changed: 50 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ type ChannelError =
3939
| InvalidFundingLocked of InvalidFundingLockedError
4040
| InvalidOpenChannel of InvalidOpenChannelError
4141
| InvalidAcceptChannel of InvalidAcceptChannelError
42+
| InvalidMonoHopUnidirectionalPayment of InvalidMonoHopUnidirectionalPaymentError
4243
| InvalidUpdateAddHTLC of InvalidUpdateAddHTLCError
4344
| InvalidRevokeAndACK of InvalidRevokeAndACKError
4445
| InvalidUpdateFee of InvalidUpdateFeeError
@@ -76,6 +77,7 @@ type ChannelError =
7677
| InvalidFundingLocked _ -> DistrustPeer
7778
| InvalidOpenChannel _ -> DistrustPeer
7879
| InvalidAcceptChannel _ -> DistrustPeer
80+
| InvalidMonoHopUnidirectionalPayment _ -> Close
7981
| InvalidUpdateAddHTLC _ -> Close
8082
| InvalidRevokeAndACK _ -> Close
8183
| InvalidUpdateFee _ -> Close
@@ -143,6 +145,8 @@ type ChannelError =
143145
"Received commitment signed when we have not pending changes"
144146
| InvalidAcceptChannel invalidAcceptChannelError ->
145147
sprintf "Invalid accept_channel msg: %s" invalidAcceptChannelError.Message
148+
| InvalidMonoHopUnidirectionalPayment invalidMonohopUnidirectionalPaymentError ->
149+
sprintf "Invalid mono hop unidrectional payment: %s" invalidMonohopUnidirectionalPaymentError.Message
146150
| InvalidUpdateAddHTLC invalidUpdateAddHTLCError ->
147151
sprintf "Invalid udpate_add_htlc msg: %s" invalidUpdateAddHTLCError.Message
148152
| InvalidRevokeAndACK invalidRevokeAndACKError ->
@@ -207,7 +211,18 @@ and InvalidAcceptChannelError = {
207211
}
208212
member this.Message =
209213
String.concat "; " this.Errors
210-
214+
and InvalidMonoHopUnidirectionalPaymentError = {
215+
NetworkMsg: MonoHopUnidirectionalPaymentMsg
216+
Errors: string list
217+
}
218+
with
219+
static member Create msg e = {
220+
NetworkMsg = msg
221+
Errors = e
222+
}
223+
member this.Message =
224+
String.concat "; " this.Errors
225+
211226
and InvalidUpdateAddHTLCError = {
212227
NetworkMsg: UpdateAddHTLCMsg
213228
Errors: string list
@@ -554,7 +569,23 @@ module internal AcceptChannelMsgValidation =
554569
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)"
555570

556571
(check1 |> Validation.ofResult) *^> check2 *^> check3 *^> check4 *^> check5 *^> check6 *^> check7
557-
572+
573+
module UpdateMonoHopUnidirectionalPaymentWithContext =
574+
let internal checkWeHaveSufficientFunds (staticChannelConfig: StaticChannelConfig) (currentSpec) =
575+
let fees =
576+
if staticChannelConfig.IsFunder then
577+
Transactions.commitTxFee staticChannelConfig.RemoteParams.DustLimitSatoshis currentSpec
578+
else
579+
Money.Zero
580+
let missing = currentSpec.ToRemote.ToMoney() - staticChannelConfig.RemoteParams.ChannelReserveSatoshis - fees
581+
if missing < Money.Zero then
582+
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"
583+
(currentSpec.ToRemote.ToMoney())
584+
staticChannelConfig.RemoteParams.ChannelReserveSatoshis
585+
fees
586+
|> Error
587+
else
588+
Ok()
558589

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

572-
603+
module internal MonoHopUnidirectionalPaymentValidationWithContext =
604+
let checkWeHaveSufficientFunds (staticChannelConfig: StaticChannelConfig) (currentSpec) =
605+
let fees =
606+
if staticChannelConfig.IsFunder then
607+
Transactions.commitTxFee staticChannelConfig.RemoteParams.DustLimitSatoshis currentSpec
608+
else
609+
Money.Zero
610+
let missing = currentSpec.ToRemote.ToMoney() - staticChannelConfig.RemoteParams.ChannelReserveSatoshis - fees
611+
if missing < Money.Zero then
612+
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"
613+
(currentSpec.ToRemote.ToMoney())
614+
staticChannelConfig.RemoteParams.ChannelReserveSatoshis
615+
fees
616+
|> Error
617+
else
618+
Ok()
619+
573620
module internal UpdateAddHTLCValidationWithContext =
574621
let checkLessThanHTLCValueInFlightLimit (currentSpec: CommitmentSpec) (limit) (add: UpdateAddHTLCMsg) =
575622
let outgoingValue =

src/DotNetLightning.Core/Channel/ChannelValidation.fs

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -214,6 +214,23 @@ module internal Validation =
214214
*> AcceptChannelMsgValidation.checkConfigPermits channelHandshakeLimits acceptChannelMsg
215215
|> Result.mapError(InvalidAcceptChannelError.Create acceptChannelMsg >> InvalidAcceptChannel)
216216

217+
let checkOurMonoHopUnidirectionalPaymentIsAcceptableWithCurrentSpec (currentSpec)
218+
(staticChannelConfig: StaticChannelConfig)
219+
(payment: MonoHopUnidirectionalPaymentMsg) =
220+
MonoHopUnidirectionalPaymentValidationWithContext.checkWeHaveSufficientFunds
221+
staticChannelConfig
222+
currentSpec
223+
|> Validation.ofResult
224+
|> Result.mapError(fun errs -> InvalidMonoHopUnidirectionalPayment { NetworkMsg = payment; Errors = errs })
225+
226+
let checkTheirMonoHopUnidirectionalPaymentIsAcceptableWithCurrentSpec (currentSpec)
227+
(staticChannelConfig: StaticChannelConfig)
228+
(payment: MonoHopUnidirectionalPaymentMsg) =
229+
MonoHopUnidirectionalPaymentValidationWithContext.checkWeHaveSufficientFunds
230+
staticChannelConfig
231+
currentSpec
232+
|> Validation.ofResult
233+
|> Result.mapError(fun errs -> InvalidMonoHopUnidirectionalPayment { NetworkMsg = payment; Errors = errs })
217234

218235
let checkOperationAddHTLC (remoteParams: RemoteParams) (op: OperationAddHTLC) =
219236
Validation.ofResult(UpdateAddHTLCValidation.checkExpiryIsNotPast op.CurrentHeight op.Expiry)

src/DotNetLightning.Core/Crypto/ShaChain.fs

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
namespace DotNetLightning.Crypto
22

3+
open System
4+
35
type Node = {
46
Value: byte[]
57
Height: int32
@@ -16,8 +18,9 @@ module ShaChain =
1618
let flip (_input: byte[]) (_index: uint64): byte[] =
1719
failwith "Not implemented: ShaChain::flip"
1820

19-
let addHash (_receiver: ShaChain) (_hash: byte[]) (_index: uint64) =
20-
failwith "Not implemented: ShaChain::addHash"
21+
let addHash (receiver: ShaChain) (_hash: byte[]) (_index: uint64) =
22+
Console.WriteLine("WARNING: Not implemented: ShaChain::addHash")
23+
receiver
2124

2225
let getHash (_receiver: ShaChain)(_index: uint64) =
2326
failwith "Not implemented: ShaChain::getHash"

src/DotNetLightning.Core/Serialization/Msgs/Msgs.fs

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,8 @@ module internal TypeFlag =
104104
let ReplyChannelRange = 264us
105105
[<Literal>]
106106
let GossipTimestampFilter = 265us
107+
[<Literal>]
108+
let MonoHopUnidirectionalPayment = 42198us
107109

108110
type ILightningMsg = interface end
109111
type ISetupMsg = inherit ILightningMsg
@@ -195,6 +197,8 @@ module ILightningSerializable =
195197
deserialize<ReplyChannelRangeMsg>(ls) :> ILightningMsg
196198
| TypeFlag.GossipTimestampFilter ->
197199
deserialize<GossipTimestampFilterMsg>(ls) :> ILightningMsg
200+
| TypeFlag.MonoHopUnidirectionalPayment ->
201+
deserialize<MonoHopUnidirectionalPaymentMsg>(ls) :> ILightningMsg
198202
| x ->
199203
raise <| FormatException(sprintf "Unknown message type %d" x)
200204
let serializeWithFlags (ls: LightningWriterStream) (data: ILightningMsg) =
@@ -283,6 +287,9 @@ module ILightningSerializable =
283287
| :? GossipTimestampFilterMsg as d ->
284288
ls.Write(TypeFlag.GossipTimestampFilter, false)
285289
(d :> ILightningSerializable<GossipTimestampFilterMsg>).Serialize(ls)
290+
| :? MonoHopUnidirectionalPaymentMsg as d ->
291+
ls.Write(TypeFlag.MonoHopUnidirectionalPayment, false)
292+
(d :> ILightningSerializable<MonoHopUnidirectionalPaymentMsg>).Serialize(ls)
286293
| x -> failwithf "%A is not known lightning message. This should never happen" x
287294

288295
module LightningMsg =
@@ -1592,4 +1599,19 @@ type GossipTimestampFilterMsg = {
15921599
ls.Write(this.ChainHash, true)
15931600
ls.Write(this.FirstTimestamp, false)
15941601
ls.Write(this.TimestampRange, false)
1595-
1602+
1603+
[<CLIMutable>]
1604+
type MonoHopUnidirectionalPaymentMsg = {
1605+
mutable ChannelId: ChannelId
1606+
mutable Amount: LNMoney
1607+
}
1608+
with
1609+
interface IHTLCMsg
1610+
interface IUpdateMsg
1611+
interface ILightningSerializable<MonoHopUnidirectionalPaymentMsg> with
1612+
member this.Deserialize(ls) =
1613+
this.ChannelId <- ls.ReadUInt256(true) |> ChannelId
1614+
this.Amount <- ls.ReadUInt64(false) |> LNMoney.MilliSatoshis
1615+
member this.Serialize(ls) =
1616+
ls.Write(this.ChannelId.Value.ToBytes())
1617+
ls.Write(this.Amount.MilliSatoshi, false)

src/DotNetLightning.Core/Transactions/CommitmentSpec.fs

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,12 @@ type CommitmentSpec = {
3434
(fun cs -> cs.ToRemote),
3535
(fun v cs -> { cs with ToRemote = v })
3636

37+
member internal this.AddOutgoingMonoHopUnidirectionalPayment(update: MonoHopUnidirectionalPaymentMsg) =
38+
{ this with ToLocal = this.ToLocal - update.Amount; ToRemote = this.ToRemote + update.Amount }
39+
40+
member internal this.AddIncomingMonoHopUnidirectionalPayment(update: MonoHopUnidirectionalPaymentMsg) =
41+
{ this with ToLocal = this.ToLocal + update.Amount; ToRemote = this.ToRemote - update.Amount }
42+
3743
member internal this.AddOutgoingHTLC(update: UpdateAddHTLCMsg) =
3844
{ this with ToLocal = (this.ToLocal - update.Amount); OutgoingHTLCs = this.OutgoingHTLCs.Add(update.HTLCId, update)}
3945

@@ -73,14 +79,32 @@ type CommitmentSpec = {
7379
UnknownHTLC htlcId |> Error
7480

7581
member internal this.Reduce(localChanges: #IUpdateMsg list, remoteChanges: #IUpdateMsg list) =
82+
let specMonoHopUnidirectionalPaymentLocal =
83+
localChanges
84+
|> List.fold(fun (acc: CommitmentSpec) updateMsg ->
85+
match box updateMsg with
86+
| :? MonoHopUnidirectionalPaymentMsg as u -> acc.AddOutgoingMonoHopUnidirectionalPayment u
87+
| _ -> acc
88+
)
89+
this
90+
91+
let specMonoHopUnidirectionalPaymentRemote =
92+
remoteChanges
93+
|> List.fold(fun (acc: CommitmentSpec) updateMsg ->
94+
match box updateMsg with
95+
| :? MonoHopUnidirectionalPaymentMsg as u -> acc.AddIncomingMonoHopUnidirectionalPayment u
96+
| _ -> acc
97+
)
98+
specMonoHopUnidirectionalPaymentLocal
99+
76100
let spec1 =
77101
localChanges
78102
|> List.fold(fun (acc: CommitmentSpec) updateMsg ->
79103
match box updateMsg with
80104
| :? UpdateAddHTLCMsg as u -> acc.AddOutgoingHTLC u
81105
| _ -> acc
82106
)
83-
this
107+
specMonoHopUnidirectionalPaymentRemote
84108

85109
let spec2 =
86110
remoteChanges

src/DotNetLightning.Core/Utils/LNMoney.fs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,8 @@ type LNMoney = | LNMoney of int64 with
3939
let satoshi = Checked.op_Multiply (amount) (decimal lnUnit)
4040
LNMoney(Checked.int64 satoshi)
4141

42+
static member FromMoney (money: Money) =
43+
LNMoney.Satoshis money.Satoshi
4244

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

0 commit comments

Comments
 (0)