diff --git a/.github/workflows/publish_master.yml b/.github/workflows/publish_master.yml index 30721c82d..6da978e02 100644 --- a/.github/workflows/publish_master.yml +++ b/.github/workflows/publish_master.yml @@ -32,10 +32,5 @@ jobs: run: | cd $GITHUB_WORKSPACE/src/DotNetLightning.Core dotnet pack . -p:Configuration=Release --version-suffix date`date +%Y%m%d-%H%M`-git-`echo $GITHUB_SHA | cut -c 1-7` -p:BouncyCastle=True - dotnet nuget push ./bin/Release/DotNetLightning.1*.nupkg -k ${{ secrets.NUGET_API_KEY }} -s https://api.nuget.org/v3/index.json + dotnet nuget push ./bin/Release/DotNetLightning.Kiss.1*.nupkg -k ${{ secrets.NUGET_API_KEY }} -s https://api.nuget.org/v3/index.json - - name: Upload nuget packages (native) - run: | - cd $GITHUB_WORKSPACE/src/DotNetLightning.Core - dotnet pack . -p:Configuration=Release --version-suffix date`date +%Y%m%d-%H%M`-git-`echo $GITHUB_SHA | cut -c 1-7`-${{ matrix.RID }} - dotnet nuget push ./bin/Release/DotNetLightning.Core.1*.nupkg -k ${{ secrets.NUGET_API_KEY }} -s https://api.nuget.org/v3/index.json \ No newline at end of file diff --git a/src/DotNetLightning.Core/Channel/Channel.fs b/src/DotNetLightning.Core/Channel/Channel.fs index 5eca01b29..4956a9228 100644 --- a/src/DotNetLightning.Core/Channel/Channel.fs +++ b/src/DotNetLightning.Core/Channel/Channel.fs @@ -388,6 +388,32 @@ module Channel = [] |> Ok // ---------- normal operation --------- + | ChannelState.Normal state, MonoHopUnidirectionalPayment op when state.LocalShutdown.IsSome || state.RemoteShutdown.IsSome -> + sprintf "Could not send mono-hop unidirectional payment %A since shutdown is already in progress." op + |> apiMisuse + | ChannelState.Normal state, MonoHopUnidirectionalPayment op -> + result { + let payment: MonoHopUnidirectionalPayment = { + ChannelId = state.Commitments.ChannelId + Amount = op.Amount + } + let commitments1 = state.Commitments.AddLocalProposal(payment) + + let remoteCommit1 = + match commitments1.RemoteNextCommitInfo with + | RemoteNextCommitInfo.Waiting info -> info.NextRemoteCommit + | RemoteNextCommitInfo.Revoked _info -> commitments1.RemoteCommit + let! reduced = remoteCommit1.Spec.Reduce(commitments1.RemoteChanges.ACKed, commitments1.LocalChanges.Proposed) |> expectTransactionError + do! Validation.checkOurMonoHopUnidirectionalPaymentIsAcceptableWithCurrentSpec reduced commitments1 payment + return [ WeAcceptedOperationMonoHopUnidirectionalPayment(payment, commitments1) ] + } + | ChannelState.Normal state, ApplyMonoHopUnidirectionalPayment msg -> + result { + let commitments1 = state.Commitments.AddRemoteProposal(msg) + let! reduced = commitments1.LocalCommit.Spec.Reduce (commitments1.LocalChanges.ACKed, commitments1.RemoteChanges.Proposed) |> expectTransactionError + do! Validation.checkTheirMonoHopUnidirectionalPaymentIsAcceptableWithCurrentSpec reduced commitments1 msg + return [ WeAcceptedMonoHopUnidirectionalPayment commitments1 ] + } | ChannelState.Normal state, AddHTLC op when state.LocalShutdown.IsSome || state.RemoteShutdown.IsSome -> sprintf "Could not add new HTLC %A since shutdown is already in progress." op |> apiMisuse @@ -484,10 +510,9 @@ module Channel = RemoteCommit = theirNextCommit RemoteNextCommitInfo = RemoteNextCommitInfo.Revoked(msg.NextPerCommitmentPoint) RemotePerCommitmentSecrets = cm.RemotePerCommitmentSecrets.AddHash (msg.PerCommitmentSecret.ToByteArray(), 0xffffffffffffUL - cm.RemoteCommit.Index) } + Console.WriteLine("WARNING: revocation is not implemented yet") let result = [ WeAcceptedRevokeAndACK(commitments1) ] result |> Ok - failwith "needs update" - | ChannelState.Normal state, ChannelCommand.Close cmd -> let localSPK = cmd.ScriptPubKey |> Option.defaultValue (state.Commitments.LocalParams.DefaultFinalScriptPubKey) @@ -730,8 +755,12 @@ module Channel = { c with State = ChannelState.Normal data } // ----- normal operation -------- + | WeAcceptedOperationMonoHopUnidirectionalPayment(_, newCommitments), ChannelState.Normal normalData -> + { c with State = ChannelState.Normal({ normalData with Commitments = newCommitments }) } | WeAcceptedOperationAddHTLC(_, newCommitments), ChannelState.Normal d -> { c with State = ChannelState.Normal({ d with Commitments = newCommitments }) } + | WeAcceptedMonoHopUnidirectionalPayment(newCommitments), ChannelState.Normal normalData -> + { c with State = ChannelState.Normal({ normalData with Commitments = newCommitments }) } | WeAcceptedUpdateAddHTLC(newCommitments), ChannelState.Normal d -> { c with State = ChannelState.Normal({ d with Commitments = newCommitments }) } diff --git a/src/DotNetLightning.Core/Channel/ChannelError.fs b/src/DotNetLightning.Core/Channel/ChannelError.fs index 662655fd7..34dfe83bd 100644 --- a/src/DotNetLightning.Core/Channel/ChannelError.fs +++ b/src/DotNetLightning.Core/Channel/ChannelError.fs @@ -36,6 +36,7 @@ type ChannelError = // --- case they sent unacceptable msg --- | InvalidOpenChannel of InvalidOpenChannelError | InvalidAcceptChannel of InvalidAcceptChannelError + | InvalidMonoHopUnidirectionalPayment of InvalidMonoHopUnidirectionalPaymentError | InvalidUpdateAddHTLC of InvalidUpdateAddHTLCError | InvalidRevokeAndACK of InvalidRevokeAndACKError | InvalidUpdateFee of InvalidUpdateFeeError @@ -69,6 +70,7 @@ type ChannelError = | TheyCannotAffordFee (_, _, _) -> Close | InvalidOpenChannel _ -> DistrustPeer | InvalidAcceptChannel _ -> DistrustPeer + | InvalidMonoHopUnidirectionalPayment _ -> Close | InvalidUpdateAddHTLC _ -> Close | InvalidRevokeAndACK _ -> Close | InvalidUpdateFee _ -> Close @@ -145,6 +147,11 @@ and InvalidAcceptChannelError = { Errors = e } +and InvalidMonoHopUnidirectionalPaymentError = { + Msg: MonoHopUnidirectionalPayment + Errors: string list +} + and InvalidUpdateAddHTLCError = { Msg: UpdateAddHTLC Errors: string list @@ -457,6 +464,22 @@ module internal AcceptChannelMsgValidation = (check1 |> Validation.ofResult) *^> check2 *^> check3 *^> check4 *^> check5 *^> check6 *^> check7 +module UpdateMonoHopUnidirectionalPaymentWithContext = + let internal checkWeHaveSufficientFunds (state: Commitments) (currentSpec) = + let fees = + if state.LocalParams.IsFunder then + Transactions.commitTxFee state.RemoteParams.DustLimitSatoshis currentSpec + else + Money.Zero + let missing = currentSpec.ToRemote.ToMoney() - state.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()) + state.RemoteParams.ChannelReserveSatoshis + fees + |> Error + else + Ok() module UpdateAddHTLCValidation = let internal checkExpiryIsNotPast (current: BlockHeight) (expiry) = @@ -471,7 +494,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 (state: Commitments) (currentSpec) = + let fees = + if state.LocalParams.IsFunder then + Transactions.commitTxFee state.RemoteParams.DustLimitSatoshis currentSpec + else + Money.Zero + let missing = currentSpec.ToRemote.ToMoney() - state.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()) + state.RemoteParams.ChannelReserveSatoshis + fees + |> Error + else + Ok() + module internal UpdateAddHTLCValidationWithContext = let checkLessThanHTLCValueInFlightLimit (currentSpec: CommitmentSpec) (limit) (add: UpdateAddHTLC) = let htlcValueInFlight = currentSpec.HTLCs |> Map.toSeq |> Seq.sumBy (fun (_, v) -> v.Add.Amount) diff --git a/src/DotNetLightning.Core/Channel/ChannelOperations.fs b/src/DotNetLightning.Core/Channel/ChannelOperations.fs index eb9fdc2d3..7ba063bbf 100644 --- a/src/DotNetLightning.Core/Channel/ChannelOperations.fs +++ b/src/DotNetLightning.Core/Channel/ChannelOperations.fs @@ -12,6 +12,10 @@ open DotNetLightning.Serialize open NBitcoin +type OperationMonoHopUnidirectionalPayment = { + Amount: LNMoney +} + type OperationAddHTLC = { Amount: LNMoney PaymentHash: PaymentHash @@ -197,6 +201,8 @@ type ChannelCommand = | CreateChannelReestablish // normal + | MonoHopUnidirectionalPayment of OperationMonoHopUnidirectionalPayment + | ApplyMonoHopUnidirectionalPayment of msg: MonoHopUnidirectionalPayment | AddHTLC of OperationAddHTLC | ApplyUpdateAddHTLC of msg: UpdateAddHTLC * currentHeight: BlockHeight | FulfillHTLC of OperationFulfillHTLC @@ -220,4 +226,4 @@ type ChannelCommand = // else | ForceClose | GetState - | GetStateData \ No newline at end of file + | GetStateData diff --git a/src/DotNetLightning.Core/Channel/ChannelTypes.fs b/src/DotNetLightning.Core/Channel/ChannelTypes.fs index e6e305084..7e674c185 100644 --- a/src/DotNetLightning.Core/Channel/ChannelTypes.fs +++ b/src/DotNetLightning.Core/Channel/ChannelTypes.fs @@ -264,6 +264,9 @@ type ChannelEvent = | BothFundingLocked of nextState: Data.NormalData // -------- normal operation ------ + | WeAcceptedOperationMonoHopUnidirectionalPayment of msg: MonoHopUnidirectionalPayment * newCommitments: Commitments + | WeAcceptedMonoHopUnidirectionalPayment of newCommitments: Commitments + | WeAcceptedOperationAddHTLC of msg: UpdateAddHTLC * newCommitments: Commitments | WeAcceptedUpdateAddHTLC of newCommitments: Commitments @@ -355,6 +358,26 @@ type ChannelState = (fun v cc -> match cc with | Normal _ -> Normal v | _ -> cc ) + member this.ChannelId: Option = + match this with + | WaitForInitInternal + | WaitForOpenChannel _ + | WaitForAcceptChannel _ + | WaitForFundingCreated _ -> None + | WaitForFundingSigned data -> Some data.ChannelId + | WaitForFundingConfirmed data -> Some data.ChannelId + | WaitForFundingLocked data -> Some data.ChannelId + | Normal data -> Some data.ChannelId + | Shutdown data -> Some data.ChannelId + | Negotiating data -> Some data.ChannelId + | Closing data -> Some data.ChannelId + | Closed _ + | Offline _ + | Syncing _ + | ErrFundingLost _ + | ErrFundingTimeOut _ + | ErrInformationLeak _ -> None + member this.Phase = match this with | WaitForInitInternal @@ -374,3 +397,24 @@ type ChannelState = | ErrFundingLost _ | ErrFundingTimeOut _ | ErrInformationLeak _ -> Abnormal + + member this.Commitments: Option = + match this with + | WaitForInitInternal + | WaitForOpenChannel _ + | WaitForAcceptChannel _ + | WaitForFundingCreated _ + | WaitForFundingSigned _ -> None + | WaitForFundingConfirmed data -> Some (data :> IHasCommitments).Commitments + | WaitForFundingLocked data -> Some (data :> IHasCommitments).Commitments + | Normal data -> Some (data :> IHasCommitments).Commitments + | Shutdown data -> Some (data :> IHasCommitments).Commitments + | Negotiating data -> Some (data :> IHasCommitments).Commitments + | Closing data -> Some (data :> IHasCommitments).Commitments + | Closed _ + | Offline _ + | Syncing _ + | ErrFundingLost _ + | ErrFundingTimeOut _ + | ErrInformationLeak _ -> None + diff --git a/src/DotNetLightning.Core/Channel/ChannelValidation.fs b/src/DotNetLightning.Core/Channel/ChannelValidation.fs index a03195cf4..b8a74cdff 100644 --- a/src/DotNetLightning.Core/Channel/ChannelValidation.fs +++ b/src/DotNetLightning.Core/Channel/ChannelValidation.fs @@ -173,6 +173,13 @@ module internal Validation = *> AcceptChannelMsgValidation.checkConfigPermits conf.PeerChannelConfigLimits msg |> Result.mapError(InvalidAcceptChannelError.Create msg >> InvalidAcceptChannel) + let checkOurMonoHopUnidirectionalPaymentIsAcceptableWithCurrentSpec (currentSpec) (state: Commitments) (payment: MonoHopUnidirectionalPayment) = + Validation.ofResult(MonoHopUnidirectionalPaymentValidationWithContext.checkWeHaveSufficientFunds state currentSpec) + |> Result.mapError(fun errs -> InvalidMonoHopUnidirectionalPayment { Msg = payment; Errors = errs }) + + let checkTheirMonoHopUnidirectionalPaymentIsAcceptableWithCurrentSpec (currentSpec) (state: Commitments) (payment: MonoHopUnidirectionalPayment) = + Validation.ofResult(MonoHopUnidirectionalPaymentValidationWithContext.checkWeHaveSufficientFunds state currentSpec) + |> Result.mapError(fun errs -> InvalidMonoHopUnidirectionalPayment { Msg = payment; Errors = errs }) let checkOperationAddHTLC (state: NormalData) (op: OperationAddHTLC) = Validation.ofResult(UpdateAddHTLCValidation.checkExpiryIsNotPast op.CurrentHeight op.Expiry) diff --git a/src/DotNetLightning.Core/Channel/Commitments.fs b/src/DotNetLightning.Core/Channel/Commitments.fs index b94eb6e46..e3220a327 100644 --- a/src/DotNetLightning.Core/Channel/Commitments.fs +++ b/src/DotNetLightning.Core/Channel/Commitments.fs @@ -183,3 +183,39 @@ type Commitments = { match remoteSigned, localSigned with | Some _, Some htlcIn -> htlcIn.Add |> Some | _ -> None + + member this.SpendableBalance(): LNMoney = + let remoteCommit = + match this.RemoteNextCommitInfo with + | RemoteNextCommitInfo.Waiting info -> info.NextRemoteCommit + | RemoteNextCommitInfo.Revoked _info -> this.RemoteCommit + let reducedRes = + remoteCommit.Spec.Reduce( + this.RemoteChanges.ACKed, + this.LocalChanges.Proposed + ) + 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 this.LocalParams.IsFunder then + Transactions.commitTxFee this.RemoteParams.DustLimitSatoshis reduced + |> LNMoney.FromMoney + else + LNMoney.Zero + let channelReserve = + this.RemoteParams.ChannelReserveSatoshis + |> LNMoney.FromMoney + let totalBalance = reduced.ToRemote + let untrimmedSpendableBalance = totalBalance - channelReserve - fees + let dustLimit = + this.RemoteParams.DustLimitSatoshis + |> LNMoney.FromMoney + let untrimmedMax = LNMoney.Min(untrimmedSpendableBalance, dustLimit) + let spendableBalance = LNMoney.Max(untrimmedMax, untrimmedSpendableBalance) + spendableBalance diff --git a/src/DotNetLightning.Core/Crypto/ShaChain.fs b/src/DotNetLightning.Core/Crypto/ShaChain.fs index 3a027ddb0..457f50e49 100644 --- a/src/DotNetLightning.Core/Crypto/ShaChain.fs +++ b/src/DotNetLightning.Core/Crypto/ShaChain.fs @@ -1,5 +1,7 @@ namespace DotNetLightning.Crypto +open System + type Node = { Value: byte[] Height: int32 @@ -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" diff --git a/src/DotNetLightning.Core/DotNetLightning.Core.fsproj b/src/DotNetLightning.Core/DotNetLightning.Core.fsproj index 0f8f16f9a..5ec0b6043 100644 --- a/src/DotNetLightning.Core/DotNetLightning.Core.fsproj +++ b/src/DotNetLightning.Core/DotNetLightning.Core.fsproj @@ -8,7 +8,7 @@ $(OtherFlags) -d:BouncyCastle - DotNetLightning + DotNetLightning.Kiss diff --git a/src/DotNetLightning.Core/Serialize/Msgs/Msgs.fs b/src/DotNetLightning.Core/Serialize/Msgs/Msgs.fs index 43dabb9a7..18a437b7e 100644 --- a/src/DotNetLightning.Core/Serialize/Msgs/Msgs.fs +++ b/src/DotNetLightning.Core/Serialize/Msgs/Msgs.fs @@ -83,6 +83,8 @@ module internal TypeFlag = let ReplyChannelRange = 264us [] let GossipTimestampFilter = 265us + [] + let MonoHopUnidirectionalPayment = 42198us type ILightningMsg = interface end type ISetupMsg = inherit ILightningMsg @@ -174,6 +176,8 @@ module ILightningSerializable = deserialize(ls) :> ILightningMsg | TypeFlag.GossipTimestampFilter -> deserialize(ls) :> ILightningMsg + | TypeFlag.MonoHopUnidirectionalPayment -> + deserialize(ls) :> ILightningMsg | x -> raise <| FormatException(sprintf "Unknown message type %d" x) let serializeWithFlags (ls: LightningWriterStream) (data: ILightningMsg) = @@ -262,6 +266,9 @@ module ILightningSerializable = | :? GossipTimestampFilter as d -> ls.Write(TypeFlag.GossipTimestampFilter, false) (d :> ILightningSerializable).Serialize(ls) + | :? MonoHopUnidirectionalPayment as d -> + ls.Write(TypeFlag.MonoHopUnidirectionalPayment, false) + (d :> ILightningSerializable).Serialize(ls) | x -> failwithf "%A is not known lightning message. This should never happen" x module LightningMsg = @@ -1543,3 +1550,19 @@ type GossipTimestampFilter = { ls.Write(this.FirstTimestamp, false) ls.Write(this.TimestampRange, false) +[] +type MonoHopUnidirectionalPayment = { + mutable ChannelId: ChannelId + mutable Amount: LNMoney +} +with + interface IHTLCMsg + interface IUpdateMsg + interface ILightningSerializable 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) + diff --git a/src/DotNetLightning.Core/Transactions/CommitmentSpec.fs b/src/DotNetLightning.Core/Transactions/CommitmentSpec.fs index 220c91927..0f52db2af 100644 --- a/src/DotNetLightning.Core/Transactions/CommitmentSpec.fs +++ b/src/DotNetLightning.Core/Transactions/CommitmentSpec.fs @@ -52,6 +52,11 @@ type CommitmentSpec = { member this.TotalFunds = this.ToLocal + this.ToRemote + (this.HTLCs |> Seq.sumBy(fun h -> h.Value.Add.Amount)) + member internal this.MonoHopUnidirectionalPayment(direction: Direction, update: MonoHopUnidirectionalPayment) = + match direction with + | Out -> { this with ToLocal = this.ToLocal - update.Amount; ToRemote = this.ToRemote + update.Amount } + | In -> { this with ToLocal = this.ToLocal + update.Amount; ToRemote = this.ToRemote - update.Amount } + member internal this.AddHTLC(direction: Direction, update: UpdateAddHTLC) = let htlc = { DirectedHTLC.Direction = direction; Add = update } match direction with @@ -81,6 +86,24 @@ 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 + | :? MonoHopUnidirectionalPayment as u -> acc.MonoHopUnidirectionalPayment(Out, u) + | _ -> acc + ) + this + + let specMonoHopUnidirectionalPaymentRemote = + remoteChanges + |> List.fold(fun (acc: CommitmentSpec) updateMsg -> + match box updateMsg with + | :? MonoHopUnidirectionalPayment as u -> acc.MonoHopUnidirectionalPayment(In, u) + | _ -> acc + ) + specMonoHopUnidirectionalPaymentLocal + let spec1 = localChanges |> List.fold(fun (acc: CommitmentSpec) updateMsg -> @@ -88,7 +111,7 @@ type CommitmentSpec = { | :? UpdateAddHTLC as u -> acc.AddHTLC(Out, u) | _ -> acc ) - this + specMonoHopUnidirectionalPaymentRemote let spec2 = remoteChanges diff --git a/src/DotNetLightning.Core/Utils/LNMoney.fs b/src/DotNetLightning.Core/Utils/LNMoney.fs index 54bce8512..dfe470f3c 100644 --- a/src/DotNetLightning.Core/Utils/LNMoney.fs +++ b/src/DotNetLightning.Core/Utils/LNMoney.fs @@ -37,6 +37,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) diff --git a/src/DotNetLightning.Infrastructure/ActorManagers/ChannelManager.fs b/src/DotNetLightning.Infrastructure/ActorManagers/ChannelManager.fs index 18f45b43c..59ff1c835 100644 --- a/src/DotNetLightning.Infrastructure/ActorManagers/ChannelManager.fs +++ b/src/DotNetLightning.Infrastructure/ActorManagers/ChannelManager.fs @@ -202,6 +202,8 @@ type ChannelManager(log: ILogger, return! (this.Actors.[e.NodeId.Value] :> IActor<_>).Put(ChannelCommand.RemoteShutdown m) | :? ClosingSigned as m -> return! (this.Actors.[e.NodeId.Value] :> IActor<_>).Put(ChannelCommand.ApplyClosingSigned m) + | :? MonoHopUnidirectionalPayment as m -> + return! (this.Actors.[e.NodeId.Value] :> IActor<_>).Put(ChannelCommand.ApplyMonoHopUnidirectionalPayment m) | :? UpdateAddHTLC as m -> return! (this.Actors.[e.NodeId.Value] :> IActor<_>).Put(ChannelCommand.ApplyUpdateAddHTLC (m, this.CurrentBlockHeight)) | :? UpdateFulfillHTLC as m -> diff --git a/src/DotNetLightning.Infrastructure/ActorManagers/PeerManager.fs b/src/DotNetLightning.Infrastructure/ActorManagers/PeerManager.fs index 831908647..2d4e60033 100644 --- a/src/DotNetLightning.Infrastructure/ActorManagers/PeerManager.fs +++ b/src/DotNetLightning.Infrastructure/ActorManagers/PeerManager.fs @@ -190,15 +190,20 @@ type PeerManager(eventAggregator: IEventAggregator, | WeSentFundingLocked fundingLockedMsg -> return! (this.KnownPeers.[peerId] :> IActor<_>).Put(EncodeMsg fundingLockedMsg) | BothFundingLocked _ -> () + | WeAcceptedMonoHopUnidirectionalPayment _ | WeAcceptedUpdateAddHTLC _ | WeAcceptedFulfillHTLC _ | WeAcceptedFailHTLC _ -> () | WeAcceptedFailMalformedHTLC _ -> () | WeAcceptedUpdateFee _ -> () - | WeAcceptedCommitmentSigned _ -> failwith "TODO: route" - | WeAcceptedRevokeAndACK _ -> failwith "TODO: route" + | WeAcceptedCommitmentSigned (msg, _) -> + return! (this.KnownPeers.[peerId] :> IActor<_>).Put(EncodeMsg msg) + | WeAcceptedRevokeAndACK _ -> + Console.WriteLine "WARNING: revoke_and_ack handling is not implemented" // The one which includes `Operation` in its names is the one started by us. // So there are no need to think about routing, just send it to specified peer. + | ChannelEvent.WeAcceptedOperationMonoHopUnidirectionalPayment (msg, _) -> + return! (this.KnownPeers.[peerId] :> IActor<_>).Put(EncodeMsg msg) | ChannelEvent.WeAcceptedOperationAddHTLC (msg, _) -> return! (this.KnownPeers.[peerId] :> IActor<_>).Put(EncodeMsg msg) | ChannelEvent.WeAcceptedOperationFulfillHTLC (msg, _) -> diff --git a/tests/DotNetLightning.Infrastructure.Tests/ChannelOperationTests.fs b/tests/DotNetLightning.Infrastructure.Tests/ChannelOperationTests.fs index 239608820..566235dc7 100644 --- a/tests/DotNetLightning.Infrastructure.Tests/ChannelOperationTests.fs +++ b/tests/DotNetLightning.Infrastructure.Tests/ChannelOperationTests.fs @@ -212,6 +212,31 @@ type internal ActorCreator = return actors } + static member sendMonoHopUnidirectionalPayment alice bob = async { + let bobNodeId = bob.CM.KeysRepository.GetNodeSecret().PubKey |> NodeId + let aliceNodeId = alice.CM.KeysRepository.GetNodeSecret().PubKey |> NodeId + + do! Async.Sleep 1000 + Console.WriteLine("testing mono-hop unidirectional payment") + + + let aliceToBobAmount = TestConstants.monoHopPaymentAmount + let monoHopUnidirectionalPaymentCmd = { + Amount = aliceToBobAmount + } + + // Send mono-hop unidirectional payment from alice to bob + let bobAcceptedMonoHopUnidirectionalPaymentTask = + bob.EventAggregator.AwaitChannelEvent(function WeAcceptedMonoHopUnidirectionalPayment _ -> Some () | _ -> None) + do! + alice.CM.AcceptCommandAsync({ + NodeId = bobNodeId + ChannelCommand = ChannelCommand.MonoHopUnidirectionalPayment monoHopUnidirectionalPaymentCmd + }).AsTask() |> Async.AwaitTask + + let! r = bobAcceptedMonoHopUnidirectionalPaymentTask + Expect.isSome r "timeout waiting for bob to accept mono-hop unidirectional payment" + } [] let tests = @@ -312,8 +337,82 @@ let tests = return () } - - ptestAsync "Normal channel operation" { + + testAsync "Send mono-hop unidirectional payment" { + do! Async.Sleep 1000 + + Console.WriteLine("starting mono-hop unidirectional payment test") + let alice = ActorCreator.getAlice() + let bob = ActorCreator.getBob() + let! actors = ActorCreator.initiateOpenedChannel(alice, bob) + let bobNodeId = bob.CM.KeysRepository.GetNodeSecret().PubKey |> NodeId + let aliceNodeId = alice.CM.KeysRepository.GetNodeSecret().PubKey |> NodeId + + do! ActorCreator.sendMonoHopUnidirectionalPayment alice bob + + do! Async.Sleep 1000 + Console.WriteLine("alice signs her commit") + + let aliceAcceptedSign = + alice.EventAggregator.AwaitChannelEvent(function WeAcceptedOperationSign (_, commitments) -> Some commitments | _ -> None) + let aliceAcceptedRevokeAndAck = + alice.EventAggregator.AwaitChannelEvent(function WeAcceptedRevokeAndACK commitments -> Some commitments | _ -> None) + let bobAcceptedSign = + bob.EventAggregator.AwaitChannelEvent(function WeAcceptedCommitmentSigned (revokeAndAck, commitments) -> Some (revokeAndAck, commitments) | _ -> None) + do! alice.CM.AcceptCommandAsync({ NodeId = bobNodeId; ChannelCommand = SignCommitment }).AsTask() |> Async.AwaitTask + let! r = aliceAcceptedSign + Expect.isSome r "timeout waiting for alice to accept sign command" + let! r = bobAcceptedSign + Expect.isSome r "timeout waiting for bob to accept commitment signature" + let revokeAndAck, bobCommitments = r.Value + let! r = aliceAcceptedRevokeAndAck + Expect.isSome r "timeout waiting for alice to accept revoke_and_ack" + let aliceCommitments = r.Value + + + do! Async.Sleep 1000 + Console.WriteLine("bob signs his commit") + + let bobAcceptedSign = + bob.EventAggregator.AwaitChannelEvent(function WeAcceptedOperationSign (_, commitments) -> Some commitments | _ -> None) + let bobAcceptedRevokeAndAck = + bob.EventAggregator.AwaitChannelEvent(function WeAcceptedRevokeAndACK commitments -> Some commitments | _ -> None) + let aliceAcceptedSign = + alice.EventAggregator.AwaitChannelEvent(function WeAcceptedCommitmentSigned (revokeAndAck, commitments) -> Some (revokeAndAck, commitments) | _ -> None) + do! bob.CM.AcceptCommandAsync({ NodeId = aliceNodeId; ChannelCommand = SignCommitment }).AsTask() |> Async.AwaitTask + let! r = bobAcceptedSign + Expect.isSome r "timeout waiting for bob to accept sign command" + let! r = aliceAcceptedSign + Expect.isSome r "timeout waiting for alice to accept commitment signature" + let revokeAndAck, aliceCommitments = r.Value + let! r = bobAcceptedRevokeAndAck + Expect.isSome r "timeout waiting for bob to accept revoke_and_ack" + let bobCommitments = r.Value + + let aliceInitialBalance = LNMoney.Satoshis(TestConstants.fundingSatoshis.Satoshi) - TestConstants.pushMsat + let aliceFinalBalance = aliceCommitments.LocalCommit.Spec.ToLocal + let aliceExpectedFinalBalance = aliceInitialBalance - TestConstants.monoHopPaymentAmount + + let bobInitialBalance = TestConstants.pushMsat + let bobFinalBalance = bobCommitments.LocalCommit.Spec.ToLocal + let bobExpectedFinalBalance = bobInitialBalance + TestConstants.monoHopPaymentAmount + + do! Async.Sleep 1000 + + Expect.equal + aliceFinalBalance + aliceExpectedFinalBalance + "alice's balance was not updated correctly" + + Expect.equal + bobFinalBalance + bobExpectedFinalBalance + "bob's balance was not updated correctly" + + Console.WriteLine("finishing mono-hop unidirectional payment with revoke_and_ack test") + } + + ptestAsync "Normal channel operation" { let alice = ActorCreator.getAlice() let bob = ActorCreator.getBob() let! actors = ActorCreator.initiateOpenedChannel(alice, bob) diff --git a/tests/DotNetLightning.Infrastructure.Tests/Constants.fs b/tests/DotNetLightning.Infrastructure.Tests/Constants.fs index 90ba3d06f..217b77355 100644 --- a/tests/DotNetLightning.Infrastructure.Tests/Constants.fs +++ b/tests/DotNetLightning.Infrastructure.Tests/Constants.fs @@ -16,6 +16,7 @@ type TestEntity = NodeParams: ChainConfig } +let monoHopPaymentAmount = 1000L |> LNMoney.MilliSatoshis let fundingSatoshis = 1000000L |> Money.Satoshis let pushMsat = 200000000L |> LNMoney.MilliSatoshis let feeratePerKw = 10000u |> FeeRatePerKw