Skip to content

Commit f9839d5

Browse files
canndrewknocte
authored andcommitted
Mono-hop unidirectional payments
1 parent f57294a commit f9839d5

File tree

10 files changed

+230
-6
lines changed

10 files changed

+230
-6
lines changed

src/DotNetLightning.Core/Channel/Channel.fs

Lines changed: 32 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -446,6 +446,32 @@ module Channel =
446446
[] |> Ok
447447

448448
// ---------- normal operation ---------
449+
| ChannelState.Normal state, MonoHopUnidirectionalPayment op when state.LocalShutdown.IsSome || state.RemoteShutdown.IsSome ->
450+
sprintf "Could not send mono-hop unidirectional payment %A since shutdown is already in progress." op
451+
|> apiMisuse
452+
| ChannelState.Normal state, MonoHopUnidirectionalPayment op ->
453+
result {
454+
let payment: MonoHopUnidirectionalPaymentMsg = {
455+
ChannelId = state.Commitments.ChannelId
456+
Amount = op.Amount
457+
}
458+
let commitments1 = state.Commitments.AddLocalProposal(payment)
459+
460+
let remoteCommit1 =
461+
match commitments1.RemoteNextCommitInfo with
462+
| RemoteNextCommitInfo.Waiting info -> info.NextRemoteCommit
463+
| RemoteNextCommitInfo.Revoked _info -> commitments1.RemoteCommit
464+
let! reduced = remoteCommit1.Spec.Reduce(commitments1.RemoteChanges.ACKed, commitments1.LocalChanges.Proposed) |> expectTransactionError
465+
do! Validation.checkOurMonoHopUnidirectionalPaymentIsAcceptableWithCurrentSpec reduced commitments1 payment
466+
return [ WeAcceptedOperationMonoHopUnidirectionalPayment(payment, commitments1) ]
467+
}
468+
| ChannelState.Normal state, ApplyMonoHopUnidirectionalPayment msg ->
469+
result {
470+
let commitments1 = state.Commitments.AddRemoteProposal(msg)
471+
let! reduced = commitments1.LocalCommit.Spec.Reduce (commitments1.LocalChanges.ACKed, commitments1.RemoteChanges.Proposed) |> expectTransactionError
472+
do! Validation.checkTheirMonoHopUnidirectionalPaymentIsAcceptableWithCurrentSpec reduced commitments1 msg
473+
return [ WeAcceptedMonoHopUnidirectionalPayment commitments1 ]
474+
}
449475
| ChannelState.Normal state, AddHTLC op when state.LocalShutdown.IsSome || state.RemoteShutdown.IsSome ->
450476
sprintf "Could not add new HTLC %A since shutdown is already in progress." op
451477
|> apiMisuse
@@ -551,8 +577,8 @@ module Channel =
551577
RemoteCommit = theirNextCommit
552578
RemoteNextCommitInfo = RemoteNextCommitInfo.Revoked msg.NextPerCommitmentPoint
553579
RemotePerCommitmentSecrets = remotePerCommitmentSecrets }
554-
let _result = Ok [ WeAcceptedRevokeAndACK commitments1 ]
555-
failwith "needs update"
580+
Console.WriteLine("WARNING: revocation is not implemented yet")
581+
Ok [ WeAcceptedRevokeAndACK(commitments1) ]
556582

557583
| ChannelState.Normal state, ChannelCommand.Close cmd ->
558584
let localSPK = cmd.ScriptPubKey |> Option.defaultValue (state.Commitments.LocalParams.DefaultFinalScriptPubKey)
@@ -832,8 +858,12 @@ module Channel =
832858
{ c with State = ChannelState.Normal data }
833859

834860
// ----- normal operation --------
861+
| WeAcceptedOperationMonoHopUnidirectionalPayment(_, newCommitments), ChannelState.Normal normalData ->
862+
{ c with State = ChannelState.Normal({ normalData with Commitments = newCommitments }) }
835863
| WeAcceptedOperationAddHTLC(_, newCommitments), ChannelState.Normal d ->
836864
{ c with State = ChannelState.Normal({ d with Commitments = newCommitments }) }
865+
| WeAcceptedMonoHopUnidirectionalPayment(newCommitments), ChannelState.Normal normalData ->
866+
{ c with State = ChannelState.Normal({ normalData with Commitments = newCommitments }) }
837867
| WeAcceptedUpdateAddHTLC(newCommitments), ChannelState.Normal d ->
838868
{ c with State = ChannelState.Normal({ d with Commitments = newCommitments }) }
839869

src/DotNetLightning.Core/Channel/ChannelError.fs

Lines changed: 49 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ type ChannelError =
3838
// --- case they sent unacceptable msg ---
3939
| InvalidOpenChannel of InvalidOpenChannelError
4040
| InvalidAcceptChannel of InvalidAcceptChannelError
41+
| InvalidMonoHopUnidirectionalPayment of InvalidMonoHopUnidirectionalPaymentError
4142
| InvalidUpdateAddHTLC of InvalidUpdateAddHTLCError
4243
| InvalidRevokeAndACK of InvalidRevokeAndACKError
4344
| InvalidUpdateFee of InvalidUpdateFeeError
@@ -71,6 +72,7 @@ type ChannelError =
7172
| TheyCannotAffordFee (_, _, _) -> Close
7273
| InvalidOpenChannel _ -> DistrustPeer
7374
| InvalidAcceptChannel _ -> DistrustPeer
75+
| InvalidMonoHopUnidirectionalPayment _ -> Close
7476
| InvalidUpdateAddHTLC _ -> Close
7577
| InvalidRevokeAndACK _ -> Close
7678
| InvalidUpdateFee _ -> Close
@@ -129,6 +131,8 @@ type ChannelError =
129131
"Received commitment signed when we have not pending changes"
130132
| InvalidAcceptChannel invalidAcceptChannelError ->
131133
sprintf "Invalid accept_channel msg: %s" invalidAcceptChannelError.Message
134+
| InvalidMonoHopUnidirectionalPayment invalidMonoHopUnidirectionalPaymentError ->
135+
sprintf "Invalid monohop unidirectional payment %s" invalidMonoHopUnidirectionalPaymentError.Message
132136
| InvalidUpdateAddHTLC invalidUpdateAddHTLCError ->
133137
sprintf "Invalid udpate_add_htlc msg: %s" invalidUpdateAddHTLCError.Message
134138
| InvalidRevokeAndACK invalidRevokeAndACKError ->
@@ -181,6 +185,18 @@ and InvalidAcceptChannelError = {
181185
member this.Message =
182186
String.concat "; " this.Errors
183187

188+
and InvalidMonoHopUnidirectionalPaymentError = {
189+
NetworkMsg: MonoHopUnidirectionalPaymentMsg
190+
Errors: string list
191+
}
192+
with
193+
static member Create msg e = {
194+
NetworkMsg = msg
195+
Errors = e
196+
}
197+
member this.Message =
198+
String.concat "; " this.Errors
199+
184200
and InvalidUpdateAddHTLCError = {
185201
NetworkMsg: UpdateAddHTLCMsg
186202
Errors: string list
@@ -509,6 +525,22 @@ module internal AcceptChannelMsgValidation =
509525

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

528+
module UpdateMonoHopUnidirectionalPaymentWithContext =
529+
let internal checkWeHaveSufficientFunds (state: Commitments) (currentSpec) =
530+
let fees =
531+
if state.LocalParams.IsFunder then
532+
Transactions.commitTxFee state.RemoteParams.DustLimitSatoshis currentSpec
533+
else
534+
Money.Zero
535+
let missing = currentSpec.ToRemote.ToMoney() - state.RemoteParams.ChannelReserveSatoshis - fees
536+
if missing < Money.Zero then
537+
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"
538+
(currentSpec.ToRemote.ToMoney())
539+
state.RemoteParams.ChannelReserveSatoshis
540+
fees
541+
|> Error
542+
else
543+
Ok()
512544

513545
module UpdateAddHTLCValidation =
514546
let internal checkExpiryIsNotPast (current: BlockHeight) (expiry) =
@@ -523,7 +555,23 @@ module UpdateAddHTLCValidation =
523555
let internal checkAmountIsLargerThanMinimum (htlcMinimum: LNMoney) (amount) =
524556
check (amount) (<) (htlcMinimum) "htlc value (%A) is too small. must be greater or equal to %A"
525557

526-
558+
module internal MonoHopUnidirectionalPaymentValidationWithContext =
559+
let checkWeHaveSufficientFunds (state: Commitments) (currentSpec) =
560+
let fees =
561+
if state.LocalParams.IsFunder then
562+
Transactions.commitTxFee state.RemoteParams.DustLimitSatoshis currentSpec
563+
else
564+
Money.Zero
565+
let missing = currentSpec.ToRemote.ToMoney() - state.RemoteParams.ChannelReserveSatoshis - fees
566+
if missing < Money.Zero then
567+
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"
568+
(currentSpec.ToRemote.ToMoney())
569+
state.RemoteParams.ChannelReserveSatoshis
570+
fees
571+
|> Error
572+
else
573+
Ok()
574+
527575
module internal UpdateAddHTLCValidationWithContext =
528576
let checkLessThanHTLCValueInFlightLimit (currentSpec: CommitmentSpec) (limit) (add: UpdateAddHTLCMsg) =
529577
let htlcValueInFlight = currentSpec.HTLCs |> Map.toSeq |> Seq.sumBy (fun (_, v) -> v.Add.Amount)

src/DotNetLightning.Core/Channel/ChannelOperations.fs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,10 @@ open NBitcoin
1515
open ResultUtils
1616
open ResultUtils.Portability
1717

18+
type OperationMonoHopUnidirectionalPayment = {
19+
Amount: LNMoney
20+
}
21+
1822
type OperationAddHTLC = {
1923
Amount: LNMoney
2024
PaymentHash: PaymentHash
@@ -203,6 +207,8 @@ type ChannelCommand =
203207
| CreateChannelReestablish
204208

205209
// normal
210+
| MonoHopUnidirectionalPayment of OperationMonoHopUnidirectionalPayment
211+
| ApplyMonoHopUnidirectionalPayment of msg: MonoHopUnidirectionalPaymentMsg
206212
| AddHTLC of OperationAddHTLC
207213
| ApplyUpdateAddHTLC of msg: UpdateAddHTLCMsg * currentHeight: BlockHeight
208214
| FulfillHTLC of OperationFulfillHTLC

src/DotNetLightning.Core/Channel/ChannelTypes.fs

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -276,6 +276,9 @@ type ChannelEvent =
276276
| BothFundingLocked of nextState: Data.NormalData
277277

278278
// -------- normal operation ------
279+
| WeAcceptedOperationMonoHopUnidirectionalPayment of msg: MonoHopUnidirectionalPaymentMsg * newCommitments: Commitments
280+
| WeAcceptedMonoHopUnidirectionalPayment of newCommitments: Commitments
281+
279282
| WeAcceptedOperationAddHTLC of msg: UpdateAddHTLCMsg * newCommitments: Commitments
280283
| WeAcceptedUpdateAddHTLC of newCommitments: Commitments
281284

@@ -368,6 +371,27 @@ type ChannelState =
368371
(fun v cc -> match cc with
369372
| Normal _ -> Normal v
370373
| _ -> cc )
374+
member this.ChannelId: Option<ChannelId> =
375+
match this with
376+
| WaitForInitInternal
377+
| WaitForOpenChannel _
378+
| WaitForAcceptChannel _
379+
| WaitForFundingTx _
380+
| WaitForFundingCreated _ -> None
381+
| WaitForFundingSigned data -> Some data.ChannelId
382+
| WaitForFundingConfirmed data -> Some data.ChannelId
383+
| WaitForFundingLocked data -> Some data.ChannelId
384+
| Normal data -> Some data.ChannelId
385+
| Shutdown data -> Some data.ChannelId
386+
| Negotiating data -> Some data.ChannelId
387+
| Closing data -> Some data.ChannelId
388+
| Closed _
389+
| Offline _
390+
| Syncing _
391+
| ErrFundingLost _
392+
| ErrFundingTimeOut _
393+
| ErrInformationLeak _ -> None
394+
371395
member this.Phase =
372396
match this with
373397
| WaitForInitInternal
@@ -388,3 +412,25 @@ type ChannelState =
388412
| ErrFundingLost _
389413
| ErrFundingTimeOut _
390414
| ErrInformationLeak _ -> Abnormal
415+
416+
member this.Commitments: Option<Commitments> =
417+
match this with
418+
| WaitForInitInternal
419+
| WaitForOpenChannel _
420+
| WaitForAcceptChannel _
421+
| WaitForFundingTx _
422+
| WaitForFundingCreated _
423+
| WaitForFundingSigned _ -> None
424+
| WaitForFundingConfirmed data -> Some (data :> IHasCommitments).Commitments
425+
| WaitForFundingLocked data -> Some (data :> IHasCommitments).Commitments
426+
| Normal data -> Some (data :> IHasCommitments).Commitments
427+
| Shutdown data -> Some (data :> IHasCommitments).Commitments
428+
| Negotiating data -> Some (data :> IHasCommitments).Commitments
429+
| Closing data -> Some (data :> IHasCommitments).Commitments
430+
| Closed _
431+
| Offline _
432+
| Syncing _
433+
| ErrFundingLost _
434+
| ErrFundingTimeOut _
435+
| ErrInformationLeak _ -> None
436+

src/DotNetLightning.Core/Channel/ChannelValidation.fs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -197,6 +197,13 @@ module internal Validation =
197197
*> AcceptChannelMsgValidation.checkConfigPermits conf.PeerChannelConfigLimits msg
198198
|> Result.mapError(InvalidAcceptChannelError.Create msg >> InvalidAcceptChannel)
199199

200+
let checkOurMonoHopUnidirectionalPaymentIsAcceptableWithCurrentSpec (currentSpec) (state: Commitments) (payment: MonoHopUnidirectionalPaymentMsg) =
201+
Validation.ofResult(MonoHopUnidirectionalPaymentValidationWithContext.checkWeHaveSufficientFunds state currentSpec)
202+
|> Result.mapError(fun errs -> InvalidMonoHopUnidirectionalPayment { NetworkMsg = payment; Errors = errs })
203+
204+
let checkTheirMonoHopUnidirectionalPaymentIsAcceptableWithCurrentSpec (currentSpec) (state: Commitments) (payment: MonoHopUnidirectionalPaymentMsg) =
205+
Validation.ofResult(MonoHopUnidirectionalPaymentValidationWithContext.checkWeHaveSufficientFunds state currentSpec)
206+
|> Result.mapError(fun errs -> InvalidMonoHopUnidirectionalPayment { NetworkMsg = payment; Errors = errs })
200207

201208
let checkOperationAddHTLC (state: NormalData) (op: OperationAddHTLC) =
202209
Validation.ofResult(UpdateAddHTLCValidation.checkExpiryIsNotPast op.CurrentHeight op.Expiry)

src/DotNetLightning.Core/Channel/Commitments.fs

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -186,3 +186,39 @@ type Commitments = {
186186
match remoteSigned, localSigned with
187187
| Some _, Some htlcIn -> htlcIn.Add |> Some
188188
| _ -> None
189+
190+
member this.SpendableBalance(): LNMoney =
191+
let remoteCommit =
192+
match this.RemoteNextCommitInfo with
193+
| RemoteNextCommitInfo.Waiting info -> info.NextRemoteCommit
194+
| RemoteNextCommitInfo.Revoked _info -> this.RemoteCommit
195+
let reducedRes =
196+
remoteCommit.Spec.Reduce(
197+
this.RemoteChanges.ACKed,
198+
this.LocalChanges.Proposed
199+
)
200+
let reduced =
201+
match reducedRes with
202+
| Error err ->
203+
failwithf
204+
"reducing commit failed even though we have not proposed any changes\
205+
error: %A"
206+
err
207+
| Ok reduced -> reduced
208+
let fees =
209+
if this.LocalParams.IsFunder then
210+
Transactions.commitTxFee this.RemoteParams.DustLimitSatoshis reduced
211+
|> LNMoney.FromMoney
212+
else
213+
LNMoney.Zero
214+
let channelReserve =
215+
this.RemoteParams.ChannelReserveSatoshis
216+
|> LNMoney.FromMoney
217+
let totalBalance = reduced.ToRemote
218+
let untrimmedSpendableBalance = totalBalance - channelReserve - fees
219+
let dustLimit =
220+
this.RemoteParams.DustLimitSatoshis
221+
|> LNMoney.FromMoney
222+
let untrimmedMax = LNMoney.Min(untrimmedSpendableBalance, dustLimit)
223+
let spendableBalance = LNMoney.Max(untrimmedMax, untrimmedSpendableBalance)
224+
spendableBalance

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 & 0 deletions
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 =
@@ -1572,3 +1579,19 @@ type GossipTimestampFilterMsg = {
15721579
ls.Write(this.FirstTimestamp, false)
15731580
ls.Write(this.TimestampRange, false)
15741581

1582+
[<CLIMutable>]
1583+
type MonoHopUnidirectionalPaymentMsg = {
1584+
mutable ChannelId: ChannelId
1585+
mutable Amount: LNMoney
1586+
}
1587+
with
1588+
interface IHTLCMsg
1589+
interface IUpdateMsg
1590+
interface ILightningSerializable<MonoHopUnidirectionalPaymentMsg> with
1591+
member this.Deserialize(ls) =
1592+
this.ChannelId <- ls.ReadUInt256(true) |> ChannelId
1593+
this.Amount <- ls.ReadUInt64(false) |> LNMoney.MilliSatoshis
1594+
member this.Serialize(ls) =
1595+
ls.Write(this.ChannelId.Value.ToBytes())
1596+
ls.Write(this.Amount.MilliSatoshi, false)
1597+

0 commit comments

Comments
 (0)