Skip to content

Commit a878176

Browse files
committed
Mono-hop unidirectional payments
1 parent 5c47042 commit a878176

File tree

13 files changed

+379
-31
lines changed

13 files changed

+379
-31
lines changed

src/DotNetLightning.Core/Channel/Channel.fs

Lines changed: 46 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -223,7 +223,7 @@ module Channel =
223223
RemoteNextHTLCId = HTLCId.Zero
224224
OriginChannels = Map.empty
225225
// we will receive their next per-commitment point in the next msg, so we temporarily put a random byte array
226-
RemoteNextCommitInfo = DataEncoders.HexEncoder() .DecodeData("0101010101010101010101010101010101010101010101010101010101010101") |> Key |> fun k -> k.PubKey |> Choice2Of2
226+
RemoteNextCommitInfo = DataEncoders.HexEncoder() .DecodeData("0101010101010101010101010101010101010101010101010101010101010101") |> Key |> fun k -> k.PubKey |> RemoteNextCommitInfo.Revoked
227227
RemotePerCommitmentSecrets = ShaChain.Zero
228228
ChannelId =
229229
msg.ChannelId }
@@ -316,7 +316,7 @@ module Channel =
316316
LocalNextHTLCId = HTLCId.Zero
317317
RemoteNextHTLCId = HTLCId.Zero
318318
OriginChannels = Map.empty
319-
RemoteNextCommitInfo = DataEncoders.HexEncoder() .DecodeData("0101010101010101010101010101010101010101010101010101010101010101") |> Key |> fun k -> k.PubKey |> Choice2Of2
319+
RemoteNextCommitInfo = DataEncoders.HexEncoder() .DecodeData("0101010101010101010101010101010101010101010101010101010101010101") |> Key |> fun k -> k.PubKey |> RemoteNextCommitInfo.Revoked
320320
RemotePerCommitmentSecrets = ShaChain.Zero
321321
ChannelId = channelId }
322322
let nextState = { WaitForFundingConfirmedData.Commitments = commitments
@@ -376,7 +376,7 @@ module Channel =
376376
true,
377377
None)
378378
let nextState = { NormalData.Buried = true
379-
Commitments = { state.Commitments with RemoteNextCommitInfo = Choice2Of2(msg.NextPerCommitmentPoint) }
379+
Commitments = { state.Commitments with RemoteNextCommitInfo = RemoteNextCommitInfo.Revoked(msg.NextPerCommitmentPoint) }
380380
ShortChannelId = state.ShortChannelId
381381
ChannelAnnouncement = None
382382
ChannelUpdate = initialChannelUpdate
@@ -388,6 +388,32 @@ module Channel =
388388
[] |> Ok
389389

390390
// ---------- normal operation ---------
391+
| ChannelState.Normal state, MonoHopUnidirectionalPayment cmd when state.LocalShutdown.IsSome || state.RemoteShutdown.IsSome ->
392+
sprintf "Could not send mono-hop unidirectional payment %A since shutdown is already in progress." cmd
393+
|> apiMisuse
394+
| ChannelState.Normal state, MonoHopUnidirectionalPayment cmd ->
395+
result {
396+
let payment: MonoHopUnidirectionalPayment = {
397+
ChannelId = state.Commitments.ChannelId
398+
Amount = cmd.Amount
399+
}
400+
let commitments1 = state.Commitments.AddLocalProposal(payment)
401+
402+
let remoteCommit1 =
403+
match commitments1.RemoteNextCommitInfo with
404+
| RemoteNextCommitInfo.WaitingForRevocation info -> info.NextRemoteCommit
405+
| RemoteNextCommitInfo.Revoked _info -> commitments1.RemoteCommit
406+
let! reduced = remoteCommit1.Spec.Reduce(commitments1.RemoteChanges.ACKed, commitments1.LocalChanges.Proposed) |> expectTransactionError
407+
do! Validation.checkOurMonoHopUnidirectionalPaymentIsAcceptableWithCurrentSpec reduced commitments1 payment
408+
return [ WeAcceptedCMDMonoHopUnidirectionalPayment(payment, commitments1) ]
409+
}
410+
| ChannelState.Normal state, ApplyMonoHopUnidirectionalPayment msg ->
411+
result {
412+
let commitments1 = state.Commitments.AddRemoteProposal(msg)
413+
let! reduced = commitments1.LocalCommit.Spec.Reduce (commitments1.LocalChanges.ACKed, commitments1.RemoteChanges.Proposed) |> expectTransactionError
414+
do! Validation.checkTheirMonoHopUnidirectionalPaymentIsAcceptableWithCurrentSpec reduced commitments1 msg
415+
return [ WeAcceptedMonoHopUnidirectionalPayment commitments1 ]
416+
}
391417
| ChannelState.Normal state, AddHTLC cmd when state.LocalShutdown.IsSome || state.RemoteShutdown.IsSome ->
392418
sprintf "Could not add new HTLC %A since shutdown is already in progress." cmd
393419
|> apiMisuse
@@ -409,8 +435,8 @@ module Channel =
409435
// we need to base the next current commitment on the last sig we sent, even if we didn't yet receive their revocation
410436
let remoteCommit1 =
411437
match commitments1.RemoteNextCommitInfo with
412-
| Choice1Of2 info -> info.NextRemoteCommit
413-
| Choice2Of2 _info -> commitments1.RemoteCommit
438+
| RemoteNextCommitInfo.WaitingForRevocation info -> info.NextRemoteCommit
439+
| RemoteNextCommitInfo.Revoked _info -> commitments1.RemoteCommit
414440
let! reduced = remoteCommit1.Spec.Reduce(commitments1.RemoteChanges.ACKed, commitments1.LocalChanges.Proposed) |> expectTransactionError
415441
do! Validation.checkOurUpdateAddHTLCIsAcceptableWithCurrentSpec reduced commitments1 add
416442
return [ WeAcceptedCMDAddHTLC(add, commitments1) ]
@@ -459,9 +485,9 @@ module Channel =
459485
| _ when (cm.LocalHasChanges() |> not) ->
460486
// Ignore SignCommitment Command (nothing to sign)
461487
return []
462-
| Choice2Of2 _ ->
488+
| RemoteNextCommitInfo.Revoked _ ->
463489
return! cm |> Commitments.sendCommit (cs.Secp256k1Context) (cs.KeysRepository) (cs.Network)
464-
| Choice1Of2 _ ->
490+
| RemoteNextCommitInfo.WaitingForRevocation _ ->
465491
// Already in the process of signing
466492
return []
467493
}
@@ -472,17 +498,17 @@ module Channel =
472498
| ChannelState.Normal state, ApplyRevokeAndACK msg ->
473499
let cm = state.Commitments
474500
match cm.RemoteNextCommitInfo with
475-
| Choice1Of2 _ when (msg.PerCommitmentSecret.ToPubKey() <> cm.RemoteCommit.RemotePerCommitmentPoint) ->
501+
| RemoteNextCommitInfo.WaitingForRevocation _ when (msg.PerCommitmentSecret.ToPubKey() <> cm.RemoteCommit.RemotePerCommitmentPoint) ->
476502
let errorMsg = sprintf "Invalid revoke_and_ack %A; must be %A" msg.PerCommitmentSecret cm.RemoteCommit.RemotePerCommitmentPoint
477503
invalidRevokeAndACK msg errorMsg
478-
| Choice2Of2 _ ->
504+
| RemoteNextCommitInfo.Revoked _ ->
479505
let errorMsg = sprintf "Unexpected revocation"
480506
invalidRevokeAndACK msg errorMsg
481-
| Choice1Of2({ NextRemoteCommit = theirNextCommit }) ->
507+
| RemoteNextCommitInfo.WaitingForRevocation({ NextRemoteCommit = theirNextCommit }) ->
482508
let commitments1 = { cm with LocalChanges = { cm.LocalChanges with Signed = []; ACKed = cm.LocalChanges.ACKed @ cm.LocalChanges.Signed }
483509
RemoteChanges = { cm.RemoteChanges with Signed = [] }
484510
RemoteCommit = theirNextCommit
485-
RemoteNextCommitInfo = Choice2Of2(msg.NextPerCommitmentPoint)
511+
RemoteNextCommitInfo = RemoteNextCommitInfo.Revoked(msg.NextPerCommitmentPoint)
486512
RemotePerCommitmentSecrets = cm.RemotePerCommitmentSecrets.AddHash (msg.PerCommitmentSecret.ToByteArray(), 0xffffffffffffUL - cm.RemoteCommit.Index) }
487513
let result = [ WeAcceptedRevokeAndACK(commitments1) ]
488514
result |> Ok
@@ -528,12 +554,12 @@ module Channel =
528554
// Are we in the middle of a signature?
529555
match cm.RemoteNextCommitInfo with
530556
// yes.
531-
| Choice1Of2 waitingForRevocation ->
557+
| RemoteNextCommitInfo.WaitingForRevocation waitingForRevocation ->
532558
let nextCommitments = { state.Commitments with
533-
RemoteNextCommitInfo = Choice1Of2({ waitingForRevocation with ReSignASAP = true }) }
559+
RemoteNextCommitInfo = RemoteNextCommitInfo.WaitingForRevocation({ waitingForRevocation with ReSignASAP = true }) }
534560
return [ AcceptedShutdownWhileWeHaveUnsignedOutgoingHTLCs(msg, nextCommitments) ]
535561
// No. let's sign right away.
536-
| Choice2Of2 _ ->
562+
| RemoteNextCommitInfo.Revoked _ ->
537563
return [ ChannelStateRequestedSignCommitment; AcceptedShutdownWhileWeHaveUnsignedOutgoingHTLCs(msg, cm) ]
538564
else
539565
let (localShutdown, sendList) = match state.LocalShutdown with
@@ -600,9 +626,9 @@ module Channel =
600626
| _ when (not <| cm.LocalHasChanges()) ->
601627
// nothing to sign
602628
[] |> Ok
603-
| Choice2Of2 _ ->
629+
| RemoteNextCommitInfo.Revoked _ ->
604630
cm |> Commitments.sendCommit (cs.Secp256k1Context) (cs.KeysRepository) (cs.Network)
605-
| Choice1Of2 _waitForRevocation ->
631+
| RemoteNextCommitInfo.WaitingForRevocation _waitForRevocation ->
606632
// Already in the process of signing.
607633
[] |> Ok
608634
| Shutdown state, ApplyCommitmentSigned msg ->
@@ -730,8 +756,12 @@ module Channel =
730756
{ c with State = ChannelState.Normal data }
731757

732758
// ----- normal operation --------
759+
| WeAcceptedCMDMonoHopUnidirectionalPayment(_, newCommitments), ChannelState.Normal d ->
760+
{ c with State = ChannelState.Normal({ d with Commitments = newCommitments }) }
733761
| WeAcceptedCMDAddHTLC(_, newCommitments), ChannelState.Normal d ->
734762
{ c with State = ChannelState.Normal({ d with Commitments = newCommitments }) }
763+
| WeAcceptedMonoHopUnidirectionalPayment(newCommitments), ChannelState.Normal d ->
764+
{ c with State = ChannelState.Normal({ d with Commitments = newCommitments }) }
735765
| WeAcceptedUpdateAddHTLC(newCommitments), ChannelState.Normal d ->
736766
{ c with State = ChannelState.Normal({ d with Commitments = newCommitments }) }
737767

src/DotNetLightning.Core/Channel/ChannelCommands.fs

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,10 @@ open NBitcoin
2121
// Y88b d88P Y88b. .d88P 888 " 888 888 " 888 d8888888888 888 Y8888 888 .d88P Y88b d88P
2222
// "Y8888P" "Y88888P" 888 888 888 888 d88P 888 888 Y888 8888888P" "Y8888P"
2323

24+
type CMDMonoHopUnidirectionalPayment = {
25+
Amount: LNMoney
26+
}
27+
2428
type CMDAddHTLC = {
2529
AmountMSat: LNMoney
2630
PaymentHash: PaymentHash
@@ -206,6 +210,8 @@ type ChannelCommand =
206210
| CreateChannelReestablish
207211

208212
// normal
213+
| MonoHopUnidirectionalPayment of CMDMonoHopUnidirectionalPayment
214+
| ApplyMonoHopUnidirectionalPayment of msg: MonoHopUnidirectionalPayment
209215
| AddHTLC of CMDAddHTLC
210216
| ApplyUpdateAddHTLC of msg: UpdateAddHTLC * currentHeight: BlockHeight
211217
| FulfillHTLC of CMDFulfillHTLC
@@ -229,4 +235,4 @@ type ChannelCommand =
229235
// else
230236
| ForceClose
231237
| GetState
232-
| GetStateData
238+
| GetStateData

src/DotNetLightning.Core/Channel/ChannelError.fs

Lines changed: 40 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ type ChannelError =
3636
// --- case they sent unacceptable msg ---
3737
| InvalidOpenChannel of InvalidOpenChannelError
3838
| InvalidAcceptChannel of InvalidAcceptChannelError
39+
| InvalidMonoHopUnidirectionalPayment of InvalidMonoHopUnidirectionalPaymentError
3940
| InvalidUpdateAddHTLC of InvalidUpdateAddHTLCError
4041
| InvalidRevokeAndACK of InvalidRevokeAndACKError
4142
| InvalidUpdateFee of InvalidUpdateFeeError
@@ -69,6 +70,7 @@ type ChannelError =
6970
| TheyCannotAffordFee (_, _, _) -> Close
7071
| InvalidOpenChannel _ -> DistrustPeer
7172
| InvalidAcceptChannel _ -> DistrustPeer
73+
| InvalidMonoHopUnidirectionalPayment _ -> Close
7274
| InvalidUpdateAddHTLC _ -> Close
7375
| InvalidRevokeAndACK _ -> Close
7476
| InvalidUpdateFee _ -> Close
@@ -145,6 +147,15 @@ and InvalidAcceptChannelError = {
145147
Errors = e
146148
}
147149

150+
and InvalidMonoHopUnidirectionalPaymentError = {
151+
Msg: MonoHopUnidirectionalPayment
152+
Errors: string list
153+
}
154+
with
155+
static member Create msg e = {
156+
Msg = msg
157+
Errors = e
158+
}
148159
and InvalidUpdateAddHTLCError = {
149160
Msg: UpdateAddHTLC
150161
Errors: string list
@@ -458,6 +469,18 @@ module internal AcceptChannelMsgValidation =
458469

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

472+
module UpdateMonoHopUnidirectionalPaymentWithContext =
473+
let internal checkWeHaveSufficientFunds (state: Commitments) (currentSpec) =
474+
let fees = if (state.LocalParams.IsFunder) then (Transactions.commitTxFee (state.RemoteParams.DustLimitSatoshis) currentSpec) else Money.Zero
475+
let missing = currentSpec.ToRemote.ToMoney() - state.RemoteParams.ChannelReserveSatoshis - fees
476+
if (missing < Money.Zero) then
477+
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"
478+
(currentSpec.ToRemote.ToMoney())
479+
(state.RemoteParams.ChannelReserveSatoshis)
480+
(fees)
481+
|> Error
482+
else
483+
Ok()
461484

462485
module UpdateAddHTLCValidation =
463486
let internal checkExpiryIsNotPast (current: BlockHeight) (expiry) =
@@ -472,7 +495,23 @@ module UpdateAddHTLCValidation =
472495
let internal checkAmountIsLargerThanMinimum (htlcMinimum: LNMoney) (amount) =
473496
check (amount) (<) (htlcMinimum) "htlc value (%A) is too small. must be greater or equal to %A"
474497

475-
498+
module internal MonoHopUnidirectionalPaymentValidationWithContext =
499+
let checkWeHaveSufficientFunds (state: Commitments) (currentSpec) =
500+
let fees =
501+
if state.LocalParams.IsFunder then
502+
Transactions.commitTxFee state.RemoteParams.DustLimitSatoshis currentSpec
503+
else
504+
Money.Zero
505+
let missing = currentSpec.ToRemote.ToMoney() - state.RemoteParams.ChannelReserveSatoshis - fees
506+
if (missing < Money.Zero) then
507+
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"
508+
(currentSpec.ToRemote.ToMoney())
509+
(state.RemoteParams.ChannelReserveSatoshis)
510+
(fees)
511+
|> Error
512+
else
513+
Ok()
514+
476515
module internal UpdateAddHTLCValidationWithContext =
477516
let checkLessThanHTLCValueInFlightLimit (currentSpec: CommitmentSpec) (limit) (add: UpdateAddHTLC) =
478517
let htlcValueInFlight = currentSpec.HTLCs |> Map.toSeq |> Seq.sumBy (fun (_, v) -> v.Add.AmountMSat)

src/DotNetLightning.Core/Channel/ChannelTypes.fs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -264,6 +264,9 @@ type ChannelEvent =
264264
| BothFundingLocked of nextState: Data.NormalData
265265

266266
// -------- normal operation ------
267+
| WeAcceptedCMDMonoHopUnidirectionalPayment of msg: MonoHopUnidirectionalPayment * newCommitments: Commitments
268+
| WeAcceptedMonoHopUnidirectionalPayment of newCommitments: Commitments
269+
267270
| WeAcceptedCMDAddHTLC of msg: UpdateAddHTLC * newCommitments: Commitments
268271
| WeAcceptedUpdateAddHTLC of newCommitments: Commitments
269272

src/DotNetLightning.Core/Channel/ChannelValidation.fs

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

172+
let checkOurMonoHopUnidirectionalPaymentIsAcceptableWithCurrentSpec (currentSpec) (state: Commitments) (payment: MonoHopUnidirectionalPayment) =
173+
Validation.ofResult(MonoHopUnidirectionalPaymentValidationWithContext.checkWeHaveSufficientFunds state currentSpec)
174+
|> Result.mapError(InvalidMonoHopUnidirectionalPaymentError.Create payment >> InvalidMonoHopUnidirectionalPayment)
175+
176+
let checkTheirMonoHopUnidirectionalPaymentIsAcceptableWithCurrentSpec (currentSpec) (state: Commitments) (payment: MonoHopUnidirectionalPayment) =
177+
Validation.ofResult(MonoHopUnidirectionalPaymentValidationWithContext.checkWeHaveSufficientFunds state currentSpec)
178+
|> Result.mapError(InvalidMonoHopUnidirectionalPaymentError.Create payment >> InvalidMonoHopUnidirectionalPayment)
172179

173180
let checkCMDAddHTLC (state: NormalData) (cmd: CMDAddHTLC) =
174181
Validation.ofResult(UpdateAddHTLCValidation.checkExpiryIsNotPast cmd.CurrentHeight cmd.Expiry)

src/DotNetLightning.Core/Channel/Commitments.fs

Lines changed: 30 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,30 @@ type WaitingForRevocation = {
8888
(fun w -> w.ReSignASAP),
8989
(fun v w -> { w with ReSignASAP = v })
9090

91+
type RemoteNextCommitInfo =
92+
| WaitingForRevocation of WaitingForRevocation
93+
| Revoked of PubKey
94+
with
95+
static member WaitingForRevocation_: Prism<RemoteNextCommitInfo, WaitingForRevocation> =
96+
(fun remoteNextCommitInfo ->
97+
match remoteNextCommitInfo with
98+
| WaitingForRevocation waitingForRevocation -> Some waitingForRevocation
99+
| Revoked _ -> None),
100+
(fun waitingForRevocation remoteNextCommitInfo ->
101+
match remoteNextCommitInfo with
102+
| WaitingForRevocation _ -> WaitingForRevocation waitingForRevocation
103+
| Revoked _ -> remoteNextCommitInfo)
104+
105+
static member Revoked_: Prism<RemoteNextCommitInfo, PubKey> =
106+
(fun remoteNextCommitInfo ->
107+
match remoteNextCommitInfo with
108+
| WaitingForRevocation _ -> None
109+
| Revoked pubKey -> Some pubKey),
110+
(fun pubKey remoteNextCommitInfo ->
111+
match remoteNextCommitInfo with
112+
| WaitingForRevocation _ -> remoteNextCommitInfo
113+
| Revoked pubKey -> Revoked pubKey)
114+
91115
type Commitments = {
92116
LocalParams: LocalParams
93117
RemoteParams: RemoteParams
@@ -100,7 +124,7 @@ type Commitments = {
100124
LocalNextHTLCId: HTLCId
101125
RemoteNextHTLCId: HTLCId
102126
OriginChannels: Map<HTLCId, HTLCSource>
103-
RemoteNextCommitInfo: Choice<WaitingForRevocation, PubKey>
127+
RemoteNextCommitInfo: RemoteNextCommitInfo
104128
RemotePerCommitmentSecrets: ShaChain
105129
ChannelId: ChannelId
106130
}
@@ -140,15 +164,18 @@ type Commitments = {
140164
this.RemoteChanges.Proposed |> List.exists(fun p -> match p with | :? UpdateAddHTLC -> true | _ -> false)
141165

142166
member internal this.HasNoPendingHTLCs() =
143-
this.LocalCommit.Spec.HTLCs.IsEmpty && this.RemoteCommit.Spec.HTLCs.IsEmpty && (this.RemoteNextCommitInfo |> function Choice1Of2 _ -> false | Choice2Of2 _ -> true)
167+
this.LocalCommit.Spec.HTLCs.IsEmpty && this.RemoteCommit.Spec.HTLCs.IsEmpty && (this.RemoteNextCommitInfo |> function WaitingForRevocation _ -> false | Revoked _ -> true)
144168

145169
member internal this.GetHTLCCrossSigned(directionRelativeToLocal: Direction, htlcId: HTLCId): UpdateAddHTLC option =
146170
let remoteSigned =
147171
this.LocalCommit.Spec.HTLCs
148172
|> Map.tryPick (fun k v -> if v.Direction = directionRelativeToLocal && v.Add.HTLCId = htlcId then Some v else None)
149173

150174
let localSigned =
151-
let lens = Commitments.RemoteNextCommitInfo_ >-> Choice.choice1Of2_ >?> WaitingForRevocation.NextRemoteCommit_
175+
let lens =
176+
Commitments.RemoteNextCommitInfo_
177+
>-> RemoteNextCommitInfo.WaitingForRevocation_
178+
>?> WaitingForRevocation.NextRemoteCommit_
152179
match Optic.get lens this with
153180
| Some v -> v
154181
| None -> this.RemoteCommit

0 commit comments

Comments
 (0)