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 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/Channel/Channel.fs b/src/DotNetLightning.Core/Channel/Channel.fs index 3c623aabd..1d9dd8aa9 100644 --- a/src/DotNetLightning.Core/Channel/Channel.fs +++ b/src/DotNetLightning.Core/Channel/Channel.fs @@ -13,874 +13,1392 @@ open System open ResultUtils open ResultUtils.Portability -type Channel = { - Config: ChannelConfig +type ChannelWaitingForFundingSigned = { + StaticChannelConfig: StaticChannelConfig + ChannelOptions: ChannelOptions + ChannelPrivKeys: ChannelPrivKeys + NodeSecret: NodeSecret + ChannelId: ChannelId + FundingTx: FinalizedTx + LocalSpec: CommitmentSpec + LocalCommitTx: CommitTx + RemoteCommit: RemoteCommit +} with + member self.ApplyFundingSigned (msg: FundingSignedMsg) + : Result = result { + let! finalizedLocalCommitTx = + let theirFundingPk = self.StaticChannelConfig.RemoteChannelPubKeys.FundingPubKey.RawPubKey() + let _, signedLocalCommitTx = + self.ChannelPrivKeys.SignWithFundingPrivKey self.LocalCommitTx.Value + let remoteSigPairOfLocalTx = (theirFundingPk, TransactionSignature(msg.Signature.Value, SigHash.All)) + let sigPairs = seq [ remoteSigPairOfLocalTx; ] + Transactions.checkTxFinalized signedLocalCommitTx CommitTx.WhichInput sigPairs |> expectTransactionError + let commitments = { + ProposedLocalChanges = List.empty + ProposedRemoteChanges = List.empty + LocalNextHTLCId = HTLCId.Zero + RemoteNextHTLCId = HTLCId.Zero + OriginChannels = Map.empty + } + let channel = { + SavedChannelState = { + StaticChannelConfig = self.StaticChannelConfig + RemotePerCommitmentSecrets = PerCommitmentSecretStore() + ShortChannelId = None + LocalCommit = { + Index = CommitmentNumber.FirstCommitment + Spec = self.LocalSpec + PublishableTxs = { + PublishableTxs.CommitTx = finalizedLocalCommitTx + HTLCTxs = [] + } + PendingHTLCSuccessTxs = [] + } + RemoteCommit = self.RemoteCommit + LocalChanges = LocalChanges.Zero + RemoteChanges = RemoteChanges.Zero + } + ChannelOptions = self.ChannelOptions + ChannelPrivKeys = self.ChannelPrivKeys + NodeSecret = self.NodeSecret + RemoteNextCommitInfo = None + NegotiatingState = NegotiatingState.New() + Commitments = commitments + } + return self.FundingTx, channel + } + +and ChannelWaitingForFundingCreated = { + AnnounceChannel: bool + RemoteNodeId: NodeId + Network: Network + FundingTxMinimumDepth: BlockHeightOffset32 + LocalStaticShutdownScriptPubKey: Option + RemoteStaticShutdownScriptPubKey: Option + IsFunder: bool + ChannelOptions: ChannelOptions + ChannelPrivKeys: ChannelPrivKeys + NodeSecret: NodeSecret + LocalParams: LocalParams + RemoteParams: RemoteParams + RemoteChannelPubKeys: ChannelPubKeys + FundingSatoshis: Money + PushMSat: LNMoney + InitialFeeRatePerKw: FeeRatePerKw + RemoteFirstPerCommitmentPoint: PerCommitmentPoint +} with + member self.ApplyFundingCreated (msg: FundingCreatedMsg) + : Result = result { + let! (localSpec, localCommitTx, remoteSpec, remoteCommitTx) = + let firstPerCommitmentPoint = + self.ChannelPrivKeys.CommitmentSeed.DerivePerCommitmentPoint + CommitmentNumber.FirstCommitment + ChannelHelpers.makeFirstCommitTxs + false + (self.ChannelPrivKeys.ToChannelPubKeys()) + self.RemoteChannelPubKeys + self.LocalParams + self.RemoteParams + self.FundingSatoshis + self.PushMSat + self.InitialFeeRatePerKw + msg.FundingOutputIndex + msg.FundingTxId + firstPerCommitmentPoint + self.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 = (self.RemoteChannelPubKeys.FundingPubKey.RawPubKey(), remoteTxSig) + let sigPairs = seq [ theirSigPair ] + let! finalizedCommitTx = + Transactions.checkTxFinalized (signedLocalCommitTx) CommitTx.WhichInput sigPairs + |> expectTransactionError + let localSigOfRemoteCommit, _ = + self.ChannelPrivKeys.SignWithFundingPrivKey remoteCommitTx.Value + let fundingScriptCoin = + ChannelHelpers.getFundingScriptCoin + (self.ChannelPrivKeys.FundingPrivKey.FundingPubKey()) + self.RemoteChannelPubKeys.FundingPubKey + msg.FundingTxId + msg.FundingOutputIndex + self.FundingSatoshis + let commitments = { + ProposedLocalChanges = List.empty + ProposedRemoteChanges = List.empty + LocalNextHTLCId = HTLCId.Zero + RemoteNextHTLCId = HTLCId.Zero + OriginChannels = Map.empty + } + let staticChannelConfig = { + AnnounceChannel = self.AnnounceChannel + RemoteNodeId = self.RemoteNodeId + Network = self.Network + FundingTxMinimumDepth = self.FundingTxMinimumDepth + LocalStaticShutdownScriptPubKey = self.LocalStaticShutdownScriptPubKey + RemoteStaticShutdownScriptPubKey = self.RemoteStaticShutdownScriptPubKey + IsFunder = self.IsFunder + FundingScriptCoin = fundingScriptCoin + LocalParams = self.LocalParams + RemoteParams = self.RemoteParams + RemoteChannelPubKeys = self.RemoteChannelPubKeys + } + let channelId = staticChannelConfig.ChannelId() + let msgToSend: FundingSignedMsg = { + ChannelId = channelId + Signature = !>localSigOfRemoteCommit.Signature + } + let channel = { + SavedChannelState = { + StaticChannelConfig = staticChannelConfig + RemotePerCommitmentSecrets = PerCommitmentSecretStore() + ShortChannelId = None + LocalCommit = { + Index = CommitmentNumber.FirstCommitment + Spec = localSpec + PublishableTxs = { + PublishableTxs.CommitTx = finalizedCommitTx + HTLCTxs = [] + } + PendingHTLCSuccessTxs = [] + } + RemoteCommit = { + Index = CommitmentNumber.FirstCommitment + Spec = remoteSpec + RemotePerCommitmentPoint = self.RemoteFirstPerCommitmentPoint + } + LocalChanges = LocalChanges.Zero + RemoteChanges = RemoteChanges.Zero + } + ChannelOptions = self.ChannelOptions + ChannelPrivKeys = self.ChannelPrivKeys + NodeSecret = self.NodeSecret + RemoteNextCommitInfo = None + NegotiatingState = NegotiatingState.New() + Commitments = commitments + } + return msgToSend, channel + } + +and ChannelWaitingForFundingTx = { + AnnounceChannel: bool + ChannelOptions: ChannelOptions ChannelPrivKeys: ChannelPrivKeys - FeeEstimator: IFeeEstimator RemoteNodeId: NodeId NodeSecret: NodeSecret - State: ChannelState 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 + LocalStaticShutdownScriptPubKey: Option + RemoteStaticShutdownScriptPubKey: Option + TemporaryChannelId: ChannelId + RemoteChannelPubKeys: ChannelPubKeys + FundingSatoshis: Money + PushMSat: LNMoney + InitFeeRatePerKw: FeeRatePerKw + LocalParams: LocalParams + RemoteFirstPerCommitmentPoint: PerCommitmentPoint + RemoteParams: RemoteParams + RemoteInit: InitMsg + FundingTxMinimumDepth: BlockHeightOffset32 +} with + member self.CreateFundingTx (fundingTx: FinalizedTx) + (outIndex: TxOutIndex) + : Result = result { + let localParams = self.LocalParams + let remoteParams = self.RemoteParams + let commitmentSpec = CommitmentSpec.Create (self.FundingSatoshis.ToLNMoney() - self.PushMSat) self.PushMSat self.InitFeeRatePerKw + let commitmentSeed = self.ChannelPrivKeys.CommitmentSeed + let fundingTxId = fundingTx.Value.GetTxId() + let! (_localSpec, localCommitTx, remoteSpec, remoteCommitTx) = + ChannelHelpers.makeFirstCommitTxs + true + (self.ChannelPrivKeys.ToChannelPubKeys()) + self.RemoteChannelPubKeys + localParams + remoteParams + self.FundingSatoshis + self.PushMSat + self.InitFeeRatePerKw + outIndex + fundingTxId + (commitmentSeed.DerivePerCommitmentPoint CommitmentNumber.FirstCommitment) + self.RemoteFirstPerCommitmentPoint + self.Network + let localSigOfRemoteCommit, _ = + self.ChannelPrivKeys.SignWithFundingPrivKey remoteCommitTx.Value + let nextMsg: FundingCreatedMsg = { + TemporaryChannelId = self.TemporaryChannelId + FundingTxId = fundingTxId + FundingOutputIndex = outIndex + Signature = !>localSigOfRemoteCommit.Signature + } + let fundingScriptCoin = + ChannelHelpers.getFundingScriptCoin + (self.ChannelPrivKeys.FundingPrivKey.FundingPubKey()) + self.RemoteChannelPubKeys.FundingPubKey + fundingTxId + outIndex + self.FundingSatoshis + let channelId = OutPoint(fundingTxId.Value, uint32 outIndex.Value).ToChannelId() + let channelWaitingForFundingSigned = { + StaticChannelConfig = { + AnnounceChannel = self.AnnounceChannel + RemoteNodeId = self.RemoteNodeId + Network = self.Network + FundingTxMinimumDepth = self.FundingTxMinimumDepth + LocalStaticShutdownScriptPubKey = self.LocalStaticShutdownScriptPubKey + RemoteStaticShutdownScriptPubKey = self.RemoteStaticShutdownScriptPubKey + IsFunder = true + FundingScriptCoin = fundingScriptCoin + LocalParams = localParams + RemoteParams = remoteParams + RemoteChannelPubKeys = self.RemoteChannelPubKeys } - static member CreateCurried = curry6 (Channel.Create) + ChannelOptions = self.ChannelOptions + ChannelPrivKeys = self.ChannelPrivKeys + NodeSecret = self.NodeSecret + ChannelId = channelId + FundingTx = fundingTx + LocalSpec = commitmentSpec + LocalCommitTx = localCommitTx + RemoteCommit = { + RemoteCommit.Index = CommitmentNumber.FirstCommitment + Spec = remoteSpec + RemotePerCommitmentPoint = self.RemoteFirstPerCommitmentPoint + } + } + return nextMsg, channelWaitingForFundingSigned + } -module Channel = - let private hex = NBitcoin.DataEncoders.HexEncoder() - let private ascii = System.Text.ASCIIEncoding.ASCII - let private dummyPrivKey = new Key(hex.DecodeData("0101010101010101010101010101010101010101010101010101010101010101")) - let private dummyPubKey = dummyPrivKey.PubKey - let private dummySig = - "01010101010101010101010101010101" |> ascii.GetBytes - |> uint256 - |> fun m -> dummyPrivKey.SignCompact(m) - |> fun d -> LNECDSASignature.FromBytesCompact(d, true) - |> fun ecdsaSig -> TransactionSignature(ecdsaSig.Value, SigHash.All) +and ChannelWaitingForAcceptChannel = { + AnnounceChannel: bool + ChannelOptions: ChannelOptions + ChannelHandshakeLimits: ChannelHandshakeLimits + ChannelPrivKeys: ChannelPrivKeys + RemoteNodeId: NodeId + NodeSecret: NodeSecret + Network: Network + LocalStaticShutdownScriptPubKey: Option + TemporaryChannelId: ChannelId + FundingSatoshis: Money + PushMSat: LNMoney + InitFeeRatePerKw: FeeRatePerKw + LocalParams: LocalParams + RemoteInit: InitMsg +} with + member self.ApplyAcceptChannel (msg: AcceptChannelMsg) + : Result = result { + do! Validation.checkAcceptChannelMsgAcceptable self.ChannelHandshakeLimits self.FundingSatoshis self.LocalParams.ChannelReserveSatoshis self.LocalParams.DustLimitSatoshis msg + let redeem = + Scripts.funding + (self.ChannelPrivKeys.ToChannelPubKeys().FundingPubKey) + msg.FundingPubKey + let remoteChannelPubKeys = { + FundingPubKey = msg.FundingPubKey + RevocationBasepoint = msg.RevocationBasepoint + PaymentBasepoint = msg.PaymentBasepoint + DelayedPaymentBasepoint = msg.DelayedPaymentBasepoint + HtlcBasepoint = msg.HTLCBasepoint + } + let destination = redeem.WitHash :> IDestination + let amount = self.FundingSatoshis + let remoteParams = RemoteParams.FromAcceptChannel self.RemoteInit msg + let channelWaitingForFundingTx = { + AnnounceChannel = self.AnnounceChannel + ChannelOptions = self.ChannelOptions + ChannelPrivKeys = self.ChannelPrivKeys + RemoteNodeId = self.RemoteNodeId + NodeSecret = self.NodeSecret + Network = self.Network + LocalStaticShutdownScriptPubKey = self.LocalStaticShutdownScriptPubKey + RemoteStaticShutdownScriptPubKey = msg.ShutdownScriptPubKey + TemporaryChannelId = msg.TemporaryChannelId + RemoteChannelPubKeys = remoteChannelPubKeys + FundingSatoshis = self.FundingSatoshis + PushMSat = self.PushMSat + InitFeeRatePerKw = self.InitFeeRatePerKw + LocalParams = self.LocalParams + RemoteInit = self.RemoteInit + RemoteFirstPerCommitmentPoint = msg.FirstPerCommitmentPoint + FundingTxMinimumDepth = msg.MinimumDepth + RemoteParams = remoteParams + } + return destination, amount, channelWaitingForFundingTx + } - module Closing = - let makeClosingTx (channelPrivKeys: ChannelPrivKeys, - cm: Commitments, - localSpk: Script, - remoteSpk: Script, - closingFee: Money, - localFundingPk: FundingPubKey, - 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 - let localSignature, psbtUpdated = channelPrivKeys.SignWithFundingPrivKey closingTx.Value - let msg: ClosingSignedMsg = { - ChannelId = cm.ChannelId - FeeSatoshis = closingFee - Signature = localSignature.Signature |> LNECDSASignature - } - return (ClosingTx psbtUpdated, msg) - } +and Channel = { + //StaticChannelConfig: StaticChannelConfig + SavedChannelState: SavedChannelState + ChannelOptions: ChannelOptions + ChannelPrivKeys: ChannelPrivKeys + NodeSecret: NodeSecret + RemoteNextCommitInfo: Option + NegotiatingState: NegotiatingState + Commitments: Commitments + } + with - let firstClosingFee (cm: Commitments, localSpk: Script, remoteSpk: Script, feeEst: IFeeEstimator, 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() - tx.Inputs.[0].WitScript <- - let witness = seq [ dummySig.ToBytes(); dummySig.ToBytes(); dummyClosingTx.Value.Inputs.[0].WitnessScript.ToBytes() ] - WitScript(witness) - let feeRatePerKw = FeeRatePerKw.Max (feeEst.GetEstSatPer1000Weight(ConfirmationTarget.HighPriority), cm.LocalCommit.Spec.FeeRatePerKw) - return feeRatePerKw.CalculateFeeFromVirtualSize(tx) - } + member internal self.RemoteNextCommitInfoIfFundingLocked (operation: string) + : Result = + match self.RemoteNextCommitInfo with + | None -> + sprintf + "cannot perform operation %s because peer has not sent funding_locked" + operation + |> apiMisuse + | Some remoteNextCommitInfo -> Ok remoteNextCommitInfo - let makeFirstClosingTx (channelPrivKeys: ChannelPrivKeys, - commitments: Commitments, - 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) - } |> expectTransactionError - - let nextClosingFee (localClosingFee: Money, remoteClosingFee: Money) = - ((localClosingFee.Satoshi + remoteClosingFee.Satoshi) / 4L) * 2L - |> Money.Satoshis - - let handleMutualClose (closingTx: FinalizedTx, d: NegotiatingData) = - let nextData = - ClosingData.Create (d.ChannelId, d.Commitments, None, DateTime.Now, (d.ClosingTxProposed |> List.collect id |> List.map (fun tx -> tx.UnsignedTx)), closingTx) - [ MutualClosePerformed (closingTx, nextData) ] - |> Ok - - let claimCurrentLocalCommitTxOutputs (channelPrivKeys: ChannelPrivKeys, - commitments: Commitments, - commitTx: CommitTx - ) = - result { - let commitmentSeed = channelPrivKeys.CommitmentSeed - do! check (commitments.LocalCommit.PublishableTxs.CommitTx.Value.GetTxId()) (=) (commitTx.Value.GetTxId()) "txid mismatch. provided txid (%A) does not match current local commit tx (%A)" - let _localPerCommitmentPoint = - commitmentSeed.DerivePerCommitmentPoint commitments.LocalCommit.Index - failwith "TODO" - } + member internal self.RemoteNextCommitInfoIfFundingLockedNormal (operation: string) + : Result = + match self.SavedChannelState.ShortChannelId with + | None -> + sprintf + "cannot perform operation %s because funding is not confirmed" + operation + |> apiMisuse + | Some _ -> + self.RemoteNextCommitInfoIfFundingLocked operation - let makeChannelReestablish (channelPrivKeys: ChannelPrivKeys) - (data: Data.IHasCommitments) - : Result = - let commitments = data.Commitments - let commitmentSeed = channelPrivKeys.CommitmentSeed - let ourChannelReestablish = - { - ChannelId = data.ChannelId - NextCommitmentNumber = - (commitments.RemotePerCommitmentSecrets.NextCommitmentNumber().NextCommitment()) - NextRevocationNumber = - commitments.RemotePerCommitmentSecrets.NextCommitmentNumber() - DataLossProtect = OptionalField.Some <| { - YourLastPerCommitmentSecret = - commitments.RemotePerCommitmentSecrets.MostRecentPerCommitmentSecret() - MyCurrentPerCommitmentPoint = - commitmentSeed.DerivePerCommitmentPoint commitments.RemoteCommit.Index - } - } - [ WeSentChannelReestablish ourChannelReestablish ] |> Ok - - let executeCommand (cs: Channel) (command: ChannelCommand): Result = - 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 + static member NewOutbound (channelHandshakeLimits: ChannelHandshakeLimits) + (channelOptions: ChannelOptions) + (announceChannel: bool) + (nodeMasterPrivKey: NodeMasterPrivKey) + (channelIndex: int) + (network: Network) + (remoteNodeId: NodeId) + (shutdownScriptPubKey: Option) + (temporaryChannelId: ChannelId) + (fundingSatoshis: Money) + (pushMSat: LNMoney) + (initFeeRatePerKw: FeeRatePerKw) + (localParams: LocalParams) + (remoteInit: InitMsg) + : Result = + let channelPrivKeys = nodeMasterPrivKey.ChannelPrivKeys channelIndex + let openChannelMsgToSend: OpenChannelMsg = { + Chainhash = network.Consensus.HashGenesisBlock + TemporaryChannelId = temporaryChannelId + FundingSatoshis = fundingSatoshis + PushMSat = pushMSat + DustLimitSatoshis = localParams.DustLimitSatoshis + MaxHTLCValueInFlightMsat = localParams.MaxHTLCValueInFlightMSat + ChannelReserveSatoshis = localParams.ChannelReserveSatoshis + HTLCMinimumMsat = localParams.HTLCMinimumMSat + FeeRatePerKw = initFeeRatePerKw + ToSelfDelay = localParams.ToSelfDelay + MaxAcceptedHTLCs = localParams.MaxAcceptedHTLCs + FundingPubKey = channelPrivKeys.FundingPrivKey.FundingPubKey() + RevocationBasepoint = channelPrivKeys.RevocationBasepointSecret.RevocationBasepoint() + PaymentBasepoint = channelPrivKeys.PaymentBasepointSecret.PaymentBasepoint() + DelayedPaymentBasepoint = channelPrivKeys.DelayedPaymentBasepointSecret.DelayedPaymentBasepoint() + HTLCBasepoint = channelPrivKeys.HtlcBasepointSecret.HtlcBasepoint() + FirstPerCommitmentPoint = channelPrivKeys.CommitmentSeed.DerivePerCommitmentPoint CommitmentNumber.FirstCommitment + ChannelFlags = { + AnnounceChannel = announceChannel } - result { - do! Validation.checkOurOpenChannelMsgAcceptable (cs.Config) openChannelMsgToSend - return [ - NewOutboundChannelStarted( - openChannelMsgToSend, { - InputInitFunder = inputInitFunder - LastSent = openChannelMsgToSend - }) - ] + ShutdownScriptPubKey = shutdownScriptPubKey + } + result { + do! Validation.checkOurOpenChannelMsgAcceptable openChannelMsgToSend + let nodeSecret = nodeMasterPrivKey.NodeSecret() + let channelWaitingForAcceptChannel = { + AnnounceChannel = announceChannel + ChannelHandshakeLimits = channelHandshakeLimits + ChannelOptions = channelOptions + ChannelPrivKeys = channelPrivKeys + RemoteNodeId = remoteNodeId + NodeSecret = nodeSecret + Network = network + LocalStaticShutdownScriptPubKey = shutdownScriptPubKey + TemporaryChannelId = temporaryChannelId + FundingSatoshis = fundingSatoshis + PushMSat = pushMSat + InitFeeRatePerKw = initFeeRatePerKw + LocalParams = localParams + RemoteInit = remoteInit } - | 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 (openChannelMsgToSend, channelWaitingForAcceptChannel) + } - return [ WeAcceptedAcceptChannel(destination, amount, nextState) ] + static member NewInbound (channelHandshakeLimits: ChannelHandshakeLimits) + (channelOptions: ChannelOptions) + (announceChannel: bool) + (nodeMasterPrivKey: NodeMasterPrivKey) + (channelIndex: int) + (network: Network) + (remoteNodeId: NodeId) + (minimumDepth: BlockHeightOffset32) + (shutdownScriptPubKey: Option) + (openChannelMsg: OpenChannelMsg) + (localParams: LocalParams) + (remoteInit: InitMsg) + : Result = + result { + let channelPrivKeys = nodeMasterPrivKey.ChannelPrivKeys channelIndex + do! + Validation.checkOpenChannelMsgAcceptable + channelHandshakeLimits + channelOptions + announceChannel + openChannelMsg + let firstPerCommitmentPoint = channelPrivKeys.CommitmentSeed.DerivePerCommitmentPoint CommitmentNumber.FirstCommitment + let acceptChannelMsg: AcceptChannelMsg = { + TemporaryChannelId = openChannelMsg.TemporaryChannelId + DustLimitSatoshis = localParams.DustLimitSatoshis + MaxHTLCValueInFlightMsat = localParams.MaxHTLCValueInFlightMSat + ChannelReserveSatoshis = localParams.ChannelReserveSatoshis + HTLCMinimumMSat = localParams.HTLCMinimumMSat + MinimumDepth = minimumDepth + ToSelfDelay = localParams.ToSelfDelay + MaxAcceptedHTLCs = localParams.MaxAcceptedHTLCs + FundingPubKey = channelPrivKeys.FundingPrivKey.FundingPubKey() + RevocationBasepoint = channelPrivKeys.RevocationBasepointSecret.RevocationBasepoint() + PaymentBasepoint = channelPrivKeys.PaymentBasepointSecret.PaymentBasepoint() + DelayedPaymentBasepoint = channelPrivKeys.DelayedPaymentBasepointSecret.DelayedPaymentBasepoint() + HTLCBasepoint = channelPrivKeys.HtlcBasepointSecret.HtlcBasepoint() + FirstPerCommitmentPoint = firstPerCommitmentPoint + ShutdownScriptPubKey = shutdownScriptPubKey } - | 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) ] + let remoteChannelPubKeys = { + FundingPubKey = openChannelMsg.FundingPubKey + RevocationBasepoint = openChannelMsg.RevocationBasepoint + PaymentBasepoint = openChannelMsg.PaymentBasepoint + DelayedPaymentBasepoint = openChannelMsg.DelayedPaymentBasepoint + HtlcBasepoint = openChannelMsg.HTLCBasepoint } - | 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) ] + let remoteParams = RemoteParams.FromOpenChannel remoteInit openChannelMsg + let nodeSecret = nodeMasterPrivKey.NodeSecret() + let channelWaitingForFundingCreated = { + AnnounceChannel = openChannelMsg.ChannelFlags.AnnounceChannel + RemoteNodeId = remoteNodeId + Network = network + FundingTxMinimumDepth = minimumDepth + LocalStaticShutdownScriptPubKey = shutdownScriptPubKey + RemoteStaticShutdownScriptPubKey = openChannelMsg.ShutdownScriptPubKey + IsFunder = false + ChannelOptions = channelOptions + ChannelPrivKeys = channelPrivKeys + NodeSecret = nodeSecret + LocalParams = localParams + RemoteParams = remoteParams + RemoteChannelPubKeys = remoteChannelPubKeys + FundingSatoshis = openChannelMsg.FundingSatoshis + PushMSat = openChannelMsg.PushMSat + InitialFeeRatePerKw = openChannelMsg.FeeRatePerKw + RemoteFirstPerCommitmentPoint = openChannelMsg.FirstPerCommitmentPoint } - // --------------- 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) ] + return (acceptChannelMsg, channelWaitingForFundingCreated) + } + + member self.CreateChannelReestablish (): ChannelReestablishMsg = + let commitmentSeed = self.ChannelPrivKeys.CommitmentSeed + let ourChannelReestablish = { + ChannelId = self.SavedChannelState.StaticChannelConfig.ChannelId() + NextCommitmentNumber = + (self.SavedChannelState.RemotePerCommitmentSecrets.NextCommitmentNumber().NextCommitment()) + NextRevocationNumber = + self.SavedChannelState.RemotePerCommitmentSecrets.NextCommitmentNumber() + DataLossProtect = OptionalField.Some <| { + YourLastPerCommitmentSecret = + self.SavedChannelState.RemotePerCommitmentSecrets.MostRecentPerCommitmentSecret() + MyCurrentPerCommitmentPoint = + commitmentSeed.DerivePerCommitmentPoint self.SavedChannelState.RemoteCommit.Index } - | WaitForOpenChannel _state, ChannelCommand.Close _spk -> - [ ChannelEvent.Closed ] |> Ok + } + ourChannelReestablish - | 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) ] + member self.ApplyFundingLocked (fundingLockedMsg: FundingLockedMsg) + : Result = result { + do! + match self.RemoteNextCommitInfo with + | None -> Ok () + | Some remoteNextCommitInfo -> + if remoteNextCommitInfo.PerCommitmentPoint() + <> fundingLockedMsg.NextPerCommitmentPoint then + Error <| InvalidFundingLocked { NetworkMsg = fundingLockedMsg } + else + Ok () + match self.SavedChannelState.ShortChannelId with + | None -> + return { + self with + RemoteNextCommitInfo = + RemoteNextCommitInfo.Revoked fundingLockedMsg.NextPerCommitmentPoint + |> Some } - | WaitForFundingConfirmed _state, ApplyFundingLocked msg -> - [ TheySentFundingLocked msg ] |> Ok - | WaitForFundingConfirmed state, ApplyFundingConfirmedOnBC(height, txindex, depth) -> - if state.Commitments.RemoteParams.MinimumDepth > depth then - [] |> Ok + | Some _shortChannelId -> + return self + } + + member self.ApplyFundingConfirmedOnBC (height: BlockHeight) + (txindex: TxIndexInBlock) + (depth: BlockHeightOffset32) + : Result, ChannelError> = result { + match self.SavedChannelState.ShortChannelId with + | None -> + if self.SavedChannelState.StaticChannelConfig.FundingTxMinimumDepth > depth then + // TODO: this should probably be an error (?) + return self, None else let nextPerCommitmentPoint = - cs.ChannelPrivKeys.CommitmentSeed.DerivePerCommitmentPoint + self.ChannelPrivKeys.CommitmentSeed.DerivePerCommitmentPoint (CommitmentNumber.FirstCommitment.NextCommitment()) - let msgToSend: FundingLockedMsg = { ChannelId = state.Commitments.ChannelId; NextPerCommitmentPoint = nextPerCommitmentPoint } - - // This is temporary channel id that we will use in our channel_update message, the goal is to be able to use our channel - // as soon as it reaches NORMAL state, and before it is announced on the network - // (this id might be updated when the funding tx gets deeply buried, if there was a reorg in the meantime) - // this is not specified in BOLT. - let shortChannelId = { ShortChannelId.BlockHeight = height; - BlockIndex = txindex - TxOutIndex = state.Commitments.FundingScriptCoin.Outpoint.N |> uint16 |> TxOutIndex } - let nextState = { Data.WaitForFundingLockedData.Commitments = state.Commitments - ShortChannelId = shortChannelId - OurMessage = msgToSend - TheirMessage = None - HaveWeSentFundingLocked = false - InitialFeeRatePerKw = state.InitialFeeRatePerKw - ChannelId = state.Commitments.ChannelId } - - match (state.Deferred) with - | None -> - [ FundingConfirmed nextState; WeSentFundingLocked msgToSend ] |> Ok - | Some msg -> - [ FundingConfirmed nextState; WeSentFundingLocked msgToSend; WeResumedDelayedFundingLocked msg ] |> Ok - | WaitForFundingLocked _state, ApplyFundingConfirmedOnBC(height, _txindex, depth) -> - if (cs.Config.ChannelHandshakeConfig.MinimumDepth <= depth) then - [] |> Ok - else - onceConfirmedFundingTxHasBecomeUnconfirmed(height, depth) - | WaitForFundingLocked state, ApplyFundingLocked msg -> - if (state.HaveWeSentFundingLocked) then - let initialChannelUpdate = - let feeBase = ChannelHelpers.getOurFeeBaseMSat cs.FeeEstimator state.InitialFeeRatePerKw state.Commitments.LocalParams.IsFunder - ChannelHelpers.makeChannelUpdate (cs.Network.Consensus.HashGenesisBlock, - cs.NodeSecret, - cs.RemoteNodeId, - state.ShortChannelId, - state.Commitments.LocalParams.ToSelfDelay, - state.Commitments.RemoteParams.HTLCMinimumMSat, - feeBase, - cs.Config.ChannelOptions.FeeProportionalMillionths, - true, - None) - let nextState = { NormalData.Buried = true - Commitments = { state.Commitments with RemoteNextCommitInfo = RemoteNextCommitInfo.Revoked(msg.NextPerCommitmentPoint) } - ShortChannelId = state.ShortChannelId - ChannelAnnouncement = None - ChannelUpdate = initialChannelUpdate - LocalShutdown = None - RemoteShutdown = None - ChannelId = state.ChannelId } - [ BothFundingLocked nextState ] |> Ok - else - [] |> Ok + let msgToSend: FundingLockedMsg = { + ChannelId = self.SavedChannelState.StaticChannelConfig.ChannelId() + NextPerCommitmentPoint = nextPerCommitmentPoint + } - // ---------- normal operation --------- - | 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 - | ChannelState.Normal state, AddHTLC op -> - result { - do! Validation.checkOperationAddHTLC state op - let add: UpdateAddHTLCMsg = { - ChannelId = state.Commitments.ChannelId - HTLCId = state.Commitments.LocalNextHTLCId - Amount = op.Amount - PaymentHash = op.PaymentHash - CLTVExpiry = op.Expiry - OnionRoutingPacket = op.Onion + // This is temporary channel id that we will use in our + // channel_update message, the goal is to be able to use our + // channel as soon as it reaches NORMAL state, and before it is + // announced on the network (this id might be updated when the + // funding tx gets deeply buried, if there was a reorg in the + // meantime) this is not specified in BOLT. + let shortChannelId = { + ShortChannelId.BlockHeight = height; + BlockIndex = txindex + TxOutIndex = + self.SavedChannelState.StaticChannelConfig.FundingScriptCoin.Outpoint.N + |> uint16 + |> TxOutIndex + } + let savedChannelState = { + self.SavedChannelState with + ShortChannelId = Some shortChannelId + } + let channel = { + self with + SavedChannelState = savedChannelState } - let commitments1 = { state.Commitments.AddLocalProposal(add) - with LocalNextHTLCId = state.Commitments.LocalNextHTLCId + 1UL } - |> fun commitments -> - match op.Origin with - | None -> commitments - | Some o -> { commitments with OriginChannels = state.Commitments.OriginChannels |> Map.add add.HTLCId o } - // we need to base the next current commitment on the last sig we sent, even if we didn't yet receive their revocation - 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.checkOurUpdateAddHTLCIsAcceptableWithCurrentSpec reduced commitments1 add - return [ WeAcceptedOperationAddHTLC(add, commitments1) ] + return channel, Some msgToSend + | Some _shortChannelId -> + if (self.SavedChannelState.StaticChannelConfig.FundingTxMinimumDepth <= depth) then + return self, None + else + return! Error <| OnceConfirmedFundingTxHasBecomeUnconfirmed(height, depth) + } + + member self.MonoHopUnidirectionalPayment (amount: LNMoney) + : Result = result { + if self.NegotiatingState.HasEnteredShutdown() then + return! + sprintf + "Could not send mono-hop unidirectional payment of amount %A since shutdown is already \ + in progress." amount + |> apiMisuse + else + let payment: MonoHopUnidirectionalPaymentMsg = { + ChannelId = self.SavedChannelState.StaticChannelConfig.ChannelId() + Amount = amount } - | ChannelState.Normal state, ApplyUpdateAddHTLC (msg, height) -> - result { - do! Validation.checkTheirUpdateAddHTLCIsAcceptable state.Commitments msg height - let commitments1 = { state.Commitments.AddRemoteProposal(msg) - with RemoteNextHTLCId = state.Commitments.LocalNextHTLCId + 1UL } - let! reduced = commitments1.LocalCommit.Spec.Reduce (commitments1.LocalChanges.ACKed, commitments1.RemoteChanges.Proposed) |> expectTransactionError - do! Validation.checkTheirUpdateAddHTLCIsAcceptableWithCurrentSpec reduced commitments1 msg - return [ WeAcceptedUpdateAddHTLC commitments1 ] + let commitments1 = self.Commitments.AddLocalProposal(payment) + + let! remoteNextCommitInfo = + self.RemoteNextCommitInfoIfFundingLockedNormal "MonoHopUniDirectionalPayment" + let remoteCommit1 = + match remoteNextCommitInfo with + | Waiting nextRemoteCommit -> nextRemoteCommit + | Revoked _info -> self.SavedChannelState.RemoteCommit + let! reduced = remoteCommit1.Spec.Reduce(self.SavedChannelState.RemoteChanges.ACKed, commitments1.ProposedLocalChanges) |> expectTransactionError + do! + Validation.checkOurMonoHopUnidirectionalPaymentIsAcceptableWithCurrentSpec + reduced + self.SavedChannelState.StaticChannelConfig + payment + let channel = { + self with + Commitments = commitments1 } + return channel, payment + } - | ChannelState.Normal state, FulfillHTLC cmd -> - result { - let! t = state.Commitments |> Commitments.sendFulfill (cmd) - return [ WeAcceptedOperationFulfillHTLC t ] + member self.ApplyMonoHopUnidirectionalPayment (msg: MonoHopUnidirectionalPaymentMsg) + : Result = result { + let commitments1 = self.Commitments.AddRemoteProposal(msg) + let! reduced = + self.SavedChannelState.LocalCommit.Spec.Reduce + (self.SavedChannelState.LocalChanges.ACKed, commitments1.ProposedRemoteChanges) + |> expectTransactionError + do! + Validation.checkTheirMonoHopUnidirectionalPaymentIsAcceptableWithCurrentSpec + reduced + self.SavedChannelState.StaticChannelConfig + msg + return { + self with + Commitments = commitments1 + } + } + + member self.AddHTLC (op: OperationAddHTLC) + : Result = result { + if self.NegotiatingState.HasEnteredShutdown() then + return! + sprintf "Could not add new HTLC %A since shutdown is already in progress." op + |> apiMisuse + else + do! Validation.checkOperationAddHTLC self.SavedChannelState.StaticChannelConfig.RemoteParams op + let add: UpdateAddHTLCMsg = { + ChannelId = self.SavedChannelState.StaticChannelConfig.ChannelId() + HTLCId = self.Commitments.LocalNextHTLCId + Amount = op.Amount + PaymentHash = op.PaymentHash + CLTVExpiry = op.Expiry + OnionRoutingPacket = op.Onion } + let commitments1 = + let commitments = { + self.Commitments.AddLocalProposal(add) with + LocalNextHTLCId = self.Commitments.LocalNextHTLCId + 1UL + } + match op.Origin with + | None -> commitments + | Some origin -> { + commitments with + OriginChannels = + self.Commitments.OriginChannels + |> Map.add add.HTLCId origin + } - | ChannelState.Normal state, ChannelCommand.ApplyUpdateFulfillHTLC msg -> - state.Commitments |> Commitments.receiveFulfill msg + let! remoteNextCommitInfo = + self.RemoteNextCommitInfoIfFundingLockedNormal "AddHTLC" + // we need to base the next current commitment on the last sig we sent, even if we didn't yet receive their revocation + let remoteCommit1 = + match remoteNextCommitInfo with + | Waiting nextRemoteCommit -> nextRemoteCommit + | Revoked _info -> self.SavedChannelState.RemoteCommit + let! reduced = remoteCommit1.Spec.Reduce(self.SavedChannelState.RemoteChanges.ACKed, commitments1.ProposedLocalChanges) |> expectTransactionError + do! + Validation.checkOurUpdateAddHTLCIsAcceptableWithCurrentSpec + reduced + self.SavedChannelState.StaticChannelConfig + add + let channel = { + self with + Commitments = commitments1 + } + return channel, add + } - | ChannelState.Normal state, FailHTLC op -> - state.Commitments |> Commitments.sendFail cs.NodeSecret op + member self.ApplyUpdateAddHTLC (msg: UpdateAddHTLCMsg) + (height: BlockHeight) + : Result = result { + do! + Validation.checkTheirUpdateAddHTLCIsAcceptable + self.Commitments + self.SavedChannelState.StaticChannelConfig.LocalParams + msg + height + let commitments1 = { + self.Commitments.AddRemoteProposal(msg) with + RemoteNextHTLCId = self.Commitments.LocalNextHTLCId + 1UL + } + let! reduced = + self.SavedChannelState.LocalCommit.Spec.Reduce ( + self.SavedChannelState.LocalChanges.ACKed, + commitments1.ProposedRemoteChanges + ) |> expectTransactionError + do! + Validation.checkTheirUpdateAddHTLCIsAcceptableWithCurrentSpec + reduced + self.SavedChannelState.StaticChannelConfig + msg + return { + self with + Commitments = commitments1 + } + } - | ChannelState.Normal state, FailMalformedHTLC op -> - state.Commitments |> Commitments.sendFailMalformed op + member self.FulFillHTLC (cmd: OperationFulfillHTLC) + : Result = result { + let! remoteNextCommitInfo = + self.RemoteNextCommitInfoIfFundingLockedNormal "FulfillHTLC" + let! updateFulfillHTLCMsg, newCommitments = + Commitments.sendFulfill + cmd + self.Commitments + self.SavedChannelState + remoteNextCommitInfo - | ChannelState.Normal state, ApplyUpdateFailHTLC msg -> - state.Commitments |> Commitments.receiveFail msg + let channel = { + self with + Commitments = newCommitments + } + return channel, updateFulfillHTLCMsg + } - | ChannelState.Normal state, ApplyUpdateFailMalformedHTLC msg -> - state.Commitments |> Commitments.receiveFailMalformed msg + member self.ApplyUpdateFulfillHTLC (msg: UpdateFulfillHTLCMsg) + : Result = result { + let! remoteNextCommitInfo = + self.RemoteNextCommitInfoIfFundingLockedNormal "ApplyUpdateFulfullHTLC" + let! newCommitments = + Commitments.receiveFulfill + msg + self.Commitments + self.SavedChannelState + remoteNextCommitInfo + return { + self with + Commitments = newCommitments + } + } - | ChannelState.Normal state, UpdateFee op -> - 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 + member self.FailHTLC (op: OperationFailHTLC) + : Result = result { + let! remoteNextCommitInfo = + self.RemoteNextCommitInfoIfFundingLockedNormal "FailHTLC" + let! updateFailHTLCMsg, newCommitments = + Commitments.sendFail + self.NodeSecret + op + self.Commitments + self.SavedChannelState + remoteNextCommitInfo + let channel = { + self with + Commitments = newCommitments + } + return channel, updateFailHTLCMsg + } - | ChannelState.Normal state, SignCommitment -> - let cm = state.Commitments - result { - match cm.RemoteNextCommitInfo with - | _ when (cm.LocalHasChanges() |> not) -> - // Ignore SignCommitment Command (nothing to sign) - return [] - | RemoteNextCommitInfo.Revoked _ -> - return! cm |> Commitments.sendCommit cs.ChannelPrivKeys (cs.Network) - | RemoteNextCommitInfo.Waiting _ -> - // Already in the process of signing - return [] - } + member self.FailMalformedHTLC (op: OperationFailMalformedHTLC) + : Result = result { + let! remoteNextCommitInfo = + self.RemoteNextCommitInfoIfFundingLockedNormal "FailMalformedHTLC" + let! updateFailMalformedHTLCMsg, newCommitments = + Commitments.sendFailMalformed + op + self.Commitments + self.SavedChannelState + remoteNextCommitInfo + let channel = { + self with + Commitments = newCommitments + } + return channel, updateFailMalformedHTLCMsg + } - | ChannelState.Normal state, ApplyCommitmentSigned msg -> - state.Commitments |> Commitments.receiveCommit cs.ChannelPrivKeys msg cs.Network - - | ChannelState.Normal state, ApplyRevokeAndACK msg -> - let cm = state.Commitments - match cm.RemoteNextCommitInfo with - | RemoteNextCommitInfo.Waiting _ when (msg.PerCommitmentSecret.PerCommitmentPoint() <> cm.RemoteCommit.RemotePerCommitmentPoint) -> - let errorMsg = sprintf "Invalid revoke_and_ack %A; must be %A" msg.PerCommitmentSecret cm.RemoteCommit.RemotePerCommitmentPoint - invalidRevokeAndACK msg errorMsg - | RemoteNextCommitInfo.Revoked _ -> - let errorMsg = sprintf "Unexpected revocation" - invalidRevokeAndACK msg errorMsg - | RemoteNextCommitInfo.Waiting({ NextRemoteCommit = theirNextCommit }) -> - let remotePerCommitmentSecretsOpt = - cm.RemotePerCommitmentSecrets.InsertPerCommitmentSecret - cm.RemoteCommit.Index - msg.PerCommitmentSecret - match remotePerCommitmentSecretsOpt with - | Error err -> invalidRevokeAndACK msg err.Message - | Ok remotePerCommitmentSecrets -> - let commitments1 = { cm with LocalChanges = { cm.LocalChanges with Signed = []; ACKed = cm.LocalChanges.ACKed @ cm.LocalChanges.Signed } - RemoteChanges = { cm.RemoteChanges with Signed = [] } - RemoteCommit = theirNextCommit - RemoteNextCommitInfo = RemoteNextCommitInfo.Revoked msg.NextPerCommitmentPoint - RemotePerCommitmentSecrets = remotePerCommitmentSecrets } - let _result = Ok [ WeAcceptedRevokeAndACK commitments1 ] - failwith "needs update" - - | 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 = { - ChannelId = state.ChannelId - ScriptPubKey = localSPK + member self.ApplyUpdateFailHTLC (msg: UpdateFailHTLCMsg) + : Result = result { + let! remoteNextCommitInfo = + self.RemoteNextCommitInfoIfFundingLockedNormal "ApplyUpdateFailHTLC" + let! newCommitments = + Commitments.receiveFail + msg + self.Commitments + self.SavedChannelState + remoteNextCommitInfo + return { + self with + Commitments = newCommitments + } + } + + member self.ApplyUpdateFailMalformedHTLC (msg: UpdateFailMalformedHTLCMsg) + : Result = result { + let! remoteNextCommitInfo = + self.RemoteNextCommitInfoIfFundingLockedNormal "ApplyUpdateFailMalformedHTLC" + let! newCommitments = + Commitments.receiveFailMalformed + msg + self.Commitments + self.SavedChannelState + remoteNextCommitInfo + return { + self with + Commitments = newCommitments + } + } + + member self.UpdateFee (op: OperationUpdateFee) + : Result = result { + let! _remoteNextCommitInfo = + self.RemoteNextCommitInfoIfFundingLockedNormal "UpdateFee" + let! updateFeeMsg, newCommitments = + Commitments.sendFee op self.SavedChannelState self.Commitments + let channel = { + self with + Commitments = newCommitments + } + return channel, updateFeeMsg + } + + member self.ApplyUpdateFee (msg: UpdateFeeMsg) + : Result = result { + let! _remoteNextCommitInfo = + self.RemoteNextCommitInfoIfFundingLockedNormal "ApplyUpdateFee" + let localFeerate = self.ChannelOptions.FeeEstimator.GetEstSatPer1000Weight(ConfirmationTarget.HighPriority) + let! newCommitments = + Commitments.receiveFee + self.ChannelOptions + localFeerate + msg + self.SavedChannelState + self.Commitments + return { + self with + Commitments = newCommitments + } + } + + member self.SignCommitment(): Result = result { + let! remoteNextCommitInfo = + self.RemoteNextCommitInfoIfFundingLockedNormal "SignCommit" + match remoteNextCommitInfo with + | _ when (self.LocalHasChanges() |> not) -> + return! Error NoUpdatesToSign + | RemoteNextCommitInfo.Revoked _ -> + let! commitmentSignedMsg, channel = + self.sendCommit remoteNextCommitInfo + return channel, commitmentSignedMsg + | RemoteNextCommitInfo.Waiting _ -> + return! Error CannotSignCommitmentBeforeRevocation + } + + member self.ApplyCommitmentSigned (msg: CommitmentSignedMsg) + : Result = result { + let! _remoteNextCommitInfo = + self.RemoteNextCommitInfoIfFundingLockedNormal "ApplyCommitmentSigned" + let! revokeAndACKMsg, channel = self.receiveCommit msg + return channel, revokeAndACKMsg + } + + member self.ApplyRevokeAndACK (msg: RevokeAndACKMsg) + : Result = result { + let! remoteNextCommitInfo = + self.RemoteNextCommitInfoIfFundingLockedNormal "ApplyRevokeAndACK" + match remoteNextCommitInfo with + | RemoteNextCommitInfo.Waiting _ when (msg.PerCommitmentSecret.PerCommitmentPoint() <> self.SavedChannelState.RemoteCommit.RemotePerCommitmentPoint) -> + let errorMsg = sprintf "Invalid revoke_and_ack %A; must be %A" msg.PerCommitmentSecret self.SavedChannelState.RemoteCommit.RemotePerCommitmentPoint + return! Error <| invalidRevokeAndACK msg errorMsg + | RemoteNextCommitInfo.Revoked _ -> + let errorMsg = sprintf "Unexpected revocation" + return! Error <| invalidRevokeAndACK msg errorMsg + | RemoteNextCommitInfo.Waiting theirNextCommit -> + let remotePerCommitmentSecretsOpt = + self.SavedChannelState.RemotePerCommitmentSecrets.InsertPerCommitmentSecret + self.SavedChannelState.RemoteCommit.Index + msg.PerCommitmentSecret + match remotePerCommitmentSecretsOpt with + | Error err -> return! Error <| invalidRevokeAndACK msg err.Message + | Ok remotePerCommitmentSecrets -> + let savedChannelState = { + self.SavedChannelState with + RemotePerCommitmentSecrets = remotePerCommitmentSecrets + RemoteCommit = theirNextCommit + LocalChanges = { + self.SavedChannelState.LocalChanges with + Signed = []; + ACKed = self.SavedChannelState.LocalChanges.ACKed @ self.SavedChannelState.LocalChanges.Signed + } + RemoteChanges = { + self.SavedChannelState.RemoteChanges with + Signed = [] + } } - [ AcceptedOperationShutdown shutDown ] - |> Ok - | ChannelState.Normal state, RemoteShutdown msg -> - result { - let cm = state.Commitments - // They have pending unsigned htlcs => they violated the spec, close the channel - // they don't have pending unsigned htlcs - // We have pending unsigned htlcs - // We already sent a shutdown msg => spec violation (we can't send htlcs after having sent shutdown) - // We did not send a shutdown msg - // We are ready to sign => we stop sending further htlcs, we initiate a signature - // We are waiting for a rev => we stop sending further htlcs, we wait for their revocation, will resign immediately after, and then we will send our shutdown msg - // We have no pending unsigned htlcs - // we already sent a shutdown msg - // There are pending signed htlcs => send our shutdown msg, go to SHUTDOWN state - // there are no htlcs => send our shutdown msg, goto NEGOTIATING state - // We did not send a shutdown msg - // There are pending signed htlcs => go to SHUTDOWN state - // there are no HTLCs => go to NEGOTIATING state - - if (cm.RemoteHasUnsignedOutgoingHTLCs()) then - return! receivedShutdownWhenRemoteHasUnsignedOutgoingHTLCs msg - // Do we have Unsigned Outgoing HTLCs? - else if (cm.LocalHasUnsignedOutgoingHTLCs()) then - if (state.LocalShutdown.IsSome) then - return failwith "can't have pending unsigned outgoing htlcs after having sent Shutdown. this should never happen" - else - // Are we in the middle of a signature? - match cm.RemoteNextCommitInfo with - // yes. - | RemoteNextCommitInfo.Waiting waitingForRevocation -> - let nextCommitments = { state.Commitments with - RemoteNextCommitInfo = RemoteNextCommitInfo.Waiting({ waitingForRevocation with ReSignASAP = true }) } - return [ AcceptedShutdownWhileWeHaveUnsignedOutgoingHTLCs(msg, nextCommitments) ] - // No. let's sign right away. - | RemoteNextCommitInfo.Revoked _ -> - return [ ChannelStateRequestedSignCommitment; AcceptedShutdownWhileWeHaveUnsignedOutgoingHTLCs(msg, cm) ] - else - let (localShutdown, _sendList) = match state.LocalShutdown with - | Some localShutdown -> (localShutdown, []) - | None -> - let localShutdown: ShutdownMsg = { - ChannelId = state.ChannelId - ScriptPubKey = cm.LocalParams.DefaultFinalScriptPubKey - } - (localShutdown, [ localShutdown ]) - if (cm.HasNoPendingHTLCs()) then - // we have to send first closing_signed msg iif we are the funder - if (cm.LocalParams.IsFunder) then - let! (closingTx, closingSignedMsg) = - Closing.makeFirstClosingTx (cs.ChannelPrivKeys, - cm, - localShutdown.ScriptPubKey, - msg.ScriptPubKey, - cs.FeeEstimator, - cm.LocalParams.ChannelPubKeys.FundingPubKey, - cs.Network) - let nextState = { NegotiatingData.ChannelId = cm.ChannelId - Commitments = cm - LocalShutdown = localShutdown - RemoteShutdown = msg - ClosingTxProposed = [ [ { ClosingTxProposed.UnsignedTx = closingTx; LocalClosingSigned = closingSignedMsg } ] ] - MaybeBestUnpublishedTx = None } - return [ AcceptedShutdownWhenNoPendingHTLCs(closingSignedMsg |> Some, nextState) ] - else - let nextState = { NegotiatingData.ChannelId = cm.ChannelId - Commitments = cm - LocalShutdown = localShutdown - RemoteShutdown = msg - ClosingTxProposed = [ [] ] - MaybeBestUnpublishedTx = None } - return [ AcceptedShutdownWhenNoPendingHTLCs(None, nextState) ] - else - let nextState = { ShutdownData.Commitments = cm - LocalShutdown = localShutdown - RemoteShutdown = msg - ChannelId = cm.ChannelId } - return [ AcceptedShutdownWhenWeHavePendingHTLCs(nextState) ] - } - // ----------- closing --------- - | Shutdown state, FulfillHTLC op -> - result { - let! t = state.Commitments |> Commitments.sendFulfill op - return [ WeAcceptedOperationFulfillHTLC t ] + return { + self with + SavedChannelState = savedChannelState + RemoteNextCommitInfo = + Some <| RemoteNextCommitInfo.Revoked msg.NextPerCommitmentPoint + } + } + + member self.Close (localShutdownScriptPubKey: ShutdownScriptPubKey) + : Result = result { + if self.NegotiatingState.LocalRequestedShutdown.IsSome then + do! Error <| cannotCloseChannel "shutdown is already in progress" + do! + Validation.checkShutdownScriptPubKeyAcceptable + self.SavedChannelState.StaticChannelConfig.LocalStaticShutdownScriptPubKey + localShutdownScriptPubKey + if (self.Commitments.LocalHasUnsignedOutgoingHTLCs()) then + do! Error <| cannotCloseChannel "Cannot close with unsigned outgoing htlcs" + let shutdownMsg: ShutdownMsg = { + ChannelId = self.SavedChannelState.StaticChannelConfig.ChannelId() + ScriptPubKey = localShutdownScriptPubKey + } + let channel = { + self with + NegotiatingState = { + self.NegotiatingState with + LocalRequestedShutdown = Some localShutdownScriptPubKey + } + } + return channel, shutdownMsg + } + + static member private Hex = NBitcoin.DataEncoders.HexEncoder() + static member private Ascii = System.Text.ASCIIEncoding.ASCII + static member private DummyPrivKey = new Key(Channel.Hex.DecodeData("0101010101010101010101010101010101010101010101010101010101010101")) + static member private DummySig = + "01010101010101010101010101010101" |> Channel.Ascii.GetBytes + |> uint256 + |> fun m -> Channel.DummyPrivKey.SignCompact(m) + |> fun d -> LNECDSASignature.FromBytesCompact(d, true) + |> fun ecdsaSig -> TransactionSignature(ecdsaSig.Value, SigHash.All) + + member internal self.MakeClosingTx (localSpk: ShutdownScriptPubKey) + (remoteSpk: ShutdownScriptPubKey) + (closingFee: Money) = result { + let channelPrivKeys = self.ChannelPrivKeys + let staticChannelConfig = self.SavedChannelState.StaticChannelConfig + let dustLimitSatoshis = Money.Max(staticChannelConfig.LocalParams.DustLimitSatoshis, staticChannelConfig.RemoteParams.DustLimitSatoshis) + let! closingTx = Transactions.makeClosingTx staticChannelConfig.FundingScriptCoin (localSpk) (remoteSpk) staticChannelConfig.IsFunder (dustLimitSatoshis) (closingFee) (self.SavedChannelState.LocalCommit.Spec) staticChannelConfig.Network + let localSignature, psbtUpdated = channelPrivKeys.SignWithFundingPrivKey closingTx.Value + let msg: ClosingSignedMsg = { + ChannelId = staticChannelConfig.ChannelId() + FeeSatoshis = closingFee + Signature = localSignature.Signature |> LNECDSASignature + } + return (ClosingTx psbtUpdated, msg) + } + + member internal self.FirstClosingFee (localSpk: ShutdownScriptPubKey) + (remoteSpk: ShutdownScriptPubKey) = result { + let feeEst = self.ChannelOptions.FeeEstimator + let staticChannelConfig = self.SavedChannelState.StaticChannelConfig + let! dummyClosingTx = Transactions.makeClosingTx staticChannelConfig.FundingScriptCoin localSpk remoteSpk staticChannelConfig.IsFunder Money.Zero Money.Zero self.SavedChannelState.LocalCommit.Spec staticChannelConfig.Network + let tx = dummyClosingTx.Value.GetGlobalTransaction() + tx.Inputs.[0].WitScript <- + let witness = seq [ Channel.DummySig.ToBytes(); Channel.DummySig.ToBytes(); dummyClosingTx.Value.Inputs.[0].WitnessScript.ToBytes() ] + WitScript(witness) + let feeRatePerKw = FeeRatePerKw.Max (feeEst.GetEstSatPer1000Weight(ConfirmationTarget.HighPriority), self.SavedChannelState.LocalCommit.Spec.FeeRatePerKw) + return feeRatePerKw.CalculateFeeFromVirtualSize(tx) + } + + static member internal NextClosingFee (localClosingFee: Money, remoteClosingFee: Money) = + ((localClosingFee.Satoshi + remoteClosingFee.Satoshi) / 4L) * 2L + |> Money.Satoshis + + member self.RemoteShutdown (msg: ShutdownMsg) + (localShutdownScriptPubKey: ShutdownScriptPubKey) + : Result * Option, ChannelError> = result { + let remoteShutdownScriptPubKey = msg.ScriptPubKey + do! + Validation.checkShutdownScriptPubKeyAcceptable + self.SavedChannelState.StaticChannelConfig.LocalStaticShutdownScriptPubKey + localShutdownScriptPubKey + do! + Validation.checkShutdownScriptPubKeyAcceptable + self.SavedChannelState.StaticChannelConfig.RemoteStaticShutdownScriptPubKey + remoteShutdownScriptPubKey + let cm = self.Commitments + // They have pending unsigned htlcs => they violated the spec, close the channel + // they don't have pending unsigned htlcs + // We have pending unsigned htlcs + // We already sent a shutdown msg => spec violation (we can't send htlcs after having sent shutdown) + // We did not send a shutdown msg + // We are ready to sign => we stop sending further htlcs, we initiate a signature + // We are waiting for a rev => we stop sending further htlcs, we wait for their revocation, will resign immediately after, and then we will send our shutdown msg + // We have no pending unsigned htlcs + // we already sent a shutdown msg + // There are pending signed htlcs => send our shutdown msg, go to SHUTDOWN state + // there are no htlcs => send our shutdown msg, goto NEGOTIATING state + // We did not send a shutdown msg + // There are pending signed htlcs => go to SHUTDOWN state + // there are no HTLCs => go to NEGOTIATING state + + if (cm.RemoteHasUnsignedOutgoingHTLCs()) then + return! receivedShutdownWhenRemoteHasUnsignedOutgoingHTLCs msg + // Do we have Unsigned Outgoing HTLCs? + else if (cm.LocalHasUnsignedOutgoingHTLCs()) then + let channel = { + self with + NegotiatingState = { + self.NegotiatingState with + RemoteRequestedShutdown = Some remoteShutdownScriptPubKey + } } - | Shutdown state, ApplyUpdateFulfillHTLC msg -> - state.Commitments |> Commitments.receiveFulfill msg - | Shutdown state, FailHTLC op -> - state.Commitments |> Commitments.sendFail cs.NodeSecret op - | Shutdown state, FailMalformedHTLC op -> - state.Commitments |> Commitments.sendFailMalformed op - | Shutdown state, ApplyUpdateFailMalformedHTLC msg -> - state.Commitments |> Commitments.receiveFailMalformed msg - | Shutdown state, UpdateFee op -> - state.Commitments |> Commitments.sendFee op - | Shutdown state, ApplyUpdateFee msg -> - let localFeerate = cs.FeeEstimator.GetEstSatPer1000Weight(ConfirmationTarget.HighPriority) - state.Commitments |> Commitments.receiveFee cs.Config localFeerate msg - | Shutdown state, SignCommitment -> - let cm = state.Commitments - match cm.RemoteNextCommitInfo with - | _ when (not <| cm.LocalHasChanges()) -> - // nothing to sign - [] |> Ok - | RemoteNextCommitInfo.Revoked _ -> - cm |> Commitments.sendCommit cs.ChannelPrivKeys (cs.Network) - | RemoteNextCommitInfo.Waiting _waitForRevocation -> - // Already in the process of signing. - [] |> Ok - | Shutdown state, ApplyCommitmentSigned msg -> - state.Commitments |> Commitments.receiveCommit cs.ChannelPrivKeys msg cs.Network - | Shutdown _state, ApplyRevokeAndACK _msg -> - failwith "not implemented" - - | Negotiating state, ApplyClosingSigned msg -> - result { - let cm = state.Commitments - let remoteChannelKeys = cm.RemoteParams.ChannelPubKeys - let lastCommitFeeSatoshi = - cm.FundingScriptCoin.TxOut.Value - (cm.LocalCommit.PublishableTxs.CommitTx.Value.TotalOut) - do! checkRemoteProposedHigherFeeThanBefore lastCommitFeeSatoshi msg.FeeSatoshis - let! closingTx, closingSignedMsg = - Closing.makeClosingTx ( - cs.ChannelPrivKeys, - cm, - state.LocalShutdown.ScriptPubKey, - state.RemoteShutdown.ScriptPubKey, - msg.FeeSatoshis, - cm.LocalParams.ChannelPubKeys.FundingPubKey, - cs.Network - ) + return channel, None, None + else + let hasNoPendingHTLCs = + match self.RemoteNextCommitInfo with + | None -> true + | Some remoteNextCommitInfo -> + self.SavedChannelState.HasNoPendingHTLCs remoteNextCommitInfo + if hasNoPendingHTLCs then + // we have to send first closing_signed msg iif we are the funder + if self.SavedChannelState.StaticChannelConfig.IsFunder then + let! closingFee = + self.FirstClosingFee + localShutdownScriptPubKey + remoteShutdownScriptPubKey + |> expectTransactionError + let! (_closingTx, closingSignedMsg) = + self.MakeClosingTx + localShutdownScriptPubKey + remoteShutdownScriptPubKey + closingFee + |> expectTransactionError + let nextState = { + LocalRequestedShutdown = Some localShutdownScriptPubKey + RemoteRequestedShutdown = Some remoteShutdownScriptPubKey + LocalClosingFeesProposed = [ closingFee ] + RemoteClosingFeeProposed = None + } + let channel = { + self with + NegotiatingState = nextState + } + return channel, None, Some closingSignedMsg + else + let nextState = { + LocalRequestedShutdown = Some localShutdownScriptPubKey + RemoteRequestedShutdown = Some remoteShutdownScriptPubKey + LocalClosingFeesProposed = [] + RemoteClosingFeeProposed = None + } + let channel = { + self with + NegotiatingState = nextState + } + return channel, None, None + else + let localShutdownMsg: ShutdownMsg = { + ChannelId = self.SavedChannelState.StaticChannelConfig.ChannelId() + ScriptPubKey = localShutdownScriptPubKey + } + let channel = { + self with + NegotiatingState = { + self.NegotiatingState with + LocalRequestedShutdown = Some localShutdownScriptPubKey + RemoteRequestedShutdown = Some remoteShutdownScriptPubKey + } + } + return channel, Some localShutdownMsg, None + } + + member self.ApplyClosingSigned (msg: ClosingSignedMsg) + : Result = result { + let! localShutdownScriptPubKey, remoteShutdownScriptPubKey = + match (self.NegotiatingState.LocalRequestedShutdown, self.NegotiatingState.RemoteRequestedShutdown) with + | (Some localShutdownScriptPubKey, Some remoteShutdownScriptPubKey) -> + Ok (localShutdownScriptPubKey, remoteShutdownScriptPubKey) + // FIXME: these should be new channel errors + | (Some _, None) -> + Error ReceivedClosingSignedBeforeReceivingShutdown + | (None, Some _) -> + Error ReceivedClosingSignedBeforeSendingShutdown + | (None, None) -> + Error ReceivedClosingSignedBeforeSendingOrReceivingShutdown + let remoteChannelKeys = self.SavedChannelState.StaticChannelConfig.RemoteChannelPubKeys + let lastCommitFeeSatoshi = + self.SavedChannelState.StaticChannelConfig.FundingScriptCoin.TxOut.Value - (self.SavedChannelState.LocalCommit.PublishableTxs.CommitTx.Value.TotalOut) + do! checkRemoteProposedHigherFeeThanBaseFee lastCommitFeeSatoshi msg.FeeSatoshis + do! + checkRemoteProposedFeeWithinNegotiatedRange + (List.tryHead self.NegotiatingState.LocalClosingFeesProposed) + (Option.map (fun (fee, _sig) -> fee) self.NegotiatingState.RemoteClosingFeeProposed) + msg.FeeSatoshis + + let! closingTx, _closingSignedMsg = + self.MakeClosingTx + localShutdownScriptPubKey + remoteShutdownScriptPubKey + msg.FeeSatoshis + |> expectTransactionError + let! finalizedTx = + Transactions.checkTxFinalized + closingTx.Value + closingTx.WhichInput + (seq [ + remoteChannelKeys.FundingPubKey.RawPubKey(), + TransactionSignature(msg.Signature.Value, SigHash.All) + ]) + |> expectTransactionError + let maybeLocalFee = + self.NegotiatingState.LocalClosingFeesProposed + |> List.tryHead + let areWeInDeal = Some(msg.FeeSatoshis) = maybeLocalFee + let hasTooManyNegotiationDone = + (self.NegotiatingState.LocalClosingFeesProposed |> List.length) >= self.ChannelOptions.MaxClosingNegotiationIterations + if (areWeInDeal || hasTooManyNegotiationDone) then + return self, MutualClose finalizedTx + else + let lastLocalClosingFee = self.NegotiatingState.LocalClosingFeesProposed |> List.tryHead + let! localF = + match lastLocalClosingFee with + | Some v -> Ok v + | None -> + self.FirstClosingFee + localShutdownScriptPubKey + remoteShutdownScriptPubKey |> expectTransactionError - let! finalizedTx = - Transactions.checkTxFinalized - closingTx.Value - closingTx.WhichInput - (seq [ - remoteChannelKeys.FundingPubKey.RawPubKey(), - TransactionSignature(msg.Signature.Value, SigHash.All) - ]) + let nextClosingFee = + Channel.NextClosingFee (localF, msg.FeeSatoshis) + if (Some nextClosingFee = lastLocalClosingFee) then + return self, MutualClose finalizedTx + else if (nextClosingFee = msg.FeeSatoshis) then + // we have reached on agreement! + return self, MutualClose finalizedTx + else + let! _closingTx, closingSignedMsg = + self.MakeClosingTx + localShutdownScriptPubKey + remoteShutdownScriptPubKey + nextClosingFee |> expectTransactionError - let maybeLocalFee = - state.ClosingTxProposed - |> List.tryHead - |> Option.bind (List.tryHead) - |> 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 - if (areWeInDeal || hasTooManyNegotiationDone) then - return! Closing.handleMutualClose (finalizedTx, { state with MaybeBestUnpublishedTx = Some(finalizedTx) }) - else - let lastLocalClosingFee = state.ClosingTxProposed |> List.tryHead |> Option.bind (List.tryHead) |> Option.map (fun txp -> txp.LocalClosingSigned.FeeSatoshis) - let! localF = - match lastLocalClosingFee with - | Some v -> Ok v - | None -> - Closing.firstClosingFee (state.Commitments, - state.LocalShutdown.ScriptPubKey, - state.RemoteShutdown.ScriptPubKey, - cs.FeeEstimator, - cs.Network) - |> expectTransactionError - let nextClosingFee = - Closing.nextClosingFee (localF, msg.FeeSatoshis) - if (Some nextClosingFee = lastLocalClosingFee) then - return! Closing.handleMutualClose (finalizedTx, { state with MaybeBestUnpublishedTx = Some(finalizedTx) }) - else if (nextClosingFee = msg.FeeSatoshis) then - // we have reached on agreement! - let closingTxProposed1 = - let newProposed = [ { ClosingTxProposed.UnsignedTx = closingTx - LocalClosingSigned = closingSignedMsg } ] - newProposed :: state.ClosingTxProposed - let negoData = { state with ClosingTxProposed = closingTxProposed1 - MaybeBestUnpublishedTx = Some(finalizedTx) } - return! Closing.handleMutualClose (finalizedTx, negoData) - else - let! closingTx, closingSignedMsg = - Closing.makeClosingTx ( - cs.ChannelPrivKeys, - cm, - state.LocalShutdown.ScriptPubKey, - state.RemoteShutdown.ScriptPubKey, - nextClosingFee, - cm.LocalParams.ChannelPubKeys.FundingPubKey, - cs.Network + let nextState = { + self.NegotiatingState with + LocalClosingFeesProposed = + nextClosingFee :: self.NegotiatingState.LocalClosingFeesProposed + RemoteClosingFeeProposed = Some (msg.FeeSatoshis, msg.Signature) + } + let channel = { + self with + NegotiatingState = nextState + } + return channel, NewClosingSigned closingSignedMsg + } + + member self.LocalHasChanges() = + (not self.SavedChannelState.RemoteChanges.ACKed.IsEmpty) + || (not self.Commitments.ProposedLocalChanges.IsEmpty) + + member self.RemoteHasChanges() = + (not self.SavedChannelState.LocalChanges.ACKed.IsEmpty) + || (not self.Commitments.ProposedRemoteChanges.IsEmpty) + + // FIXME: This is a temporary hack to make the geewallet updates easier. + static member SpendableBalanceFromParts (savedChannelState: SavedChannelState) + (remoteNextCommitInfo: Option) + (commitments: Commitments) + : LNMoney = + let remoteParams = savedChannelState.StaticChannelConfig.RemoteParams + let remoteCommit = + match remoteNextCommitInfo with + | Some (RemoteNextCommitInfo.Waiting nextRemoteCommit) -> nextRemoteCommit + | _ -> savedChannelState.RemoteCommit + let reducedRes = + remoteCommit.Spec.Reduce( + savedChannelState.RemoteChanges.ACKed, + commitments.ProposedLocalChanges + ) + 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 savedChannelState.StaticChannelConfig.IsFunder then + Transactions.commitTxFee remoteParams.DustLimitSatoshis reduced + |> LNMoney.FromMoney + else + LNMoney.Zero + let channelReserve = + remoteParams.ChannelReserveSatoshis + |> LNMoney.FromMoney + let totalBalance = reduced.ToRemote + let untrimmedSpendableBalance = totalBalance - channelReserve - fees + let dustLimit = + remoteParams.DustLimitSatoshis + |> LNMoney.FromMoney + let untrimmedMax = LNMoney.Min(untrimmedSpendableBalance, dustLimit) + let spendableBalance = LNMoney.Max(untrimmedMax, untrimmedSpendableBalance) + spendableBalance + + + member self.SpendableBalance (): LNMoney = + Channel.SpendableBalanceFromParts + self.SavedChannelState + self.RemoteNextCommitInfo + self.Commitments + + member private self.sendCommit (remoteNextCommitInfo: RemoteNextCommitInfo) + : Result = + let channelPrivKeys = self.ChannelPrivKeys + let cm = self.Commitments + let savedChannelState = self.SavedChannelState + match remoteNextCommitInfo with + | RemoteNextCommitInfo.Revoked remoteNextPerCommitmentPoint -> + result { + // remote commitment will include all local changes + remote acked changes + let! spec = savedChannelState.RemoteCommit.Spec.Reduce(savedChannelState.RemoteChanges.ACKed, cm.ProposedLocalChanges) |> expectTransactionError + let! (remoteCommitTx, htlcTimeoutTxs, htlcSuccessTxs) = + Commitments.Helpers.makeRemoteTxs savedChannelState.StaticChannelConfig + (savedChannelState.RemoteCommit.Index.NextCommitment()) + (channelPrivKeys.ToChannelPubKeys()) + (remoteNextPerCommitmentPoint) + (spec) + |> expectTransactionErrors + let signature,_ = channelPrivKeys.SignWithFundingPrivKey remoteCommitTx.Value + let sortedHTLCTXs = Commitments.Helpers.sortBothHTLCs htlcTimeoutTxs htlcSuccessTxs + let htlcSigs = + sortedHTLCTXs + |> List.map( + (fun htlc -> channelPrivKeys.SignHtlcTx htlc.Value remoteNextPerCommitmentPoint) + >> fst + >> (fun txSig -> txSig.Signature) ) - |> expectTransactionError - let closingTxProposed1 = - let newProposed = [ { ClosingTxProposed.UnsignedTx = closingTx - LocalClosingSigned = closingSignedMsg } ] - newProposed :: state.ClosingTxProposed - let nextState = { state with ClosingTxProposed = closingTxProposed1; MaybeBestUnpublishedTx = Some(finalizedTx) } - return [ WeProposedNewClosingSigned(closingSignedMsg, nextState) ] + let msg = { + CommitmentSignedMsg.ChannelId = savedChannelState.StaticChannelConfig.ChannelId() + Signature = !> signature.Signature + HTLCSignatures = htlcSigs |> List.map (!>) + } + let nextRemoteCommitInfo = { + savedChannelState.RemoteCommit + with + Index = savedChannelState.RemoteCommit.Index.NextCommitment() + Spec = spec + RemotePerCommitmentPoint = remoteNextPerCommitmentPoint + } + let nextCommitments = { + cm with + ProposedLocalChanges = [] + } + let nextSavedChannelState = { + self.SavedChannelState with + LocalChanges = { + self.SavedChannelState.LocalChanges with + Signed = cm.ProposedLocalChanges + } + RemoteChanges = { + self.SavedChannelState.RemoteChanges with + ACKed = [] + Signed = self.SavedChannelState.RemoteChanges.ACKed + } + } + let channel = { + self with + Commitments = nextCommitments + SavedChannelState = nextSavedChannelState + RemoteNextCommitInfo = + Some <| RemoteNextCommitInfo.Waiting nextRemoteCommitInfo + } + return msg, channel } - | Closing state, FulfillHTLC op -> - // got valid payment preimage, recalculating txs to redeem the corresponding htlc on-chain + | RemoteNextCommitInfo.Waiting _ -> + CanNotSignBeforeRevocation |> Error + + member private self.receiveCommit (msg: CommitmentSignedMsg) + : Result = + let channelPrivKeys = self.ChannelPrivKeys + let cm = self.Commitments + let savedChannelState = self.SavedChannelState + if self.RemoteHasChanges() |> not then + ReceivedCommitmentSignedWhenWeHaveNoPendingChanges |> Error + else + let commitmentSeed = channelPrivKeys.CommitmentSeed + let localChannelKeys = channelPrivKeys.ToChannelPubKeys() + let remoteChannelKeys = savedChannelState.StaticChannelConfig.RemoteChannelPubKeys + let nextI = savedChannelState.LocalCommit.Index.NextCommitment() result { - let cm = state.Commitments - let! (_msgToSend, newCommitments) = cm |> Commitments.sendFulfill op - let _localCommitPublished = - state.LocalCommitPublished - |> Option.map (fun localCommitPublished -> - Closing.claimCurrentLocalCommitTxOutputs ( - cs.ChannelPrivKeys, - newCommitments, - localCommitPublished.CommitTx + let! spec = savedChannelState.LocalCommit.Spec.Reduce(savedChannelState.LocalChanges.ACKed, cm.ProposedRemoteChanges) |> expectTransactionError + let localPerCommitmentPoint = commitmentSeed.DerivePerCommitmentPoint nextI + let! (localCommitTx, htlcTimeoutTxs, htlcSuccessTxs) = + Commitments.Helpers.makeLocalTXs + savedChannelState.StaticChannelConfig + nextI + (channelPrivKeys.ToChannelPubKeys()) + localPerCommitmentPoint + spec + |> expectTransactionErrors + let signature, signedCommitTx = channelPrivKeys.SignWithFundingPrivKey localCommitTx.Value + + let sigPair = + let localSigPair = seq [(localChannelKeys.FundingPubKey.RawPubKey(), signature)] + let remoteSigPair = seq[ (remoteChannelKeys.FundingPubKey.RawPubKey(), TransactionSignature(msg.Signature.Value, SigHash.All)) ] + Seq.append localSigPair remoteSigPair + let tmp = + Transactions.checkTxFinalized signedCommitTx CommitTx.WhichInput sigPair + |> expectTransactionError + let! finalizedCommitTx = tmp + let sortedHTLCTXs = Commitments.Helpers.sortBothHTLCs htlcTimeoutTxs htlcSuccessTxs + do! Commitments.checkSignatureCountMismatch sortedHTLCTXs msg + + let _localHTLCSigs, sortedHTLCTXs = + let localHtlcSigsAndHTLCTxs = + sortedHTLCTXs |> List.map(fun htlc -> + channelPrivKeys.SignHtlcTx htlc.Value localPerCommitmentPoint ) + localHtlcSigsAndHTLCTxs |> List.map(fst), localHtlcSigsAndHTLCTxs |> List.map(snd) |> Seq.cast |> List.ofSeq + + let remoteHTLCPubKey = localPerCommitmentPoint.DeriveHtlcPubKey remoteChannelKeys.HtlcBasepoint + + let checkHTLCSig (htlc: IHTLCTx, remoteECDSASig: LNECDSASignature): Result<_, _> = + let remoteS = TransactionSignature(remoteECDSASig.Value, SigHash.All) + match htlc with + | :? HTLCTimeoutTx -> + (Transactions.checkTxFinalized (htlc.Value) (0) (seq [(remoteHTLCPubKey.RawPubKey(), remoteS)])) + |> Result.map(box) + // we cannot check that htlc-success tx are spendable because we need the payment preimage; thus we only check the remote sig + | :? HTLCSuccessTx -> + (Transactions.checkSigAndAdd (htlc) (remoteS) (remoteHTLCPubKey.RawPubKey())) + |> Result.map(box) + | _ -> failwith "Unreachable!" + + let! txList = + List.zip sortedHTLCTXs msg.HTLCSignatures + |> List.map(checkHTLCSig) + |> List.sequenceResultA + |> expectTransactionErrors + let successTxs = + txList |> List.choose(fun o -> + match o with + | :? HTLCSuccessTx as tx -> Some tx + | _ -> None ) - return failwith "Not Implemented yet" + let finalizedTxs = + txList |> List.choose(fun o -> + match o with + | :? FinalizedTx as tx -> Some tx + | _ -> None + ) + let localPerCommitmentSecret = + channelPrivKeys.CommitmentSeed.DerivePerCommitmentSecret savedChannelState.LocalCommit.Index + let localNextPerCommitmentPoint = + let perCommitmentSecret = + channelPrivKeys.CommitmentSeed.DerivePerCommitmentSecret + (savedChannelState.LocalCommit.Index.NextCommitment().NextCommitment()) + perCommitmentSecret.PerCommitmentPoint() + + let nextMsg = { + RevokeAndACKMsg.ChannelId = savedChannelState.StaticChannelConfig.ChannelId() + PerCommitmentSecret = localPerCommitmentSecret + NextPerCommitmentPoint = localNextPerCommitmentPoint + } + + let localCommit1 = { LocalCommit.Index = savedChannelState.LocalCommit.Index.NextCommitment() + Spec = spec + PublishableTxs = { PublishableTxs.CommitTx = finalizedCommitTx + HTLCTxs = finalizedTxs } + PendingHTLCSuccessTxs = successTxs } + let nextSavedChannelState = { + savedChannelState with + LocalCommit = localCommit1 + LocalChanges = { + savedChannelState.LocalChanges with + ACKed = [] + } + RemoteChanges = { + savedChannelState.RemoteChanges with + ACKed = (savedChannelState.RemoteChanges.ACKed @ cm.ProposedRemoteChanges) + } + } + let nextCommitments = + let completedOutgoingHTLCs = + let t1 = savedChannelState.LocalCommit.Spec.OutgoingHTLCs + |> Map.toSeq |> Seq.map (fun (k, _) -> k) |> Set.ofSeq + let t2 = localCommit1.Spec.OutgoingHTLCs + |> Map.toSeq |> Seq.map (fun (k, _) -> k) |> Set.ofSeq + Set.difference t1 t2 + let originChannels1 = cm.OriginChannels |> Map.filter(fun k _ -> Set.contains k completedOutgoingHTLCs) + { + cm with + ProposedRemoteChanges = [] + OriginChannels = originChannels1 + } + let nextChannel = { + self with + SavedChannelState = nextSavedChannelState + Commitments = nextCommitments + } + return nextMsg, nextChannel } - | state, cmd -> - undefinedStateAndCmdPair state cmd - - 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 } - | TheySentFundingLocked msg, WaitForFundingConfirmed s -> - { c with State = WaitForFundingConfirmed({ s with Deferred = Some(msg) }) } - | TheySentFundingLocked msg, WaitForFundingLocked s -> - let feeBase = ChannelHelpers.getOurFeeBaseMSat c.FeeEstimator s.InitialFeeRatePerKw s.Commitments.LocalParams.IsFunder - let channelUpdate = ChannelHelpers.makeChannelUpdate (c.Network.Consensus.HashGenesisBlock, - c.NodeSecret, - c.RemoteNodeId, - s.ShortChannelId, - s.Commitments.LocalParams.ToSelfDelay, - s.Commitments.RemoteParams.HTLCMinimumMSat, - feeBase, - c.Config.ChannelOptions.FeeProportionalMillionths, - true, - None) - let nextState = { NormalData.Buried = false; - Commitments = s.Commitments - ShortChannelId = s.ShortChannelId - ChannelAnnouncement = None - ChannelUpdate = channelUpdate - LocalShutdown = None - RemoteShutdown = None - ChannelId = msg.ChannelId } - { 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 -------- - | WeAcceptedOperationAddHTLC(_, newCommitments), ChannelState.Normal d -> - { c with State = ChannelState.Normal({ d with Commitments = newCommitments }) } - | WeAcceptedUpdateAddHTLC(newCommitments), ChannelState.Normal d -> - { c with State = ChannelState.Normal({ d with Commitments = newCommitments }) } - - | WeAcceptedOperationFulfillHTLC(_, newCommitments), ChannelState.Normal d -> - { c with State = ChannelState.Normal({ d with Commitments = newCommitments }) } - | WeAcceptedFulfillHTLC(_msg, _origin, _htlc, newCommitments), ChannelState.Normal d -> - { c with State = ChannelState.Normal({ d with Commitments = newCommitments }) } - - | WeAcceptedOperationFailHTLC(_msg, newCommitments), ChannelState.Normal d -> - { c with State = ChannelState.Normal({ d with Commitments = newCommitments }) } - | WeAcceptedFailHTLC(_origin, _msg, newCommitments), ChannelState.Normal d -> - { c with State = ChannelState.Normal({ d with Commitments = newCommitments }) } - - | WeAcceptedOperationFailMalformedHTLC(_msg, newCommitments), ChannelState.Normal d -> - { c with State = ChannelState.Normal({ d with Commitments = newCommitments }) } - | WeAcceptedFailMalformedHTLC(_origin, _msg, newCommitments), ChannelState.Normal d -> - { c with State = ChannelState.Normal({ d with Commitments = newCommitments }) } - - | WeAcceptedOperationUpdateFee(_msg, newCommitments), ChannelState.Normal d -> - { c with State = ChannelState.Normal({ d with Commitments = newCommitments }) } - | WeAcceptedUpdateFee(_msg), ChannelState.Normal _d -> c - - | WeAcceptedOperationSign(_msg, newCommitments), ChannelState.Normal d -> - { c with State = ChannelState.Normal({ d with Commitments = newCommitments }) } - | WeAcceptedCommitmentSigned(_msg, newCommitments), ChannelState.Normal d -> - { c with State = ChannelState.Normal({ d with Commitments = newCommitments }) } - - | WeAcceptedRevokeAndACK(newCommitments), ChannelState.Normal d -> - { c with State = ChannelState.Normal({ d with Commitments = newCommitments }) } - - // ----- closing ------ - | AcceptedOperationShutdown msg, ChannelState.Normal d -> - { c with State = ChannelState.Normal({ d with LocalShutdown = Some msg }) } - | AcceptedShutdownWhileWeHaveUnsignedOutgoingHTLCs(remoteShutdown, nextCommitments), ChannelState.Normal d -> - { c with State = ChannelState.Normal ({ d with RemoteShutdown = Some remoteShutdown; Commitments = nextCommitments }) } - | AcceptedShutdownWhenNoPendingHTLCs(_maybeMsg, nextState), ChannelState.Normal _d -> - { c with State = Negotiating nextState } - | AcceptedShutdownWhenWeHavePendingHTLCs(nextState), ChannelState.Normal _d -> - { c with State = Shutdown nextState } - | MutualClosePerformed (_txToPublish, nextState), ChannelState.Negotiating _d -> - { c with State = Closing nextState } - | WeProposedNewClosingSigned(_msg, nextState), ChannelState.Negotiating _d -> - { c with State = Negotiating(nextState) } - // ----- else ----- - | _otherEvent -> c diff --git a/src/DotNetLightning.Core/Channel/ChannelError.fs b/src/DotNetLightning.Core/Channel/ChannelError.fs index cd9b94db5..208c4f895 100644 --- a/src/DotNetLightning.Core/Channel/ChannelError.fs +++ b/src/DotNetLightning.Core/Channel/ChannelError.fs @@ -36,19 +36,26 @@ type ChannelError = | TheyCannotAffordFee of toRemote: LNMoney * fee: Money * channelReserve: Money // --- case they sent unacceptable msg --- + | InvalidFundingLocked of InvalidFundingLockedError | InvalidOpenChannel of InvalidOpenChannelError | InvalidAcceptChannel of InvalidAcceptChannelError + | InvalidMonoHopUnidirectionalPayment of InvalidMonoHopUnidirectionalPaymentError | InvalidUpdateAddHTLC of InvalidUpdateAddHTLCError | InvalidRevokeAndACK of InvalidRevokeAndACKError | InvalidUpdateFee of InvalidUpdateFeeError // ------------------ /// Consumer of the api (usually, that is wallet) failed to give an funding tx + | ReceivedClosingSignedBeforeReceivingShutdown + | ReceivedClosingSignedBeforeSendingShutdown + | ReceivedClosingSignedBeforeSendingOrReceivingShutdown | FundingTxNotGiven of msg: string | OnceConfirmedFundingTxHasBecomeUnconfirmed of height: BlockHeight * depth: BlockHeightOffset32 | CannotCloseChannel of msg: string - | UndefinedStateAndCmdPair of state: ChannelState * cmd: ChannelCommand - | RemoteProposedHigherFeeThanBefore of previous: Money * current: Money + | RemoteProposedHigherFeeThanBaseFee of baseFee: Money * proposedFee: Money + | RemoteProposedFeeOutOfNegotiatedRange of ourPreviousFee: Money * theirPreviousFee: Money * theirNextFee: Money + | NoUpdatesToSign + | CannotSignCommitmentBeforeRevocation // ---- invalid command ---- | InvalidOperationAddHTLC of InvalidOperationAddHTLCError // ------------------------- @@ -69,17 +76,24 @@ type ChannelError = | ReceivedShutdownWhenRemoteHasUnsignedOutgoingHTLCs _ -> Close | SignatureCountMismatch (_, _) -> Close | TheyCannotAffordFee (_, _, _) -> Close + | InvalidFundingLocked _ -> DistrustPeer | InvalidOpenChannel _ -> DistrustPeer | InvalidAcceptChannel _ -> DistrustPeer + | InvalidMonoHopUnidirectionalPayment _ -> Close | InvalidUpdateAddHTLC _ -> Close | InvalidRevokeAndACK _ -> Close | InvalidUpdateFee _ -> Close + | ReceivedClosingSignedBeforeReceivingShutdown -> Close + | ReceivedClosingSignedBeforeSendingShutdown -> Close + | ReceivedClosingSignedBeforeSendingOrReceivingShutdown -> Close | FundingTxNotGiven _ -> Ignore | OnceConfirmedFundingTxHasBecomeUnconfirmed _ -> Close | CannotCloseChannel _ -> Ignore - | UndefinedStateAndCmdPair _ -> Ignore + | NoUpdatesToSign -> Ignore + | CannotSignCommitmentBeforeRevocation -> Ignore | InvalidOperationAddHTLC _ -> Ignore - | RemoteProposedHigherFeeThanBefore(_, _) -> Close + | RemoteProposedHigherFeeThanBaseFee(_, _) -> Close + | RemoteProposedFeeOutOfNegotiatedRange(_, _, _) -> Close member this.Message = match this with @@ -100,17 +114,23 @@ type ChannelError = sprintf "Number of signatures went from the remote (%A) does not match the number expected (%A)" actual expected | TheyCannotAffordFee (toRemote, fee, channelReserve) -> sprintf "they are funder but cannot afford their fee. to_remote output is: %A; actual fee is %A; channel_reserve_satoshis is: %A" toRemote fee channelReserve + | InvalidFundingLocked invalidFundingLockedError -> + sprintf "Invalid funding_locked from the peer: %s" invalidFundingLockedError.Message | InvalidOpenChannel invalidOpenChannelError -> sprintf "Invalid open_channel from the peer.: %s" invalidOpenChannelError.Message | OnceConfirmedFundingTxHasBecomeUnconfirmed (height, depth) -> sprintf "once confirmed funding tx has become less confirmed than threshold %A! This is probably caused by reorg. current depth is: %A " height depth | ReceivedShutdownWhenRemoteHasUnsignedOutgoingHTLCs msg -> sprintf "They sent shutdown msg (%A) while they have pending unsigned HTLCs, this is protocol violation" msg - | UndefinedStateAndCmdPair (state, cmd) -> - sprintf "DotNetLightning does not know how to handle command (%A) while in state (%A)" cmd state - | RemoteProposedHigherFeeThanBefore(prev, current) -> - "remote proposed a commitment fee higher than the last commitment fee in the course of fee negotiation" - + sprintf "previous fee=%A; fee remote proposed=%A;" prev current + | RemoteProposedHigherFeeThanBaseFee(baseFee, proposedFee) -> + "remote proposed a closing fee higher than commitment fee of the final commitment transaction. " + + sprintf "commitment fee=%A; fee remote proposed=%A;" baseFee proposedFee + | RemoteProposedFeeOutOfNegotiatedRange(ourPreviousFee, theirPreviousFee, theirNextFee) -> + "remote proposed a closing fee which was not strictly between the previous fee that \ + we proposed and the previous fee that they proposed. " + + sprintf + "our previous fee = %A; their previous fee = %A; their next fee = %A" + ourPreviousFee theirPreviousFee theirNextFee | CryptoError cryptoError -> sprintf "Crypto error: %s" cryptoError.Message | TransactionRelatedErrors transactionErrors -> @@ -129,16 +149,28 @@ 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 -> sprintf "Invalid revoke_and_ack msg: %s" invalidRevokeAndACKError.Message | InvalidUpdateFee invalidUpdateFeeError -> sprintf "Invalid update_fee msg: %s" invalidUpdateFeeError.Message + | ReceivedClosingSignedBeforeReceivingShutdown -> + sprintf "received closing_signed before receiving shutdown" + | ReceivedClosingSignedBeforeSendingShutdown -> + sprintf "received closing_signed before sending shutdown" + | ReceivedClosingSignedBeforeSendingOrReceivingShutdown -> + sprintf "received closing_signed before sending or receiving shutdown" | FundingTxNotGiven msg -> sprintf "Funding tx not given: %s" msg | CannotCloseChannel msg -> sprintf "Cannot close channel: %s" msg + | NoUpdatesToSign -> + "No updates to sign" + | CannotSignCommitmentBeforeRevocation -> + "Cannot sign commitment before previous commitment is revoked" | InvalidOperationAddHTLC invalidOperationAddHTLCError -> sprintf "Invalid operation (add htlc): %s" invalidOperationAddHTLCError.Message @@ -157,6 +189,13 @@ and ChannelConsumerAction = /// The error is not critical to the channel operation. /// But it maybe good to report the log message, or maybe lower the peer score. | Ignore +and InvalidFundingLockedError ={ + NetworkMsg: FundingLockedMsg +} + with + member this.Message = + "remote peer sent a second funding_locked message which does not match their first" + and InvalidOpenChannelError = { NetworkMsg: OpenChannelMsg Errors: string list @@ -181,6 +220,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 @@ -241,11 +292,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 @@ -291,21 +337,38 @@ module internal ChannelError = Result.mapError(FundingTxNotGiven) msg let invalidRevokeAndACK msg e = - InvalidRevokeAndACKError.Create msg ([e]) |> InvalidRevokeAndACK |> Error + InvalidRevokeAndACKError.Create msg ([e]) |> InvalidRevokeAndACK let cannotCloseChannel msg = - msg |> CannotCloseChannel|> Error + msg |> CannotCloseChannel let receivedShutdownWhenRemoteHasUnsignedOutgoingHTLCs msg = msg |> ReceivedShutdownWhenRemoteHasUnsignedOutgoingHTLCs |> Error - let undefinedStateAndCmdPair state cmd = - UndefinedStateAndCmdPair (state, cmd) |> Error - let checkRemoteProposedHigherFeeThanBefore prev curr = - if (prev < curr) then - RemoteProposedHigherFeeThanBefore(prev, curr) |> Error + let checkRemoteProposedHigherFeeThanBaseFee baseFee proposedFee = + if (baseFee < proposedFee) then + RemoteProposedHigherFeeThanBaseFee(baseFee, proposedFee) |> Error else Ok() + + let checkRemoteProposedFeeWithinNegotiatedRange (ourPreviousFeeOpt: Option) + (theirPreviousFeeOpt: Option) + (theirNextFee: Money) = + match (ourPreviousFeeOpt, theirPreviousFeeOpt) with + | (Some ourPreviousFee, Some theirPreviousFee) -> + let feeWithinRange = + ((theirNextFee < theirPreviousFee) && (theirNextFee > ourPreviousFee)) || + ((theirNextFee < ourPreviousFee) && (theirNextFee > theirPreviousFee)) + if feeWithinRange then + Ok () + else + RemoteProposedFeeOutOfNegotiatedRange( + ourPreviousFee, + theirPreviousFee, + theirNextFee + ) |> Error + | _ -> Ok () + module internal OpenChannelMsgValidation = let checkMaxAcceptedHTLCs (msg: OpenChannelMsg) = if (msg.MaxAcceptedHTLCs < 1us) || (msg.MaxAcceptedHTLCs > 483us) then @@ -352,7 +415,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" @@ -399,9 +462,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 theirAnnounce = (msg.ChannelFlags &&& 1uy) = 1uy - if (config.PeerChannelConfigLimits.ForceChannelAnnouncementPreference) && config.ChannelOptions.AnnounceChannel <> theirAnnounce then + let checkChannelAnnouncementPreferenceAcceptable (channelHandshakeLimits: ChannelHandshakeLimits) + (announceChannel: bool) + (msg: OpenChannelMsg) = + let theirAnnounce = msg.ChannelFlags.AnnounceChannel + if (channelHandshakeLimits.ForceChannelAnnouncementPreference) && announceChannel <> theirAnnounce then "Peer tried to open channel but their announcement preference is different from ours" |> Error else @@ -466,27 +531,32 @@ module internal AcceptChannelMsgValidation = else Ok() - let checkChannelReserveSatoshis (state: Data.WaitForAcceptChannelData) msg = - if msg.ChannelReserveSatoshis > state.LastSent.FundingSatoshis then - sprintf "bogus channel_reserve_satoshis %A . Must be larger than funding_satoshis %A" (msg.ChannelReserveSatoshis) (state.InputInitFunder.FundingSatoshis) + let checkChannelReserveSatoshis (fundingSatoshis: Money) + (channelReserveSatoshis: Money) + (dustLimitSatoshis: Money) + (acceptChannelMsg: AcceptChannelMsg) = + if acceptChannelMsg.ChannelReserveSatoshis > fundingSatoshis then + sprintf "bogus channel_reserve_satoshis %A . Must be larger than funding_satoshis %A" (acceptChannelMsg.ChannelReserveSatoshis) fundingSatoshis |> Error - else if msg.DustLimitSatoshis > state.LastSent.ChannelReserveSatoshis then - sprintf "Bogus channel_reserve and dust_limit. dust_limit: %A; channel_reserve %A" msg.DustLimitSatoshis (state.LastSent.ChannelReserveSatoshis) + else if acceptChannelMsg.DustLimitSatoshis > channelReserveSatoshis then + sprintf "Bogus channel_reserve and dust_limit. dust_limit: %A; channel_reserve %A" acceptChannelMsg.DustLimitSatoshis channelReserveSatoshis |> Error - else if msg.ChannelReserveSatoshis < state.LastSent.DustLimitSatoshis then - sprintf "Peer never wants payout outputs? channel_reserve_satoshis are %A; dust_limit_satoshis in our last sent msg is %A" msg.ChannelReserveSatoshis (state.LastSent.DustLimitSatoshis) + else if acceptChannelMsg.ChannelReserveSatoshis < dustLimitSatoshis then + sprintf "Peer never wants payout outputs? channel_reserve_satoshis are %A; dust_limit_satoshis in our last sent msg is %A" acceptChannelMsg.ChannelReserveSatoshis dustLimitSatoshis |> Error else Ok() - let checkDustLimitIsLargerThanOurChannelReserve (state: Data.WaitForAcceptChannelData) msg = + let checkDustLimitIsLargerThanOurChannelReserve (channelReserveSatoshis: Money) + (acceptChannelMsg: AcceptChannelMsg) = check - msg.DustLimitSatoshis (>) state.LastSent.ChannelReserveSatoshis + acceptChannelMsg.DustLimitSatoshis (>) channelReserveSatoshis "dust limit (%A) is bigger than our channel reserve (%A)" - let checkMinimumHTLCValueIsAcceptable (state: Data.WaitForAcceptChannelData) (msg: AcceptChannelMsg) = - if (msg.HTLCMinimumMSat.ToMoney() >= (state.LastSent.FundingSatoshis - msg.ChannelReserveSatoshis)) then - sprintf "Minimum HTLC value is greater than full channel value HTLCMinimum %A satoshi; funding_satoshis %A; channel_reserve: %A" (msg.HTLCMinimumMSat.ToMoney()) (state.LastSent.FundingSatoshis) (msg.ChannelReserveSatoshis) + let checkMinimumHTLCValueIsAcceptable (fundingSatoshis: Money) + (acceptChannelMsg: AcceptChannelMsg) = + if (acceptChannelMsg.HTLCMinimumMSat.ToMoney() >= (fundingSatoshis - acceptChannelMsg.ChannelReserveSatoshis)) then + sprintf "Minimum HTLC value is greater than full channel value HTLCMinimum %A satoshi; funding_satoshis %A; channel_reserve: %A" (acceptChannelMsg.HTLCMinimumMSat.ToMoney()) (fundingSatoshis) (acceptChannelMsg.ChannelReserveSatoshis) |> Error else Ok() @@ -509,6 +579,22 @@ module internal AcceptChannelMsgValidation = (check1 |> Validation.ofResult) *^> check2 *^> check3 *^> check4 *^> check5 *^> check6 *^> check7 +module UpdateMonoHopUnidirectionalPaymentWithContext = + let internal checkWeHaveSufficientFunds (staticChannelConfig: StaticChannelConfig) (currentSpec) = + let fees = + if staticChannelConfig.IsFunder then + Transactions.commitTxFee staticChannelConfig.RemoteParams.DustLimitSatoshis currentSpec + else + Money.Zero + let missing = currentSpec.ToRemote.ToMoney() - staticChannelConfig.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()) + staticChannelConfig.RemoteParams.ChannelReserveSatoshis + fees + |> Error + else + Ok() module UpdateAddHTLCValidation = let internal checkExpiryIsNotPast (current: BlockHeight) (expiry) = @@ -523,10 +609,34 @@ 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 (staticChannelConfig: StaticChannelConfig) (currentSpec) = + let fees = + if staticChannelConfig.IsFunder then + Transactions.commitTxFee staticChannelConfig.RemoteParams.DustLimitSatoshis currentSpec + else + Money.Zero + let missing = currentSpec.ToRemote.ToMoney() - staticChannelConfig.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()) + staticChannelConfig.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) + let outgoingValue = + currentSpec.OutgoingHTLCs + |> Map.toSeq + |> Seq.sumBy (fun (_, v) -> v.Amount) + let incomingValue = + currentSpec.IncomingHTLCs + |> Map.toSeq + |> Seq.sumBy (fun (_, v) -> v.Amount) + let htlcValueInFlight = outgoingValue + incomingValue if (htlcValueInFlight > limit) then sprintf "Too much HTLC value is in flight. Current: %A. Limit: %A \n Could not add new one with value: %A" htlcValueInFlight @@ -537,16 +647,22 @@ module internal UpdateAddHTLCValidationWithContext = Ok() let checkLessThanMaxAcceptedHTLC (currentSpec: CommitmentSpec) (limit: uint16) = - let acceptedHTLCs = currentSpec.HTLCs |> Map.toSeq |> Seq.filter (fun kv -> (snd kv).Direction = In) |> Seq.length + let acceptedHTLCs = currentSpec.IncomingHTLCs |> Map.count check acceptedHTLCs (>) (int limit) "We have much number of HTLCs (%A). Limit specified by remote is (%A). So not going to relay" - 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 + let checkWeHaveSufficientFunds (staticChannelConfig: StaticChannelConfig) (currentSpec) = + let fees = + if staticChannelConfig.IsFunder then + Transactions.commitTxFee + staticChannelConfig.RemoteParams.DustLimitSatoshis + currentSpec + else + Money.Zero + let missing = currentSpec.ToRemote.ToMoney() - staticChannelConfig.RemoteParams.ChannelReserveSatoshis - fees if (missing < Money.Zero) then sprintf "We don't have sufficient funds to send HTLC. current to_remote amount is: %A. Remote Channel Reserve is: %A. and fee is %A" (currentSpec.ToRemote.ToMoney()) - (state.RemoteParams.ChannelReserveSatoshis) + (staticChannelConfig.RemoteParams.ChannelReserveSatoshis) (fees) |> Error else @@ -554,7 +670,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/Channel/ChannelOperations.fs b/src/DotNetLightning.Core/Channel/ChannelOperations.fs index 349a43b8e..f96bb404e 100644 --- a/src/DotNetLightning.Core/Channel/ChannelOperations.fs +++ b/src/DotNetLightning.Core/Channel/ChannelOperations.fs @@ -11,10 +11,15 @@ open DotNetLightning.Transactions open DotNetLightning.Serialization open NBitcoin +open Aether open ResultUtils open ResultUtils.Portability +type OperationMonoHopUnidirectionalPayment = { + Amount: LNMoney +} + type OperationAddHTLC = { Amount: LNMoney PaymentHash: PaymentHash @@ -58,172 +63,83 @@ 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 = { - NodeId: NodeId - ChannelPubKeys: ChannelPubKeys DustLimitSatoshis: Money MaxHTLCValueInFlightMSat: LNMoney ChannelReserveSatoshis: Money HTLCMinimumMSat: LNMoney ToSelfDelay: BlockHeightOffset16 MaxAcceptedHTLCs: uint16 - IsFunder: bool - DefaultFinalScriptPubKey: Script Features: FeatureBits } type RemoteParams = { - NodeId: NodeId DustLimitSatoshis: Money MaxHTLCValueInFlightMSat: LNMoney ChannelReserveSatoshis: Money HTLCMinimumMSat: LNMoney ToSelfDelay: BlockHeightOffset16 MaxAcceptedHTLCs: uint16 - ChannelPubKeys: ChannelPubKeys Features: FeatureBits - MinimumDepth: BlockHeightOffset32 } with - static member FromAcceptChannel nodeId (remoteInit: InitMsg) (msg: AcceptChannelMsg) = - let channelPubKeys = { - FundingPubKey = msg.FundingPubKey - RevocationBasepoint = msg.RevocationBasepoint - PaymentBasepoint = msg.PaymentBasepoint - DelayedPaymentBasepoint = msg.DelayedPaymentBasepoint - HtlcBasepoint = msg.HTLCBasepoint - } + static member FromAcceptChannel (remoteInit: InitMsg) (msg: AcceptChannelMsg) = { - NodeId = nodeId DustLimitSatoshis = msg.DustLimitSatoshis MaxHTLCValueInFlightMSat = msg.MaxHTLCValueInFlightMsat ChannelReserveSatoshis = msg.ChannelReserveSatoshis HTLCMinimumMSat = msg.HTLCMinimumMSat ToSelfDelay = msg.ToSelfDelay MaxAcceptedHTLCs = msg.MaxAcceptedHTLCs - ChannelPubKeys = channelPubKeys Features = remoteInit.Features - MinimumDepth = msg.MinimumDepth } - static member FromOpenChannel (nodeId) (remoteInit: InitMsg) (msg: OpenChannelMsg) (channelHandshakeConfig: ChannelHandshakeConfig) = - let channelPubKeys = { - FundingPubKey = msg.FundingPubKey - RevocationBasepoint = msg.RevocationBasepoint - PaymentBasepoint = msg.PaymentBasepoint - DelayedPaymentBasepoint = msg.DelayedPaymentBasepoint - HtlcBasepoint = msg.HTLCBasepoint - } + static member FromOpenChannel (remoteInit: InitMsg) + (msg: OpenChannelMsg) + : RemoteParams = { - NodeId = nodeId DustLimitSatoshis = msg.DustLimitSatoshis MaxHTLCValueInFlightMSat = msg.MaxHTLCValueInFlightMsat ChannelReserveSatoshis = msg.ChannelReserveSatoshis HTLCMinimumMSat = msg.HTLCMinimumMsat ToSelfDelay = msg.ToSelfDelay MaxAcceptedHTLCs = msg.MaxAcceptedHTLCs - ChannelPubKeys = channelPubKeys Features = remoteInit.Features - MinimumDepth = channelHandshakeConfig.MinimumDepth } -type InputInitFunder = { - TemporaryChannelId: ChannelId - FundingSatoshis: Money - PushMSat: LNMoney - InitFeeRatePerKw: FeeRatePerKw - FundingTxFeeRatePerKw: FeeRatePerKw +/// Channel config which is static, ie. config parameters which are established +/// during the channel handshake and persist unchagned through the lifetime of +/// the channel. +type StaticChannelConfig = { + AnnounceChannel: bool + RemoteNodeId: NodeId + Network: Network + FundingTxMinimumDepth: BlockHeightOffset32 + LocalStaticShutdownScriptPubKey: Option + RemoteStaticShutdownScriptPubKey: Option + IsFunder: bool + FundingScriptCoin: ScriptCoin LocalParams: LocalParams - RemoteInit: InitMsg - ChannelFlags: uint8 - ChannelPrivKeys: ChannelPrivKeys + RemoteParams: RemoteParams + RemoteChannelPubKeys: ChannelPubKeys } with - static member FromOpenChannel (localParams) (remoteInit) (channelPrivKeys) (o: OpenChannelMsg) = - { - InputInitFunder.TemporaryChannelId = o.TemporaryChannelId - FundingSatoshis = o.FundingSatoshis - PushMSat = o.PushMSat - InitFeeRatePerKw = o.FeeRatePerKw - FundingTxFeeRatePerKw = o.FeeRatePerKw - LocalParams = localParams - RemoteInit = remoteInit - ChannelFlags = o.ChannelFlags - ChannelPrivKeys = channelPrivKeys - } - - member this.DeriveCommitmentSpec() = - CommitmentSpec.Create this.ToLocal this.PushMSat this.FundingTxFeeRatePerKw - - member this.ToLocal = - this.FundingSatoshis.ToLNMoney() - this.PushMSat - -and InputInitFundee = { - TemporaryChannelId: ChannelId - LocalParams: LocalParams - RemoteInit: InitMsg - ToLocal: LNMoney - ChannelPrivKeys: ChannelPrivKeys -} - + member this.ChannelId(): ChannelId = + this.FundingScriptCoin.Outpoint.ToChannelId() + +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 + // update messages sent to notify all nodes of our updated relay fee. + FeeProportionalMillionths: uint32 + /// We don't exchange more than this many signatures when negotiating the closing fee + MaxClosingNegotiationIterations: int32 + FeeEstimator: IFeeEstimator + } + with -/// possible input to the channel. Command prefixed from `Apply` is passive. i.e. -/// it has caused by the outside world and not by the user. Mostly this is a message sent -/// from this channel's remote peer. -/// others are active commands which is caused by the user. -/// However, these two kinds of command has no difference from architectural viewpoint. -/// 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 - | ApplyFundingLocked of FundingLockedMsg - | ApplyFundingConfirmedOnBC of height: BlockHeight * txIndex: TxIndexInBlock * depth: BlockHeightOffset32 - - // open: fundee - | CreateInbound of InputInitFundee - | ApplyOpenChannel of OpenChannelMsg - | ApplyFundingCreated of FundingCreatedMsg - - | CreateChannelReestablish - - // normal - | AddHTLC of OperationAddHTLC - | ApplyUpdateAddHTLC of msg: UpdateAddHTLCMsg * currentHeight: BlockHeight - | FulfillHTLC of OperationFulfillHTLC - | ApplyUpdateFulfillHTLC of UpdateFulfillHTLCMsg - | FailHTLC of OperationFailHTLC - | ApplyUpdateFailHTLC of UpdateFailHTLCMsg - | FailMalformedHTLC of OperationFailMalformedHTLC - | ApplyUpdateFailMalformedHTLC of UpdateFailMalformedHTLCMsg - | UpdateFee of OperationUpdateFee - | ApplyUpdateFee of UpdateFeeMsg - - | SignCommitment - | ApplyCommitmentSigned of CommitmentSignedMsg - | ApplyRevokeAndACK of RevokeAndACKMsg - - // close - | Close of OperationClose - | ApplyClosingSigned of ClosingSignedMsg - | RemoteShutdown of ShutdownMsg - - // else - | ForceClose - | GetState - | GetStateData + static member FeeProportionalMillionths_: Lens<_, _> = + (fun cc -> cc.FeeProportionalMillionths), + (fun v cc -> { cc with FeeProportionalMillionths = v }) + diff --git a/src/DotNetLightning.Core/Channel/ChannelTypes.fs b/src/DotNetLightning.Core/Channel/ChannelTypes.fs index 761f64dca..93465867a 100644 --- a/src/DotNetLightning.Core/Channel/ChannelTypes.fs +++ b/src/DotNetLightning.Core/Channel/ChannelTypes.fs @@ -1,13 +1,14 @@ namespace DotNetLightning.Channel +open DotNetLightning.Chain open DotNetLightning.Utils open DotNetLightning.Utils.Aether -open DotNetLightning.DomainUtils.Types open DotNetLightning.Serialization.Msgs open DotNetLightning.Transactions open DotNetLightning.Crypto open NBitcoin + (* based on eclair's channel state management *) @@ -23,368 +24,68 @@ open NBitcoin [] module Data = - type ClosingTxProposed = { - UnsignedTx: ClosingTx - LocalClosingSigned: ClosingSignedMsg - } - with - static member LocalClosingSigned_: Lens<_ ,_> = - (fun p -> p.LocalClosingSigned), - (fun v p -> { p with LocalClosingSigned = v }) - - type LocalCommitPublished = { - CommitTx: CommitTx - ClaimMainDelayedOutputTx: ClaimDelayedOutputTx option - HTLCSuccessTxs: HTLCSuccessTx list - HTLCTimeoutTxs: HTLCTimeoutTx list - } - - type RemoteCommitPublished = { - CommitTx: CommitTx - ClaimMainOutputTx: ClaimP2WPKHOutputTx - ClaimHTLCSuccessTxs: ClaimHTLCSuccessTx list - ClaimHTLCTimeoutTxs: ClaimHTLCTimeoutTx list - } - - type RevokedCommitPublished = { - CommitTx: CommitTx - ClaimMainOutputTx: ClaimP2WPKHOutputTx option - MainPenaltyTx: MainPenaltyTx option - ClaimHTLCTimeoutTxs: ClaimHTLCTimeoutTx list - HTLCTimeoutTxs: HTLCTimeoutTx list - HTLCPenaltyTxs: HTLCPenaltyTx list - } - - type IChannelStateData = interface inherit IStateData end - type IHasCommitments = - inherit IChannelStateData - abstract member ChannelId: ChannelId - abstract member Commitments: Commitments - - - type WaitForOpenChannelData = { InitFundee: InputInitFundee } - with interface IChannelStateData - - type WaitForAcceptChannelData = { - InputInitFunder: InputInitFunder; - LastSent: OpenChannelMsg + type NegotiatingState = { + LocalRequestedShutdown: Option + RemoteRequestedShutdown: Option + LocalClosingFeesProposed: List + RemoteClosingFeeProposed: Option + } with + static member New(): NegotiatingState = { + LocalRequestedShutdown = None + RemoteRequestedShutdown = None + LocalClosingFeesProposed = List.empty + RemoteClosingFeeProposed = None } - with interface IChannelStateData - - type WaitForFundingTxData = { - InputInitFunder: InputInitFunder - LastSent: OpenChannelMsg - LastReceived: AcceptChannelMsg - } - - type WaitForFundingInternalData = { - TemporaryChannelId: ChannelId - LocalParams: LocalParams - RemoteParams:RemoteParams - FundingSatoshis: Money - PushMsat: LNMoney - InitialFeeRatePerKw: FeeRatePerKw - RemoteFirstPerCommitmentPoint: PerCommitmentPoint - LastSent: OpenChannelMsg - } - with interface IChannelStateData - - type WaitForFundingCreatedData = { - TemporaryFailure: ChannelId - LocalParams: LocalParams - RemoteParams: RemoteParams - FundingSatoshis: Money - PushMSat: LNMoney - InitialFeeRatePerKw: FeeRatePerKw - RemoteFirstPerCommitmentPoint: PerCommitmentPoint - ChannelFlags: uint8 - LastSent: AcceptChannelMsg - } - with - interface IChannelStateData - static member Create (localParams) (remoteParams) (msg: OpenChannelMsg) acceptChannelMsg = - { ChannelFlags = msg.ChannelFlags - TemporaryFailure = msg.TemporaryChannelId - LocalParams = localParams - RemoteParams = remoteParams - FundingSatoshis = msg.FundingSatoshis - PushMSat = msg.PushMSat - InitialFeeRatePerKw = msg.FeeRatePerKw - RemoteFirstPerCommitmentPoint = msg.FirstPerCommitmentPoint - LastSent = acceptChannelMsg } - type WaitForFundingSignedData = { - ChannelId: ChannelId - LocalParams: LocalParams - RemoteParams: RemoteParams - FundingTx: FinalizedTx - LocalSpec: CommitmentSpec - LocalCommitTx: CommitTx - RemoteCommit: RemoteCommit - ChannelFlags: uint8 - LastSent: FundingCreatedMsg - InitialFeeRatePerKw: FeeRatePerKw - } - with interface IChannelStateData - - type WaitForFundingConfirmedData = { - Commitments: Commitments - Deferred: FundingLockedMsg option - LastSent: Choice - InitialFeeRatePerKw: FeeRatePerKw - ChannelId: ChannelId - } - with - interface IHasCommitments with - member this.ChannelId: ChannelId = - this.ChannelId - member this.Commitments: Commitments = - this.Commitments - - - type WaitForFundingLockedData = { Commitments: Commitments; - ShortChannelId: ShortChannelId; - OurMessage: FundingLockedMsg - TheirMessage: FundingLockedMsg option - InitialFeeRatePerKw: FeeRatePerKw - HaveWeSentFundingLocked:bool - ChannelId: ChannelId } - with interface IHasCommitments with - member this.ChannelId: ChannelId = - this.ChannelId - member this.Commitments: Commitments = - this.Commitments - - type NormalData = { - Commitments: Commitments; - ShortChannelId: ShortChannelId; - Buried: bool; - ChannelAnnouncement: ChannelAnnouncementMsg option - ChannelUpdate: ChannelUpdateMsg - LocalShutdown: ShutdownMsg option - RemoteShutdown: ShutdownMsg option - ChannelId: ChannelId - } - with - static member Commitments_: Lens<_, _> = - (fun nd -> nd.Commitments), (fun v nd -> { nd with Commitments = v }) - - interface IHasCommitments with - member this.ChannelId: ChannelId = - this.ChannelId - member this.Commitments: Commitments = - this.Commitments - - type ShutdownData = { Commitments: Commitments; LocalShutdown: ShutdownMsg; RemoteShutdown: ShutdownMsg; ChannelId: ChannelId } - with interface IHasCommitments with - member this.ChannelId: ChannelId = - this.ChannelId - member this.Commitments: Commitments = - this.Commitments - - type NegotiatingData = { - Commitments: Commitments; - LocalShutdown: ShutdownMsg; - RemoteShutdown: ShutdownMsg; - ClosingTxProposed: ClosingTxProposed list list - MaybeBestUnpublishedTx: FinalizedTx option - ChannelId: ChannelId - } - with - interface IHasCommitments with - member this.ChannelId: ChannelId = - this.ChannelId - member this.Commitments: Commitments = - this.Commitments - - type ClosingData = { - ChannelId: ChannelId - Commitments: Commitments - MaybeFundingTx: Transaction option - WaitingSince: System.DateTime - MutualCloseProposed: ClosingTx list - MutualClosePublished: FinalizedTx - LocalCommitPublished: LocalCommitPublished option - RemoteCommitPublished: RemoteCommitPublished option - NextRemoteCommitPublished: RemoteCommitPublished option - FutureRemoteCommitPublished: RemoteCommitPublished option - RevokedCommitPublished: RevokedCommitPublished list - } - with - interface IHasCommitments with - member this.ChannelId: ChannelId = - this.ChannelId - member this.Commitments: Commitments = - this.Commitments - - member this.FinalizedTx = - this.MutualClosePublished - static member Create(channelId, commitments, maybeFundingTx, waitingSince, mutualCloseProposed, mutualClosePublished) = - { - ChannelId = channelId - Commitments = commitments - MaybeFundingTx = maybeFundingTx - WaitingSince = waitingSince - MutualCloseProposed = mutualCloseProposed - MutualClosePublished = mutualClosePublished - LocalCommitPublished = None - RemoteCommitPublished = None - NextRemoteCommitPublished = None - FutureRemoteCommitPublished = None - RevokedCommitPublished = [] - } - - type WaitForRemotePublishFutureCommitmentData = { - Commitments: Commitments; - RemoteChannelReestablish: ChannelReestablishMsg - ChannelId: ChannelId - } - with interface IHasCommitments with - member this.ChannelId: ChannelId = - this.ChannelId - member this.Commitments: Commitments = - this.Commitments - -// 8888888888 888 888 8888888888 888b 888 88888888888 .d8888b. -// 888 888 888 888 8888b 888 888 d88P Y88b -// 888 888 888 888 88888b 888 888 Y88b. -// 8888888 Y88b d88P 8888888 888Y88b 888 888 "Y888b. -// 888 Y88b d88P 888 888 Y88b888 888 "Y88b. -// 888 Y88o88P 888 888 Y88888 888 "888 -// 888 Y888P 888 888 Y8888 888 Y88b d88P -// 8888888888 Y8P 8888888888 888 Y888 888 "Y8888P" - - -/// 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 - | WeSentFundingLocked of msg: FundingLockedMsg - | WeResumedDelayedFundingLocked of msg: FundingLockedMsg - | BothFundingLocked of nextState: Data.NormalData - - // -------- normal operation ------ - | WeAcceptedOperationAddHTLC of msg: UpdateAddHTLCMsg * newCommitments: Commitments - | WeAcceptedUpdateAddHTLC of newCommitments: Commitments - - | WeAcceptedOperationFulfillHTLC of msg: UpdateFulfillHTLCMsg * newCommitments: Commitments - | WeAcceptedFulfillHTLC of msg: UpdateFulfillHTLCMsg * origin: HTLCSource * htlc: UpdateAddHTLCMsg * newCommitments: Commitments - - | WeAcceptedOperationFailHTLC of msg: UpdateFailHTLCMsg * newCommitments: Commitments - | WeAcceptedFailHTLC of origin: HTLCSource * msg: UpdateAddHTLCMsg * nextCommitments: Commitments - - | WeAcceptedOperationFailMalformedHTLC of msg: UpdateFailMalformedHTLCMsg * newCommitments: Commitments - | WeAcceptedFailMalformedHTLC of origin: HTLCSource * msg: UpdateAddHTLCMsg * newCommitments: Commitments - - | WeAcceptedOperationUpdateFee of msg: UpdateFeeMsg * nextCommitments: Commitments - | WeAcceptedUpdateFee of msg: UpdateFeeMsg - - | WeAcceptedOperationSign of msg: CommitmentSignedMsg * nextCommitments: Commitments - | WeAcceptedCommitmentSigned of msg: RevokeAndACKMsg * nextCommitments: Commitments - - | WeAcceptedRevokeAndACK of nextCommitments: Commitments - - | AcceptedOperationShutdown of msg: ShutdownMsg - | AcceptedShutdownWhileWeHaveUnsignedOutgoingHTLCs of remoteShutdown: ShutdownMsg * nextCommitments: Commitments - /// We have to send closing_signed to initiate the negotiation only when if we are the funder - | AcceptedShutdownWhenNoPendingHTLCs of msgToSend: ClosingSignedMsg option * nextState: NegotiatingData - | AcceptedShutdownWhenWeHavePendingHTLCs of nextState: ShutdownData - - // ------ closing ------ - | MutualClosePerformed of txToPublish: FinalizedTx * nextState : ClosingData - | WeProposedNewClosingSigned of msgToSend: ClosingSignedMsg * nextState: NegotiatingData - // -------- else --------- - | Closed - | Disconnected - | ChannelStateRequestedSignCommitment - | WeSentChannelReestablish of msg: ChannelReestablishMsg - - -// .d8888b. 88888888888 d8888 88888888888 8888888888 .d8888b. -// d88P Y88b 888 d88888 888 888 d88P Y88b -// Y88b. 888 d88P888 888 888 Y88b. -// "Y888b. 888 d88P 888 888 8888888 "Y888b. -// "Y88b. 888 d88P 888 888 888 "Y88b. -// "888 888 d88P 888 888 888 "888 -// Y88b d88P 888 d8888888888 888 888 Y88b d88P -// "Y8888P" 888 d88P 888 888 8888888888 "Y8888P" - // --- setup --- - -open Data -type ChannelStatePhase = - | Opening - | Normal - | Closing - | Closed - | 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 - - /// normal - | Normal of NormalData - - /// Closing - | 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 + member self.HasEnteredShutdown(): bool = + self.LocalRequestedShutdown.IsSome && self.RemoteRequestedShutdown.IsSome + +type ClosingSignedResponse = + | NewClosingSigned of ClosingSignedMsg + | MutualClose of FinalizedTx + +type SavedChannelState = { + StaticChannelConfig: StaticChannelConfig + RemotePerCommitmentSecrets: PerCommitmentSecretStore + ShortChannelId: Option + LocalCommit: LocalCommit + RemoteCommit: RemoteCommit + LocalChanges: LocalChanges + RemoteChanges: RemoteChanges +} with + member internal this.HasNoPendingHTLCs (remoteNextCommitInfo: RemoteNextCommitInfo) = + this.LocalCommit.Spec.OutgoingHTLCs.IsEmpty + && this.LocalCommit.Spec.IncomingHTLCs.IsEmpty + && this.RemoteCommit.Spec.OutgoingHTLCs.IsEmpty + && this.RemoteCommit.Spec.IncomingHTLCs.IsEmpty + && (remoteNextCommitInfo |> function Waiting _ -> false | Revoked _ -> true) + + member internal this.GetOutgoingHTLCCrossSigned (remoteNextCommitInfo: RemoteNextCommitInfo) + (htlcId: HTLCId) + : Option = + let remoteSigned = + Map.tryFind htlcId this.LocalCommit.Spec.OutgoingHTLCs + let localSigned = + let remoteCommit = + match remoteNextCommitInfo with + | Revoked _ -> this.RemoteCommit + | Waiting nextRemoteCommit -> nextRemoteCommit + Map.tryFind htlcId remoteCommit.Spec.IncomingHTLCs + match remoteSigned, localSigned with + | Some _, Some htlcIn -> htlcIn |> Some + | _ -> None + + member internal this.GetIncomingHTLCCrossSigned (remoteNextCommitInfo: RemoteNextCommitInfo) + (htlcId: HTLCId) + : Option = + let remoteSigned = + Map.tryFind htlcId this.LocalCommit.Spec.IncomingHTLCs + let localSigned = + let remoteCommit = + match remoteNextCommitInfo with + | Revoked _ -> this.RemoteCommit + | Waiting nextRemoteCommit -> nextRemoteCommit + Map.tryFind htlcId remoteCommit.Spec.OutgoingHTLCs + match remoteSigned, localSigned with + | Some _, Some htlcIn -> htlcIn |> Some + | _ -> None - static member Zero = WaitForInitInternal - static member Normal_: Prism<_, _> = - (fun cc -> match cc with - | Normal s -> Some s - | _ -> None ), - (fun v cc -> match cc with - | Normal _ -> Normal v - | _ -> cc ) - member this.Phase = - match this with - | WaitForInitInternal - | WaitForOpenChannel _ - | WaitForAcceptChannel _ - | WaitForFundingTx _ - | WaitForFundingCreated _ - | WaitForFundingSigned _ - | WaitForFundingConfirmed _ - | WaitForFundingLocked _ -> Opening - | Normal _ -> ChannelStatePhase.Normal - | Shutdown _ - | Negotiating _ - | Closing _ -> ChannelStatePhase.Closing - | Closed _ -> ChannelStatePhase.Closed - | Offline _ - | Syncing _ - | ErrFundingLost _ - | ErrFundingTimeOut _ - | ErrInformationLeak _ -> Abnormal diff --git a/src/DotNetLightning.Core/Channel/ChannelValidation.fs b/src/DotNetLightning.Core/Channel/ChannelValidation.fs index 7a22cc13c..8e02f209c 100644 --- a/src/DotNetLightning.Core/Channel/ChannelValidation.fs +++ b/src/DotNetLightning.Core/Channel/ChannelValidation.fs @@ -79,20 +79,23 @@ module internal ChannelHelpers = res + (uint64 (feeEstimator.GetEstSatPer1000Weight(ConfirmationTarget.Normal).Value) * SPENDING_INPUT_FOR_A_OUTPUT_WEIGHT) / 1000UL res |> LNMoney.Satoshis - let makeFirstCommitTxs (localParams: LocalParams) - (remoteParams: RemoteParams) - (fundingSatoshis: Money) - (pushMSat: LNMoney) - (initialFeeRatePerKw: FeeRatePerKw) - (fundingOutputIndex: TxOutIndex) - (fundingTxId: TxId) - (localPerCommitmentPoint: PerCommitmentPoint) - (remotePerCommitmentPoint: PerCommitmentPoint) - (n: Network): Result = - let toLocal = if (localParams.IsFunder) then fundingSatoshis.ToLNMoney() - pushMSat else pushMSat - let toRemote = if (localParams.IsFunder) then pushMSat else fundingSatoshis.ToLNMoney() - pushMSat - let localChannelKeys = localParams.ChannelPubKeys - let remoteChannelKeys = remoteParams.ChannelPubKeys + let makeFirstCommitTxs (localIsFunder: bool) + (localChannelPubKeys: ChannelPubKeys) + (remoteChannelPubKeys: ChannelPubKeys) + (localParams: LocalParams) + (remoteParams: RemoteParams) + (fundingSatoshis: Money) + (pushMSat: LNMoney) + (initialFeeRatePerKw: FeeRatePerKw) + (fundingOutputIndex: TxOutIndex) + (fundingTxId: TxId) + (localPerCommitmentPoint: PerCommitmentPoint) + (remotePerCommitmentPoint: PerCommitmentPoint) + (n: Network) + : Result = + let toLocal = if localIsFunder then fundingSatoshis.ToLNMoney() - pushMSat else pushMSat + let toRemote = if localIsFunder then pushMSat else fundingSatoshis.ToLNMoney() - pushMSat + let localChannelKeys = localChannelPubKeys let localSpec = CommitmentSpec.Create toLocal toRemote initialFeeRatePerKw let remoteSpec = CommitmentSpec.Create toRemote toLocal initialFeeRatePerKw let checkTheyCanAffordFee() = @@ -105,19 +108,19 @@ module internal ChannelHelpers = Ok() let makeFirstCommitTxCore() = let scriptCoin = getFundingScriptCoin localChannelKeys.FundingPubKey - remoteChannelKeys.FundingPubKey + remoteChannelPubKeys.FundingPubKey fundingTxId fundingOutputIndex fundingSatoshis let localPubKeysForLocalCommitment = localPerCommitmentPoint.DeriveCommitmentPubKeys localChannelKeys - let remotePubKeysForLocalCommitment = localPerCommitmentPoint.DeriveCommitmentPubKeys remoteChannelKeys + let remotePubKeysForLocalCommitment = localPerCommitmentPoint.DeriveCommitmentPubKeys remoteChannelPubKeys let localCommitTx = Transactions.makeCommitTx scriptCoin CommitmentNumber.FirstCommitment localChannelKeys.PaymentBasepoint - remoteChannelKeys.PaymentBasepoint - localParams.IsFunder + remoteChannelPubKeys.PaymentBasepoint + localIsFunder localParams.DustLimitSatoshis remotePubKeysForLocalCommitment.RevocationPubKey remoteParams.ToSelfDelay @@ -129,14 +132,14 @@ module internal ChannelHelpers = n let localPubKeysForRemoteCommitment = remotePerCommitmentPoint.DeriveCommitmentPubKeys localChannelKeys - let remotePubKeysForRemoteCommitment = remotePerCommitmentPoint.DeriveCommitmentPubKeys remoteChannelKeys + let remotePubKeysForRemoteCommitment = remotePerCommitmentPoint.DeriveCommitmentPubKeys remoteChannelPubKeys let remoteCommitTx = Transactions.makeCommitTx scriptCoin CommitmentNumber.FirstCommitment - remoteChannelKeys.PaymentBasepoint + remoteChannelPubKeys.PaymentBasepoint localChannelKeys.PaymentBasepoint - (not localParams.IsFunder) + (not localIsFunder) (remoteParams.DustLimitSatoshis) localPubKeysForRemoteCommitment.RevocationPubKey localParams.ToSelfDelay @@ -149,7 +152,7 @@ module internal ChannelHelpers = (localSpec, localCommitTx, remoteSpec, remoteCommitTx) |> Ok - if (not localParams.IsFunder) then + if (not localIsFunder) then result { do! checkTheyCanAffordFee() return! (makeFirstCommitTxCore()) @@ -161,7 +164,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,55 +173,101 @@ 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 feeRate = feeEstimator.GetEstSatPer1000Weight(ConfirmationTarget.Background) + let internal checkOpenChannelMsgAcceptable (channelHandshakeLimits: ChannelHandshakeLimits) + (channelOptions: ChannelOptions) + (announceChannel: bool) + (msg: OpenChannelMsg) = + let feeRate = channelOptions.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 channelOptions.FeeEstimator msg.FeeRatePerKw channelOptions.MaxFeeRateMismatchRatio *^> OpenChannelMsgValidation.checkToSelfDelayIsInAcceptableRange msg *^> OpenChannelMsgValidation.checkMaxAcceptedHTLCs msg - *> OpenChannelMsgValidation.checkConfigPermits conf.PeerChannelConfigLimits msg - *^> OpenChannelMsgValidation.checkChannelAnnouncementPreferenceAcceptable conf msg - *> OpenChannelMsgValidation.checkIsAcceptableByCurrentFeeRate feeEstimator msg + *> OpenChannelMsgValidation.checkConfigPermits channelHandshakeLimits msg + *^> OpenChannelMsgValidation.checkChannelAnnouncementPreferenceAcceptable channelHandshakeLimits announceChannel msg + *> OpenChannelMsgValidation.checkIsAcceptableByCurrentFeeRate channelOptions.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) = - Validation.ofResult(AcceptChannelMsgValidation.checkMaxAcceptedHTLCs msg) - *^> AcceptChannelMsgValidation.checkDustLimit msg - *^> (AcceptChannelMsgValidation.checkChannelReserveSatoshis state msg) - *^> AcceptChannelMsgValidation.checkChannelReserveSatoshis state msg - *^> AcceptChannelMsgValidation.checkDustLimitIsLargerThanOurChannelReserve state msg - *^> AcceptChannelMsgValidation.checkMinimumHTLCValueIsAcceptable state msg - *^> AcceptChannelMsgValidation.checkToSelfDelayIsAcceptable msg - *> AcceptChannelMsgValidation.checkConfigPermits conf.PeerChannelConfigLimits msg - |> Result.mapError(InvalidAcceptChannelError.Create msg >> InvalidAcceptChannel) + let internal checkAcceptChannelMsgAcceptable (channelHandshakeLimits: ChannelHandshakeLimits) + (fundingSatoshis: Money) + (channelReserveSatoshis: Money) + (dustLimitSatoshis: Money) + (acceptChannelMsg: AcceptChannelMsg) = + Validation.ofResult(AcceptChannelMsgValidation.checkMaxAcceptedHTLCs acceptChannelMsg) + *^> AcceptChannelMsgValidation.checkDustLimit acceptChannelMsg + *^> AcceptChannelMsgValidation.checkChannelReserveSatoshis fundingSatoshis channelReserveSatoshis dustLimitSatoshis acceptChannelMsg + *^> AcceptChannelMsgValidation.checkDustLimitIsLargerThanOurChannelReserve channelReserveSatoshis acceptChannelMsg + *^> AcceptChannelMsgValidation.checkMinimumHTLCValueIsAcceptable fundingSatoshis acceptChannelMsg + *^> AcceptChannelMsgValidation.checkToSelfDelayIsAcceptable acceptChannelMsg + *> AcceptChannelMsgValidation.checkConfigPermits channelHandshakeLimits acceptChannelMsg + |> Result.mapError(InvalidAcceptChannelError.Create acceptChannelMsg >> InvalidAcceptChannel) + let checkOurMonoHopUnidirectionalPaymentIsAcceptableWithCurrentSpec (currentSpec) + (staticChannelConfig: StaticChannelConfig) + (payment: MonoHopUnidirectionalPaymentMsg) = + MonoHopUnidirectionalPaymentValidationWithContext.checkWeHaveSufficientFunds + staticChannelConfig + currentSpec + |> Validation.ofResult + |> Result.mapError(fun errs -> InvalidMonoHopUnidirectionalPayment { NetworkMsg = payment; Errors = errs }) - let checkOperationAddHTLC (state: NormalData) (op: OperationAddHTLC) = + let checkTheirMonoHopUnidirectionalPaymentIsAcceptableWithCurrentSpec (currentSpec) + (staticChannelConfig: StaticChannelConfig) + (payment: MonoHopUnidirectionalPaymentMsg) = + MonoHopUnidirectionalPaymentValidationWithContext.checkWeHaveSufficientFunds + staticChannelConfig + currentSpec + |> Validation.ofResult + |> Result.mapError(fun errs -> InvalidMonoHopUnidirectionalPayment { NetworkMsg = payment; Errors = errs }) + + let checkOperationAddHTLC (remoteParams: RemoteParams) (op: OperationAddHTLC) = Validation.ofResult(UpdateAddHTLCValidation.checkExpiryIsNotPast op.CurrentHeight op.Expiry) *> UpdateAddHTLCValidation.checkExpiryIsInAcceptableRange op.CurrentHeight op.Expiry - *^> UpdateAddHTLCValidation.checkAmountIsLargerThanMinimum state.Commitments.RemoteParams.HTLCMinimumMSat op.Amount + *^> UpdateAddHTLCValidation.checkAmountIsLargerThanMinimum remoteParams.HTLCMinimumMSat op.Amount |> Result.mapError(InvalidOperationAddHTLCError.Create op >> InvalidOperationAddHTLC) - let checkOurUpdateAddHTLCIsAcceptableWithCurrentSpec (currentSpec) (state: Commitments) (add: UpdateAddHTLCMsg) = - Validation.ofResult(UpdateAddHTLCValidationWithContext.checkLessThanHTLCValueInFlightLimit currentSpec state.RemoteParams.MaxHTLCValueInFlightMSat add) - *^> UpdateAddHTLCValidationWithContext.checkLessThanMaxAcceptedHTLC currentSpec state.RemoteParams.MaxAcceptedHTLCs - *^> UpdateAddHTLCValidationWithContext.checkWeHaveSufficientFunds state currentSpec + let checkOurUpdateAddHTLCIsAcceptableWithCurrentSpec (currentSpec) + (staticChannelConfig: StaticChannelConfig) + (add: UpdateAddHTLCMsg) = + Validation.ofResult( + UpdateAddHTLCValidationWithContext.checkLessThanHTLCValueInFlightLimit + currentSpec + staticChannelConfig.RemoteParams.MaxHTLCValueInFlightMSat + add + ) + *^> UpdateAddHTLCValidationWithContext.checkLessThanMaxAcceptedHTLC currentSpec staticChannelConfig.RemoteParams.MaxAcceptedHTLCs + *^> UpdateAddHTLCValidationWithContext.checkWeHaveSufficientFunds staticChannelConfig currentSpec |> Result.mapError(InvalidUpdateAddHTLCError.Create add >> InvalidUpdateAddHTLC) - let checkTheirUpdateAddHTLCIsAcceptable (state: Commitments) (add: UpdateAddHTLCMsg) (currentHeight: BlockHeight) = + let checkTheirUpdateAddHTLCIsAcceptable (state: Commitments) + (localParams: LocalParams) + (add: UpdateAddHTLCMsg) + (currentHeight: BlockHeight) = Validation.ofResult(ValidationHelper.check add.HTLCId (<>) state.RemoteNextHTLCId "Received Unexpected HTLCId (%A). Must be (%A)") *^> UpdateAddHTLCValidation.checkExpiryIsNotPast currentHeight add.CLTVExpiry *> UpdateAddHTLCValidation.checkExpiryIsInAcceptableRange currentHeight add.CLTVExpiry - *^> UpdateAddHTLCValidation.checkAmountIsLargerThanMinimum state.LocalParams.HTLCMinimumMSat add.Amount + *^> UpdateAddHTLCValidation.checkAmountIsLargerThanMinimum localParams.HTLCMinimumMSat add.Amount |> Result.mapError(InvalidUpdateAddHTLCError.Create add >> InvalidUpdateAddHTLC) - let checkTheirUpdateAddHTLCIsAcceptableWithCurrentSpec (currentSpec) (state: Commitments) (add: UpdateAddHTLCMsg) = - Validation.ofResult(UpdateAddHTLCValidationWithContext.checkLessThanHTLCValueInFlightLimit currentSpec state.LocalParams.MaxHTLCValueInFlightMSat add) - *^> UpdateAddHTLCValidationWithContext.checkLessThanMaxAcceptedHTLC currentSpec state.LocalParams.MaxAcceptedHTLCs - *^> UpdateAddHTLCValidationWithContext.checkWeHaveSufficientFunds state currentSpec + let checkTheirUpdateAddHTLCIsAcceptableWithCurrentSpec (currentSpec) + (staticChannelConfig: StaticChannelConfig) + (add: UpdateAddHTLCMsg) = + Validation.ofResult(UpdateAddHTLCValidationWithContext.checkLessThanHTLCValueInFlightLimit currentSpec staticChannelConfig.LocalParams.MaxHTLCValueInFlightMSat add) + *^> UpdateAddHTLCValidationWithContext.checkLessThanMaxAcceptedHTLC currentSpec staticChannelConfig.LocalParams.MaxAcceptedHTLCs + *^> UpdateAddHTLCValidationWithContext.checkWeHaveSufficientFunds staticChannelConfig currentSpec |> Result.mapError(InvalidUpdateAddHTLCError.Create add >> InvalidUpdateAddHTLC) + + let checkShutdownScriptPubKeyAcceptable (staticShutdownScriptPubKey: Option) + (requestedShutdownScriptPubKey: ShutdownScriptPubKey) + : Result = + match staticShutdownScriptPubKey with + | Some scriptPubKey when scriptPubKey <> requestedShutdownScriptPubKey -> + Error <| + cannotCloseChannel + "requested shutdown script does not match shutdown \ + script in open/accept channel" + | _ -> Ok () 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/Commitments.fs b/src/DotNetLightning.Core/Channel/Commitments.fs index 89b303d18..b5b5e6c7f 100644 --- a/src/DotNetLightning.Core/Channel/Commitments.fs +++ b/src/DotNetLightning.Core/Channel/Commitments.fs @@ -12,17 +12,13 @@ open ResultUtils open ResultUtils.Portability type LocalChanges = { - Proposed: IUpdateMsg list Signed: IUpdateMsg list ACKed: IUpdateMsg list } with - static member Zero = { Proposed = []; Signed = []; ACKed = [] } + static member Zero = { Signed = []; ACKed = [] } // -- lenses - static member Proposed_: Lens<_, _> = - (fun lc -> lc.Proposed), - (fun ps lc -> { lc with Proposed = ps }) static member Signed_: Lens<_, _> = (fun lc -> lc.Signed), (fun v lc -> { lc with Signed = v }) @@ -31,19 +27,13 @@ type LocalChanges = { (fun lc -> lc.ACKed), (fun v lc -> { lc with ACKed = v }) - member this.All() = - this.Proposed @ this.Signed @ this.ACKed - type RemoteChanges = { - Proposed: IUpdateMsg list Signed: IUpdateMsg list ACKed: IUpdateMsg list } with - static member Zero = { Proposed = []; Signed = []; ACKed = [] } - static member Proposed_: Lens<_, _> = - (fun lc -> lc.Proposed), - (fun ps lc -> { lc with Proposed = ps }) + static member Zero = { Signed = []; ACKed = [] } + static member Signed_: Lens<_, _> = (fun lc -> lc.Signed), (fun v lc -> { lc with Signed = v }) @@ -53,10 +43,6 @@ type RemoteChanges = { (fun v lc -> { lc with ACKed = v }) -type Changes = - | Local of LocalChanges - | Remote of RemoteChanges - type PublishableTxs = { CommitTx: FinalizedTx HTLCTxs: FinalizedTx list @@ -72,33 +58,17 @@ type LocalCommit = { type RemoteCommit = { Index: CommitmentNumber Spec: CommitmentSpec - TxId: TxId RemotePerCommitmentPoint: PerCommitmentPoint } -type WaitingForRevocation = { - NextRemoteCommit: RemoteCommit - Sent: CommitmentSignedMsg - SentAfterLocalCommitmentIndex: CommitmentNumber - ReSignASAP: bool -} - with - static member NextRemoteCommit_: Lens<_,_> = - (fun w -> w.NextRemoteCommit), - (fun v w -> { w with NextRemoteCommit = v}) - - static member ReSignASAP_: Lens<_,_> = - (fun w -> w.ReSignASAP), - (fun v w -> { w with ReSignASAP = v }) - type RemoteNextCommitInfo = - | Waiting of WaitingForRevocation + | Waiting of RemoteCommit | Revoked of PerCommitmentPoint with - static member Waiting_: Prism = + static member Waiting_: Prism = (fun remoteNextCommitInfo -> match remoteNextCommitInfo with - | Waiting waitingForRevocation -> Some waitingForRevocation + | Waiting remoteCommit -> Some remoteCommit | Revoked _ -> None), (fun waitingForRevocation remoteNextCommitInfo -> match remoteNextCommitInfo with @@ -115,74 +85,37 @@ type RemoteNextCommitInfo = | Waiting _ -> remoteNextCommitInfo | Revoked _ -> Revoked commitmentPubKey) + member self.PerCommitmentPoint(): PerCommitmentPoint = + match self with + | Waiting remoteCommit -> remoteCommit.RemotePerCommitmentPoint + | Revoked perCommitmentPoint -> perCommitmentPoint + type Commitments = { - LocalParams: LocalParams - RemoteParams: RemoteParams - ChannelFlags: uint8 - FundingScriptCoin: ScriptCoin - LocalCommit: LocalCommit - RemoteCommit: RemoteCommit - LocalChanges: LocalChanges - RemoteChanges: RemoteChanges + ProposedLocalChanges: list + ProposedRemoteChanges: list LocalNextHTLCId: HTLCId RemoteNextHTLCId: HTLCId OriginChannels: Map - RemoteNextCommitInfo: RemoteNextCommitInfo - RemotePerCommitmentSecrets: PerCommitmentSecretStore - ChannelId: ChannelId } with - static member LocalChanges_: Lens<_, _> = - (fun c -> c.LocalChanges), - (fun v c -> { c with LocalChanges = v }) - static member RemoteChanges_: Lens<_, _> = - (fun c -> c.RemoteChanges), - (fun v c -> { c with RemoteChanges = v }) - static member RemoteNextCommitInfo_: Lens<_, _> = - (fun c -> c.RemoteNextCommitInfo), - (fun v c -> { c with RemoteNextCommitInfo = v }) - - member this.AddLocalProposal(proposal: IUpdateMsg) = - let lens = Commitments.LocalChanges_ >-> LocalChanges.Proposed_ - Optic.map lens (fun proposalList -> proposal :: proposalList) this + { + this with + ProposedLocalChanges = proposal :: this.ProposedLocalChanges + } member this.AddRemoteProposal(proposal: IUpdateMsg) = - let lens = Commitments.RemoteChanges_ >-> RemoteChanges.Proposed_ - Optic.map lens (fun proposalList -> proposal :: proposalList) this + { + this with + ProposedRemoteChanges = proposal :: this.ProposedRemoteChanges + } member this.IncrLocalHTLCId() = { this with LocalNextHTLCId = this.LocalNextHTLCId + 1UL } member this.IncrRemoteHTLCId() = { this with RemoteNextHTLCId = this.RemoteNextHTLCId + 1UL } - member this.LocalHasChanges() = - (not this.RemoteChanges.ACKed.IsEmpty) || (not this.LocalChanges.Proposed.IsEmpty) - - member this.RemoteHasChanges() = - (not this.LocalChanges.ACKed.IsEmpty) || (not this.RemoteChanges.Proposed.IsEmpty) - member internal this.LocalHasUnsignedOutgoingHTLCs() = - this.LocalChanges.Proposed |> List.exists(fun p -> match p with | :? UpdateAddHTLCMsg -> true | _ -> false) + this.ProposedLocalChanges |> List.exists(fun p -> match p with | :? UpdateAddHTLCMsg -> true | _ -> false) member internal this.RemoteHasUnsignedOutgoingHTLCs() = - this.RemoteChanges.Proposed |> List.exists(fun p -> match p with | :? UpdateAddHTLCMsg -> true | _ -> false) - - member internal this.HasNoPendingHTLCs() = - this.LocalCommit.Spec.HTLCs.IsEmpty && this.RemoteCommit.Spec.HTLCs.IsEmpty && (this.RemoteNextCommitInfo |> function Waiting _ -> false | Revoked _ -> true) - - member internal this.GetHTLCCrossSigned(directionRelativeToLocal: Direction, htlcId: HTLCId): UpdateAddHTLCMsg option = - let remoteSigned = - this.LocalCommit.Spec.HTLCs - |> Map.tryPick (fun _k v -> if v.Direction = directionRelativeToLocal && v.Add.HTLCId = htlcId then Some v else None) - - let localSigned = - let lens = - Commitments.RemoteNextCommitInfo_ - >-> RemoteNextCommitInfo.Waiting_ - >?> WaitingForRevocation.NextRemoteCommit_ - match Optic.get lens this with - | Some v -> v - | None -> this.RemoteCommit - |> fun v -> v.Spec.HTLCs |> Map.tryPick(fun _k v -> if v.Direction = directionRelativeToLocal.Opposite && v.Add.HTLCId = htlcId then Some v else None) - match remoteSigned, localSigned with - | Some _, Some htlcIn -> htlcIn.Add |> Some - | _ -> None + this.ProposedRemoteChanges |> List.exists(fun p -> match p with | :? UpdateAddHTLCMsg -> true | _ -> false) + diff --git a/src/DotNetLightning.Core/Channel/CommitmentsModule.fs b/src/DotNetLightning.Core/Channel/CommitmentsModule.fs index 7c83b3d26..577a31243 100644 --- a/src/DotNetLightning.Core/Channel/CommitmentsModule.fs +++ b/src/DotNetLightning.Core/Channel/CommitmentsModule.fs @@ -13,7 +13,7 @@ open ResultUtils.Portability [] module internal Commitments = - module private Helpers = + module Helpers = let isAlreadySent (htlc: UpdateAddHTLCMsg) (proposed: IUpdateMsg list) = proposed |> List.exists(fun p -> match p with @@ -23,83 +23,79 @@ module internal Commitments = | _ -> false) let makeRemoteTxs + (staticChannelConfig: StaticChannelConfig) (commitTxNumber: CommitmentNumber) - (localParams: LocalParams) - (remoteParams: RemoteParams) - (commitmentInput: ScriptCoin) + (localChannelPubKeys: ChannelPubKeys) (remotePerCommitmentPoint: PerCommitmentPoint) - (spec) (n) = - let localChannelKeys = localParams.ChannelPubKeys - let remoteChannelKeys = remoteParams.ChannelPubKeys - let localCommitmentPubKeys = remotePerCommitmentPoint.DeriveCommitmentPubKeys localChannelKeys - let remoteCommitmentPubKeys = remotePerCommitmentPoint.DeriveCommitmentPubKeys remoteChannelKeys + (spec: CommitmentSpec) = + let localCommitmentPubKeys = remotePerCommitmentPoint.DeriveCommitmentPubKeys localChannelPubKeys + let remoteCommitmentPubKeys = remotePerCommitmentPoint.DeriveCommitmentPubKeys staticChannelConfig.RemoteChannelPubKeys let commitTx = - Transactions.makeCommitTx commitmentInput + Transactions.makeCommitTx staticChannelConfig.FundingScriptCoin commitTxNumber - remoteChannelKeys.PaymentBasepoint - localChannelKeys.PaymentBasepoint - (not localParams.IsFunder) - (remoteParams.DustLimitSatoshis) + staticChannelConfig.RemoteChannelPubKeys.PaymentBasepoint + localChannelPubKeys.PaymentBasepoint + (not staticChannelConfig.IsFunder) + (staticChannelConfig.RemoteParams.DustLimitSatoshis) localCommitmentPubKeys.RevocationPubKey - (localParams.ToSelfDelay) + staticChannelConfig.LocalParams.ToSelfDelay remoteCommitmentPubKeys.DelayedPaymentPubKey localCommitmentPubKeys.PaymentPubKey remoteCommitmentPubKeys.HtlcPubKey localCommitmentPubKeys.HtlcPubKey - (spec) - (n) + spec + staticChannelConfig.Network result { let! (htlcTimeoutTxs, htlcSuccessTxs) = Transactions.makeHTLCTxs (commitTx.Value.GetGlobalTransaction()) - (remoteParams.DustLimitSatoshis) + (staticChannelConfig.RemoteParams.DustLimitSatoshis) localCommitmentPubKeys.RevocationPubKey - (localParams.ToSelfDelay) + (staticChannelConfig.LocalParams.ToSelfDelay) remoteCommitmentPubKeys.DelayedPaymentPubKey remoteCommitmentPubKeys.HtlcPubKey localCommitmentPubKeys.HtlcPubKey - (spec) (n) + spec + staticChannelConfig.Network return (commitTx, htlcTimeoutTxs, htlcSuccessTxs) } let makeLocalTXs + (staticChannelConfig: StaticChannelConfig) (commitTxNumber: CommitmentNumber) - (localParams: LocalParams) - (remoteParams: RemoteParams) - (commitmentInput: ScriptCoin) + (localChannelPubKeys: ChannelPubKeys) (localPerCommitmentPoint: PerCommitmentPoint) (spec: CommitmentSpec) - n: Result<(CommitTx * HTLCTimeoutTx list * HTLCSuccessTx list), _> = - let localChannelKeys = localParams.ChannelPubKeys - let remoteChannelKeys = remoteParams.ChannelPubKeys - let localCommitmentPubKeys = localPerCommitmentPoint.DeriveCommitmentPubKeys localChannelKeys - let remoteCommitmentPubKeys = localPerCommitmentPoint.DeriveCommitmentPubKeys remoteChannelKeys + : Result<(CommitTx * HTLCTimeoutTx list * HTLCSuccessTx list), _> = + let localCommitmentPubKeys = localPerCommitmentPoint.DeriveCommitmentPubKeys localChannelPubKeys + let remoteCommitmentPubKeys = localPerCommitmentPoint.DeriveCommitmentPubKeys staticChannelConfig.RemoteChannelPubKeys let commitTx = - Transactions.makeCommitTx commitmentInput + Transactions.makeCommitTx staticChannelConfig.FundingScriptCoin commitTxNumber - localChannelKeys.PaymentBasepoint - remoteChannelKeys.PaymentBasepoint - localParams.IsFunder - localParams.DustLimitSatoshis + localChannelPubKeys.PaymentBasepoint + staticChannelConfig.RemoteChannelPubKeys.PaymentBasepoint + staticChannelConfig.IsFunder + staticChannelConfig.LocalParams.DustLimitSatoshis remoteCommitmentPubKeys.RevocationPubKey - remoteParams.ToSelfDelay + staticChannelConfig.RemoteParams.ToSelfDelay localCommitmentPubKeys.DelayedPaymentPubKey remoteCommitmentPubKeys.PaymentPubKey localCommitmentPubKeys.HtlcPubKey remoteCommitmentPubKeys.HtlcPubKey - spec n + spec + staticChannelConfig.Network result { let! (htlcTimeoutTxs, htlcSuccessTxs) = Transactions.makeHTLCTxs (commitTx.Value.GetGlobalTransaction()) - (localParams.DustLimitSatoshis) + (staticChannelConfig.LocalParams.DustLimitSatoshis) remoteCommitmentPubKeys.RevocationPubKey - (remoteParams.ToSelfDelay) + staticChannelConfig.RemoteParams.ToSelfDelay localCommitmentPubKeys.DelayedPaymentPubKey localCommitmentPubKeys.HtlcPubKey remoteCommitmentPubKeys.HtlcPubKey (spec) - (n) + staticChannelConfig.Network return (commitTx, htlcTimeoutTxs, htlcSuccessTxs) } @@ -111,17 +107,25 @@ 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) = - match cm.GetHTLCCrossSigned(Direction.In, op.Id) with - | Some htlc when (cm.LocalChanges.Proposed |> Helpers.isAlreadySent htlc) -> + let sendFulfill (op: OperationFulfillHTLC) + (cm: Commitments) + (savedChannelState: SavedChannelState) + (remoteNextCommitInfo: RemoteNextCommitInfo) = + match savedChannelState.GetIncomingHTLCCrossSigned remoteNextCommitInfo op.Id with + | Some htlc when (cm.ProposedLocalChanges |> Helpers.isAlreadySent htlc) -> htlc.HTLCId |> htlcAlreadySent | Some htlc when (htlc.PaymentHash = op.PaymentPreimage.Hash) -> - let msgToSend: UpdateFulfillHTLCMsg = - { ChannelId = cm.ChannelId; HTLCId = op.Id; PaymentPreimage = op.PaymentPreimage } + let msgToSend: UpdateFulfillHTLCMsg = { + ChannelId = savedChannelState.StaticChannelConfig.ChannelId() + HTLCId = op.Id + PaymentPreimage = op.PaymentPreimage + } let newCommitments = cm.AddLocalProposal(msgToSend) (msgToSend, newCommitments) |> Ok | Some htlc -> @@ -131,12 +135,14 @@ module internal Commitments = op.Id |> unknownHTLCId - let receiveFulfill(msg: UpdateFulfillHTLCMsg) (cm: Commitments) = - match cm.GetHTLCCrossSigned(Direction.Out, msg.HTLCId) with + let receiveFulfill (msg: UpdateFulfillHTLCMsg) + (cm: Commitments) + (savedChannelState: SavedChannelState) + (remoteNextCommitInfo: RemoteNextCommitInfo) = + match savedChannelState.GetOutgoingHTLCCrossSigned remoteNextCommitInfo msg.HTLCId with | Some htlc when htlc.PaymentHash = msg.PaymentPreimage.Hash -> let commitments = cm.AddRemoteProposal(msg) - let origin = cm.OriginChannels |> Map.find(msg.HTLCId) - [WeAcceptedFulfillHTLC(msg, origin, htlc, commitments)] |> Ok + commitments |> Ok | Some htlc -> (htlc.PaymentHash, msg.PaymentPreimage) |> invalidPaymentPreimage @@ -144,9 +150,13 @@ module internal Commitments = msg.HTLCId |> unknownHTLCId - let sendFail (nodeSecret: NodeSecret) (op: OperationFailHTLC) (cm: Commitments) = - match cm.GetHTLCCrossSigned(Direction.In, op.Id) with - | Some htlc when (cm.LocalChanges.Proposed |> Helpers.isAlreadySent htlc) -> + let sendFail (nodeSecret: NodeSecret) + (op: OperationFailHTLC) + (cm: Commitments) + (savedChannelState: SavedChannelState) + (remoteNextCommitInfo: RemoteNextCommitInfo) = + match savedChannelState.GetIncomingHTLCCrossSigned remoteNextCommitInfo op.Id with + | Some htlc when (cm.ProposedLocalChanges |> Helpers.isAlreadySent htlc) -> htlc.HTLCId |> htlcAlreadySent | Some htlc -> let ad = htlc.PaymentHash.ToBytes() @@ -157,261 +167,307 @@ module internal Commitments = let reason = op.Reason |> function Choice1Of2 b -> Sphinx.forwardErrorPacket(b, ss) | Choice2Of2 f -> Sphinx.ErrorPacket.Create(ss, f) - let f = { UpdateFailHTLCMsg.ChannelId = cm.ChannelId - HTLCId = op.Id - Reason = { Data = reason } } - let nextComitments = cm.AddLocalProposal(f) - [ WeAcceptedOperationFailHTLC(f, nextComitments) ] - |> Ok + let f = { + UpdateFailHTLCMsg.ChannelId = savedChannelState.StaticChannelConfig.ChannelId() + HTLCId = op.Id + Reason = { Data = reason } + } + let nextCommitments = cm.AddLocalProposal(f) + Ok (f, nextCommitments) | None -> op.Id |> unknownHTLCId - let receiveFail (msg: UpdateFailHTLCMsg) (cm: Commitments) = - match cm.GetHTLCCrossSigned(Direction.Out, msg.HTLCId) with - | Some htlc -> + let receiveFail (msg: UpdateFailHTLCMsg) + (cm: Commitments) + (savedChannelState: SavedChannelState) + (remoteNextCommitInfo: RemoteNextCommitInfo) = + match savedChannelState.GetOutgoingHTLCCrossSigned remoteNextCommitInfo msg.HTLCId with + | Some _htlc -> result { - let! o = + let! _origin = match cm.OriginChannels.TryGetValue(msg.HTLCId) with | true, origin -> Ok origin | false, _ -> msg.HTLCId |> htlcOriginNowKnown let nextC = cm.AddRemoteProposal(msg) - return [WeAcceptedFailHTLC(o, htlc, nextC)] + return nextC } | None -> msg.HTLCId |> unknownHTLCId - let sendFailMalformed (op: OperationFailMalformedHTLC) (cm: Commitments) = + let sendFailMalformed (op: OperationFailMalformedHTLC) + (cm: Commitments) + (savedChannelState: SavedChannelState) + (remoteNextCommitInfo: RemoteNextCommitInfo) = // BADONION bit must be set in failure code if (op.FailureCode.Value &&& OnionError.BADONION) = 0us then op.FailureCode |> invalidFailureCode else - match cm.GetHTLCCrossSigned(Direction.In, op.Id) with - | Some htlc when (cm.LocalChanges.Proposed |> Helpers.isAlreadySent htlc) -> + match savedChannelState.GetIncomingHTLCCrossSigned remoteNextCommitInfo op.Id with + | Some htlc when (cm.ProposedLocalChanges |> Helpers.isAlreadySent htlc) -> htlc.HTLCId |> htlcAlreadySent | Some _htlc -> - let msg = { UpdateFailMalformedHTLCMsg.ChannelId = cm.ChannelId - HTLCId = op.Id - Sha256OfOnion = op.Sha256OfOnion - FailureCode = op.FailureCode } + let msg = { + UpdateFailMalformedHTLCMsg.ChannelId = savedChannelState.StaticChannelConfig.ChannelId() + HTLCId = op.Id + Sha256OfOnion = op.Sha256OfOnion + FailureCode = op.FailureCode + } let nextCommitments = cm.AddLocalProposal(msg) - [ WeAcceptedOperationFailMalformedHTLC(msg, nextCommitments) ] - |> Ok + Ok (msg, nextCommitments) | None -> op.Id |> unknownHTLCId - let receiveFailMalformed (msg: UpdateFailMalformedHTLCMsg) (cm: Commitments) = + let receiveFailMalformed (msg: UpdateFailMalformedHTLCMsg) + (cm: Commitments) + (savedChannelState: SavedChannelState) + (remoteNextCommitInfo: RemoteNextCommitInfo) = if msg.FailureCode.Value &&& OnionError.BADONION = 0us then msg.FailureCode |> invalidFailureCode else - match cm.GetHTLCCrossSigned(Direction.Out, msg.HTLCId) with - | Some htlc -> + match savedChannelState.GetOutgoingHTLCCrossSigned remoteNextCommitInfo msg.HTLCId with + | Some _htlc -> result { - let! o = + let! _origin = match cm.OriginChannels.TryGetValue(msg.HTLCId) with | true, o -> Ok o | false, _ -> msg.HTLCId |> htlcOriginNowKnown let nextC = cm.AddRemoteProposal(msg) - return [WeAcceptedFailMalformedHTLC(o, htlc, nextC)] + return nextC } | None -> msg.HTLCId |> unknownHTLCId - let sendFee(op: OperationUpdateFee) (cm: Commitments) = - if (not cm.LocalParams.IsFunder) then + let sendFee (op: OperationUpdateFee) + (savedChannelState: SavedChannelState) + (cm: Commitments) = + if (not savedChannelState.StaticChannelConfig.IsFunder) then "Local is Fundee so it cannot send update fee" |> apiMisuse else - let fee = { UpdateFeeMsg.ChannelId = cm.ChannelId - FeeRatePerKw = op.FeeRatePerKw } + let fee = { + UpdateFeeMsg.ChannelId = savedChannelState.StaticChannelConfig.ChannelId() + FeeRatePerKw = op.FeeRatePerKw + } let c1 = cm.AddLocalProposal(fee) result { let! reduced = - c1.RemoteCommit.Spec.Reduce(c1.RemoteChanges.ACKed, c1.LocalChanges.Proposed) |> expectTransactionError + savedChannelState.RemoteCommit.Spec.Reduce(savedChannelState.RemoteChanges.ACKed, c1.ProposedLocalChanges) |> expectTransactionError // A node cannot spend pending incoming htlcs, and need to keep funds above the reserve required by // the counter party, after paying the fee, we look from remote's point of view, so if local is funder // remote doesn't pay the fees. - let fees = Transactions.commitTxFee(c1.RemoteParams.DustLimitSatoshis) reduced - let missing = reduced.ToRemote.ToMoney() - c1.RemoteParams.ChannelReserveSatoshis - fees + let fees = Transactions.commitTxFee(savedChannelState.StaticChannelConfig.RemoteParams.DustLimitSatoshis) reduced + let missing = reduced.ToRemote.ToMoney() - savedChannelState.StaticChannelConfig.RemoteParams.ChannelReserveSatoshis - fees if (missing < Money.Zero) then return! - (c1.LocalParams.ChannelReserveSatoshis, fees, (-1 * missing)) + (savedChannelState.StaticChannelConfig.LocalParams.ChannelReserveSatoshis, fees, (-1 * missing)) |> cannotAffordFee else - return - [ WeAcceptedOperationUpdateFee(fee, c1) ] + return fee, c1 } - let receiveFee (config: ChannelConfig) (localFeerate) (msg: UpdateFeeMsg) (cm: Commitments) = - if (cm.LocalParams.IsFunder) then + let receiveFee (channelOptions: ChannelOptions) + (localFeerate) + (msg: UpdateFeeMsg) + (savedChannelState: SavedChannelState) + (cm: Commitments) = + if savedChannelState.StaticChannelConfig.IsFunder then "Remote is Fundee so it cannot send update fee" |> apiMisuse else result { - do! Helpers.checkUpdateFee (config) (msg) (localFeerate) - let c1 = cm.AddRemoteProposal(msg) + do! Helpers.checkUpdateFee channelOptions msg localFeerate + let nextCommitments = cm.AddRemoteProposal(msg) let! reduced = - c1.LocalCommit.Spec.Reduce(c1.LocalChanges.ACKed, c1.RemoteChanges.Proposed) |> expectTransactionError + savedChannelState.LocalCommit.Spec.Reduce( + savedChannelState.LocalChanges.ACKed, + nextCommitments.ProposedRemoteChanges + ) |> expectTransactionError - let fees = Transactions.commitTxFee(c1.RemoteParams.DustLimitSatoshis) reduced - let missing = reduced.ToRemote.ToMoney() - c1.RemoteParams.ChannelReserveSatoshis - fees + let fees = Transactions.commitTxFee(savedChannelState.StaticChannelConfig.RemoteParams.DustLimitSatoshis) reduced + let missing = reduced.ToRemote.ToMoney() - savedChannelState.StaticChannelConfig.RemoteParams.ChannelReserveSatoshis - fees if (missing < Money.Zero) then return! - (c1.LocalParams.ChannelReserveSatoshis, fees, (-1 * missing)) + (savedChannelState.StaticChannelConfig.LocalParams.ChannelReserveSatoshis, fees, (-1 * missing)) |> cannotAffordFee else - return - [ WeAcceptedUpdateFee msg ] - } - - let sendCommit (channelPrivKeys: ChannelPrivKeys) (n: Network) (cm: Commitments) = - match cm.RemoteNextCommitInfo with - | RemoteNextCommitInfo.Revoked remoteNextPerCommitmentPoint -> - result { - // remote commitment will include all local changes + remote acked changes - let! spec = cm.RemoteCommit.Spec.Reduce(cm.RemoteChanges.ACKed, cm.LocalChanges.Proposed) |> expectTransactionError - let! (remoteCommitTx, htlcTimeoutTxs, htlcSuccessTxs) = - Helpers.makeRemoteTxs (cm.RemoteCommit.Index.NextCommitment()) - (cm.LocalParams) - (cm.RemoteParams) - (cm.FundingScriptCoin) - (remoteNextPerCommitmentPoint) - (spec) n |> expectTransactionErrors - let signature,_ = channelPrivKeys.SignWithFundingPrivKey remoteCommitTx.Value - let sortedHTLCTXs = Helpers.sortBothHTLCs htlcTimeoutTxs htlcSuccessTxs - let htlcSigs = - sortedHTLCTXs - |> List.map( - (fun htlc -> channelPrivKeys.SignHtlcTx htlc.Value remoteNextPerCommitmentPoint) - >> fst - >> (fun txSig -> txSig.Signature) - ) - let msg = { CommitmentSignedMsg.ChannelId = cm.ChannelId - Signature = !> signature.Signature - HTLCSignatures = htlcSigs |> List.map (!>) } - let nextCommitments = - let nextRemoteCommitInfo = { - WaitingForRevocation.NextRemoteCommit = { - cm.RemoteCommit - with - Index = cm.RemoteCommit.Index.NextCommitment() - Spec = spec - RemotePerCommitmentPoint = remoteNextPerCommitmentPoint - TxId = remoteCommitTx.GetTxId() - } - Sent = msg - SentAfterLocalCommitmentIndex = cm.LocalCommit.Index - ReSignASAP = false - } - { cm with RemoteNextCommitInfo = RemoteNextCommitInfo.Waiting(nextRemoteCommitInfo) - LocalChanges = { cm.LocalChanges with Proposed = []; Signed = cm.LocalChanges.Proposed } - RemoteChanges = { cm.RemoteChanges with ACKed = []; Signed = cm.RemoteChanges.ACKed } } - return [ WeAcceptedOperationSign (msg, nextCommitments) ] + return nextCommitments } - | RemoteNextCommitInfo.Waiting _ -> - CanNotSignBeforeRevocation |> Error - let private checkSignatureCountMismatch(sortedHTLCTXs: IHTLCTx list) (msg) = + let checkSignatureCountMismatch(sortedHTLCTXs: IHTLCTx list) (msg) = if (sortedHTLCTXs.Length <> msg.HTLCSignatures.Length) then signatureCountMismatch (sortedHTLCTXs.Length, msg.HTLCSignatures.Length) else Ok() - let receiveCommit (channelPrivKeys: ChannelPrivKeys) (msg: CommitmentSignedMsg) (n: Network) (cm: Commitments): Result = - if cm.RemoteHasChanges() |> not then - ReceivedCommitmentSignedWhenWeHaveNoPendingChanges |> Error + + +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 - let commitmentSeed = channelPrivKeys.CommitmentSeed - let localChannelKeys = cm.LocalParams.ChannelPubKeys - let remoteChannelKeys = cm.RemoteParams.ChannelPubKeys - let nextI = cm.LocalCommit.Index.NextCommitment() - result { - let! spec = cm.LocalCommit.Spec.Reduce(cm.LocalChanges.ACKed, cm.RemoteChanges.Proposed) |> expectTransactionError - let localPerCommitmentPoint = commitmentSeed.DerivePerCommitmentPoint nextI - let! (localCommitTx, htlcTimeoutTxs, htlcSuccessTxs) = - Helpers.makeLocalTXs (nextI) (cm.LocalParams) (cm.RemoteParams) (cm.FundingScriptCoin) (localPerCommitmentPoint) spec n - |> expectTransactionErrors - let signature, signedCommitTx = channelPrivKeys.SignWithFundingPrivKey localCommitTx.Value - - let sigPair = - let localSigPair = seq [(localChannelKeys.FundingPubKey.RawPubKey(), signature)] - let remoteSigPair = seq[ (remoteChannelKeys.FundingPubKey.RawPubKey(), TransactionSignature(msg.Signature.Value, SigHash.All)) ] - Seq.append localSigPair remoteSigPair - let tmp = - Transactions.checkTxFinalized signedCommitTx localCommitTx.WhichInput sigPair - |> expectTransactionError - let! finalizedCommitTx = tmp - let sortedHTLCTXs = Helpers.sortBothHTLCs htlcTimeoutTxs htlcSuccessTxs - do! checkSignatureCountMismatch sortedHTLCTXs msg - - let _localHTLCSigs, sortedHTLCTXs = - let localHtlcSigsAndHTLCTxs = - sortedHTLCTXs |> List.map(fun htlc -> - channelPrivKeys.SignHtlcTx htlc.Value localPerCommitmentPoint - ) - localHtlcSigsAndHTLCTxs |> List.map(fst), localHtlcSigsAndHTLCTxs |> List.map(snd) |> Seq.cast |> List.ofSeq - - let remoteHTLCPubKey = localPerCommitmentPoint.DeriveHtlcPubKey remoteChannelKeys.HtlcBasepoint - - let checkHTLCSig (htlc: IHTLCTx, remoteECDSASig: LNECDSASignature): Result<_, _> = - let remoteS = TransactionSignature(remoteECDSASig.Value, SigHash.All) - match htlc with - | :? HTLCTimeoutTx -> - (Transactions.checkTxFinalized (htlc.Value) (0) (seq [(remoteHTLCPubKey.RawPubKey(), remoteS)])) - |> Result.map(box) - // we cannot check that htlc-success tx are spendable because we need the payment preimage; thus we only check the remote sig - | :? HTLCSuccessTx -> - (Transactions.checkSigAndAdd (htlc) (remoteS) (remoteHTLCPubKey.RawPubKey())) - |> Result.map(box) - | _ -> failwith "Unreachable!" - - let! txList = - List.zip sortedHTLCTXs msg.HTLCSignatures - |> List.map(checkHTLCSig) - |> List.sequenceResultA - |> expectTransactionErrors - let successTxs = - txList |> List.choose(fun o -> - match o with - | :? HTLCSuccessTx as tx -> Some tx - | _ -> None - ) - let finalizedTxs = - txList |> List.choose(fun o -> - match o with - | :? FinalizedTx as tx -> Some tx - | _ -> None - ) - let localPerCommitmentSecret = - channelPrivKeys.CommitmentSeed.DerivePerCommitmentSecret cm.LocalCommit.Index - let localNextPerCommitmentPoint = - let perCommitmentSecret = - channelPrivKeys.CommitmentSeed.DerivePerCommitmentSecret - (cm.LocalCommit.Index.NextCommitment().NextCommitment()) - perCommitmentSecret.PerCommitmentPoint() - - let nextMsg = { RevokeAndACKMsg.ChannelId = cm.ChannelId - PerCommitmentSecret = localPerCommitmentSecret - NextPerCommitmentPoint = localNextPerCommitmentPoint } - - let nextCommitments = - let localCommit1 = { LocalCommit.Index = cm.LocalCommit.Index.NextCommitment() - Spec = spec - PublishableTxs = { PublishableTxs.CommitTx = finalizedCommitTx - HTLCTxs = finalizedTxs } - PendingHTLCSuccessTxs = successTxs } - let ourChanges1 = { cm.LocalChanges with ACKed = []} - let theirChanges1 = { cm.RemoteChanges with Proposed = []; ACKed = (cm.RemoteChanges.ACKed @ cm.RemoteChanges.Proposed) } - let completedOutgoingHTLCs = - let t1 = cm.LocalCommit.Spec.HTLCs - |> Map.filter(fun _ v -> v.Direction = Out) - |> Map.toSeq |> Seq.map (fun (k, _) -> k) |> Set.ofSeq - let t2 = localCommit1.Spec.HTLCs |> Map.filter(fun _ v -> v.Direction = Out) - |> Map.toSeq |> Seq.map (fun (k, _) -> k) |> Set.ofSeq - Set.difference t1 t2 - let originChannels1 = cm.OriginChannels |> Map.filter(fun k _ -> Set.contains k completedOutgoingHTLCs) - { cm with LocalCommit = localCommit1 - LocalChanges = ourChanges1 - RemoteChanges = theirChanges1 - OriginChannels = originChannels1 } - return [ WeAcceptedCommitmentSigned(nextMsg, nextCommitments) ;] - } + 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 (localChannelPrivKeys: ChannelPrivKeys) + (savedChannelState: SavedChannelState) + (transaction: Transaction) + : Option = option { + let! obscuredCommitmentNumber = + tryGetObscuredCommitmentNumber + savedChannelState.StaticChannelConfig.FundingScriptCoin.Outpoint + transaction + let localChannelPubKeys = localChannelPrivKeys.ToChannelPubKeys() + let remoteChannelPubKeys = savedChannelState.StaticChannelConfig.RemoteChannelPubKeys + let commitmentNumber = + obscuredCommitmentNumber.Unobscure + false + localChannelPubKeys.PaymentBasepoint + remoteChannelPubKeys.PaymentBasepoint + let perCommitmentSecretOpt = + savedChannelState.RemotePerCommitmentSecrets.GetPerCommitmentSecret commitmentNumber + let! perCommitmentPoint = + match perCommitmentSecretOpt with + | Some perCommitmentSecret -> Some <| perCommitmentSecret.PerCommitmentPoint() + | None -> + if savedChannelState.RemoteCommit.Index = commitmentNumber then + Some savedChannelState.RemoteCommit.RemotePerCommitmentPoint + else + None + + let localCommitmentPubKeys = + perCommitmentPoint.DeriveCommitmentPubKeys localChannelPubKeys + let remoteCommitmentPubKeys = + perCommitmentPoint.DeriveCommitmentPubKeys remoteChannelPubKeys + + let transactionBuilder = savedChannelState.StaticChannelConfig.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 + savedChannelState.StaticChannelConfig.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 (localChannelPrivKeys: ChannelPrivKeys) + (staticChannelConfig: StaticChannelConfig) + (transaction: Transaction) + : Option = option { + let! obscuredCommitmentNumber = + tryGetObscuredCommitmentNumber + staticChannelConfig.FundingScriptCoin.Outpoint + transaction + let localChannelPubKeys = localChannelPrivKeys.ToChannelPubKeys() + let remoteChannelPubKeys = staticChannelConfig.RemoteChannelPubKeys + 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 = staticChannelConfig.Network.CreateTransactionBuilder() + + let toLocalScriptPubKey = + Scripts.toLocalDelayed + remoteCommitmentPubKeys.RevocationPubKey + staticChannelConfig.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 (localChannelPrivKeys: ChannelPrivKeys) + (savedChannelState: SavedChannelState) + (transaction: Transaction) + : Option = + let attemptsSeq = seq { + yield + tryGetFundsFromRemoteCommitmentTx + localChannelPrivKeys + savedChannelState + transaction + yield + tryGetFundsFromLocalCommitmentTx + localChannelPrivKeys + savedChannelState.StaticChannelConfig + transaction + } + Seq.tryPick (fun opt -> opt) attemptsSeq + 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/DomainUtils/Types.fs b/src/DotNetLightning.Core/DomainUtils/Types.fs deleted file mode 100644 index 1b9a2d09d..000000000 --- a/src/DotNetLightning.Core/DomainUtils/Types.fs +++ /dev/null @@ -1,7 +0,0 @@ -namespace DotNetLightning.DomainUtils.Types - -type IState = interface end -type IStateData = interface end -type ICommand = interface end -type IEvent = interface end - diff --git a/src/DotNetLightning.Core/DotNetLightning.Core.fsproj b/src/DotNetLightning.Core/DotNetLightning.Core.fsproj index e7cbef336..3821bfa98 100644 --- a/src/DotNetLightning.Core/DotNetLightning.Core.fsproj +++ b/src/DotNetLightning.Core/DotNetLightning.Core.fsproj @@ -8,7 +8,7 @@ - DotNetLightning + DotNetLightning.Kiss @@ -27,6 +27,7 @@ + @@ -59,7 +60,6 @@ - @@ -68,6 +68,7 @@ + diff --git a/src/DotNetLightning.Core/Routing/Router.fs b/src/DotNetLightning.Core/Routing/Router.fs index 088b0b9c5..ce0f5d8da 100644 --- a/src/DotNetLightning.Core/Routing/Router.fs +++ b/src/DotNetLightning.Core/Routing/Router.fs @@ -136,8 +136,6 @@ module Routing = // ----- ------ let executeCommand (state: RouterState) (cmd: RouterCommand) = match state, cmd with - | RouterState.Normal _d, (ChannelEvent(_)) -> - failwith "Not implemented: Routing::executeCommand when state,cmd = Normal,ChannelEvent" | RouterState.Normal _d, (NetworkEvent (ChannelUpdateReceived (_update))) -> failwith "Not implemented: Routing::executeCommand when state,cmd = Normal,NetworkEvent ChannelUpdateRecv" | RouterState.Normal d, (NetworkCommand (CalculateRoute (routeRequest))) -> diff --git a/src/DotNetLightning.Core/Routing/RouterTypes.fs b/src/DotNetLightning.Core/Routing/RouterTypes.fs index e9178cbf6..5d0dbe88c 100644 --- a/src/DotNetLightning.Core/Routing/RouterTypes.fs +++ b/src/DotNetLightning.Core/Routing/RouterTypes.fs @@ -34,7 +34,6 @@ type RouterError = module internal RouterError = let routeFindingError msg = RouteFindingError msg |> Error type RouterCommand = - | ChannelEvent of ChannelEvent | NetworkEvent of NetworkEvent | NetworkCommand of NetworkCommand diff --git a/src/DotNetLightning.Core/Serialization/LightningStream.fs b/src/DotNetLightning.Core/Serialization/LightningStream.fs index c33763e97..55d3a8360 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() @@ -58,10 +61,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) @@ -324,9 +323,13 @@ type LightningReaderStream(inner: Stream) = let len = this.ReadUInt16(false) this.ReadBytes(int32 len) + member this.ReadChannelFlags(): ChannelFlags = + let flags = this.ReadUInt8() + ChannelFlags.FromUInt8 flags + 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 @@ -420,4 +423,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 6ef781222..48c02afed 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 = @@ -512,8 +519,8 @@ type OpenChannelMsg = { mutable DelayedPaymentBasepoint: DelayedPaymentBasepoint mutable HTLCBasepoint: HtlcBasepoint mutable FirstPerCommitmentPoint: PerCommitmentPoint - mutable ChannelFlags: uint8 - mutable ShutdownScriptPubKey: OptionalField