From 1600b5204db5e7494d46de759941066d94b44672 Mon Sep 17 00:00:00 2001 From: Andrew Cann Date: Thu, 4 Mar 2021 20:33:58 +0800 Subject: [PATCH 01/99] Fix build warnings Fix several build warnings. --- src/DotNetLightning.Core/Channel/Channel.fs | 7 +------ src/DotNetLightning.Core/Serialization/LightningStream.fs | 6 +----- tests/DotNetLightning.Core.Tests/KeyRepositoryTests.fs | 4 ++-- tests/DotNetLightning.Core.Tests/TransactionTests.fs | 6 ------ .../TxOutLexicographicCompareTests.fs | 4 ++-- tests/EventAggregator.Tests/Sample.fs | 7 ++++--- 6 files changed, 10 insertions(+), 24 deletions(-) diff --git a/src/DotNetLightning.Core/Channel/Channel.fs b/src/DotNetLightning.Core/Channel/Channel.fs index 3c623aabd..7097ae775 100644 --- a/src/DotNetLightning.Core/Channel/Channel.fs +++ b/src/DotNetLightning.Core/Channel/Channel.fs @@ -62,7 +62,6 @@ module Channel = localSpk: Script, remoteSpk: Script, closingFee: Money, - localFundingPk: FundingPubKey, network: Network ) = assert (Scripts.isValidFinalScriptPubKey (remoteSpk) && Scripts.isValidFinalScriptPubKey (localSpk)) @@ -94,12 +93,11 @@ module Channel = localSpk: Script, remoteSpk: Script, feeEst: IFeeEstimator, - localFundingPk: FundingPubKey, network: Network ) = result { let! closingFee = firstClosingFee (commitments, localSpk, remoteSpk, feeEst, network) - return! makeClosingTx (channelPrivKeys, commitments, localSpk, remoteSpk, closingFee, localFundingPk, network) + return! makeClosingTx (channelPrivKeys, commitments, localSpk, remoteSpk, closingFee, network) } |> expectTransactionError let nextClosingFee (localClosingFee: Money, remoteClosingFee: Money) = @@ -622,7 +620,6 @@ module Channel = localShutdown.ScriptPubKey, msg.ScriptPubKey, cs.FeeEstimator, - cm.LocalParams.ChannelPubKeys.FundingPubKey, cs.Network) let nextState = { NegotiatingData.ChannelId = cm.ChannelId Commitments = cm @@ -695,7 +692,6 @@ module Channel = state.LocalShutdown.ScriptPubKey, state.RemoteShutdown.ScriptPubKey, msg.FeeSatoshis, - cm.LocalParams.ChannelPubKeys.FundingPubKey, cs.Network ) |> expectTransactionError @@ -751,7 +747,6 @@ module Channel = state.LocalShutdown.ScriptPubKey, state.RemoteShutdown.ScriptPubKey, nextClosingFee, - cm.LocalParams.ChannelPubKeys.FundingPubKey, cs.Network ) |> expectTransactionError diff --git a/src/DotNetLightning.Core/Serialization/LightningStream.fs b/src/DotNetLightning.Core/Serialization/LightningStream.fs index c33763e97..660f0b501 100644 --- a/src/DotNetLightning.Core/Serialization/LightningStream.fs +++ b/src/DotNetLightning.Core/Serialization/LightningStream.fs @@ -58,10 +58,6 @@ type LightningWriterStream(inner: Stream) = override this.Write(buffer: byte[], offset: int, count: int) = this.Inner.Write(buffer, offset, count) - // FIXME: how to fix the warning reported below??: - member this.Write(buffer: ReadOnlySpan) = - this.Inner.Write(buffer.ToArray(), 0, buffer.Length) - member this.Write(buf: byte[]) = this.Write(buf, 0, buf.Length) @@ -326,7 +322,7 @@ type LightningReaderStream(inner: Stream) = member this.ReadKey(): Key = let bytes: array = this.ReadBytes Key.BytesLength - Key bytes + new Key(bytes) member this.ReadPubKey(): PubKey = let bytes = this.ReadBytes PubKey.BytesLength diff --git a/tests/DotNetLightning.Core.Tests/KeyRepositoryTests.fs b/tests/DotNetLightning.Core.Tests/KeyRepositoryTests.fs index 239f40c9b..a080b514e 100644 --- a/tests/DotNetLightning.Core.Tests/KeyRepositoryTests.fs +++ b/tests/DotNetLightning.Core.Tests/KeyRepositoryTests.fs @@ -132,9 +132,9 @@ let tests = n let _remoteSigForRemoteCommit, remoteCommitTx2 = remotePrivKeys.SignWithFundingPrivKey remoteCommitTx.Value - let localSigForRemoteCommit, commitTx3 = localPrivKeys.SignWithFundingPrivKey remoteCommitTx2 + let localSigForRemoteCommit, _commitTx3 = localPrivKeys.SignWithFundingPrivKey remoteCommitTx2 let localSigs = seq [(localPubKeys.FundingPubKey.RawPubKey(), TransactionSignature(localSigForRemoteCommit.Signature, SigHash.All))] - let finalizedTx = Transactions.checkTxFinalized remoteCommitTx2 0 localSigs |> Result.deref + let _finalizedTx = Transactions.checkTxFinalized remoteCommitTx2 0 localSigs |> Result.deref () ] diff --git a/tests/DotNetLightning.Core.Tests/TransactionTests.fs b/tests/DotNetLightning.Core.Tests/TransactionTests.fs index faf98c449..bb655f670 100644 --- a/tests/DotNetLightning.Core.Tests/TransactionTests.fs +++ b/tests/DotNetLightning.Core.Tests/TransactionTests.fs @@ -14,17 +14,11 @@ let n = Network.RegTest [] let testList = [ testCase "check pre-computed transaction weights" <| fun _ -> - let localRevocationPriv = [| for _ in 0..31 -> 0xccuy |] |> fun b -> new Key(b) let localPaymentPriv = [| for _ in 0..31 -> 0xdduy |] |> fun b -> new Key(b) - let remotePaymentPriv = [| for _ in 0..31 -> 0xeeuy |] |> fun b -> new Key(b) - let localHtlcPriv = [| for _ in 0..31 -> 0xeauy |] |> fun b -> new Key(b) - let remoteHtlcPriv = [| for _ in 0..31 -> 0xebuy |] |> fun b -> new Key(b) - let localFinalPriv = [| for _ in 0..31 -> 0xffuy |] |> fun b -> new Key(b) let finalSpk = let s = [| for _ in 0..31 -> 0xfeuy |] |> fun b -> new Key(b) s.PubKey.WitHash let localDustLimit = 546L |> Money.Satoshis - let toLocalDelay= 144us |> BlockHeightOffset16 let feeRatePerKw = 1000u |> FeeRatePerKw let _ = diff --git a/tests/DotNetLightning.Core.Tests/TxOutLexicographicCompareTests.fs b/tests/DotNetLightning.Core.Tests/TxOutLexicographicCompareTests.fs index 873c7b19b..7c5c4a2e9 100644 --- a/tests/DotNetLightning.Core.Tests/TxOutLexicographicCompareTests.fs +++ b/tests/DotNetLightning.Core.Tests/TxOutLexicographicCompareTests.fs @@ -11,8 +11,8 @@ let tests = (script0: array) (script1: array) (expected: int) = - let txOut0 = TxOut(Money amount0, Script script0) - let txOut1 = TxOut(Money amount1, Script script1) + let txOut0 = TxOut(Money (int64 amount0), Script script0) + let txOut1 = TxOut(Money (int64 amount1), Script script1) let result = TxOut.LexicographicCompare txOut0 txOut1 Expect.equal result diff --git a/tests/EventAggregator.Tests/Sample.fs b/tests/EventAggregator.Tests/Sample.fs index ffce8645e..a861398dd 100644 --- a/tests/EventAggregator.Tests/Sample.fs +++ b/tests/EventAggregator.Tests/Sample.fs @@ -14,14 +14,14 @@ let tests = let mutable eventWasRaised = false let agg: IEventAggregator = ReactiveEventAggregator() :> IEventAggregator - agg.GetObservable() |> Observable.subscribe(fun se -> eventWasRaised <- true) |> ignore + agg.GetObservable() |> Observable.subscribe(fun _se -> eventWasRaised <- true) |> ignore agg.Publish({ Status = 1 }) Expect.isTrue eventWasRaised "" testCase "Unsubscribe" <| fun _ -> let mutable eventWasRaised = false let agg = ReactiveEventAggregator() :> IEventAggregator - let subscription = agg.GetObservable() |> Observable.subscribe(fun se -> eventWasRaised <- true) + let subscription = agg.GetObservable() |> Observable.subscribe(fun _se -> eventWasRaised <- true) subscription.Dispose() |> ignore agg.Publish({ Status = 1}) Expect.isFalse eventWasRaised "" @@ -31,7 +31,8 @@ let tests = let agg = ReactiveEventAggregator() :> IEventAggregator agg.GetObservable() |> Observable.filter(fun se -> se.Status = 1) - |> Observable.subscribe(fun se -> eventWasRaised <- true) + |> Observable.subscribe(fun _se -> eventWasRaised <- true) + |> ignore agg.Publish({ Status = 0 }) Expect.isFalse eventWasRaised "" From 5ca24f5451ac2f946f1b0850532009c3c0daf89e Mon Sep 17 00:00:00 2001 From: "Andres G. Aragoneses" Date: Wed, 4 Nov 2020 20:51:19 +0800 Subject: [PATCH 02/99] DotNetLightning.Kiss fork - LICENSE: change from MIT to AGPL (.Kiss fork) - Change package name suffix from .Core to .Kiss (skipping native build) --- .github/workflows/publish_master.yml | 9 ++---- LICENSE.txt | 28 ++++++++----------- README.md | 17 ++++------- .../DotNetLightning.Core.fsproj | 2 +- 4 files changed, 21 insertions(+), 35 deletions(-) diff --git a/.github/workflows/publish_master.yml b/.github/workflows/publish_master.yml index 786023dea..d504ce086 100644 --- a/.github/workflows/publish_master.yml +++ b/.github/workflows/publish_master.yml @@ -32,10 +32,5 @@ jobs: - name: Upload nuget packages (Portability) if: startsWith(matrix.os, 'ubuntu') run: | - dotnet pack ./src/DotNetLightning.Core -p:Configuration=Release --version-suffix date`date +%Y%m%d-%H%M`-git-`echo $GITHUB_SHA | head -c 7` -p:Portability=True - dotnet nuget push ./src/DotNetLightning.Core/bin/Release/DotNetLightning.1*.nupkg -k ${{ secrets.NUGET_API_KEY }} -s https://api.nuget.org/v3/index.json - - - name: Upload nuget packages (native) - run: | - bash -c "dotnet pack ./src/DotNetLightning.Core -p:Configuration=Release --version-suffix date$(date +%Y%m%d-%H%M).git-$(git rev-parse --short=7 HEAD)-${{ matrix.RID }}" - bash -c "dotnet nuget push ./src/DotNetLightning.Core/bin/Release/DotNetLightning.Core.1*.nupkg -k ${{ secrets.NUGET_API_KEY }} -s https://api.nuget.org/v3/index.json" + dotnet pack ./src/DotNetLightning.Core -p:Configuration=Release --version-suffix date`date +%Y%m%d-%H%M`-git-`echo $GITHUB_SHA | head -c 7` -p:BouncyCastle=True + dotnet nuget push ./src/DotNetLightning.Core/bin/Release/DotNetLightning.Kiss.1*.nupkg -k ${{ secrets.NUGET_API_KEY }} -s https://api.nuget.org/v3/index.json diff --git a/LICENSE.txt b/LICENSE.txt index fb52ef9ff..9f67b0d06 100644 --- a/LICENSE.txt +++ b/LICENSE.txt @@ -1,21 +1,17 @@ -MIT License +AGPL License Copyright (c) 2020 Joe Miyamoto +Copyright (c) 2020 Node Effect Ltd -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU Affero General Public License as +published by the Free Software Foundation, either version 3 of the +License, or (at your option) any later version. -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Affero General Public License for more details. -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. +You should have received a copy of the GNU Affero General Public License +along with this program. If not, see . diff --git a/README.md b/README.md index fe0ce895c..9ccf661a9 100644 --- a/README.md +++ b/README.md @@ -4,17 +4,12 @@ The main entry point is `DotNetLightning.Core`. ## Installation -The package is compiled and published with two variants - -* [`DotNetLightning`](https://www.nuget.org/packages/DotNetLightning/) - * This does not use native bindings for cryptographic operations. - * This is the one you want to use if you run your code everywhere, but possibly slower than below. -* [`DotNetLightning.Core`](https://www.nuget.org/packages/DotNetLightning.Core/) - * This uses a pre-compiled `libsodium` for cryptographic operations. - * It only supports `windows`, `mac` and `linux` environments. - * This is what you want if you need performance and the environments above are the only ones you are planning to support. - -run `dotnet add package` with the one you want. +The package is compiled and published in nuget: + +* [`DotNetLightning.Kiss`](https://www.nuget.org/packages/DotNetLightning.Kiss/) + +It does not use native bindings for cryptographic operations. + Currently it is in alpha, so you probably want to install a latest version by specifying it with `--version`. The version is prefixed with git commit hash and date. Please take a look at the nuget page. diff --git a/src/DotNetLightning.Core/DotNetLightning.Core.fsproj b/src/DotNetLightning.Core/DotNetLightning.Core.fsproj index e7cbef336..6e994d024 100644 --- a/src/DotNetLightning.Core/DotNetLightning.Core.fsproj +++ b/src/DotNetLightning.Core/DotNetLightning.Core.fsproj @@ -8,7 +8,7 @@ - DotNetLightning + DotNetLightning.Kiss From 70f9932216b12b211d611a6f799d538b9e62b31d Mon Sep 17 00:00:00 2001 From: Andrew Cann Date: Thu, 12 Nov 2020 16:38:55 +0800 Subject: [PATCH 03/99] Mono-hop unidirectional payments --- src/DotNetLightning.Core/Channel/Channel.fs | 34 ++++++++++++- .../Channel/ChannelError.fs | 50 ++++++++++++++++++- .../Channel/ChannelOperations.fs | 6 +++ .../Channel/ChannelTypes.fs | 46 +++++++++++++++++ .../Channel/ChannelValidation.fs | 7 +++ .../Channel/Commitments.fs | 36 +++++++++++++ src/DotNetLightning.Core/Crypto/ShaChain.fs | 7 ++- .../Serialization/Msgs/Msgs.fs | 23 +++++++++ .../Transactions/CommitmentSpec.fs | 25 +++++++++- src/DotNetLightning.Core/Utils/LNMoney.fs | 2 + 10 files changed, 230 insertions(+), 6 deletions(-) diff --git a/src/DotNetLightning.Core/Channel/Channel.fs b/src/DotNetLightning.Core/Channel/Channel.fs index 7097ae775..269d755fb 100644 --- a/src/DotNetLightning.Core/Channel/Channel.fs +++ b/src/DotNetLightning.Core/Channel/Channel.fs @@ -446,6 +446,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: MonoHopUnidirectionalPaymentMsg = { + 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 @@ -551,8 +577,8 @@ module Channel = RemoteCommit = theirNextCommit RemoteNextCommitInfo = RemoteNextCommitInfo.Revoked msg.NextPerCommitmentPoint RemotePerCommitmentSecrets = remotePerCommitmentSecrets } - let _result = Ok [ WeAcceptedRevokeAndACK commitments1 ] - failwith "needs update" + Console.WriteLine("WARNING: revocation is not implemented yet") + Ok [ WeAcceptedRevokeAndACK(commitments1) ] | ChannelState.Normal state, ChannelCommand.Close cmd -> let localSPK = cmd.ScriptPubKey |> Option.defaultValue (state.Commitments.LocalParams.DefaultFinalScriptPubKey) @@ -832,8 +858,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 cd9b94db5..daaff9671 100644 --- a/src/DotNetLightning.Core/Channel/ChannelError.fs +++ b/src/DotNetLightning.Core/Channel/ChannelError.fs @@ -38,6 +38,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 @@ -71,6 +72,7 @@ type ChannelError = | TheyCannotAffordFee (_, _, _) -> Close | InvalidOpenChannel _ -> DistrustPeer | InvalidAcceptChannel _ -> DistrustPeer + | InvalidMonoHopUnidirectionalPayment _ -> Close | InvalidUpdateAddHTLC _ -> Close | InvalidRevokeAndACK _ -> Close | InvalidUpdateFee _ -> Close @@ -129,6 +131,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 -> @@ -181,6 +185,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 @@ -509,6 +525,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) = @@ -523,7 +555,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: UpdateAddHTLCMsg) = 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 349a43b8e..4dbcaa86b 100644 --- a/src/DotNetLightning.Core/Channel/ChannelOperations.fs +++ b/src/DotNetLightning.Core/Channel/ChannelOperations.fs @@ -15,6 +15,10 @@ open NBitcoin open ResultUtils open ResultUtils.Portability +type OperationMonoHopUnidirectionalPayment = { + Amount: LNMoney +} + type OperationAddHTLC = { Amount: LNMoney PaymentHash: PaymentHash @@ -203,6 +207,8 @@ type ChannelCommand = | CreateChannelReestablish // normal + | MonoHopUnidirectionalPayment of OperationMonoHopUnidirectionalPayment + | ApplyMonoHopUnidirectionalPayment of msg: MonoHopUnidirectionalPaymentMsg | AddHTLC of OperationAddHTLC | ApplyUpdateAddHTLC of msg: UpdateAddHTLCMsg * currentHeight: BlockHeight | FulfillHTLC of OperationFulfillHTLC diff --git a/src/DotNetLightning.Core/Channel/ChannelTypes.fs b/src/DotNetLightning.Core/Channel/ChannelTypes.fs index 761f64dca..29931fd97 100644 --- a/src/DotNetLightning.Core/Channel/ChannelTypes.fs +++ b/src/DotNetLightning.Core/Channel/ChannelTypes.fs @@ -276,6 +276,9 @@ type ChannelEvent = | BothFundingLocked of nextState: Data.NormalData // -------- normal operation ------ + | WeAcceptedOperationMonoHopUnidirectionalPayment of msg: MonoHopUnidirectionalPaymentMsg * newCommitments: Commitments + | WeAcceptedMonoHopUnidirectionalPayment of newCommitments: Commitments + | WeAcceptedOperationAddHTLC of msg: UpdateAddHTLCMsg * newCommitments: Commitments | WeAcceptedUpdateAddHTLC of newCommitments: Commitments @@ -368,6 +371,27 @@ type ChannelState = (fun v cc -> match cc with | Normal _ -> Normal v | _ -> cc ) + member this.ChannelId: Option = + match this with + | WaitForInitInternal + | WaitForOpenChannel _ + | WaitForAcceptChannel _ + | WaitForFundingTx _ + | 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 @@ -388,3 +412,25 @@ type ChannelState = | ErrFundingLost _ | ErrFundingTimeOut _ | ErrInformationLeak _ -> Abnormal + + member this.Commitments: Option = + match this with + | WaitForInitInternal + | WaitForOpenChannel _ + | WaitForAcceptChannel _ + | WaitForFundingTx _ + | 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 7a22cc13c..71b0fb3d3 100644 --- a/src/DotNetLightning.Core/Channel/ChannelValidation.fs +++ b/src/DotNetLightning.Core/Channel/ChannelValidation.fs @@ -197,6 +197,13 @@ module internal Validation = *> AcceptChannelMsgValidation.checkConfigPermits conf.PeerChannelConfigLimits msg |> Result.mapError(InvalidAcceptChannelError.Create msg >> InvalidAcceptChannel) + let checkOurMonoHopUnidirectionalPaymentIsAcceptableWithCurrentSpec (currentSpec) (state: Commitments) (payment: MonoHopUnidirectionalPaymentMsg) = + Validation.ofResult(MonoHopUnidirectionalPaymentValidationWithContext.checkWeHaveSufficientFunds state currentSpec) + |> Result.mapError(fun errs -> InvalidMonoHopUnidirectionalPayment { NetworkMsg = payment; Errors = errs }) + + let checkTheirMonoHopUnidirectionalPaymentIsAcceptableWithCurrentSpec (currentSpec) (state: Commitments) (payment: MonoHopUnidirectionalPaymentMsg) = + Validation.ofResult(MonoHopUnidirectionalPaymentValidationWithContext.checkWeHaveSufficientFunds state currentSpec) + |> Result.mapError(fun errs -> InvalidMonoHopUnidirectionalPayment { NetworkMsg = 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 89b303d18..f4b7f06e6 100644 --- a/src/DotNetLightning.Core/Channel/Commitments.fs +++ b/src/DotNetLightning.Core/Channel/Commitments.fs @@ -186,3 +186,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/Serialization/Msgs/Msgs.fs b/src/DotNetLightning.Core/Serialization/Msgs/Msgs.fs index 6ef781222..1b1cdbd6f 100644 --- a/src/DotNetLightning.Core/Serialization/Msgs/Msgs.fs +++ b/src/DotNetLightning.Core/Serialization/Msgs/Msgs.fs @@ -104,6 +104,8 @@ module internal TypeFlag = let ReplyChannelRange = 264us [] let GossipTimestampFilter = 265us + [] + let MonoHopUnidirectionalPayment = 42198us type ILightningMsg = interface end type ISetupMsg = inherit ILightningMsg @@ -195,6 +197,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) = @@ -283,6 +287,9 @@ module ILightningSerializable = | :? GossipTimestampFilterMsg as d -> ls.Write(TypeFlag.GossipTimestampFilter, false) (d :> ILightningSerializable).Serialize(ls) + | :? MonoHopUnidirectionalPaymentMsg 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 = @@ -1572,3 +1579,19 @@ type GossipTimestampFilterMsg = { ls.Write(this.FirstTimestamp, false) ls.Write(this.TimestampRange, false) +[] +type MonoHopUnidirectionalPaymentMsg = { + 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 508d5ff8f..0052b892d 100644 --- a/src/DotNetLightning.Core/Transactions/CommitmentSpec.fs +++ b/src/DotNetLightning.Core/Transactions/CommitmentSpec.fs @@ -53,6 +53,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: MonoHopUnidirectionalPaymentMsg) = + 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: UpdateAddHTLCMsg) = let htlc = { DirectedHTLC.Direction = direction; Add = update } match direction with @@ -82,6 +87,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 + | :? MonoHopUnidirectionalPaymentMsg as u -> acc.MonoHopUnidirectionalPayment(Out, u) + | _ -> acc + ) + this + + let specMonoHopUnidirectionalPaymentRemote = + remoteChanges + |> List.fold(fun (acc: CommitmentSpec) updateMsg -> + match box updateMsg with + | :? MonoHopUnidirectionalPaymentMsg as u -> acc.MonoHopUnidirectionalPayment(In, u) + | _ -> acc + ) + specMonoHopUnidirectionalPaymentLocal + let spec1 = localChanges |> List.fold(fun (acc: CommitmentSpec) updateMsg -> @@ -89,7 +112,7 @@ type CommitmentSpec = { | :? UpdateAddHTLCMsg 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 9ecdfe80b..dfcef90ae 100644 --- a/src/DotNetLightning.Core/Utils/LNMoney.fs +++ b/src/DotNetLightning.Core/Utils/LNMoney.fs @@ -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) From 898f4704c71820d1ba7bdd13b0007539686bfe64 Mon Sep 17 00:00:00 2001 From: Andrew Cann Date: Thu, 12 Nov 2020 14:25:09 +0800 Subject: [PATCH 04/99] Implement revocation (#11) This commit adds the following: * getFundsFromForceClosingTransaction This function takes an on-chain transaction which spends the channel funds and tries to extract spendable utxos out of it. If successful, it returns a TransactionBuilder with txins added for each spendable output of the closing transaction and with the necessary keys and BuilderExtension added. To recover funds from a broadcast commitment transaction you just need to call this function, add outputs to the returned TransactionBuilder to send the money where you want it to go, then broadcast the transaction. * CommitmentToLocalBuilderExtension This is an NBitcoin BuilderExtension that tells TransactionBuilder how to recognise and sign lightning commitment transaction to_local outputs. getFundsFromForceClosingTransaction adds this extension to the TransactionBuilder it returns if it's needed to sign the transaction. * SeqConsumer A computation expression which makes it easy to write code that consumes a sequence one element at a time. * OptionCE A computation expression for creation options. Similar to the result computation expression. --- src/DotNetLightning.Core/Channel/Channel.fs | 1 - .../Channel/CommitmentToLocalExtension.fs | 127 +++++++++++++ .../Channel/CommitmentsModule.fs | 176 ++++++++++++++++++ .../DotNetLightning.Core.fsproj | 2 + src/DotNetLightning.Core/Utils/Keys.fs | 6 + src/DotNetLightning.Core/Utils/SeqConsumer.fs | 59 ++++++ src/ResultUtils/OptionCE.fs | 73 ++++++++ src/ResultUtils/ResultUtils.fsproj | 1 + 8 files changed, 444 insertions(+), 1 deletion(-) create mode 100644 src/DotNetLightning.Core/Channel/CommitmentToLocalExtension.fs create mode 100644 src/DotNetLightning.Core/Utils/SeqConsumer.fs create mode 100644 src/ResultUtils/OptionCE.fs diff --git a/src/DotNetLightning.Core/Channel/Channel.fs b/src/DotNetLightning.Core/Channel/Channel.fs index 269d755fb..16b566d84 100644 --- a/src/DotNetLightning.Core/Channel/Channel.fs +++ b/src/DotNetLightning.Core/Channel/Channel.fs @@ -577,7 +577,6 @@ module Channel = RemoteCommit = theirNextCommit RemoteNextCommitInfo = RemoteNextCommitInfo.Revoked msg.NextPerCommitmentPoint RemotePerCommitmentSecrets = remotePerCommitmentSecrets } - Console.WriteLine("WARNING: revocation is not implemented yet") Ok [ WeAcceptedRevokeAndACK(commitments1) ] | ChannelState.Normal state, ChannelCommand.Close cmd -> diff --git a/src/DotNetLightning.Core/Channel/CommitmentToLocalExtension.fs b/src/DotNetLightning.Core/Channel/CommitmentToLocalExtension.fs new file mode 100644 index 000000000..ad4ca3297 --- /dev/null +++ b/src/DotNetLightning.Core/Channel/CommitmentToLocalExtension.fs @@ -0,0 +1,127 @@ +namespace DotNetLightning.Channel + +open NBitcoin +open NBitcoin.BuilderExtensions +open DotNetLightning.Utils +open DotNetLightning.Utils.SeqConsumer +open DotNetLightning.Crypto + +type CommitmentToLocalParameters = { + RevocationPubKey: RevocationPubKey + ToSelfDelay: BlockHeightOffset16 + LocalDelayedPubKey: DelayedPaymentPubKey +} + with + static member TryExtractParameters (scriptPubKey: Script): Option = + let ops = + scriptPubKey.ToOps() + // we have to collect it into a list and convert back to a seq + // because the IEnumerable that NBitcoin gives us is internally + // mutable. + |> List.ofSeq + |> Seq.ofList + let checkOpCode(opcodeType: OpcodeType) = seqConsumer { + let! op = NextInSeq() + if op.Code = opcodeType then + return () + else + return! AbortSeqConsumer() + } + let consumeAllResult = + SeqConsumer.ConsumeAll ops <| seqConsumer { + do! checkOpCode OpcodeType.OP_IF + let! opRevocationPubKey = NextInSeq() + let! revocationPubKey = seqConsumer { + match opRevocationPubKey.PushData with + | null -> return! AbortSeqConsumer() + | bytes -> return RevocationPubKey.FromBytes bytes // FIXME: catch exception + } + do! checkOpCode OpcodeType.OP_ELSE + let! opToSelfDelay = NextInSeq() + let! toSelfDelay = seqConsumer { + let nullableToSelfDelay = opToSelfDelay.GetLong() + if nullableToSelfDelay.HasValue then + // FIXME: catch exception + return BlockHeightOffset16 (uint16 nullableToSelfDelay.Value) + else + return! AbortSeqConsumer() + } + do! checkOpCode OpcodeType.OP_CHECKSEQUENCEVERIFY + do! checkOpCode OpcodeType.OP_DROP + let! opLocalDelayedPubKey = NextInSeq() + let! localDelayedPubKey = seqConsumer { + match opLocalDelayedPubKey.PushData with + | null -> return! AbortSeqConsumer() + | bytes -> return DelayedPaymentPubKey.FromBytes bytes // FIXME: catch exception + } + do! checkOpCode OpcodeType.OP_ENDIF + do! checkOpCode OpcodeType.OP_CHECKSIG + return { + RevocationPubKey = revocationPubKey + ToSelfDelay = toSelfDelay + LocalDelayedPubKey = localDelayedPubKey + } + } + match consumeAllResult with + | Ok data -> Some data + | Error _consumeAllError -> None + +type internal CommitmentToLocalExtension() = + inherit BuilderExtension() + override self.CanGenerateScriptSig (scriptPubKey: Script): bool = + (CommitmentToLocalParameters.TryExtractParameters scriptPubKey).IsSome + + override self.GenerateScriptSig(scriptPubKey: Script, keyRepo: IKeyRepository, signer: ISigner): Script = + let parameters = + match (CommitmentToLocalParameters.TryExtractParameters scriptPubKey) with + | Some parameters -> parameters + | None -> + failwith + "NBitcoin should not call this unless CanGenerateScriptSig returns true" + let pubKey = keyRepo.FindKey scriptPubKey + // FindKey will return null if it can't find a key for + // scriptPubKey. If we can't find a valid key then this method + // should return null, indicating to NBitcoin that the sigScript + // could not be generated. + match pubKey with + | null -> null + | _ when pubKey = parameters.RevocationPubKey.RawPubKey() -> + let revocationSig = signer.Sign (parameters.RevocationPubKey.RawPubKey()) + Script [ + Op.GetPushOp (revocationSig.ToBytes()) + Op.op_Implicit OpcodeType.OP_TRUE + ] + | _ when pubKey = parameters.LocalDelayedPubKey.RawPubKey() -> + let localDelayedSig = signer.Sign (parameters.LocalDelayedPubKey.RawPubKey()) + Script [ + Op.GetPushOp (localDelayedSig.ToBytes()) + Op.op_Implicit OpcodeType.OP_FALSE + ] + | _ -> null + + override self.CanDeduceScriptPubKey(_scriptSig: Script): bool = + false + + override self.DeduceScriptPubKey(_scriptSig: Script): Script = + raise <| System.NotSupportedException() + + override self.CanEstimateScriptSigSize(_scriptPubKey: Script): bool = + false + + override self.EstimateScriptSigSize(_scriptPubKey: Script): int = + raise <| System.NotSupportedException() + + override self.CanCombineScriptSig(_scriptPubKey: Script, _a: Script, _b: Script): bool = + false + + override self.CombineScriptSig(_scriptPubKey: Script, _a: Script, _b: Script): Script = + raise <| System.NotSupportedException() + + override self.IsCompatibleKey(pubKey: PubKey, scriptPubKey: Script): bool = + match CommitmentToLocalParameters.TryExtractParameters scriptPubKey with + | None -> false + | Some parameters -> + parameters.RevocationPubKey.RawPubKey() = pubKey + || parameters.LocalDelayedPubKey.RawPubKey() = pubKey + + diff --git a/src/DotNetLightning.Core/Channel/CommitmentsModule.fs b/src/DotNetLightning.Core/Channel/CommitmentsModule.fs index 7c83b3d26..fa9bc90b2 100644 --- a/src/DotNetLightning.Core/Channel/CommitmentsModule.fs +++ b/src/DotNetLightning.Core/Channel/CommitmentsModule.fs @@ -415,3 +415,179 @@ module internal Commitments = OriginChannels = originChannels1 } return [ WeAcceptedCommitmentSigned(nextMsg, nextCommitments) ;] } + +module RemoteForceClose = + // The lightning spec specifies that commitment txs use version 2 bitcoin transactions. + let TxVersionNumberOfCommitmentTxs = 2u + + let check(thing: bool): Option = + if thing then + Some () + else + None + + let tryGetObscuredCommitmentNumber (fundingOutPoint: OutPoint) + (transaction: Transaction) + : Option = option { + do! check (transaction.Version = TxVersionNumberOfCommitmentTxs) + let! txIn = Seq.tryExactlyOne transaction.Inputs + do! check (fundingOutPoint = txIn.PrevOut) + let! obscuredCommitmentNumber = + ObscuredCommitmentNumber.TryFromLockTimeAndSequence transaction.LockTime txIn.Sequence + return obscuredCommitmentNumber + } + + let tryGetFundsFromRemoteCommitmentTx (commitments: Commitments) + (localChannelPrivKeys: ChannelPrivKeys) + (network: Network) + (transaction: Transaction) + : Option = option { + let! obscuredCommitmentNumber = + tryGetObscuredCommitmentNumber + commitments.FundingScriptCoin.Outpoint + transaction + let localChannelPubKeys = commitments.LocalParams.ChannelPubKeys + let remoteChannelPubKeys = commitments.RemoteParams.ChannelPubKeys + let commitmentNumber = + obscuredCommitmentNumber.Unobscure + false + localChannelPubKeys.PaymentBasepoint + remoteChannelPubKeys.PaymentBasepoint + let perCommitmentSecretOpt = + commitments.RemotePerCommitmentSecrets.GetPerCommitmentSecret commitmentNumber + let! perCommitmentPoint = + match perCommitmentSecretOpt with + | Some perCommitmentSecret -> Some <| perCommitmentSecret.PerCommitmentPoint() + | None -> + if commitments.RemoteCommit.Index = commitmentNumber then + Some commitments.RemoteCommit.RemotePerCommitmentPoint + else + None + + let localCommitmentPubKeys = + perCommitmentPoint.DeriveCommitmentPubKeys localChannelPubKeys + let remoteCommitmentPubKeys = + perCommitmentPoint.DeriveCommitmentPubKeys remoteChannelPubKeys + + let transactionBuilder = network.CreateTransactionBuilder() + + let toRemoteScriptPubKey = + localCommitmentPubKeys.PaymentPubKey.RawPubKey().WitHash.ScriptPubKey + let toRemoteIndexOpt = + Seq.tryFindIndex + (fun (txOut: TxOut) -> txOut.ScriptPubKey = toRemoteScriptPubKey) + transaction.Outputs + let spentToRemoteOutput = + match toRemoteIndexOpt with + | None -> false + | Some toRemoteIndex -> + let localPaymentPrivKey = + perCommitmentPoint.DerivePaymentPrivKey + localChannelPrivKeys.PaymentBasepointSecret + (transactionBuilder.AddKeys (localPaymentPrivKey.RawKey())) + .AddCoins (Coin(transaction, uint32 toRemoteIndex)) + |> ignore + true + + let spentToLocalOutput = + match perCommitmentSecretOpt with + | None -> false + | Some perCommitmentSecret -> + let toLocalScriptPubKey = + Scripts.toLocalDelayed + localCommitmentPubKeys.RevocationPubKey + commitments.RemoteParams.ToSelfDelay + remoteCommitmentPubKeys.DelayedPaymentPubKey + let toLocalIndexOpt = + let toLocalWitScriptPubKey = toLocalScriptPubKey.WitHash.ScriptPubKey + Seq.tryFindIndex + (fun (txOut: TxOut) -> txOut.ScriptPubKey = toLocalWitScriptPubKey) + transaction.Outputs + match toLocalIndexOpt with + | None -> false + | Some toLocalIndex -> + let revocationPrivKey = + perCommitmentSecret.DeriveRevocationPrivKey + localChannelPrivKeys.RevocationBasepointSecret + transactionBuilder.Extensions.Add (CommitmentToLocalExtension()) + (transactionBuilder.AddKeys (revocationPrivKey.RawKey())) + .AddCoins + (ScriptCoin(transaction, uint32 toLocalIndex, toLocalScriptPubKey)) + |> ignore + true + + do! check (spentToRemoteOutput || spentToLocalOutput) + + return transactionBuilder + } + + let tryGetFundsFromLocalCommitmentTx (commitments: Commitments) + (localChannelPrivKeys: ChannelPrivKeys) + (network: Network) + (transaction: Transaction) + : Option = option { + let! obscuredCommitmentNumber = + tryGetObscuredCommitmentNumber + commitments.FundingScriptCoin.Outpoint + transaction + let localChannelPubKeys = commitments.LocalParams.ChannelPubKeys + let remoteChannelPubKeys = commitments.RemoteParams.ChannelPubKeys + let commitmentNumber = + obscuredCommitmentNumber.Unobscure + true + localChannelPubKeys.PaymentBasepoint + remoteChannelPubKeys.PaymentBasepoint + + let perCommitmentPoint = + localChannelPrivKeys.CommitmentSeed.DerivePerCommitmentPoint commitmentNumber + let localCommitmentPubKeys = + perCommitmentPoint.DeriveCommitmentPubKeys localChannelPubKeys + let remoteCommitmentPubKeys = + perCommitmentPoint.DeriveCommitmentPubKeys remoteChannelPubKeys + + let transactionBuilder = network.CreateTransactionBuilder() + + let toLocalScriptPubKey = + Scripts.toLocalDelayed + remoteCommitmentPubKeys.RevocationPubKey + commitments.LocalParams.ToSelfDelay + localCommitmentPubKeys.DelayedPaymentPubKey + let! toLocalIndex = + let toLocalWitScriptPubKey = toLocalScriptPubKey.WitHash.ScriptPubKey + Seq.tryFindIndex + (fun (txOut: TxOut) -> txOut.ScriptPubKey = toLocalWitScriptPubKey) + transaction.Outputs + + let delayedPaymentPrivKey = + perCommitmentPoint.DeriveDelayedPaymentPrivKey + localChannelPrivKeys.DelayedPaymentBasepointSecret + transactionBuilder.Extensions.Add (CommitmentToLocalExtension()) + (transactionBuilder.AddKeys (delayedPaymentPrivKey.RawKey())) + .AddCoins + (ScriptCoin(transaction, uint32 toLocalIndex, toLocalScriptPubKey)) + |> ignore + + return transactionBuilder + } + + let getFundsFromForceClosingTransaction (commitments: Commitments) + (localChannelPrivKeys: ChannelPrivKeys) + (network: Network) + (transaction: Transaction) + : Option = + let attemptsSeq = seq { + yield + tryGetFundsFromRemoteCommitmentTx + commitments + localChannelPrivKeys + network + transaction + yield + tryGetFundsFromLocalCommitmentTx + commitments + localChannelPrivKeys + network + transaction + } + Seq.tryPick (fun opt -> opt) attemptsSeq + diff --git a/src/DotNetLightning.Core/DotNetLightning.Core.fsproj b/src/DotNetLightning.Core/DotNetLightning.Core.fsproj index 6e994d024..45650fc31 100644 --- a/src/DotNetLightning.Core/DotNetLightning.Core.fsproj +++ b/src/DotNetLightning.Core/DotNetLightning.Core.fsproj @@ -27,6 +27,7 @@ + @@ -68,6 +69,7 @@ + diff --git a/src/DotNetLightning.Core/Utils/Keys.fs b/src/DotNetLightning.Core/Utils/Keys.fs index 791e288bc..78345743a 100644 --- a/src/DotNetLightning.Core/Utils/Keys.fs +++ b/src/DotNetLightning.Core/Utils/Keys.fs @@ -62,6 +62,9 @@ type RevocationPubKey = let (RevocationPubKey pubKey) = this pubKey + static member FromBytes(bytes: array): RevocationPubKey = + RevocationPubKey <| PubKey bytes + member this.ToBytes(): array = this.RawPubKey().ToBytes() @@ -154,6 +157,9 @@ type DelayedPaymentPubKey = let (DelayedPaymentPubKey pubKey) = this pubKey + static member FromBytes(bytes: array): DelayedPaymentPubKey = + DelayedPaymentPubKey <| PubKey bytes + member this.ToBytes(): array = this.RawPubKey().ToBytes() diff --git a/src/DotNetLightning.Core/Utils/SeqConsumer.fs b/src/DotNetLightning.Core/Utils/SeqConsumer.fs new file mode 100644 index 000000000..6728fc306 --- /dev/null +++ b/src/DotNetLightning.Core/Utils/SeqConsumer.fs @@ -0,0 +1,59 @@ +namespace DotNetLightning.Utils + +type SeqConsumer<'SeqElement, 'T> = { + Consume: seq<'SeqElement> -> Option * 'T> +} + +type SeqConsumerBuilder<'SeqElement>() = + member __.Bind<'Arg, 'Return>(seqConsumer0: SeqConsumer<'SeqElement, 'Arg>, + func: 'Arg -> SeqConsumer<'SeqElement, 'Return> + ): SeqConsumer<'SeqElement, 'Return> = { + Consume = fun (sequence0: seq<'SeqElement>) -> + match seqConsumer0.Consume sequence0 with + | None -> None + | Some (sequence1, value0) -> + let seqConsumer1 = func value0 + seqConsumer1.Consume sequence1 + } + + member __.Return<'T>(value: 'T) + : SeqConsumer<'SeqElement, 'T> = { + Consume = fun (sequence: seq<'SeqElement>) -> Some (sequence, value) + } + + member __.ReturnFrom<'T>(seqConsumer: SeqConsumer<'SeqElement, 'T>): SeqConsumer<'SeqElement, 'T> = + seqConsumer + + member __.Zero(): SeqConsumer<'SeqElement, unit> = { + Consume = fun (sequence: seq<'SeqElement>) -> Some (sequence, ()) + } + +module SeqConsumer = + let seqConsumer<'SeqElement> = SeqConsumerBuilder<'SeqElement>() + + let NextInSeq<'SeqElement>(): SeqConsumer<'SeqElement, 'SeqElement> = { + Consume = fun (sequence: seq<'SeqElement>) -> + match Seq.tryHead sequence with + | None -> None + | Some value -> Some (Seq.tail sequence, value) + } + + let AbortSeqConsumer<'SeqElement, 'T>(): SeqConsumer<'SeqElement, 'T> = { + Consume = fun (_sequence: seq<'SeqElement>) -> None + } + + type ConsumeAllError = + | SequenceEndedTooEarly + | SequenceNotReadToEnd + + let ConsumeAll<'SeqElement, 'T>(sequence: seq<'SeqElement>) (seqConsumer: SeqConsumer<'SeqElement, 'T>) + : Result<'T, ConsumeAllError> = + match seqConsumer.Consume sequence with + | None -> Error SequenceEndedTooEarly + | Some (consumedSequence, value) -> + if Seq.isEmpty consumedSequence then + Ok value + else + Error SequenceNotReadToEnd + + diff --git a/src/ResultUtils/OptionCE.fs b/src/ResultUtils/OptionCE.fs new file mode 100644 index 000000000..6ecd18c45 --- /dev/null +++ b/src/ResultUtils/OptionCE.fs @@ -0,0 +1,73 @@ +namespace ResultUtils + +open System + +[] +module OptionCE = + type OptionBuilder() = + member __.Return<'T>(value: 'T): Option<'T> = + Some value + + member __.ReturnFrom<'T>(opt: Option<'T>): Option<'T> = + opt + + member this.Zero(): Option = + Some () + + member __.Bind<'T, 'U>(opt: Option<'T>, binder: 'T -> Option<'U>) + : Option<'U> = + Option.bind binder opt + + member __.Delay<'T>(continuation: unit -> Option<'T>) + : unit -> Option<'T> = + continuation + + member __.Run<'T>(continuation: unit -> Option<'T>) + : Option<'T> = + continuation() + + member this.Combine<'T>(opt: Option, binder: unit -> Option<'T>) + : Option<'T> = + this.Bind(opt, binder) + + member this.TryWith<'T>(generator: unit -> Option<'T>, handler: exn -> Option<'T>) + : Option<'T> = + try + this.Run generator + with + | e -> handler e + + member this.TryFinally<'T>(generator: unit -> Option<'T>, final: unit -> unit) + : Option<'T> = + try + this.Run generator + finally + final() + + member this.Using<'T, 'U when 'T :> IDisposable>(resource: 'T, binder: 'T -> Option<'U>) + : Option<'U> = + this.TryFinally ( + (fun () -> binder resource), + (fun () -> + if not <| obj.ReferenceEquals(resource, null) then + resource.Dispose () + ) + ) + + member this.While(guard: unit -> bool, generator: unit -> Option) + : Option = + if not <| guard () then + this.Zero () + else + this.Bind(this.Run generator, fun () -> this.While (guard, generator)) + + member this.For<'T, 'Sequence when 'Sequence :> seq<'T> >(sequence: 'Sequence, binder: 'T -> Option) + : Option = + this.Using(sequence.GetEnumerator (), fun enumerator -> + this.While(enumerator.MoveNext, this.Delay(fun () -> binder enumerator.Current)) + ) + +[] +module OptionCEExtensions = + let option = OptionBuilder() + diff --git a/src/ResultUtils/ResultUtils.fsproj b/src/ResultUtils/ResultUtils.fsproj index 0d2c2ec83..409d5b895 100644 --- a/src/ResultUtils/ResultUtils.fsproj +++ b/src/ResultUtils/ResultUtils.fsproj @@ -9,6 +9,7 @@ + From 1779487aaaced611755ddeaf42a2d10c5c38fe33 Mon Sep 17 00:00:00 2001 From: Andrew Cann Date: Tue, 24 Nov 2020 16:48:05 +0800 Subject: [PATCH 05/99] Re-add txout shuffling work-around (#13) This NBitcoin issue: https://github.com/MetacoSA/NBitcoin/issues/931 somehow still hasn't been fixed properly (our geewallet CI still encounters it, surprisingly). So let's reapply the workaround[1] that we had removed[2]. [1] https://github.com/joemphilips/DotNetLightning/commit/d813a979b50c4966d2cdd8902228bcf135a6d8a0 [2] https://github.com/joemphilips/DotNetLightning/commit/256893cf18136e0d6f88e67c7702a3b85f48ed77 --- src/DotNetLightning.Core/Transactions/Transactions.fs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/DotNetLightning.Core/Transactions/Transactions.fs b/src/DotNetLightning.Core/Transactions/Transactions.fs index 012d8d4a2..284676685 100644 --- a/src/DotNetLightning.Core/Transactions/Transactions.fs +++ b/src/DotNetLightning.Core/Transactions/Transactions.fs @@ -304,6 +304,7 @@ module Transactions = let txb = network.CreateTransactionBuilder() txb.ShuffleOutputs <- false txb.ShuffleInputs <- false + txb.ShuffleRandom <- null txb let UINT32_MAX = 0xffffffffu From ceb40f0cdef0fa23cfa6de19cdc7780056af5331 Mon Sep 17 00:00:00 2001 From: Andrew Cann Date: Thu, 26 Nov 2020 15:10:29 +0800 Subject: [PATCH 06/99] Exclude macaroons tests on CI These tests are failing randomly and macaroons aren't used by geewallet anyway. So exclude them from CI. --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 43e80c85b..78ab8a5ce 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -30,7 +30,7 @@ jobs: dotnet run --project tests/DotNetLightning.Core.Tests - name: Run other tests run: | - dotnet test + dotnet test --filter FullyQualifiedName\!~Macaroons build_with_fsharp_from_mono: runs-on: ubuntu-18.04 From f2bde1f8e080dbf3b8a4ef8e70c3aadcb4f0db70 Mon Sep 17 00:00:00 2001 From: Andrew Cann Date: Mon, 23 Nov 2020 15:43:08 +0800 Subject: [PATCH 07/99] Move and export fee mismatch calculation function Move/rename ChannelError.feeRateMismatch to FeeRatePerKw.MismatchRatio. This function is useful outside of DotNetLightning, so we now export it so that library consumers can use it. --- src/DotNetLightning.Core/Channel/ChannelError.fs | 9 ++------- src/DotNetLightning.Core/Utils/Primitives.fs | 6 ++++++ 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/src/DotNetLightning.Core/Channel/ChannelError.fs b/src/DotNetLightning.Core/Channel/ChannelError.fs index daaff9671..78ad45f81 100644 --- a/src/DotNetLightning.Core/Channel/ChannelError.fs +++ b/src/DotNetLightning.Core/Channel/ChannelError.fs @@ -257,11 +257,6 @@ module private ValidationHelper = /// Helpers to create channel error [] module internal ChannelError = - let feeRateMismatch (FeeRatePerKw remote, FeeRatePerKw local) = - let remote = float remote - let local = float local - abs (2.0 * (remote - local) / (remote + local)) - let inline feeDeltaTooHigh msg (actualDelta, maxAccepted) = InvalidUpdateFeeError.Create msg @@ -368,7 +363,7 @@ module internal OpenChannelMsgValidation = (maxFeeRateMismatchRatio: float) = let localFeeRatePerKw = feeEstimator.GetEstSatPer1000Weight(ConfirmationTarget.Background) - let diff = feeRateMismatch(remoteFeeRatePerKw, localFeeRatePerKw) + let diff = remoteFeeRatePerKw.MismatchRatio localFeeRatePerKw if (diff > maxFeeRateMismatchRatio) then sprintf "Peer's feerate (%A) was unacceptably far from the estimated fee rate of %A" @@ -602,7 +597,7 @@ module internal UpdateAddHTLCValidationWithContext = module internal UpdateFeeValidation = let checkFeeDiffTooHigh (msg: UpdateFeeMsg) (localFeeRatePerKw: FeeRatePerKw) (maxFeeRateMismatchRatio) = let remoteFeeRatePerKw = msg.FeeRatePerKw - let diff = feeRateMismatch(remoteFeeRatePerKw, localFeeRatePerKw) + let diff = remoteFeeRatePerKw.MismatchRatio localFeeRatePerKw if (diff > maxFeeRateMismatchRatio) then (diff, maxFeeRateMismatchRatio) |> feeDeltaTooHigh msg diff --git a/src/DotNetLightning.Core/Utils/Primitives.fs b/src/DotNetLightning.Core/Utils/Primitives.fs index cbd9af6d4..9eff5f6d2 100644 --- a/src/DotNetLightning.Core/Utils/Primitives.fs +++ b/src/DotNetLightning.Core/Utils/Primitives.fs @@ -338,12 +338,18 @@ module Primitives = member this.AsNBitcoinFeeRate() = this.Value |> uint64 |> (*)4UL |> Money.Satoshis |> FeeRate + member this.MismatchRatio (other: FeeRatePerKw) = + let local = double this.Value + let remote = double other.Value + abs (2.0 * (remote - local) / (remote + local)) + static member Max(a: FeeRatePerKw, b: FeeRatePerKw) = if (a.Value >= b.Value) then a else b static member (+) (a: FeeRatePerKw, b: uint32) = (a.Value + b) |> FeeRatePerKw static member (*) (a: FeeRatePerKw, b: uint32) = (a.Value * b) |> FeeRatePerKw + /// Block Hash type BlockId = | BlockId of uint256 with member x.Value = let (BlockId v) = x in v From 0e0a9281c8b08294230833dce1055f107bd4e648 Mon Sep 17 00:00:00 2001 From: Andrew Cann Date: Wed, 25 Nov 2020 15:45:06 +0800 Subject: [PATCH 08/99] Actually apply update fee messages Prior to this commit, apply an update_fee message would cause DNL to validate the message but not actually apply it to its commitments. It now updates its commitments as it should. --- src/DotNetLightning.Core/Channel/Channel.fs | 3 ++- src/DotNetLightning.Core/Channel/ChannelTypes.fs | 2 +- .../Channel/CommitmentsModule.fs | 15 +++++++++------ 3 files changed, 12 insertions(+), 8 deletions(-) diff --git a/src/DotNetLightning.Core/Channel/Channel.fs b/src/DotNetLightning.Core/Channel/Channel.fs index 16b566d84..8c999f00e 100644 --- a/src/DotNetLightning.Core/Channel/Channel.fs +++ b/src/DotNetLightning.Core/Channel/Channel.fs @@ -883,7 +883,8 @@ module Channel = | WeAcceptedOperationUpdateFee(_msg, newCommitments), ChannelState.Normal d -> { c with State = ChannelState.Normal({ d with Commitments = newCommitments }) } - | WeAcceptedUpdateFee(_msg), ChannelState.Normal _d -> c + | WeAcceptedUpdateFee(_msg, newCommitments), ChannelState.Normal normalData -> + { c with State = ChannelState.Normal({ normalData with Commitments = newCommitments }) } | WeAcceptedOperationSign(_msg, newCommitments), ChannelState.Normal d -> { c with State = ChannelState.Normal({ d with Commitments = newCommitments }) } diff --git a/src/DotNetLightning.Core/Channel/ChannelTypes.fs b/src/DotNetLightning.Core/Channel/ChannelTypes.fs index 29931fd97..19ec6caf5 100644 --- a/src/DotNetLightning.Core/Channel/ChannelTypes.fs +++ b/src/DotNetLightning.Core/Channel/ChannelTypes.fs @@ -292,7 +292,7 @@ type ChannelEvent = | WeAcceptedFailMalformedHTLC of origin: HTLCSource * msg: UpdateAddHTLCMsg * newCommitments: Commitments | WeAcceptedOperationUpdateFee of msg: UpdateFeeMsg * nextCommitments: Commitments - | WeAcceptedUpdateFee of msg: UpdateFeeMsg + | WeAcceptedUpdateFee of msg: UpdateFeeMsg * newCommitments: Commitments | WeAcceptedOperationSign of msg: CommitmentSignedMsg * nextCommitments: Commitments | WeAcceptedCommitmentSigned of msg: RevokeAndACKMsg * nextCommitments: Commitments diff --git a/src/DotNetLightning.Core/Channel/CommitmentsModule.fs b/src/DotNetLightning.Core/Channel/CommitmentsModule.fs index fa9bc90b2..342f1f2e0 100644 --- a/src/DotNetLightning.Core/Channel/CommitmentsModule.fs +++ b/src/DotNetLightning.Core/Channel/CommitmentsModule.fs @@ -249,19 +249,22 @@ module internal Commitments = else result { do! Helpers.checkUpdateFee (config) (msg) (localFeerate) - let c1 = cm.AddRemoteProposal(msg) + let nextCommitments = cm.AddRemoteProposal(msg) let! reduced = - c1.LocalCommit.Spec.Reduce(c1.LocalChanges.ACKed, c1.RemoteChanges.Proposed) |> expectTransactionError + nextCommitments.LocalCommit.Spec.Reduce( + nextCommitments.LocalChanges.ACKed, + nextCommitments.RemoteChanges.Proposed + ) |> expectTransactionError - let fees = Transactions.commitTxFee(c1.RemoteParams.DustLimitSatoshis) reduced - let missing = reduced.ToRemote.ToMoney() - c1.RemoteParams.ChannelReserveSatoshis - fees + let fees = Transactions.commitTxFee(nextCommitments.RemoteParams.DustLimitSatoshis) reduced + let missing = reduced.ToRemote.ToMoney() - nextCommitments.RemoteParams.ChannelReserveSatoshis - fees if (missing < Money.Zero) then return! - (c1.LocalParams.ChannelReserveSatoshis, fees, (-1 * missing)) + (nextCommitments.LocalParams.ChannelReserveSatoshis, fees, (-1 * missing)) |> cannotAffordFee else return - [ WeAcceptedUpdateFee msg ] + [ WeAcceptedUpdateFee(msg, nextCommitments) ] } let sendCommit (channelPrivKeys: ChannelPrivKeys) (n: Network) (cm: Commitments) = From 863a8283fc128759c4e0217b4ea165c6735d7fe4 Mon Sep 17 00:00:00 2001 From: Andrew Cann Date: Thu, 26 Nov 2020 18:21:06 +0800 Subject: [PATCH 09/99] Remove pre-channel states These states don't have commitments, only exist before the existence of a channel is confirmed, don't need to be saved in a wallet, and can only handle one specific ChannelCommand each. As such, it doesn't make much sense to have them be part of ChannelState. They can instead be seperate types which exist prior to the creation of a channel and which have specialized methods for performing the single operation which they can handle. --- src/DotNetLightning.Core/Channel/Channel.fs | 621 ++++++++++-------- .../Channel/ChannelOperations.fs | 2 - .../Channel/ChannelTypes.fs | 38 +- 3 files changed, 348 insertions(+), 313 deletions(-) diff --git a/src/DotNetLightning.Core/Channel/Channel.fs b/src/DotNetLightning.Core/Channel/Channel.fs index 8c999f00e..cd18b5054 100644 --- a/src/DotNetLightning.Core/Channel/Channel.fs +++ b/src/DotNetLightning.Core/Channel/Channel.fs @@ -13,7 +13,258 @@ open System open ResultUtils open ResultUtils.Portability -type Channel = { +type ChannelWaitingForFundingSigned = { + Config: ChannelConfig + ChannelPrivKeys: ChannelPrivKeys + FeeEstimator: IFeeEstimator + RemoteNodeId: NodeId + NodeSecret: NodeSecret + Network: Network + WaitForFundingSignedData: WaitForFundingSignedData +} with + member self.ApplyFundingSigned (msg: FundingSignedMsg) + : Result = result { + let state = self.WaitForFundingSignedData + let remoteChannelKeys = state.RemoteParams.ChannelPubKeys + let! finalizedLocalCommitTx = + let theirFundingPk = remoteChannelKeys.FundingPubKey.RawPubKey() + let _, signedLocalCommitTx = + self.ChannelPrivKeys.SignWithFundingPrivKey state.LocalCommitTx.Value + let remoteSigPairOfLocalTx = (theirFundingPk, TransactionSignature(msg.Signature.Value, SigHash.All)) + let sigPairs = seq [ remoteSigPairOfLocalTx; ] + Transactions.checkTxFinalized signedLocalCommitTx state.LocalCommitTx.WhichInput sigPairs |> expectTransactionError + let commitments = { Commitments.LocalParams = state.LocalParams + RemoteParams = state.RemoteParams + ChannelFlags = state.ChannelFlags + FundingScriptCoin = + let amount = state.FundingTx.Value.Outputs.[int state.LastSent.FundingOutputIndex.Value].Value + ChannelHelpers.getFundingScriptCoin + state.LocalParams.ChannelPubKeys.FundingPubKey + remoteChannelKeys.FundingPubKey + state.LastSent.FundingTxId + state.LastSent.FundingOutputIndex + amount + LocalCommit = { Index = CommitmentNumber.FirstCommitment; + Spec = state.LocalSpec; + PublishableTxs = { PublishableTxs.CommitTx = finalizedLocalCommitTx + HTLCTxs = [] } + PendingHTLCSuccessTxs = [] } + RemoteCommit = state.RemoteCommit + LocalChanges = LocalChanges.Zero + RemoteChanges = RemoteChanges.Zero + LocalNextHTLCId = HTLCId.Zero + RemoteNextHTLCId = HTLCId.Zero + OriginChannels = Map.empty + // we will receive their next per-commitment point in the next msg, so we temporarily put a random byte array + RemoteNextCommitInfo = DataEncoders.HexEncoder().DecodeData("0101010101010101010101010101010101010101010101010101010101010101") |> fun h -> new Key(h) |> fun k -> k.PubKey |> PerCommitmentPoint |> RemoteNextCommitInfo.Revoked + RemotePerCommitmentSecrets = PerCommitmentSecretStore() + ChannelId = + msg.ChannelId } + let nextState = { WaitForFundingConfirmedData.Commitments = commitments + Deferred = None + LastSent = Choice1Of2 state.LastSent + InitialFeeRatePerKw = state.InitialFeeRatePerKw + ChannelId = msg.ChannelId } + let channel = { + Config = self.Config + ChannelPrivKeys = self.ChannelPrivKeys + FeeEstimator = self.FeeEstimator + RemoteNodeId = self.RemoteNodeId + NodeSecret = self.NodeSecret + State = WaitForFundingConfirmed nextState + Network = self.Network + } + return state.FundingTx, channel + } + +and ChannelWaitingForFundingCreated = { + Config: ChannelConfig + ChannelPrivKeys: ChannelPrivKeys + FeeEstimator: IFeeEstimator + RemoteNodeId: NodeId + NodeSecret: NodeSecret + Network: Network + WaitForFundingCreatedData: WaitForFundingCreatedData +} with + member self.ApplyFundingCreated (msg: FundingCreatedMsg) + : Result = result { + let state = self.WaitForFundingCreatedData + let remoteChannelKeys = state.RemoteParams.ChannelPubKeys + let! (localSpec, localCommitTx, remoteSpec, remoteCommitTx) = + ChannelHelpers.makeFirstCommitTxs + state.LocalParams + state.RemoteParams + state.FundingSatoshis + state.PushMSat + state.InitialFeeRatePerKw + msg.FundingOutputIndex + msg.FundingTxId + state.LastSent.FirstPerCommitmentPoint + state.RemoteFirstPerCommitmentPoint + self.Network + assert (localCommitTx.Value.IsReadyToSign()) + let _s, signedLocalCommitTx = + self.ChannelPrivKeys.SignWithFundingPrivKey localCommitTx.Value + let remoteTxSig = TransactionSignature(msg.Signature.Value, SigHash.All) + let theirSigPair = (remoteChannelKeys.FundingPubKey.RawPubKey(), remoteTxSig) + let sigPairs = seq [ theirSigPair ] + let! finalizedCommitTx = + Transactions.checkTxFinalized (signedLocalCommitTx) (localCommitTx.WhichInput) sigPairs + |> expectTransactionError + let localSigOfRemoteCommit, _ = + self.ChannelPrivKeys.SignWithFundingPrivKey remoteCommitTx.Value + let channelId = OutPoint(msg.FundingTxId.Value, uint32 msg.FundingOutputIndex.Value).ToChannelId() + let msgToSend: FundingSignedMsg = { ChannelId = channelId; Signature = !>localSigOfRemoteCommit.Signature } + let commitments = { Commitments.LocalParams = state.LocalParams + RemoteParams = state.RemoteParams + ChannelFlags = state.ChannelFlags + FundingScriptCoin = + ChannelHelpers.getFundingScriptCoin + state.LocalParams.ChannelPubKeys.FundingPubKey + remoteChannelKeys.FundingPubKey + msg.FundingTxId + msg.FundingOutputIndex + state.FundingSatoshis + LocalCommit = { LocalCommit.Index = CommitmentNumber.FirstCommitment + Spec = localSpec + PublishableTxs = { PublishableTxs.CommitTx = finalizedCommitTx; + HTLCTxs = [] } + PendingHTLCSuccessTxs = [] } + RemoteCommit = { RemoteCommit.Index = CommitmentNumber.FirstCommitment + Spec = remoteSpec + TxId = remoteCommitTx.Value.GetGlobalTransaction().GetTxId() + RemotePerCommitmentPoint = state.RemoteFirstPerCommitmentPoint } + LocalChanges = LocalChanges.Zero + RemoteChanges = RemoteChanges.Zero + LocalNextHTLCId = HTLCId.Zero + RemoteNextHTLCId = HTLCId.Zero + OriginChannels = Map.empty + RemoteNextCommitInfo = DataEncoders.HexEncoder().DecodeData("0101010101010101010101010101010101010101010101010101010101010101") |> fun h -> new Key(h) |> fun k -> k.PubKey |> PerCommitmentPoint |> RemoteNextCommitInfo.Revoked + RemotePerCommitmentSecrets = PerCommitmentSecretStore() + ChannelId = channelId } + let nextState = { WaitForFundingConfirmedData.Commitments = commitments + Deferred = None + LastSent = msgToSend |> Choice2Of2 + InitialFeeRatePerKw = state.InitialFeeRatePerKw + ChannelId = channelId } + let channel = { + Config = self.Config + ChannelPrivKeys = self.ChannelPrivKeys + FeeEstimator = self.FeeEstimator + RemoteNodeId = self.RemoteNodeId + NodeSecret = self.NodeSecret + Network = self.Network + State = WaitForFundingConfirmed nextState + } + return msgToSend, channel + } + +and ChannelWaitingForFundingTx = { + Config: ChannelConfig + ChannelPrivKeys: ChannelPrivKeys + FeeEstimator: IFeeEstimator + RemoteNodeId: NodeId + NodeSecret: NodeSecret + Network: Network + WaitForFundingTxData: WaitForFundingTxData +} with + member self.CreateFundingTx (fundingTx: FinalizedTx) + (outIndex: TxOutIndex) + : Result = result { + let state = self.WaitForFundingTxData + let remoteParams = RemoteParams.FromAcceptChannel self.RemoteNodeId (state.InputInitFunder.RemoteInit) state.LastReceived + let localParams = state.InputInitFunder.LocalParams + assert (state.LastSent.FundingPubKey = localParams.ChannelPubKeys.FundingPubKey) + let commitmentSpec = state.InputInitFunder.DeriveCommitmentSpec() + let commitmentSeed = state.InputInitFunder.ChannelPrivKeys.CommitmentSeed + let fundingTxId = fundingTx.Value.GetTxId() + let! (_localSpec, localCommitTx, remoteSpec, remoteCommitTx) = + ChannelHelpers.makeFirstCommitTxs localParams + remoteParams + state.LastSent.FundingSatoshis + state.LastSent.PushMSat + state.LastSent.FeeRatePerKw + outIndex + fundingTxId + (commitmentSeed.DerivePerCommitmentPoint CommitmentNumber.FirstCommitment) + state.LastReceived.FirstPerCommitmentPoint + self.Network + let localSigOfRemoteCommit, _ = + self.ChannelPrivKeys.SignWithFundingPrivKey remoteCommitTx.Value + let nextMsg: FundingCreatedMsg = { + TemporaryChannelId = state.LastReceived.TemporaryChannelId + FundingTxId = fundingTxId + FundingOutputIndex = outIndex + Signature = !>localSigOfRemoteCommit.Signature + } + let channelId = OutPoint(fundingTxId.Value, uint32 outIndex.Value).ToChannelId() + let waitForFundingSignedData = { + Data.WaitForFundingSignedData.ChannelId = channelId + LocalParams = localParams + RemoteParams = remoteParams + Data.WaitForFundingSignedData.FundingTx = fundingTx + Data.WaitForFundingSignedData.LocalSpec = commitmentSpec + LocalCommitTx = localCommitTx + RemoteCommit = { + RemoteCommit.Index = CommitmentNumber.FirstCommitment + Spec = remoteSpec + TxId = remoteCommitTx.Value.GetGlobalTransaction().GetTxId() + RemotePerCommitmentPoint = state.LastReceived.FirstPerCommitmentPoint + } + ChannelFlags = state.InputInitFunder.ChannelFlags + LastSent = nextMsg + InitialFeeRatePerKw = state.InputInitFunder.InitFeeRatePerKw + } + let channelWaitingForFundingSigned = { + Config = self.Config + ChannelPrivKeys = self.ChannelPrivKeys + FeeEstimator = self.FeeEstimator + RemoteNodeId = self.RemoteNodeId + NodeSecret = self.NodeSecret + Network = self.Network + WaitForFundingSignedData = waitForFundingSignedData + } + return nextMsg, channelWaitingForFundingSigned + } + + +and ChannelWaitingForAcceptChannel = { + Config: ChannelConfig + ChannelPrivKeys: ChannelPrivKeys + FeeEstimator: IFeeEstimator + RemoteNodeId: NodeId + NodeSecret: NodeSecret + Network: Network + WaitForAcceptChannelData: WaitForAcceptChannelData +} with + member self.ApplyAcceptChannel (msg: AcceptChannelMsg) + : Result = result { + let state = self.WaitForAcceptChannelData + do! Validation.checkAcceptChannelMsgAcceptable (self.Config) state msg + let redeem = + Scripts.funding + (state.InputInitFunder.ChannelPrivKeys.ToChannelPubKeys().FundingPubKey) + msg.FundingPubKey + let destination = redeem.WitHash :> IDestination + let amount = state.InputInitFunder.FundingSatoshis + let waitForFundingTxData = { + InputInitFunder = state.InputInitFunder + LastSent = state.LastSent + LastReceived = msg + } + let channelWaitingForFundingTx = { + Config = self.Config + ChannelPrivKeys = self.ChannelPrivKeys + FeeEstimator = self.FeeEstimator + RemoteNodeId = self.RemoteNodeId + NodeSecret = self.NodeSecret + Network = self.Network + WaitForFundingTxData = waitForFundingTxData + } + return destination, amount, channelWaitingForFundingTx + } + +and Channel = { Config: ChannelConfig ChannelPrivKeys: ChannelPrivKeys FeeEstimator: IFeeEstimator @@ -23,25 +274,101 @@ type Channel = { Network: Network } with - static member Create (config: ChannelConfig, - nodeMasterPrivKey: NodeMasterPrivKey, - channelIndex: int, - feeEstimator: IFeeEstimator, - n: Network, - remoteNodeId: NodeId - ) = - let channelPrivKeys = nodeMasterPrivKey.ChannelPrivKeys channelIndex - let nodeSecret = nodeMasterPrivKey.NodeSecret() - { - Config = config - ChannelPrivKeys = channelPrivKeys - FeeEstimator = feeEstimator - RemoteNodeId = remoteNodeId - NodeSecret = nodeSecret - State = WaitForInitInternal - Network = n + static member NewOutbound(config: ChannelConfig, + nodeMasterPrivKey: NodeMasterPrivKey, + channelIndex: int, + feeEstimator: IFeeEstimator, + network: Network, + remoteNodeId: NodeId, + inputInitFunder: InputInitFunder + ): Result = + let openChannelMsgToSend: OpenChannelMsg = { + Chainhash = network.Consensus.HashGenesisBlock + TemporaryChannelId = inputInitFunder.TemporaryChannelId + FundingSatoshis = inputInitFunder.FundingSatoshis + PushMSat = inputInitFunder.PushMSat + DustLimitSatoshis = inputInitFunder.LocalParams.DustLimitSatoshis + MaxHTLCValueInFlightMsat = inputInitFunder.LocalParams.MaxHTLCValueInFlightMSat + ChannelReserveSatoshis = inputInitFunder.LocalParams.ChannelReserveSatoshis + HTLCMinimumMsat = inputInitFunder.LocalParams.HTLCMinimumMSat + FeeRatePerKw = inputInitFunder.InitFeeRatePerKw + ToSelfDelay = inputInitFunder.LocalParams.ToSelfDelay + MaxAcceptedHTLCs = inputInitFunder.LocalParams.MaxAcceptedHTLCs + FundingPubKey = inputInitFunder.ChannelPrivKeys.FundingPrivKey.FundingPubKey() + RevocationBasepoint = inputInitFunder.ChannelPrivKeys.RevocationBasepointSecret.RevocationBasepoint() + PaymentBasepoint = inputInitFunder.ChannelPrivKeys.PaymentBasepointSecret.PaymentBasepoint() + DelayedPaymentBasepoint = inputInitFunder.ChannelPrivKeys.DelayedPaymentBasepointSecret.DelayedPaymentBasepoint() + HTLCBasepoint = inputInitFunder.ChannelPrivKeys.HtlcBasepointSecret.HtlcBasepoint() + FirstPerCommitmentPoint = inputInitFunder.ChannelPrivKeys.CommitmentSeed.DerivePerCommitmentPoint CommitmentNumber.FirstCommitment + ChannelFlags = inputInitFunder.ChannelFlags + ShutdownScriptPubKey = config.ChannelOptions.ShutdownScriptPubKey + } + result { + do! Validation.checkOurOpenChannelMsgAcceptable config openChannelMsgToSend + let channelPrivKeys = nodeMasterPrivKey.ChannelPrivKeys channelIndex + let nodeSecret = nodeMasterPrivKey.NodeSecret() + let waitForAcceptChannelData = { + InputInitFunder = inputInitFunder + LastSent = openChannelMsgToSend + } + let channelWaitingForAcceptChannel = { + Config = config + ChannelPrivKeys = channelPrivKeys + FeeEstimator = feeEstimator + RemoteNodeId = remoteNodeId + NodeSecret = nodeSecret + Network = network + WaitForAcceptChannelData = waitForAcceptChannelData + } + return (openChannelMsgToSend, channelWaitingForAcceptChannel) + } + + static member NewInbound (config: ChannelConfig, + nodeMasterPrivKey: NodeMasterPrivKey, + channelIndex: int, + feeEstimator: IFeeEstimator, + network: Network, + remoteNodeId: NodeId, + inputInitFundee: InputInitFundee, + openChannelMsg: OpenChannelMsg + ): Result = + result { + do! Validation.checkOpenChannelMsgAcceptable feeEstimator config openChannelMsg + let localParams = inputInitFundee.LocalParams + let channelKeys = inputInitFundee.ChannelPrivKeys + let localCommitmentPubKey = channelKeys.CommitmentSeed.DerivePerCommitmentPoint CommitmentNumber.FirstCommitment + let acceptChannelMsg: AcceptChannelMsg = { + TemporaryChannelId = openChannelMsg.TemporaryChannelId + DustLimitSatoshis = localParams.DustLimitSatoshis + MaxHTLCValueInFlightMsat = localParams.MaxHTLCValueInFlightMSat + ChannelReserveSatoshis = localParams.ChannelReserveSatoshis + HTLCMinimumMSat = localParams.HTLCMinimumMSat + MinimumDepth = config.ChannelHandshakeConfig.MinimumDepth + ToSelfDelay = localParams.ToSelfDelay + MaxAcceptedHTLCs = localParams.MaxAcceptedHTLCs + FundingPubKey = channelKeys.FundingPrivKey.FundingPubKey() + RevocationBasepoint = channelKeys.RevocationBasepointSecret.RevocationBasepoint() + PaymentBasepoint = channelKeys.PaymentBasepointSecret.PaymentBasepoint() + DelayedPaymentBasepoint = channelKeys.DelayedPaymentBasepointSecret.DelayedPaymentBasepoint() + HTLCBasepoint = channelKeys.HtlcBasepointSecret.HtlcBasepoint() + FirstPerCommitmentPoint = localCommitmentPubKey + ShutdownScriptPubKey = config.ChannelOptions.ShutdownScriptPubKey + } + let remoteParams = RemoteParams.FromOpenChannel remoteNodeId inputInitFundee.RemoteInit openChannelMsg config.ChannelHandshakeConfig + let waitForFundingCreatedData = Data.WaitForFundingCreatedData.Create localParams remoteParams openChannelMsg acceptChannelMsg + let channelPrivKeys = nodeMasterPrivKey.ChannelPrivKeys channelIndex + let nodeSecret = nodeMasterPrivKey.NodeSecret() + let channelWaitingForFundingCreated = { + Config = config + ChannelPrivKeys = channelPrivKeys + FeeEstimator = feeEstimator + RemoteNodeId = remoteNodeId + NodeSecret = nodeSecret + Network = network + WaitForFundingCreatedData = waitForFundingCreatedData + } + return (acceptChannelMsg, channelWaitingForFundingCreated) } - static member CreateCurried = curry6 (Channel.Create) module Channel = @@ -147,242 +474,10 @@ module Channel = match cs.State, command with // --------------- open channel procedure: case we are funder ------------- - | WaitForInitInternal, CreateOutbound inputInitFunder -> - let openChannelMsgToSend: OpenChannelMsg = { - Chainhash = cs.Network.Consensus.HashGenesisBlock - TemporaryChannelId = inputInitFunder.TemporaryChannelId - FundingSatoshis = inputInitFunder.FundingSatoshis - PushMSat = inputInitFunder.PushMSat - DustLimitSatoshis = inputInitFunder.LocalParams.DustLimitSatoshis - MaxHTLCValueInFlightMsat = inputInitFunder.LocalParams.MaxHTLCValueInFlightMSat - ChannelReserveSatoshis = inputInitFunder.LocalParams.ChannelReserveSatoshis - HTLCMinimumMsat = inputInitFunder.LocalParams.HTLCMinimumMSat - FeeRatePerKw = inputInitFunder.InitFeeRatePerKw - ToSelfDelay = inputInitFunder.LocalParams.ToSelfDelay - MaxAcceptedHTLCs = inputInitFunder.LocalParams.MaxAcceptedHTLCs - FundingPubKey = inputInitFunder.ChannelPrivKeys.FundingPrivKey.FundingPubKey() - RevocationBasepoint = inputInitFunder.ChannelPrivKeys.RevocationBasepointSecret.RevocationBasepoint() - PaymentBasepoint = inputInitFunder.ChannelPrivKeys.PaymentBasepointSecret.PaymentBasepoint() - DelayedPaymentBasepoint = inputInitFunder.ChannelPrivKeys.DelayedPaymentBasepointSecret.DelayedPaymentBasepoint() - HTLCBasepoint = inputInitFunder.ChannelPrivKeys.HtlcBasepointSecret.HtlcBasepoint() - FirstPerCommitmentPoint = inputInitFunder.ChannelPrivKeys.CommitmentSeed.DerivePerCommitmentPoint CommitmentNumber.FirstCommitment - ChannelFlags = inputInitFunder.ChannelFlags - ShutdownScriptPubKey = cs.Config.ChannelOptions.ShutdownScriptPubKey - } - result { - do! Validation.checkOurOpenChannelMsgAcceptable (cs.Config) openChannelMsgToSend - return [ - NewOutboundChannelStarted( - openChannelMsgToSend, { - InputInitFunder = inputInitFunder - LastSent = openChannelMsgToSend - }) - ] - } - | WaitForAcceptChannel state, ApplyAcceptChannel msg -> - result { - do! Validation.checkAcceptChannelMsgAcceptable (cs.Config) state msg - let redeem = - Scripts.funding - (state.InputInitFunder.ChannelPrivKeys.ToChannelPubKeys().FundingPubKey) - msg.FundingPubKey - let destination = redeem.WitHash :> IDestination - let amount = state.InputInitFunder.FundingSatoshis - let nextState = { - InputInitFunder = state.InputInitFunder - LastSent = state.LastSent - LastReceived = msg - } - - return [ WeAcceptedAcceptChannel(destination, amount, nextState) ] - } - | WaitForFundingTx state, CreateFundingTx(fundingTx, outIndex) -> - result { - let remoteParams = RemoteParams.FromAcceptChannel cs.RemoteNodeId (state.InputInitFunder.RemoteInit) state.LastReceived - let localParams = state.InputInitFunder.LocalParams - assert (state.LastSent.FundingPubKey = localParams.ChannelPubKeys.FundingPubKey) - let commitmentSpec = state.InputInitFunder.DeriveCommitmentSpec() - let commitmentSeed = state.InputInitFunder.ChannelPrivKeys.CommitmentSeed - let fundingTxId = fundingTx.Value.GetTxId() - let! (_localSpec, localCommitTx, remoteSpec, remoteCommitTx) = - ChannelHelpers.makeFirstCommitTxs localParams - remoteParams - state.LastSent.FundingSatoshis - state.LastSent.PushMSat - state.LastSent.FeeRatePerKw - outIndex - fundingTxId - (commitmentSeed.DerivePerCommitmentPoint CommitmentNumber.FirstCommitment) - state.LastReceived.FirstPerCommitmentPoint - cs.Network - let localSigOfRemoteCommit, _ = - cs.ChannelPrivKeys.SignWithFundingPrivKey remoteCommitTx.Value - let nextMsg: FundingCreatedMsg = { - TemporaryChannelId = state.LastReceived.TemporaryChannelId - FundingTxId = fundingTxId - FundingOutputIndex = outIndex - Signature = !>localSigOfRemoteCommit.Signature - } - let channelId = OutPoint(fundingTxId.Value, uint32 outIndex.Value).ToChannelId() - let data = { Data.WaitForFundingSignedData.ChannelId = channelId - LocalParams = localParams - RemoteParams = remoteParams - Data.WaitForFundingSignedData.FundingTx = fundingTx - Data.WaitForFundingSignedData.LocalSpec = commitmentSpec - LocalCommitTx = localCommitTx - RemoteCommit = { RemoteCommit.Index = CommitmentNumber.FirstCommitment - Spec = remoteSpec - TxId = remoteCommitTx.Value.GetGlobalTransaction().GetTxId() - RemotePerCommitmentPoint = state.LastReceived.FirstPerCommitmentPoint } - ChannelFlags = state.InputInitFunder.ChannelFlags - LastSent = nextMsg - InitialFeeRatePerKw = state.InputInitFunder.InitFeeRatePerKw } - return [ WeCreatedFundingTx(nextMsg, data) ] - } - | WaitForFundingSigned state, ApplyFundingSigned msg -> - result { - let remoteChannelKeys = state.RemoteParams.ChannelPubKeys - let! finalizedLocalCommitTx = - let theirFundingPk = remoteChannelKeys.FundingPubKey.RawPubKey() - let _, signedLocalCommitTx = - cs.ChannelPrivKeys.SignWithFundingPrivKey state.LocalCommitTx.Value - let remoteSigPairOfLocalTx = (theirFundingPk, TransactionSignature(msg.Signature.Value, SigHash.All)) - let sigPairs = seq [ remoteSigPairOfLocalTx; ] - Transactions.checkTxFinalized signedLocalCommitTx state.LocalCommitTx.WhichInput sigPairs |> expectTransactionError - let commitments = { Commitments.LocalParams = state.LocalParams - RemoteParams = state.RemoteParams - ChannelFlags = state.ChannelFlags - FundingScriptCoin = - let amount = state.FundingTx.Value.Outputs.[int state.LastSent.FundingOutputIndex.Value].Value - ChannelHelpers.getFundingScriptCoin - state.LocalParams.ChannelPubKeys.FundingPubKey - remoteChannelKeys.FundingPubKey - state.LastSent.FundingTxId - state.LastSent.FundingOutputIndex - amount - LocalCommit = { Index = CommitmentNumber.FirstCommitment; - Spec = state.LocalSpec; - PublishableTxs = { PublishableTxs.CommitTx = finalizedLocalCommitTx - HTLCTxs = [] } - PendingHTLCSuccessTxs = [] } - RemoteCommit = state.RemoteCommit - LocalChanges = LocalChanges.Zero - RemoteChanges = RemoteChanges.Zero - LocalNextHTLCId = HTLCId.Zero - RemoteNextHTLCId = HTLCId.Zero - OriginChannels = Map.empty - // we will receive their next per-commitment point in the next msg, so we temporarily put a random byte array - RemoteNextCommitInfo = DataEncoders.HexEncoder().DecodeData("0101010101010101010101010101010101010101010101010101010101010101") |> fun h -> new Key(h) |> fun k -> k.PubKey |> PerCommitmentPoint |> RemoteNextCommitInfo.Revoked - RemotePerCommitmentSecrets = PerCommitmentSecretStore() - ChannelId = - msg.ChannelId } - let nextState = { WaitForFundingConfirmedData.Commitments = commitments - Deferred = None - LastSent = Choice1Of2 state.LastSent - InitialFeeRatePerKw = state.InitialFeeRatePerKw - ChannelId = msg.ChannelId } - return [ WeAcceptedFundingSigned(state.FundingTx, nextState) ] - } - // --------------- open channel procedure: case we are fundee ------------- - | WaitForInitInternal, CreateInbound inputInitFundee -> - [ NewInboundChannelStarted({ InitFundee = inputInitFundee }) ] |> Ok - | WaitForFundingConfirmed state, CreateChannelReestablish -> makeChannelReestablish cs.ChannelPrivKeys state | ChannelState.Normal state, CreateChannelReestablish -> makeChannelReestablish cs.ChannelPrivKeys state - | WaitForOpenChannel state, ApplyOpenChannel msg -> - result { - do! Validation.checkOpenChannelMsgAcceptable (cs.FeeEstimator) (cs.Config) msg - let localParams = state.InitFundee.LocalParams - let channelKeys = state.InitFundee.ChannelPrivKeys - let localCommitmentPubKey = channelKeys.CommitmentSeed.DerivePerCommitmentPoint CommitmentNumber.FirstCommitment - let acceptChannelMsg: AcceptChannelMsg = { - TemporaryChannelId = msg.TemporaryChannelId - DustLimitSatoshis = localParams.DustLimitSatoshis - MaxHTLCValueInFlightMsat = localParams.MaxHTLCValueInFlightMSat - ChannelReserveSatoshis = localParams.ChannelReserveSatoshis - HTLCMinimumMSat = localParams.HTLCMinimumMSat - MinimumDepth = cs.Config.ChannelHandshakeConfig.MinimumDepth - ToSelfDelay = localParams.ToSelfDelay - MaxAcceptedHTLCs = localParams.MaxAcceptedHTLCs - FundingPubKey = channelKeys.FundingPrivKey.FundingPubKey() - RevocationBasepoint = channelKeys.RevocationBasepointSecret.RevocationBasepoint() - PaymentBasepoint = channelKeys.PaymentBasepointSecret.PaymentBasepoint() - DelayedPaymentBasepoint = channelKeys.DelayedPaymentBasepointSecret.DelayedPaymentBasepoint() - HTLCBasepoint = channelKeys.HtlcBasepointSecret.HtlcBasepoint() - FirstPerCommitmentPoint = localCommitmentPubKey - ShutdownScriptPubKey = cs.Config.ChannelOptions.ShutdownScriptPubKey - } - let remoteParams = RemoteParams.FromOpenChannel cs.RemoteNodeId state.InitFundee.RemoteInit msg cs.Config.ChannelHandshakeConfig - let data = Data.WaitForFundingCreatedData.Create localParams remoteParams msg acceptChannelMsg - return [ WeAcceptedOpenChannel(acceptChannelMsg, data) ] - } - | WaitForOpenChannel _state, ChannelCommand.Close _spk -> - [ ChannelEvent.Closed ] |> Ok - - | WaitForFundingCreated state, ApplyFundingCreated msg -> - result { - let remoteChannelKeys = state.RemoteParams.ChannelPubKeys - let! (localSpec, localCommitTx, remoteSpec, remoteCommitTx) = - ChannelHelpers.makeFirstCommitTxs - state.LocalParams - state.RemoteParams - state.FundingSatoshis - state.PushMSat - state.InitialFeeRatePerKw - msg.FundingOutputIndex - msg.FundingTxId - state.LastSent.FirstPerCommitmentPoint - state.RemoteFirstPerCommitmentPoint - cs.Network - assert (localCommitTx.Value.IsReadyToSign()) - let _s, signedLocalCommitTx = - cs.ChannelPrivKeys.SignWithFundingPrivKey localCommitTx.Value - let remoteTxSig = TransactionSignature(msg.Signature.Value, SigHash.All) - let theirSigPair = (remoteChannelKeys.FundingPubKey.RawPubKey(), remoteTxSig) - let sigPairs = seq [ theirSigPair ] - let! finalizedCommitTx = - Transactions.checkTxFinalized (signedLocalCommitTx) (localCommitTx.WhichInput) sigPairs - |> expectTransactionError - let localSigOfRemoteCommit, _ = - cs.ChannelPrivKeys.SignWithFundingPrivKey remoteCommitTx.Value - let channelId = OutPoint(msg.FundingTxId.Value, uint32 msg.FundingOutputIndex.Value).ToChannelId() - let msgToSend: FundingSignedMsg = { ChannelId = channelId; Signature = !>localSigOfRemoteCommit.Signature } - let commitments = { Commitments.LocalParams = state.LocalParams - RemoteParams = state.RemoteParams - ChannelFlags = state.ChannelFlags - FundingScriptCoin = - ChannelHelpers.getFundingScriptCoin - state.LocalParams.ChannelPubKeys.FundingPubKey - remoteChannelKeys.FundingPubKey - msg.FundingTxId - msg.FundingOutputIndex - state.FundingSatoshis - LocalCommit = { LocalCommit.Index = CommitmentNumber.FirstCommitment - Spec = localSpec - PublishableTxs = { PublishableTxs.CommitTx = finalizedCommitTx; - HTLCTxs = [] } - PendingHTLCSuccessTxs = [] } - RemoteCommit = { RemoteCommit.Index = CommitmentNumber.FirstCommitment - Spec = remoteSpec - TxId = remoteCommitTx.Value.GetGlobalTransaction().GetTxId() - RemotePerCommitmentPoint = state.RemoteFirstPerCommitmentPoint } - LocalChanges = LocalChanges.Zero - RemoteChanges = RemoteChanges.Zero - LocalNextHTLCId = HTLCId.Zero - RemoteNextHTLCId = HTLCId.Zero - OriginChannels = Map.empty - RemoteNextCommitInfo = DataEncoders.HexEncoder().DecodeData("0101010101010101010101010101010101010101010101010101010101010101") |> fun h -> new Key(h) |> fun k -> k.PubKey |> PerCommitmentPoint |> RemoteNextCommitInfo.Revoked - RemotePerCommitmentSecrets = PerCommitmentSecretStore() - ChannelId = channelId } - let nextState = { WaitForFundingConfirmedData.Commitments = commitments - Deferred = None - LastSent = msgToSend |> Choice2Of2 - InitialFeeRatePerKw = state.InitialFeeRatePerKw - ChannelId = channelId } - return [ WeAcceptedFundingCreated(msgToSend, nextState) ] - } | WaitForFundingConfirmed _state, ApplyFundingLocked msg -> [ TheySentFundingLocked msg ] |> Ok | WaitForFundingConfirmed state, ApplyFundingConfirmedOnBC(height, txindex, depth) -> @@ -803,26 +898,6 @@ module Channel = let applyEvent c (e: ChannelEvent): Channel = match e, c.State with - | NewOutboundChannelStarted(_, data), WaitForInitInternal -> - { c with State = (WaitForAcceptChannel data) } - | NewInboundChannelStarted(data), WaitForInitInternal -> - { c with State = (WaitForOpenChannel data) } - // --------- init fundee ----- - | WeAcceptedOpenChannel(_, data), WaitForOpenChannel _ -> - let state = WaitForFundingCreated data - { c with State = state } - | WeAcceptedFundingCreated(_, data), WaitForFundingCreated _ -> - let state = WaitForFundingConfirmed data - { c with State = state } - - // --------- init funder ----- - | WeAcceptedAcceptChannel(_, _, data), WaitForAcceptChannel _ -> - { c with State = WaitForFundingTx data } - | WeCreatedFundingTx(_, data), WaitForFundingTx _ -> - { c with State = WaitForFundingSigned data } - | WeAcceptedFundingSigned(_, data), WaitForFundingSigned _ -> - { c with State = WaitForFundingConfirmed data } - // --------- init both ------ | FundingConfirmed data, WaitForFundingConfirmed _ -> { c with State = WaitForFundingLocked data } @@ -851,10 +926,6 @@ module Channel = { c with State = ChannelState.Normal nextState } | WeSentFundingLocked msg, WaitForFundingLocked prevState -> { c with State = WaitForFundingLocked { prevState with OurMessage = msg; HaveWeSentFundingLocked = true } } - | BothFundingLocked data, WaitForFundingSigned _s -> - { c with State = ChannelState.Normal data } - | BothFundingLocked data, WaitForFundingLocked _s -> - { c with State = ChannelState.Normal data } // ----- normal operation -------- | WeAcceptedOperationMonoHopUnidirectionalPayment(_, newCommitments), ChannelState.Normal normalData -> diff --git a/src/DotNetLightning.Core/Channel/ChannelOperations.fs b/src/DotNetLightning.Core/Channel/ChannelOperations.fs index 4dbcaa86b..e2ab04a44 100644 --- a/src/DotNetLightning.Core/Channel/ChannelOperations.fs +++ b/src/DotNetLightning.Core/Channel/ChannelOperations.fs @@ -192,7 +192,6 @@ and InputInitFundee = { /// It is just an input to the state. type ChannelCommand = // open: funder - | CreateOutbound of InputInitFunder | ApplyAcceptChannel of AcceptChannelMsg | CreateFundingTx of fundingTx: FinalizedTx * outIndex: TxOutIndex | ApplyFundingSigned of FundingSignedMsg @@ -200,7 +199,6 @@ type ChannelCommand = | ApplyFundingConfirmedOnBC of height: BlockHeight * txIndex: TxIndexInBlock * depth: BlockHeightOffset32 // open: fundee - | CreateInbound of InputInitFundee | ApplyOpenChannel of OpenChannelMsg | ApplyFundingCreated of FundingCreatedMsg diff --git a/src/DotNetLightning.Core/Channel/ChannelTypes.fs b/src/DotNetLightning.Core/Channel/ChannelTypes.fs index 19ec6caf5..29ea152a9 100644 --- a/src/DotNetLightning.Core/Channel/ChannelTypes.fs +++ b/src/DotNetLightning.Core/Channel/ChannelTypes.fs @@ -1,5 +1,6 @@ namespace DotNetLightning.Channel +open DotNetLightning.Chain open DotNetLightning.Utils open DotNetLightning.Utils.Aether open DotNetLightning.DomainUtils.Types @@ -8,6 +9,7 @@ open DotNetLightning.Transactions open DotNetLightning.Crypto open NBitcoin + (* based on eclair's channel state management *) @@ -257,17 +259,6 @@ module Data = /// The one that includes `Operation` in its name is the event which we are the initiator type ChannelEvent = // --- ln events --- - // --------- init fundee -------- - | NewInboundChannelStarted of nextState: Data.WaitForOpenChannelData - | WeAcceptedOpenChannel of nextMsg: AcceptChannelMsg * nextState: Data.WaitForFundingCreatedData - | WeAcceptedFundingCreated of nextMsg: FundingSignedMsg * nextState: Data.WaitForFundingConfirmedData - - // --------- init fender -------- - | NewOutboundChannelStarted of nextMsg: OpenChannelMsg * nextState: Data.WaitForAcceptChannelData - | WeAcceptedAcceptChannel of fundingDestination: IDestination * fundingAmount: Money * nextState: Data.WaitForFundingTxData - | WeCreatedFundingTx of nextMsg: FundingCreatedMsg * nextState: Data.WaitForFundingSignedData - | WeAcceptedFundingSigned of txToPublish: FinalizedTx * nextState: Data.WaitForFundingConfirmedData - /// -------- init both ----- | FundingConfirmed of nextState: Data.WaitForFundingLockedData | TheySentFundingLocked of msg: FundingLockedMsg @@ -334,12 +325,6 @@ type ChannelStatePhase = | Abnormal type ChannelState = /// Establishing - | WaitForInitInternal - | WaitForOpenChannel of WaitForOpenChannelData - | WaitForAcceptChannel of WaitForAcceptChannelData - | WaitForFundingTx of WaitForFundingTxData - | WaitForFundingCreated of WaitForFundingCreatedData - | WaitForFundingSigned of WaitForFundingSignedData | WaitForFundingConfirmed of WaitForFundingConfirmedData | WaitForFundingLocked of WaitForFundingLockedData @@ -363,7 +348,6 @@ type ChannelState = with interface IState - static member Zero = WaitForInitInternal static member Normal_: Prism<_, _> = (fun cc -> match cc with | Normal s -> Some s @@ -373,12 +357,6 @@ type ChannelState = | _ -> cc ) member this.ChannelId: Option = match this with - | WaitForInitInternal - | WaitForOpenChannel _ - | WaitForAcceptChannel _ - | WaitForFundingTx _ - | WaitForFundingCreated _ -> None - | WaitForFundingSigned data -> Some data.ChannelId | WaitForFundingConfirmed data -> Some data.ChannelId | WaitForFundingLocked data -> Some data.ChannelId | Normal data -> Some data.ChannelId @@ -394,12 +372,6 @@ type ChannelState = member this.Phase = match this with - | WaitForInitInternal - | WaitForOpenChannel _ - | WaitForAcceptChannel _ - | WaitForFundingTx _ - | WaitForFundingCreated _ - | WaitForFundingSigned _ | WaitForFundingConfirmed _ | WaitForFundingLocked _ -> Opening | Normal _ -> ChannelStatePhase.Normal @@ -415,12 +387,6 @@ type ChannelState = member this.Commitments: Option = match this with - | WaitForInitInternal - | WaitForOpenChannel _ - | WaitForAcceptChannel _ - | WaitForFundingTx _ - | WaitForFundingCreated _ - | WaitForFundingSigned _ -> None | WaitForFundingConfirmed data -> Some (data :> IHasCommitments).Commitments | WaitForFundingLocked data -> Some (data :> IHasCommitments).Commitments | Normal data -> Some (data :> IHasCommitments).Commitments From 1c16fd0914032cf85c31a34c82448cc02c246504 Mon Sep 17 00:00:00 2001 From: Andrew Cann Date: Mon, 30 Nov 2020 20:53:19 +0800 Subject: [PATCH 10/99] Remove unused channel states --- .../Channel/ChannelTypes.fs | 30 ------------------- 1 file changed, 30 deletions(-) diff --git a/src/DotNetLightning.Core/Channel/ChannelTypes.fs b/src/DotNetLightning.Core/Channel/ChannelTypes.fs index 29ea152a9..3635a5b4c 100644 --- a/src/DotNetLightning.Core/Channel/ChannelTypes.fs +++ b/src/DotNetLightning.Core/Channel/ChannelTypes.fs @@ -321,8 +321,6 @@ type ChannelStatePhase = | Opening | Normal | Closing - | Closed - | Abnormal type ChannelState = /// Establishing | WaitForFundingConfirmed of WaitForFundingConfirmedData @@ -335,16 +333,6 @@ type ChannelState = | Shutdown of ShutdownData | Negotiating of NegotiatingData | Closing of ClosingData - | Closed of IChannelStateData - - /// Abnormal - | Offline of IChannelStateData - | Syncing of IChannelStateData - - /// Error - | ErrFundingLost of IChannelStateData - | ErrFundingTimeOut of IChannelStateData - | ErrInformationLeak of IChannelStateData with interface IState @@ -363,12 +351,6 @@ type ChannelState = | 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 @@ -378,12 +360,6 @@ type ChannelState = | Shutdown _ | Negotiating _ | Closing _ -> ChannelStatePhase.Closing - | Closed _ -> ChannelStatePhase.Closed - | Offline _ - | Syncing _ - | ErrFundingLost _ - | ErrFundingTimeOut _ - | ErrInformationLeak _ -> Abnormal member this.Commitments: Option = match this with @@ -393,10 +369,4 @@ type ChannelState = | 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 From ce73f19cbd5aa7ecf50cd5baa04e15555b7fcfad Mon Sep 17 00:00:00 2001 From: Andrew Cann Date: Mon, 30 Nov 2020 21:00:26 +0800 Subject: [PATCH 11/99] Make ChannelState.{Commitments,ChannelId} not return Option They don't need to anymore since all channel states have a channel id and commitments. --- .../Channel/ChannelTypes.fs | 28 +++++++++---------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/src/DotNetLightning.Core/Channel/ChannelTypes.fs b/src/DotNetLightning.Core/Channel/ChannelTypes.fs index 3635a5b4c..8b2addec8 100644 --- a/src/DotNetLightning.Core/Channel/ChannelTypes.fs +++ b/src/DotNetLightning.Core/Channel/ChannelTypes.fs @@ -343,14 +343,14 @@ type ChannelState = (fun v cc -> match cc with | Normal _ -> Normal v | _ -> cc ) - member this.ChannelId: Option = + member this.ChannelId: ChannelId = match this with - | 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 + | WaitForFundingConfirmed data -> data.ChannelId + | WaitForFundingLocked data -> data.ChannelId + | Normal data -> data.ChannelId + | Shutdown data -> data.ChannelId + | Negotiating data -> data.ChannelId + | Closing data -> data.ChannelId member this.Phase = match this with @@ -361,12 +361,12 @@ type ChannelState = | Negotiating _ | Closing _ -> ChannelStatePhase.Closing - member this.Commitments: Option = + member this.Commitments: Commitments = match this with - | 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 + | WaitForFundingConfirmed data -> (data :> IHasCommitments).Commitments + | WaitForFundingLocked data -> (data :> IHasCommitments).Commitments + | Normal data -> (data :> IHasCommitments).Commitments + | Shutdown data -> (data :> IHasCommitments).Commitments + | Negotiating data -> (data :> IHasCommitments).Commitments + | Closing data -> (data :> IHasCommitments).Commitments From 0f84d6ac0d28c99f54b07eef9f350dd38b7e7c07 Mon Sep 17 00:00:00 2001 From: Andrew Cann Date: Tue, 1 Dec 2020 13:58:47 +0800 Subject: [PATCH 12/99] Remove LocalParams.NodeId This field is unused. --- src/DotNetLightning.Core/Channel/ChannelOperations.fs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/DotNetLightning.Core/Channel/ChannelOperations.fs b/src/DotNetLightning.Core/Channel/ChannelOperations.fs index e2ab04a44..aaea23091 100644 --- a/src/DotNetLightning.Core/Channel/ChannelOperations.fs +++ b/src/DotNetLightning.Core/Channel/ChannelOperations.fs @@ -76,7 +76,6 @@ module OperationClose = cmdClose.ScriptPubKey type LocalParams = { - NodeId: NodeId ChannelPubKeys: ChannelPubKeys DustLimitSatoshis: Money MaxHTLCValueInFlightMSat: LNMoney From 74daf7cf75518c43a9bf215c6b5d0bdc5c250fcc Mon Sep 17 00:00:00 2001 From: Andrew Cann Date: Tue, 1 Dec 2020 14:34:05 +0800 Subject: [PATCH 13/99] De-duplicate minimum depth field --- src/DotNetLightning.Core/Channel/Channel.fs | 16 ++++++++++++---- .../Channel/ChannelOperations.fs | 8 ++++---- src/DotNetLightning.Core/Utils/Config.fs | 15 --------------- 3 files changed, 16 insertions(+), 23 deletions(-) diff --git a/src/DotNetLightning.Core/Channel/Channel.fs b/src/DotNetLightning.Core/Channel/Channel.fs index cd18b5054..6848ee09e 100644 --- a/src/DotNetLightning.Core/Channel/Channel.fs +++ b/src/DotNetLightning.Core/Channel/Channel.fs @@ -20,6 +20,7 @@ type ChannelWaitingForFundingSigned = { RemoteNodeId: NodeId NodeSecret: NodeSecret Network: Network + FundingTxMinimumDepth: BlockHeightOffset32 WaitForFundingSignedData: WaitForFundingSignedData } with member self.ApplyFundingSigned (msg: FundingSignedMsg) @@ -73,6 +74,7 @@ type ChannelWaitingForFundingSigned = { NodeSecret = self.NodeSecret State = WaitForFundingConfirmed nextState Network = self.Network + FundingTxMinimumDepth = self.FundingTxMinimumDepth } return state.FundingTx, channel } @@ -84,6 +86,7 @@ and ChannelWaitingForFundingCreated = { RemoteNodeId: NodeId NodeSecret: NodeSecret Network: Network + FundingTxMinimumDepth: BlockHeightOffset32 WaitForFundingCreatedData: WaitForFundingCreatedData } with member self.ApplyFundingCreated (msg: FundingCreatedMsg) @@ -155,6 +158,7 @@ and ChannelWaitingForFundingCreated = { NodeSecret = self.NodeSecret Network = self.Network State = WaitForFundingConfirmed nextState + FundingTxMinimumDepth = self.FundingTxMinimumDepth } return msgToSend, channel } @@ -222,6 +226,7 @@ and ChannelWaitingForFundingTx = { RemoteNodeId = self.RemoteNodeId NodeSecret = self.NodeSecret Network = self.Network + FundingTxMinimumDepth = state.LastReceived.MinimumDepth WaitForFundingSignedData = waitForFundingSignedData } return nextMsg, channelWaitingForFundingSigned @@ -272,6 +277,7 @@ and Channel = { NodeSecret: NodeSecret State: ChannelState Network: Network + FundingTxMinimumDepth: BlockHeightOffset32 } with static member NewOutbound(config: ChannelConfig, @@ -330,6 +336,7 @@ and Channel = { network: Network, remoteNodeId: NodeId, inputInitFundee: InputInitFundee, + minimumDepth: BlockHeightOffset32, openChannelMsg: OpenChannelMsg ): Result = result { @@ -343,7 +350,7 @@ and Channel = { MaxHTLCValueInFlightMsat = localParams.MaxHTLCValueInFlightMSat ChannelReserveSatoshis = localParams.ChannelReserveSatoshis HTLCMinimumMSat = localParams.HTLCMinimumMSat - MinimumDepth = config.ChannelHandshakeConfig.MinimumDepth + MinimumDepth = minimumDepth ToSelfDelay = localParams.ToSelfDelay MaxAcceptedHTLCs = localParams.MaxAcceptedHTLCs FundingPubKey = channelKeys.FundingPrivKey.FundingPubKey() @@ -354,7 +361,7 @@ and Channel = { FirstPerCommitmentPoint = localCommitmentPubKey ShutdownScriptPubKey = config.ChannelOptions.ShutdownScriptPubKey } - let remoteParams = RemoteParams.FromOpenChannel remoteNodeId inputInitFundee.RemoteInit openChannelMsg config.ChannelHandshakeConfig + let remoteParams = RemoteParams.FromOpenChannel remoteNodeId inputInitFundee.RemoteInit openChannelMsg let waitForFundingCreatedData = Data.WaitForFundingCreatedData.Create localParams remoteParams openChannelMsg acceptChannelMsg let channelPrivKeys = nodeMasterPrivKey.ChannelPrivKeys channelIndex let nodeSecret = nodeMasterPrivKey.NodeSecret() @@ -365,6 +372,7 @@ and Channel = { RemoteNodeId = remoteNodeId NodeSecret = nodeSecret Network = network + FundingTxMinimumDepth = minimumDepth WaitForFundingCreatedData = waitForFundingCreatedData } return (acceptChannelMsg, channelWaitingForFundingCreated) @@ -481,7 +489,7 @@ module Channel = | WaitForFundingConfirmed _state, ApplyFundingLocked msg -> [ TheySentFundingLocked msg ] |> Ok | WaitForFundingConfirmed state, ApplyFundingConfirmedOnBC(height, txindex, depth) -> - if state.Commitments.RemoteParams.MinimumDepth > depth then + if cs.FundingTxMinimumDepth > depth then [] |> Ok else let nextPerCommitmentPoint = @@ -510,7 +518,7 @@ module Channel = | Some msg -> [ FundingConfirmed nextState; WeSentFundingLocked msgToSend; WeResumedDelayedFundingLocked msg ] |> Ok | WaitForFundingLocked _state, ApplyFundingConfirmedOnBC(height, _txindex, depth) -> - if (cs.Config.ChannelHandshakeConfig.MinimumDepth <= depth) then + if (cs.FundingTxMinimumDepth <= depth) then [] |> Ok else onceConfirmedFundingTxHasBecomeUnconfirmed(height, depth) diff --git a/src/DotNetLightning.Core/Channel/ChannelOperations.fs b/src/DotNetLightning.Core/Channel/ChannelOperations.fs index aaea23091..c50f0851b 100644 --- a/src/DotNetLightning.Core/Channel/ChannelOperations.fs +++ b/src/DotNetLightning.Core/Channel/ChannelOperations.fs @@ -98,7 +98,6 @@ type RemoteParams = { MaxAcceptedHTLCs: uint16 ChannelPubKeys: ChannelPubKeys Features: FeatureBits - MinimumDepth: BlockHeightOffset32 } with static member FromAcceptChannel nodeId (remoteInit: InitMsg) (msg: AcceptChannelMsg) = @@ -119,10 +118,12 @@ type RemoteParams = { MaxAcceptedHTLCs = msg.MaxAcceptedHTLCs ChannelPubKeys = channelPubKeys Features = remoteInit.Features - MinimumDepth = msg.MinimumDepth } - static member FromOpenChannel (nodeId) (remoteInit: InitMsg) (msg: OpenChannelMsg) (channelHandshakeConfig: ChannelHandshakeConfig) = + static member FromOpenChannel (nodeId: NodeId) + (remoteInit: InitMsg) + (msg: OpenChannelMsg) + : RemoteParams = let channelPubKeys = { FundingPubKey = msg.FundingPubKey RevocationBasepoint = msg.RevocationBasepoint @@ -140,7 +141,6 @@ type RemoteParams = { MaxAcceptedHTLCs = msg.MaxAcceptedHTLCs ChannelPubKeys = channelPubKeys Features = remoteInit.Features - MinimumDepth = channelHandshakeConfig.MinimumDepth } type InputInitFunder = { diff --git a/src/DotNetLightning.Core/Utils/Config.fs b/src/DotNetLightning.Core/Utils/Config.fs index c1710fcbf..39e590244 100644 --- a/src/DotNetLightning.Core/Utils/Config.fs +++ b/src/DotNetLightning.Core/Utils/Config.fs @@ -4,19 +4,6 @@ open NBitcoin open Aether -type ChannelHandshakeConfig = { - /// Confirmations we will wait for before considering the channel locked in. - /// Applied only for inbound channels (see `ChannelHandshakeLimits.MaxMinimumDepth` for the - /// equivalent limit applied to outbound channel) - MinimumDepth: BlockHeightOffset32 - } - with - - static member Zero = - { - MinimumDepth = BlockHeightOffset32 6u - } - /// Optional Channel limits which are applied during channel creation. /// These limits are only applied to our counterparty's limits, not our own type ChannelHandshakeLimits = { @@ -74,7 +61,6 @@ type ChannelHandshakeLimits = { /// Configuration containing all information used by Channel type ChannelConfig = { - ChannelHandshakeConfig: ChannelHandshakeConfig PeerChannelConfigLimits: ChannelHandshakeLimits ChannelOptions: ChannelOptions } @@ -82,7 +68,6 @@ type ChannelConfig = { with static member Zero = { - ChannelHandshakeConfig = ChannelHandshakeConfig.Zero PeerChannelConfigLimits = ChannelHandshakeLimits.Zero ChannelOptions = ChannelOptions.Zero } From c22658f8543d375135c63c59c8b1393dfde3f925 Mon Sep 17 00:00:00 2001 From: Andrew Cann Date: Tue, 1 Dec 2020 14:57:46 +0800 Subject: [PATCH 14/99] Don't carry ChannelHandshakeLimits through the lifetime of the channel These settings are only used for choosing whether to accept open/accept channel messages. They don't need to be stored in the channel. --- src/DotNetLightning.Core/Channel/Channel.fs | 50 ++++++++++--------- .../Channel/ChannelError.fs | 6 ++- .../Channel/ChannelValidation.fs | 19 ++++--- .../Channel/CommitmentsModule.fs | 13 +++-- src/DotNetLightning.Core/Utils/Config.fs | 31 ++---------- 5 files changed, 57 insertions(+), 62 deletions(-) diff --git a/src/DotNetLightning.Core/Channel/Channel.fs b/src/DotNetLightning.Core/Channel/Channel.fs index 6848ee09e..ca147059f 100644 --- a/src/DotNetLightning.Core/Channel/Channel.fs +++ b/src/DotNetLightning.Core/Channel/Channel.fs @@ -14,7 +14,7 @@ open ResultUtils open ResultUtils.Portability type ChannelWaitingForFundingSigned = { - Config: ChannelConfig + ChannelOptions: ChannelOptions ChannelPrivKeys: ChannelPrivKeys FeeEstimator: IFeeEstimator RemoteNodeId: NodeId @@ -67,7 +67,7 @@ type ChannelWaitingForFundingSigned = { InitialFeeRatePerKw = state.InitialFeeRatePerKw ChannelId = msg.ChannelId } let channel = { - Config = self.Config + ChannelOptions = self.ChannelOptions ChannelPrivKeys = self.ChannelPrivKeys FeeEstimator = self.FeeEstimator RemoteNodeId = self.RemoteNodeId @@ -80,7 +80,7 @@ type ChannelWaitingForFundingSigned = { } and ChannelWaitingForFundingCreated = { - Config: ChannelConfig + ChannelOptions: ChannelOptions ChannelPrivKeys: ChannelPrivKeys FeeEstimator: IFeeEstimator RemoteNodeId: NodeId @@ -151,7 +151,7 @@ and ChannelWaitingForFundingCreated = { InitialFeeRatePerKw = state.InitialFeeRatePerKw ChannelId = channelId } let channel = { - Config = self.Config + ChannelOptions = self.ChannelOptions ChannelPrivKeys = self.ChannelPrivKeys FeeEstimator = self.FeeEstimator RemoteNodeId = self.RemoteNodeId @@ -164,7 +164,7 @@ and ChannelWaitingForFundingCreated = { } and ChannelWaitingForFundingTx = { - Config: ChannelConfig + ChannelOptions: ChannelOptions ChannelPrivKeys: ChannelPrivKeys FeeEstimator: IFeeEstimator RemoteNodeId: NodeId @@ -220,7 +220,7 @@ and ChannelWaitingForFundingTx = { InitialFeeRatePerKw = state.InputInitFunder.InitFeeRatePerKw } let channelWaitingForFundingSigned = { - Config = self.Config + ChannelOptions = self.ChannelOptions ChannelPrivKeys = self.ChannelPrivKeys FeeEstimator = self.FeeEstimator RemoteNodeId = self.RemoteNodeId @@ -234,7 +234,8 @@ and ChannelWaitingForFundingTx = { and ChannelWaitingForAcceptChannel = { - Config: ChannelConfig + ChannelOptions: ChannelOptions + ChannelHandshakeLimits: ChannelHandshakeLimits ChannelPrivKeys: ChannelPrivKeys FeeEstimator: IFeeEstimator RemoteNodeId: NodeId @@ -245,7 +246,7 @@ and ChannelWaitingForAcceptChannel = { member self.ApplyAcceptChannel (msg: AcceptChannelMsg) : Result = result { let state = self.WaitForAcceptChannelData - do! Validation.checkAcceptChannelMsgAcceptable (self.Config) state msg + do! Validation.checkAcceptChannelMsgAcceptable self.ChannelHandshakeLimits state msg let redeem = Scripts.funding (state.InputInitFunder.ChannelPrivKeys.ToChannelPubKeys().FundingPubKey) @@ -258,7 +259,7 @@ and ChannelWaitingForAcceptChannel = { LastReceived = msg } let channelWaitingForFundingTx = { - Config = self.Config + ChannelOptions = self.ChannelOptions ChannelPrivKeys = self.ChannelPrivKeys FeeEstimator = self.FeeEstimator RemoteNodeId = self.RemoteNodeId @@ -270,7 +271,7 @@ and ChannelWaitingForAcceptChannel = { } and Channel = { - Config: ChannelConfig + ChannelOptions: ChannelOptions ChannelPrivKeys: ChannelPrivKeys FeeEstimator: IFeeEstimator RemoteNodeId: NodeId @@ -280,7 +281,8 @@ and Channel = { FundingTxMinimumDepth: BlockHeightOffset32 } with - static member NewOutbound(config: ChannelConfig, + static member NewOutbound(channelHandshakeLimits: ChannelHandshakeLimits, + channelOptions: ChannelOptions, nodeMasterPrivKey: NodeMasterPrivKey, channelIndex: int, feeEstimator: IFeeEstimator, @@ -307,10 +309,10 @@ and Channel = { HTLCBasepoint = inputInitFunder.ChannelPrivKeys.HtlcBasepointSecret.HtlcBasepoint() FirstPerCommitmentPoint = inputInitFunder.ChannelPrivKeys.CommitmentSeed.DerivePerCommitmentPoint CommitmentNumber.FirstCommitment ChannelFlags = inputInitFunder.ChannelFlags - ShutdownScriptPubKey = config.ChannelOptions.ShutdownScriptPubKey + ShutdownScriptPubKey = channelOptions.ShutdownScriptPubKey } result { - do! Validation.checkOurOpenChannelMsgAcceptable config openChannelMsgToSend + do! Validation.checkOurOpenChannelMsgAcceptable openChannelMsgToSend let channelPrivKeys = nodeMasterPrivKey.ChannelPrivKeys channelIndex let nodeSecret = nodeMasterPrivKey.NodeSecret() let waitForAcceptChannelData = { @@ -318,7 +320,8 @@ and Channel = { LastSent = openChannelMsgToSend } let channelWaitingForAcceptChannel = { - Config = config + ChannelHandshakeLimits = channelHandshakeLimits + ChannelOptions = channelOptions ChannelPrivKeys = channelPrivKeys FeeEstimator = feeEstimator RemoteNodeId = remoteNodeId @@ -329,7 +332,8 @@ and Channel = { return (openChannelMsgToSend, channelWaitingForAcceptChannel) } - static member NewInbound (config: ChannelConfig, + static member NewInbound (channelHandshakeLimits: ChannelHandshakeLimits, + channelOptions: ChannelOptions, nodeMasterPrivKey: NodeMasterPrivKey, channelIndex: int, feeEstimator: IFeeEstimator, @@ -340,7 +344,7 @@ and Channel = { openChannelMsg: OpenChannelMsg ): Result = result { - do! Validation.checkOpenChannelMsgAcceptable feeEstimator config openChannelMsg + do! Validation.checkOpenChannelMsgAcceptable feeEstimator channelHandshakeLimits channelOptions openChannelMsg let localParams = inputInitFundee.LocalParams let channelKeys = inputInitFundee.ChannelPrivKeys let localCommitmentPubKey = channelKeys.CommitmentSeed.DerivePerCommitmentPoint CommitmentNumber.FirstCommitment @@ -359,14 +363,14 @@ and Channel = { DelayedPaymentBasepoint = channelKeys.DelayedPaymentBasepointSecret.DelayedPaymentBasepoint() HTLCBasepoint = channelKeys.HtlcBasepointSecret.HtlcBasepoint() FirstPerCommitmentPoint = localCommitmentPubKey - ShutdownScriptPubKey = config.ChannelOptions.ShutdownScriptPubKey + ShutdownScriptPubKey = channelOptions.ShutdownScriptPubKey } let remoteParams = RemoteParams.FromOpenChannel remoteNodeId inputInitFundee.RemoteInit openChannelMsg let waitForFundingCreatedData = Data.WaitForFundingCreatedData.Create localParams remoteParams openChannelMsg acceptChannelMsg let channelPrivKeys = nodeMasterPrivKey.ChannelPrivKeys channelIndex let nodeSecret = nodeMasterPrivKey.NodeSecret() let channelWaitingForFundingCreated = { - Config = config + ChannelOptions = channelOptions ChannelPrivKeys = channelPrivKeys FeeEstimator = feeEstimator RemoteNodeId = remoteNodeId @@ -533,7 +537,7 @@ module Channel = state.Commitments.LocalParams.ToSelfDelay, state.Commitments.RemoteParams.HTLCMinimumMSat, feeBase, - cs.Config.ChannelOptions.FeeProportionalMillionths, + cs.ChannelOptions.FeeProportionalMillionths, true, None) let nextState = { NormalData.Buried = true @@ -639,7 +643,7 @@ module Channel = state.Commitments |> Commitments.sendFee op | ChannelState.Normal state, ApplyUpdateFee msg -> let localFeerate = cs.FeeEstimator.GetEstSatPer1000Weight(ConfirmationTarget.HighPriority) - state.Commitments |> Commitments.receiveFee cs.Config localFeerate msg + state.Commitments |> Commitments.receiveFee cs.ChannelOptions localFeerate msg | ChannelState.Normal state, SignCommitment -> let cm = state.Commitments @@ -789,7 +793,7 @@ module Channel = state.Commitments |> Commitments.sendFee op | Shutdown state, ApplyUpdateFee msg -> let localFeerate = cs.FeeEstimator.GetEstSatPer1000Weight(ConfirmationTarget.HighPriority) - state.Commitments |> Commitments.receiveFee cs.Config localFeerate msg + state.Commitments |> Commitments.receiveFee cs.ChannelOptions localFeerate msg | Shutdown state, SignCommitment -> let cm = state.Commitments match cm.RemoteNextCommitInfo with @@ -839,7 +843,7 @@ module Channel = |> Option.map (fun v -> v.LocalClosingSigned.FeeSatoshis) let areWeInDeal = Some(msg.FeeSatoshis) = maybeLocalFee let hasTooManyNegotiationDone = - (state.ClosingTxProposed |> List.collect (id) |> List.length) >= cs.Config.PeerChannelConfigLimits.MaxClosingNegotiationIterations + (state.ClosingTxProposed |> List.collect (id) |> List.length) >= cs.ChannelOptions.MaxClosingNegotiationIterations if (areWeInDeal || hasTooManyNegotiationDone) then return! Closing.handleMutualClose (finalizedTx, { state with MaybeBestUnpublishedTx = Some(finalizedTx) }) else @@ -920,7 +924,7 @@ module Channel = s.Commitments.LocalParams.ToSelfDelay, s.Commitments.RemoteParams.HTLCMinimumMSat, feeBase, - c.Config.ChannelOptions.FeeProportionalMillionths, + c.ChannelOptions.FeeProportionalMillionths, true, None) let nextState = { NormalData.Buried = false; diff --git a/src/DotNetLightning.Core/Channel/ChannelError.fs b/src/DotNetLightning.Core/Channel/ChannelError.fs index 78ad45f81..808f89fff 100644 --- a/src/DotNetLightning.Core/Channel/ChannelError.fs +++ b/src/DotNetLightning.Core/Channel/ChannelError.fs @@ -410,9 +410,11 @@ module internal OpenChannelMsgValidation = msg.DustLimitSatoshis (>) config.MaxDustLimitSatoshis "dust_limit_satoshis is greater than the user specified limit. received: %A; limit: %A" Validation.ofResult(check1) *^> check2 *^> check3 *^> check4 *^> check5 *^> check6 *^> check7 - let checkChannelAnnouncementPreferenceAcceptable (config: ChannelConfig) (msg: OpenChannelMsg) = + let checkChannelAnnouncementPreferenceAcceptable (channelHandshakeLimits: ChannelHandshakeLimits) + (channelOptions: ChannelOptions) + (msg: OpenChannelMsg) = let theirAnnounce = (msg.ChannelFlags &&& 1uy) = 1uy - if (config.PeerChannelConfigLimits.ForceChannelAnnouncementPreference) && config.ChannelOptions.AnnounceChannel <> theirAnnounce then + if (channelHandshakeLimits.ForceChannelAnnouncementPreference) && channelOptions.AnnounceChannel <> theirAnnounce then "Peer tried to open channel but their announcement preference is different from ours" |> Error else diff --git a/src/DotNetLightning.Core/Channel/ChannelValidation.fs b/src/DotNetLightning.Core/Channel/ChannelValidation.fs index 71b0fb3d3..bed0a3b11 100644 --- a/src/DotNetLightning.Core/Channel/ChannelValidation.fs +++ b/src/DotNetLightning.Core/Channel/ChannelValidation.fs @@ -161,7 +161,7 @@ module internal ChannelHelpers = module internal Validation = open DotNetLightning.Channel - let checkOurOpenChannelMsgAcceptable (_conf: ChannelConfig) (msg: OpenChannelMsg) = + let checkOurOpenChannelMsgAcceptable (msg: OpenChannelMsg) = Validation.ofResult(OpenChannelMsgValidation.checkFundingSatoshisLessThanMax msg) *^> OpenChannelMsgValidation.checkChannelReserveSatohisLessThanFundingSatoshis msg *^> OpenChannelMsgValidation.checkPushMSatLesserThanFundingValue msg @@ -170,23 +170,28 @@ module internal Validation = *^> OpenChannelMsgValidation.checkFunderCanAffordFee (msg.FeeRatePerKw) msg |> Result.mapError((@)["open_channel msg is invalid"] >> InvalidOpenChannelError.Create msg >> InvalidOpenChannel) - let internal checkOpenChannelMsgAcceptable (feeEstimator: IFeeEstimator) (conf: ChannelConfig) (msg: OpenChannelMsg) = + let internal checkOpenChannelMsgAcceptable (feeEstimator: IFeeEstimator) + (channelHandshakeLimits: ChannelHandshakeLimits) + (channelOptions: ChannelOptions) + (msg: OpenChannelMsg) = let feeRate = feeEstimator.GetEstSatPer1000Weight(ConfirmationTarget.Background) Validation.ofResult(OpenChannelMsgValidation.checkFundingSatoshisLessThanMax msg) *^> OpenChannelMsgValidation.checkChannelReserveSatohisLessThanFundingSatoshis msg *^> OpenChannelMsgValidation.checkPushMSatLesserThanFundingValue msg *^> OpenChannelMsgValidation.checkFundingSatoshisLessThanDustLimitSatoshis msg - *^> OpenChannelMsgValidation.checkRemoteFee feeEstimator msg.FeeRatePerKw conf.ChannelOptions.MaxFeeRateMismatchRatio + *^> OpenChannelMsgValidation.checkRemoteFee feeEstimator msg.FeeRatePerKw channelOptions.MaxFeeRateMismatchRatio *^> OpenChannelMsgValidation.checkToSelfDelayIsInAcceptableRange msg *^> OpenChannelMsgValidation.checkMaxAcceptedHTLCs msg - *> OpenChannelMsgValidation.checkConfigPermits conf.PeerChannelConfigLimits msg - *^> OpenChannelMsgValidation.checkChannelAnnouncementPreferenceAcceptable conf msg + *> OpenChannelMsgValidation.checkConfigPermits channelHandshakeLimits msg + *^> OpenChannelMsgValidation.checkChannelAnnouncementPreferenceAcceptable channelHandshakeLimits channelOptions msg *> OpenChannelMsgValidation.checkIsAcceptableByCurrentFeeRate feeEstimator msg *^> OpenChannelMsgValidation.checkFunderCanAffordFee feeRate msg |> Result.mapError((@)["rejected received open_channel msg"] >> InvalidOpenChannelError.Create msg >> InvalidOpenChannel) - let internal checkAcceptChannelMsgAcceptable (conf: ChannelConfig) (state) (msg: AcceptChannelMsg) = + let internal checkAcceptChannelMsgAcceptable (channelHandshakeLimits: ChannelHandshakeLimits) + (state: WaitForAcceptChannelData) + (msg: AcceptChannelMsg) = Validation.ofResult(AcceptChannelMsgValidation.checkMaxAcceptedHTLCs msg) *^> AcceptChannelMsgValidation.checkDustLimit msg *^> (AcceptChannelMsgValidation.checkChannelReserveSatoshis state msg) @@ -194,7 +199,7 @@ module internal Validation = *^> AcceptChannelMsgValidation.checkDustLimitIsLargerThanOurChannelReserve state msg *^> AcceptChannelMsgValidation.checkMinimumHTLCValueIsAcceptable state msg *^> AcceptChannelMsgValidation.checkToSelfDelayIsAcceptable msg - *> AcceptChannelMsgValidation.checkConfigPermits conf.PeerChannelConfigLimits msg + *> AcceptChannelMsgValidation.checkConfigPermits channelHandshakeLimits msg |> Result.mapError(InvalidAcceptChannelError.Create msg >> InvalidAcceptChannel) let checkOurMonoHopUnidirectionalPaymentIsAcceptableWithCurrentSpec (currentSpec) (state: Commitments) (payment: MonoHopUnidirectionalPaymentMsg) = diff --git a/src/DotNetLightning.Core/Channel/CommitmentsModule.fs b/src/DotNetLightning.Core/Channel/CommitmentsModule.fs index 342f1f2e0..f74a47499 100644 --- a/src/DotNetLightning.Core/Channel/CommitmentsModule.fs +++ b/src/DotNetLightning.Core/Channel/CommitmentsModule.fs @@ -111,8 +111,10 @@ module internal Commitments = |> List.ofSeq |> List.sortBy(fun htlc -> htlc.Value.GetGlobalTransaction().Inputs.[htlc.WhichInput].PrevOut.N) - let checkUpdateFee (config: ChannelConfig) (msg: UpdateFeeMsg) (localFeeRate: FeeRatePerKw) = - let maxMismatch = config.ChannelOptions.MaxFeeRateMismatchRatio + let checkUpdateFee (channelOptions: ChannelOptions) + (msg: UpdateFeeMsg) + (localFeeRate: FeeRatePerKw) = + let maxMismatch = channelOptions.MaxFeeRateMismatchRatio UpdateFeeValidation.checkFeeDiffTooHigh (msg) (localFeeRate) (maxMismatch) let sendFulfill (op: OperationFulfillHTLC) (cm: Commitments) = @@ -243,12 +245,15 @@ module internal Commitments = [ WeAcceptedOperationUpdateFee(fee, c1) ] } - let receiveFee (config: ChannelConfig) (localFeerate) (msg: UpdateFeeMsg) (cm: Commitments) = + let receiveFee (channelOptions: ChannelOptions) + (localFeerate) + (msg: UpdateFeeMsg) + (cm: Commitments) = if (cm.LocalParams.IsFunder) then "Remote is Fundee so it cannot send update fee" |> apiMisuse else result { - do! Helpers.checkUpdateFee (config) (msg) (localFeerate) + do! Helpers.checkUpdateFee channelOptions msg localFeerate let nextCommitments = cm.AddRemoteProposal(msg) let! reduced = nextCommitments.LocalCommit.Spec.Reduce( diff --git a/src/DotNetLightning.Core/Utils/Config.fs b/src/DotNetLightning.Core/Utils/Config.fs index 39e590244..66dc5fb08 100644 --- a/src/DotNetLightning.Core/Utils/Config.fs +++ b/src/DotNetLightning.Core/Utils/Config.fs @@ -38,9 +38,6 @@ type ChannelHandshakeLimits = { /// Defaults to true to make the default that no announced channels are possible (which is /// appropriate for any nodes which are not online very reliably) ForceChannelAnnouncementPreference: bool - - /// We don't exchange more than this many signatures when negotiating the closing fee - MaxClosingNegotiationIterations: int32 } with @@ -55,32 +52,10 @@ type ChannelHandshakeLimits = { MaxDustLimitSatoshis = Money.Coins(21_000_000m) MaxMinimumDepth = 144u |> BlockHeightOffset32 ForceChannelAnnouncementPreference = true - MaxClosingNegotiationIterations = 20 } -/// Configuration containing all information used by Channel -type ChannelConfig = { - PeerChannelConfigLimits: ChannelHandshakeLimits - ChannelOptions: ChannelOptions - } - - with - static member Zero = - { - PeerChannelConfigLimits = ChannelHandshakeLimits.Zero - ChannelOptions = ChannelOptions.Zero - } - - static member PeerChannelConfigLimits_: Lens<_, _> = - (fun uc -> uc.PeerChannelConfigLimits), - (fun v uc -> { uc with PeerChannelConfigLimits = v }) - - static member ChannelOptions_: Lens<_, _> = - (fun uc -> uc.ChannelOptions), - (fun v uc -> { uc with ChannelOptions = v }) - -and ChannelOptions = { +type ChannelOptions = { MaxFeeRateMismatchRatio: float // Amount (in millionth of a satoshi) the channel will charge per transferred satoshi. // This may be allowed to change at runtime in a later update, however doing so must result in @@ -92,6 +67,9 @@ and ChannelOptions = { // `ChannelHandshakeLimits.ForceAnnouncedChannelPreferences` is set. AnnounceChannel: bool + /// We don't exchange more than this many signatures when negotiating the closing fee + MaxClosingNegotiationIterations: int32 + ShutdownScriptPubKey: Script option } with @@ -101,6 +79,7 @@ and ChannelOptions = { FeeProportionalMillionths = 0u AnnounceChannel = false MaxFeeRateMismatchRatio = 0. + MaxClosingNegotiationIterations = 20 ShutdownScriptPubKey = None } From a2d1690dc737c29b2a3525b76dc2c89f0cd80cfc Mon Sep 17 00:00:00 2001 From: Andrew Cann Date: Tue, 1 Dec 2020 19:21:50 +0800 Subject: [PATCH 15/99] Refactor shutdown scripts Previously the local shutdown script was stored in two places in the channel and also provided as an argument when initiating a shutdown. This made it possible to send invalid shutdown messages if the shutdown scripts provided by the user at different times were different. Also, shutdown scripts have to be in certain forms, but the only place that they were validated was when initiating a shutdown. There's now a ShutdownScriptPubKey type which wraps a Script and enforces that the script is a valid shutdown script. This is used throughout the code for all scripts which are shutdown scripts, which forces us to check their validity at all required points. The DefaultFinalScriptPubKey field has been removed from LocalParams so that we only store the local shutdown script in (at most) one place in the channel datastructure. This shutdown script is an Option, and can be None in the case that we didn't specify a shutdown script to give to the remote peer when creating the channel. As such as we still pass a shutdown script as an argument when closing a channel but we also check that it matches the recorded shutdown script that we previously gave the remote peer in the case that it's Some. --- src/DotNetLightning.Core/Channel/Channel.fs | 82 ++++++++++++------- .../Channel/ChannelOperations.fs | 18 +--- .../Serialization/LightningStream.fs | 10 ++- .../Serialization/Msgs/Msgs.fs | 12 +-- .../Transactions/Scripts.fs | 12 --- .../Transactions/Transactions.fs | 8 +- src/DotNetLightning.Core/Utils/Config.fs | 3 - src/DotNetLightning.Core/Utils/Primitives.fs | 38 +++++++++ .../Generators/Msgs.fs | 6 +- .../Generators/Primitives.fs | 4 + .../Serialization.fs | 20 +++-- 11 files changed, 134 insertions(+), 79 deletions(-) diff --git a/src/DotNetLightning.Core/Channel/Channel.fs b/src/DotNetLightning.Core/Channel/Channel.fs index ca147059f..010053a1b 100644 --- a/src/DotNetLightning.Core/Channel/Channel.fs +++ b/src/DotNetLightning.Core/Channel/Channel.fs @@ -22,6 +22,7 @@ type ChannelWaitingForFundingSigned = { Network: Network FundingTxMinimumDepth: BlockHeightOffset32 WaitForFundingSignedData: WaitForFundingSignedData + LocalShutdownScriptPubKey: Option } with member self.ApplyFundingSigned (msg: FundingSignedMsg) : Result = result { @@ -75,6 +76,7 @@ type ChannelWaitingForFundingSigned = { State = WaitForFundingConfirmed nextState Network = self.Network FundingTxMinimumDepth = self.FundingTxMinimumDepth + LocalShutdownScriptPubKey = self.LocalShutdownScriptPubKey } return state.FundingTx, channel } @@ -87,6 +89,7 @@ and ChannelWaitingForFundingCreated = { NodeSecret: NodeSecret Network: Network FundingTxMinimumDepth: BlockHeightOffset32 + LocalShutdownScriptPubKey: Option WaitForFundingCreatedData: WaitForFundingCreatedData } with member self.ApplyFundingCreated (msg: FundingCreatedMsg) @@ -158,6 +161,7 @@ and ChannelWaitingForFundingCreated = { NodeSecret = self.NodeSecret Network = self.Network State = WaitForFundingConfirmed nextState + LocalShutdownScriptPubKey = self.LocalShutdownScriptPubKey FundingTxMinimumDepth = self.FundingTxMinimumDepth } return msgToSend, channel @@ -170,6 +174,7 @@ and ChannelWaitingForFundingTx = { RemoteNodeId: NodeId NodeSecret: NodeSecret Network: Network + LocalShutdownScriptPubKey: Option WaitForFundingTxData: WaitForFundingTxData } with member self.CreateFundingTx (fundingTx: FinalizedTx) @@ -228,6 +233,7 @@ and ChannelWaitingForFundingTx = { Network = self.Network FundingTxMinimumDepth = state.LastReceived.MinimumDepth WaitForFundingSignedData = waitForFundingSignedData + LocalShutdownScriptPubKey = self.LocalShutdownScriptPubKey } return nextMsg, channelWaitingForFundingSigned } @@ -241,6 +247,7 @@ and ChannelWaitingForAcceptChannel = { RemoteNodeId: NodeId NodeSecret: NodeSecret Network: Network + LocalShutdownScriptPubKey: Option WaitForAcceptChannelData: WaitForAcceptChannelData } with member self.ApplyAcceptChannel (msg: AcceptChannelMsg) @@ -265,6 +272,7 @@ and ChannelWaitingForAcceptChannel = { RemoteNodeId = self.RemoteNodeId NodeSecret = self.NodeSecret Network = self.Network + LocalShutdownScriptPubKey = self.LocalShutdownScriptPubKey WaitForFundingTxData = waitForFundingTxData } return destination, amount, channelWaitingForFundingTx @@ -278,6 +286,7 @@ and Channel = { NodeSecret: NodeSecret State: ChannelState Network: Network + LocalShutdownScriptPubKey: Option FundingTxMinimumDepth: BlockHeightOffset32 } with @@ -288,7 +297,8 @@ and Channel = { feeEstimator: IFeeEstimator, network: Network, remoteNodeId: NodeId, - inputInitFunder: InputInitFunder + inputInitFunder: InputInitFunder, + shutdownScriptPubKey: Option ): Result = let openChannelMsgToSend: OpenChannelMsg = { Chainhash = network.Consensus.HashGenesisBlock @@ -309,7 +319,7 @@ and Channel = { HTLCBasepoint = inputInitFunder.ChannelPrivKeys.HtlcBasepointSecret.HtlcBasepoint() FirstPerCommitmentPoint = inputInitFunder.ChannelPrivKeys.CommitmentSeed.DerivePerCommitmentPoint CommitmentNumber.FirstCommitment ChannelFlags = inputInitFunder.ChannelFlags - ShutdownScriptPubKey = channelOptions.ShutdownScriptPubKey + ShutdownScriptPubKey = shutdownScriptPubKey } result { do! Validation.checkOurOpenChannelMsgAcceptable openChannelMsgToSend @@ -328,6 +338,7 @@ and Channel = { NodeSecret = nodeSecret Network = network WaitForAcceptChannelData = waitForAcceptChannelData + LocalShutdownScriptPubKey = shutdownScriptPubKey } return (openChannelMsgToSend, channelWaitingForAcceptChannel) } @@ -341,6 +352,7 @@ and Channel = { remoteNodeId: NodeId, inputInitFundee: InputInitFundee, minimumDepth: BlockHeightOffset32, + shutdownScriptPubKey: Option, openChannelMsg: OpenChannelMsg ): Result = result { @@ -363,7 +375,7 @@ and Channel = { DelayedPaymentBasepoint = channelKeys.DelayedPaymentBasepointSecret.DelayedPaymentBasepoint() HTLCBasepoint = channelKeys.HtlcBasepointSecret.HtlcBasepoint() FirstPerCommitmentPoint = localCommitmentPubKey - ShutdownScriptPubKey = channelOptions.ShutdownScriptPubKey + ShutdownScriptPubKey = shutdownScriptPubKey } let remoteParams = RemoteParams.FromOpenChannel remoteNodeId inputInitFundee.RemoteInit openChannelMsg let waitForFundingCreatedData = Data.WaitForFundingCreatedData.Create localParams remoteParams openChannelMsg acceptChannelMsg @@ -377,6 +389,7 @@ and Channel = { NodeSecret = nodeSecret Network = network FundingTxMinimumDepth = minimumDepth + LocalShutdownScriptPubKey = shutdownScriptPubKey WaitForFundingCreatedData = waitForFundingCreatedData } return (acceptChannelMsg, channelWaitingForFundingCreated) @@ -398,12 +411,11 @@ module Channel = module Closing = let makeClosingTx (channelPrivKeys: ChannelPrivKeys, cm: Commitments, - localSpk: Script, - remoteSpk: Script, + localSpk: ShutdownScriptPubKey, + remoteSpk: ShutdownScriptPubKey, closingFee: Money, network: Network ) = - assert (Scripts.isValidFinalScriptPubKey (remoteSpk) && Scripts.isValidFinalScriptPubKey (localSpk)) let dustLimitSatoshis = Money.Max(cm.LocalParams.DustLimitSatoshis, cm.RemoteParams.DustLimitSatoshis) result { let! closingTx = Transactions.makeClosingTx (cm.FundingScriptCoin) (localSpk) (remoteSpk) (cm.LocalParams.IsFunder) (dustLimitSatoshis) (closingFee) (cm.LocalCommit.Spec) network @@ -416,7 +428,11 @@ module Channel = return (ClosingTx psbtUpdated, msg) } - let firstClosingFee (cm: Commitments, localSpk: Script, remoteSpk: Script, feeEst: IFeeEstimator, network) = + let firstClosingFee (cm: Commitments) + (localSpk: ShutdownScriptPubKey) + (remoteSpk: ShutdownScriptPubKey) + (feeEst: IFeeEstimator) + (network: Network) = result { let! dummyClosingTx = Transactions.makeClosingTx cm.FundingScriptCoin localSpk remoteSpk cm.LocalParams.IsFunder Money.Zero Money.Zero cm.LocalCommit.Spec network let tx = dummyClosingTx.Value.GetGlobalTransaction() @@ -429,13 +445,13 @@ module Channel = let makeFirstClosingTx (channelPrivKeys: ChannelPrivKeys, commitments: Commitments, - localSpk: Script, - remoteSpk: Script, + localSpk: ShutdownScriptPubKey, + remoteSpk: ShutdownScriptPubKey, feeEst: IFeeEstimator, network: Network ) = result { - let! closingFee = firstClosingFee (commitments, localSpk, remoteSpk, feeEst, network) + let! closingFee = firstClosingFee commitments localSpk remoteSpk feeEst network return! makeClosingTx (channelPrivKeys, commitments, localSpk, remoteSpk, closingFee, network) } |> expectTransactionError @@ -686,21 +702,30 @@ module Channel = RemotePerCommitmentSecrets = remotePerCommitmentSecrets } Ok [ WeAcceptedRevokeAndACK(commitments1) ] - | ChannelState.Normal state, ChannelCommand.Close cmd -> - let localSPK = cmd.ScriptPubKey |> Option.defaultValue (state.Commitments.LocalParams.DefaultFinalScriptPubKey) - if (state.LocalShutdown.IsSome) then - cannotCloseChannel "shutdown is already in progress" - else if (state.Commitments.LocalHasUnsignedOutgoingHTLCs()) then - cannotCloseChannel "Cannot close with unsigned outgoing htlcs" - else - let shutDown: ShutdownMsg = { + | ChannelState.Normal state, ChannelCommand.Close localShutdownScriptPubKey -> + result { + match cs.LocalShutdownScriptPubKey with + | Some commitedShutdownScriptPubKey -> + if commitedShutdownScriptPubKey <> localShutdownScriptPubKey then + do! cannotCloseChannel "Shutdown script does not match the shutdown script we orginally gave the peer" + | None -> () + if (state.LocalShutdown.IsSome) then + do! cannotCloseChannel "shutdown is already in progress" + if (state.Commitments.LocalHasUnsignedOutgoingHTLCs()) then + do! cannotCloseChannel "Cannot close with unsigned outgoing htlcs" + let shutdownMsg: ShutdownMsg = { ChannelId = state.ChannelId - ScriptPubKey = localSPK + ScriptPubKey = localShutdownScriptPubKey } - [ AcceptedOperationShutdown shutDown ] - |> Ok - | ChannelState.Normal state, RemoteShutdown msg -> + return [ AcceptedOperationShutdown shutdownMsg ] + } + | ChannelState.Normal state, RemoteShutdown(msg, localShutdownScriptPubKey) -> result { + match cs.LocalShutdownScriptPubKey with + | Some commitedShutdownScriptPubKey -> + if commitedShutdownScriptPubKey <> localShutdownScriptPubKey then + do! cannotCloseChannel "Shutdown script does not match the shutdown script we orginally gave the peer" + | None -> () let cm = state.Commitments // They have pending unsigned htlcs => they violated the spec, close the channel // they don't have pending unsigned htlcs @@ -740,7 +765,7 @@ module Channel = | None -> let localShutdown: ShutdownMsg = { ChannelId = state.ChannelId - ScriptPubKey = cm.LocalParams.DefaultFinalScriptPubKey + ScriptPubKey = localShutdownScriptPubKey } (localShutdown, [ localShutdown ]) if (cm.HasNoPendingHTLCs()) then @@ -852,11 +877,12 @@ module Channel = match lastLocalClosingFee with | Some v -> Ok v | None -> - Closing.firstClosingFee (state.Commitments, - state.LocalShutdown.ScriptPubKey, - state.RemoteShutdown.ScriptPubKey, - cs.FeeEstimator, - cs.Network) + Closing.firstClosingFee + state.Commitments + state.LocalShutdown.ScriptPubKey + state.RemoteShutdown.ScriptPubKey + cs.FeeEstimator + cs.Network |> expectTransactionError let nextClosingFee = Closing.nextClosingFee (localF, msg.FeeSatoshis) diff --git a/src/DotNetLightning.Core/Channel/ChannelOperations.fs b/src/DotNetLightning.Core/Channel/ChannelOperations.fs index c50f0851b..1226153cf 100644 --- a/src/DotNetLightning.Core/Channel/ChannelOperations.fs +++ b/src/DotNetLightning.Core/Channel/ChannelOperations.fs @@ -62,19 +62,6 @@ type OperationUpdateFee = { FeeRatePerKw: FeeRatePerKw } -type OperationClose = private { ScriptPubKey: Script option } - with - static member Zero = { ScriptPubKey = None } - static member Create scriptPubKey = - result { - do! Scripts.checkIsValidFinalScriptPubKey scriptPubKey - return { ScriptPubKey = Some scriptPubKey } - } - -module OperationClose = - let value cmdClose = - cmdClose.ScriptPubKey - type LocalParams = { ChannelPubKeys: ChannelPubKeys DustLimitSatoshis: Money @@ -84,7 +71,6 @@ type LocalParams = { ToSelfDelay: BlockHeightOffset16 MaxAcceptedHTLCs: uint16 IsFunder: bool - DefaultFinalScriptPubKey: Script Features: FeatureBits } @@ -222,9 +208,9 @@ type ChannelCommand = | ApplyRevokeAndACK of RevokeAndACKMsg // close - | Close of OperationClose + | Close of ShutdownScriptPubKey | ApplyClosingSigned of ClosingSignedMsg - | RemoteShutdown of ShutdownMsg + | RemoteShutdown of ShutdownMsg * ShutdownScriptPubKey // else | ForceClose diff --git a/src/DotNetLightning.Core/Serialization/LightningStream.fs b/src/DotNetLightning.Core/Serialization/LightningStream.fs index 660f0b501..44c60caf0 100644 --- a/src/DotNetLightning.Core/Serialization/LightningStream.fs +++ b/src/DotNetLightning.Core/Serialization/LightningStream.fs @@ -5,6 +5,9 @@ open System.IO open DotNetLightning.Utils open NBitcoin +open ResultUtils +open ResultUtils.Portability + type Scope(openAction: Action, closeAction: Action) = let _close = closeAction do openAction.Invoke() @@ -416,4 +419,9 @@ type LightningReaderStream(inner: Stream) = rest <- int32 this.Length - int32 this.Position result |> Seq.toArray - + member this.ReadShutdownScriptPubKey(): ShutdownScriptPubKey = + let script = this.ReadScript() + match ShutdownScriptPubKey.TryFromScript script with + | Ok shutdownScript -> shutdownScript + | Error errorMsg -> failwith errorMsg + diff --git a/src/DotNetLightning.Core/Serialization/Msgs/Msgs.fs b/src/DotNetLightning.Core/Serialization/Msgs/Msgs.fs index 1b1cdbd6f..51dfeb4ee 100644 --- a/src/DotNetLightning.Core/Serialization/Msgs/Msgs.fs +++ b/src/DotNetLightning.Core/Serialization/Msgs/Msgs.fs @@ -520,7 +520,7 @@ type OpenChannelMsg = { mutable HTLCBasepoint: HtlcBasepoint mutable FirstPerCommitmentPoint: PerCommitmentPoint mutable ChannelFlags: uint8 - mutable ShutdownScriptPubKey: OptionalField