Skip to content

Instantly share code, notes, and snippets.

@knocte
Created June 9, 2020 06:31
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save knocte/d30ca67e3b7fc32def1a85dbf43564d4 to your computer and use it in GitHub Desktop.
Save knocte/d30ca67e3b7fc32def1a85dbf43564d4 to your computer and use it in GitHub Desktop.
From 3caa604be0918f8b264984451348735f30faab2b Mon Sep 17 00:00:00 2001
From: "Andres G. Aragoneses" <knocte@gmail.com>
Date: Mon, 8 Jun 2020 00:50:05 +0800
Subject: [PATCH 1/3] Backend,Frontend.Console: improve B's API so that F.C.'s
doesn't need refs
Improve the API design of GWallet.Backend project so that
GWallet.Frontend.Console doesn't need to reference directly
some sub-dependencies of GWallet.Backend, such as NBitcoin
and DotNetLightning.
---
src/GWallet.Backend/GWallet.Backend.fsproj | 1 +
src/GWallet.Backend/PublicKey.fs | 11 +
.../UtxoCoin/Lightning/Lightning.fs | 265 +++++++++++++-----
.../UtxoCoin/Lightning/SerializedChannel.fs | 21 +-
.../UtxoCoin/UtxoCoinAccount.fs | 12 +-
.../GWallet.Frontend.Console.fsproj | 12 -
src/GWallet.Frontend.Console/Program.fs | 76 ++---
.../UserInteraction.fs | 16 +-
src/GWallet.Frontend.Console/packages.config | 2 -
9 files changed, 256 insertions(+), 160 deletions(-)
create mode 100644 src/GWallet.Backend/PublicKey.fs
diff --git a/src/GWallet.Backend/GWallet.Backend.fsproj b/src/GWallet.Backend/GWallet.Backend.fsproj
index 678a79fc..c841a96c 100644
--- a/src/GWallet.Backend/GWallet.Backend.fsproj
+++ b/src/GWallet.Backend/GWallet.Backend.fsproj
@@ -61,6 +61,7 @@ <Project ToolsVersion="14.0" DefaultTargets="Build" xmlns="http://schemas.mic
<Compile Include="Config.fs" />
<Compile Include="Networking.fs" />
<Compile Include="JsonRpcTcpClient.fs" />
+ <Compile Include="PublicKey.fs" />
<Compile Include="IBlockchainFeeInfo.fs" />
<Compile Include="TransferAmount.fs" />
<Compile Include="Infrastructure.fs" />
diff --git a/src/GWallet.Backend/PublicKey.fs b/src/GWallet.Backend/PublicKey.fs
new file mode 100644
index 00000000..74842efa
--- /dev/null
+++ b/src/GWallet.Backend/PublicKey.fs
@@ -0,0 +1,11 @@
+
+namespace GWallet.Backend
+
+type PublicKey(pubKey: string, currency: Currency) =
+ do
+ if currency = Currency.BTC || currency = Currency.LTC then
+ NBitcoin.PubKey pubKey |> ignore
+
+ override __.ToString() =
+ pubKey
+
diff --git a/src/GWallet.Backend/UtxoCoin/Lightning/Lightning.fs b/src/GWallet.Backend/UtxoCoin/Lightning/Lightning.fs
index ee1a23ee..5f4d2785 100644
--- a/src/GWallet.Backend/UtxoCoin/Lightning/Lightning.fs
+++ b/src/GWallet.Backend/UtxoCoin/Lightning/Lightning.fs
@@ -25,6 +25,45 @@ open FSharp.Core
module Lightning =
+
+ type Connection =
+ {
+ Client: TcpClient
+
+ // ideally we would mark the members below as 'internal' only, but: https://stackoverflow.com/q/62274013/544947
+ Init: Init
+ Peer: Peer
+ }
+
+ type IChannelToBeOpened =
+ abstract member ConfirmationsRequired: uint32 with get
+
+ type FullChannel =
+ {
+ // ideally we would mark all the members as 'internal' only, but: https://stackoverflow.com/q/62274013/544947
+ InboundChannel: AcceptChannel
+ OutboundChannel: Channel
+ Peer: Peer
+ }
+ interface IChannelToBeOpened with
+ member self.ConfirmationsRequired
+ with get(): uint32 =
+ self.InboundChannel.MinimumDepth.Value
+
+ type PotentialChannel internal (channelKeysSeed, temporaryChannelId) =
+ member val internal KeysSeed = channelKeysSeed with get
+ member val internal TemporaryId = temporaryChannelId with get
+
+ type ChannelCreationDetails =
+ {
+ Client: TcpClient
+ Password: Ref<string>
+
+ // ideally we would mark the members below as 'internal' only, but: https://stackoverflow.com/q/62274013/544947
+ ChannelInfo: PotentialChannel
+ FullChannel: FullChannel
+ }
+
let private hex = DataEncoders.HexEncoder()
let GetIndexOfDestinationInOutputSeq (dest: IDestination) (outputs: seq<IndexedTxOut>): TxOutIndex =
@@ -56,7 +95,7 @@ module Lightning =
During: string
}
- type LNError =
+ type internal LNInternalError =
| DNLError of ErrorMessage
| ConnectError of seq<SocketException>
| StringError of StringErrorInner
@@ -94,10 +133,13 @@ module Lightning =
else
"Error: peer disconnected for unknown reason"
+ type LNError internal (error: LNInternalError) =
+ member val internal Inner = error with get
+ member val Message = error.Message with get
let internal ReadExactAsync (stream: NetworkStream)
(numberBytesToRead: int)
- : Async<Result<array<byte>, LNError>> =
+ : Async<Result<array<byte>, LNInternalError>> =
let buf: array<byte> = Array.zeroCreate numberBytesToRead
let rec read buf totalBytesRead = async {
let! bytesRead =
@@ -117,7 +159,7 @@ module Lightning =
}
read buf 0
- let ReadAsync (keyRepo: DefaultKeyRepository) (peer: Peer) (stream: NetworkStream): Async<Result<PeerCommand, LNError>> =
+ let internal ReadAsync (keyRepo: DefaultKeyRepository) (peer: Peer) (stream: NetworkStream): Async<Result<PeerCommand, LNInternalError>> =
match peer.ChannelEncryptor.GetNoiseStep() with
| ActTwo ->
async {
@@ -216,8 +258,12 @@ module Lightning =
let channelConfig = CreateChannelConfig account
Channel.CreateCurried channelConfig
- type ChannelEnvironment = { Account: UtxoCoin.NormalUtxoAccount; NodeIdForResponder: NodeId; KeyRepo: DefaultKeyRepository }
- type Connection = { Init: Init; Peer: Peer; Client: TcpClient }
+ type ChannelEnvironment internal (account: UtxoCoin.NormalUtxoAccount,
+ nodeIdForResponder: NodeId,
+ keyRepo: DefaultKeyRepository) =
+ member val internal Account = account with get
+ member val internal NodeIdForResponder = nodeIdForResponder with get
+ member val internal KeyRepo = keyRepo with get
let internal Send (msg: ILightningMsg) (peer: Peer) (stream: NetworkStream): Async<Peer> =
async {
@@ -242,10 +288,12 @@ module Lightning =
context
Infrastructure.ReportWarningMessage msg
- let ConnectAndHandshake ({ Account = _; NodeIdForResponder = nodeIdForResponder; KeyRepo = keyRepo }: ChannelEnvironment)
+ let ConnectAndHandshake (channelEnv: ChannelEnvironment)
(channelCounterpartyIP: IPEndPoint)
: Async<Result<Connection, LNError>> =
async {
+ let nodeIdForResponder = channelEnv.NodeIdForResponder
+ let keyRepo = channelEnv.KeyRepo
let responderId = channelCounterpartyIP :> EndPoint |> PeerId
let initialPeer = Peer.CreateOutbound(responderId, nodeIdForResponder, keyRepo.NodeSecret.PrivateKey)
let act1, peerEncryptor = PeerChannelEncryptor.getActOne initialPeer.ChannelEncryptor
@@ -264,7 +312,7 @@ module Lightning =
return Error <| ConnectError socketExceptions
}
match connectRes with
- | Error err -> return Error err
+ | Error err -> return Error <| LNError err
| Ok () ->
let stream = client.GetStream()
do! stream.WriteAsync(act1, 0, act1.Length) |> Async.AwaitTask
@@ -275,8 +323,8 @@ module Lightning =
match act2Res with
| Error (PeerDisconnected abruptly) ->
ReportDisconnection channelCounterpartyIP nodeIdForResponder abruptly "receiving act 2"
- return Error (PeerDisconnected abruptly)
- | Error err -> return Error err
+ return Error <| LNError (PeerDisconnected abruptly)
+ | Error err -> return Error <| LNError err
| Ok act2 ->
let actThree, receivedAct2Peer =
match Peer.executeCommand sentAct1Peer act2 with
@@ -300,21 +348,22 @@ module Lightning =
| Error (PeerDisconnected abruptly) ->
let context = SPrintF1 "receiving init message while connecting, our init: %A" plainInit
ReportDisconnection channelCounterpartyIP nodeIdForResponder abruptly context
- return Error (PeerDisconnected abruptly)
- | Error err -> return Error err
+ return Error <| LNError (PeerDisconnected abruptly)
+ | Error err -> return Error <| LNError err
| Ok init ->
return
match Peer.executeCommand sentInitPeer init with
| Ok (ReceivedInit (newInit, _) as evt::[]) ->
let peer = Peer.applyEvent sentInitPeer evt
- Ok { Init = newInit; Peer = peer; Client = client }
+ Ok <| { Init = newInit; Peer = peer; Client = client }
| Ok _ ->
failwith "not one good ReceivedInit event"
| Error peerError ->
failwith <| SPrintF1 "couldn't parse init: %s" peerError.Message
}
- let rec ReadUntilChannelMessage (keyRepo: DefaultKeyRepository, peer: Peer, stream: NetworkStream): Async<Result<Peer * IChannelMsg, LNError>> =
+ let rec internal ReadUntilChannelMessage (keyRepo: DefaultKeyRepository, peer: Peer, stream: NetworkStream)
+ : Async<Result<Peer * IChannelMsg, LNInternalError>> =
async {
let! channelMsgRes = ReadAsync keyRepo peer stream
match channelMsgRes with
@@ -356,14 +405,18 @@ module Lightning =
DefaultFinalScriptPubKey = account |> CreatePayoutScript
}
- let GetAcceptChannel ({ Account = account; NodeIdForResponder = nodeIdForResponder; KeyRepo = keyRepo }: ChannelEnvironment)
- ({ Init = receivedInit; Peer = receivedInitPeer; Client = client }: Connection)
+ let GetAcceptChannel (potentialChannel: PotentialChannel)
+ (channelEnv: ChannelEnvironment)
+ (connection: Connection)
(channelCapacity: TransferAmount)
(metadata: TransactionMetadata)
(password: unit -> string)
(balance: decimal)
- (temporaryChannelId: ChannelId)
- : Async<Result<AcceptChannel * Channel * Peer, LNError>> =
+ : Async<Result<FullChannel, LNError>> =
+ let client = connection.Client
+ let receivedInit = connection.Init
+ let receivedInitPeer = connection.Peer
+ let account = channelEnv.Account
let fundingTxProvider (dest: IDestination, amount: Money, _: FeeRatePerKw) =
let transferAmount = TransferAmount (amount.ToDecimal MoneyUnit.BTC, balance, Currency.BTC)
Debug.Assert (
@@ -378,6 +431,8 @@ module Lightning =
(fundingTransaction |> FinalizedTx, GetIndexOfDestinationInOutputSeq dest outputs) |> Ok
let fundingAmount = Money (channelCapacity.ValueToSend, MoneyUnit.BTC)
+ let nodeIdForResponder = channelEnv.NodeIdForResponder
+ let keyRepo = channelEnv.KeyRepo
let channelKeys, localParams = GetLocalParams true fundingAmount nodeIdForResponder account keyRepo
async {
@@ -386,7 +441,7 @@ module Lightning =
let initFunder =
{
InputInitFunder.PushMSat = LNMoney.MilliSatoshis 0L
- TemporaryChannelId = temporaryChannelId
+ TemporaryChannelId = potentialChannel.TemporaryId
FundingSatoshis = fundingAmount
InitFeeRatePerKw = feeEstimator.GetEstSatPer1000Weight <| ConfirmationTarget.Normal
FundingTxFeeRatePerKw = feeEstimator.GetEstSatPer1000Weight <| ConfirmationTarget.Normal
@@ -420,29 +475,33 @@ module Lightning =
| Error (PeerDisconnected abruptly) ->
let context = SPrintF1 "receiving accept_channel, our open_channel == %A" openChanMsg
ReportDisconnection channelCounterpartyIP nodeIdForResponder abruptly context
- return Error (PeerDisconnected abruptly)
- | Error errorMsg -> return Error errorMsg
+ return Error <| LNError (PeerDisconnected abruptly)
+ | Error err -> return Error <| LNError err
| Ok (receivedOpenChanReplyPeer, chanMsg) ->
match chanMsg with
| :? AcceptChannel as acceptChannel ->
- return Ok (acceptChannel, sentOpenChan, receivedOpenChanReplyPeer)
+ return Ok ({
+ InboundChannel = acceptChannel
+ OutboundChannel = sentOpenChan
+ Peer = receivedOpenChanReplyPeer
+ })
| _ ->
- return Error <| StringError {
+ return Error <| LNError (StringError {
Msg = SPrintF1 "channel message is not accept channel: %s" (chanMsg.GetType().Name)
During = "waiting for accept_channel"
- }
+ })
| Ok evtList ->
return failwith <| SPrintF1 "event was not a single NewOutboundChannelStarted, it was: %A" evtList
| Error channelError ->
- return Error <| DNLChannelError channelError
+ return Error <| LNError (DNLChannelError channelError)
}
- let ContinueFromAcceptChannel (keyRepo: DefaultKeyRepository)
- (acceptChannel: AcceptChannel)
- (sentOpenChan: Channel)
- (stream: NetworkStream)
- (receivedOpenChanReplyPeer: Peer)
- : Async<Result<string * Channel, LNError>> =
+ let internal ContinueFromAcceptChannel (keyRepo: DefaultKeyRepository)
+ (acceptChannel: AcceptChannel)
+ (sentOpenChan: Channel)
+ (stream: NetworkStream)
+ (receivedOpenChanReplyPeer: Peer)
+ : Async<Result<string * Channel, LNInternalError>> =
async {
match Channel.executeCommand sentOpenChan (ApplyAcceptChannel acceptChannel) with
| Ok (ChannelEvent.WeAcceptedAcceptChannel(fundingCreated, _) as evt::[]) ->
@@ -458,9 +517,9 @@ module Lightning =
| Error (PeerDisconnected abruptly) ->
let context = SPrintF1 "receiving funding_created, their accept_channel == %A" acceptChannel
ReportDisconnection channelCounterpartyIP nodeIdForResponder abruptly context
- return Error (PeerDisconnected abruptly)
- | Error errorMsg ->
- return Error errorMsg
+ return Error <| PeerDisconnected abruptly
+ | Error error ->
+ return Error error
| Ok (_, chanMsg) ->
match chanMsg with
| :? FundingSigned as fundingSigned ->
@@ -503,7 +562,7 @@ module Lightning =
return Error <| StringError innerError
}
- let GetSeedAndRepo (random: Random): uint256 * DefaultKeyRepository * ChannelId =
+ let private GetSeedAndRepo (random: Random): uint256 * DefaultKeyRepository * ChannelId =
let channelKeysSeedBytes = Array.zeroCreate 32
random.NextBytes channelKeysSeedBytes
let channelKeysSeed = uint256 channelKeysSeedBytes
@@ -516,6 +575,12 @@ module Lightning =
|> ChannelId
channelKeysSeed, keyRepo, temporaryChannelId
+ let GenerateNewPotentialChannelDetails account (channelCounterpartyPubKey: PublicKey) (random: Random) =
+ let channelKeysSeed, keyRepo, temporaryChannelId = GetSeedAndRepo random
+ let pubKey = NBitcoin.PubKey (channelCounterpartyPubKey.ToString())
+ let channelEnv = ChannelEnvironment(account, NodeId pubKey, keyRepo)
+ PotentialChannel(channelKeysSeed, temporaryChannelId), channelEnv
+
let GetNewChannelFilename(): string =
SerializedChannel.ChannelFilePrefix
// this offset is the approximate time this feature was added (making filenames shorter)
@@ -523,21 +588,28 @@ module Lightning =
+ SerializedChannel.ChannelFileEnding
let ContinueFromAcceptChannelAndSave (account: UtxoCoin.NormalUtxoAccount)
- (channelKeysSeed: uint256)
(channelCounterpartyIP: IPEndPoint)
- (acceptChannel: AcceptChannel)
- (chan: Channel)
- (stream: NetworkStream)
- (peer: Peer)
+ (channelDetails: ChannelCreationDetails)
: Async<Result<string, LNError>> = // TxId of Funding Transaction is returned
async {
- let keyRepo = SerializedChannel.UIntToKeyRepo channelKeysSeed
- let! res = ContinueFromAcceptChannel keyRepo acceptChannel chan stream peer
+ let stream = channelDetails.Client.GetStream()
+ let keyRepo = SerializedChannel.UIntToKeyRepo channelDetails.ChannelInfo.KeysSeed
+ let! res =
+ ContinueFromAcceptChannel keyRepo
+ channelDetails.FullChannel.InboundChannel
+ channelDetails.FullChannel.OutboundChannel
+ stream
+ channelDetails.FullChannel.Peer
match res with
- | Error errorMsg -> return Error errorMsg
+ | Error error -> return Error <| LNError error
| Ok (fundingTxId, receivedFundingSignedChan) ->
let fileName = GetNewChannelFilename()
- SerializedChannel.Save account receivedFundingSignedChan channelKeysSeed channelCounterpartyIP acceptChannel.MinimumDepth fileName
+ SerializedChannel.Save account
+ receivedFundingSignedChan
+ channelDetails.ChannelInfo.KeysSeed
+ channelCounterpartyIP
+ channelDetails.FullChannel.InboundChannel.MinimumDepth
+ fileName
Infrastructure.LogDebug <| SPrintF1 "Channel saved to %s" fileName
return Ok fundingTxId
}
@@ -568,8 +640,8 @@ module Lightning =
}
type NotReadyReason = {
- CurrentConfirmations: BlockHeightOffset32
- NeededConfirmations: BlockHeightOffset32
+ CurrentConfirmations: uint32
+ NeededConfirmations: uint32
}
type internal ChannelMessageOrDeepEnough =
@@ -601,8 +673,8 @@ module Lightning =
}
else
NotReady {
- CurrentConfirmations = details.ConfirmationsCount
- NeededConfirmations = details.SerializedChannel.MinSafeDepth
+ CurrentConfirmations = details.ConfirmationsCount.Value
+ NeededConfirmations = details.SerializedChannel.MinSafeDepth.Value
}
let internal GetFundingLockedMsg (channel: Channel) (channelCommand: ChannelCommand): Channel * FundingLocked =
@@ -699,7 +771,7 @@ module Lightning =
let! channelCommand = channelCommandAction
let keyRepo = SerializedChannel.UIntToKeyRepo channelKeysSeed
let channelEnvironment: ChannelEnvironment =
- { Account = details.Account; NodeIdForResponder = notReestablishedChannel.RemoteNodeId; KeyRepo = keyRepo }
+ ChannelEnvironment(details.Account, notReestablishedChannel.RemoteNodeId, keyRepo)
let! connectionRes = ConnectAndHandshake channelEnvironment channelCounterpartyIP
match connectionRes with
| Error err -> return Error err
@@ -726,8 +798,8 @@ module Lightning =
match msgRes with
| Error (PeerDisconnected abruptly) ->
ReportDisconnection channelCounterpartyIP nodeIdForResponder abruptly "receiving channel_reestablish or funding_locked"
- return Error (PeerDisconnected abruptly)
- | Error errorMsg -> return Error errorMsg
+ return Error <| LNError (PeerDisconnected abruptly)
+ | Error error -> return Error <| LNError error
| Ok (receivedChannelReestablishPeer, chanMsg) ->
let! fundingLockedRes =
match chanMsg with
@@ -762,7 +834,7 @@ module Lightning =
return Error <| StringError { Msg = msg; During = "reception of reply to channel_reestablish" }
}
match fundingLockedRes with
- | Error errorMsg -> return Error errorMsg
+ | Error error -> return Error <| LNError error
| Ok fundingLocked ->
match Channel.executeCommand channelWithFundingLockedSent (ApplyFundingLocked fundingLocked) with
| Ok ((ChannelEvent.BothFundingLocked _) as evt::[]) ->
@@ -776,13 +848,19 @@ module Lightning =
connection.Client.Dispose()
return Ok (UsableChannel txIdHex)
| Error channelError ->
- return Error <| DNLChannelError channelError
+ return Error <| LNError (DNLChannelError channelError)
| Ok (evt::[]) ->
let msg = SPrintF1 "expected event BothFundingLocked, is %s" (evt.GetType().Name)
- return Error <| StringError { Msg = msg; During = "application of funding_locked" }
+ return Error <| LNError (StringError {
+ Msg = msg
+ During = "application of funding_locked"
+ })
| Ok _ ->
let msg = "expected only one event"
- return Error <| StringError { Msg = msg; During = "application of funding_locked" }
+ return Error <| LNError (StringError {
+ Msg = msg
+ During = "application of funding_locked"
+ })
}
let AcceptTheirChannel (random: Random)
@@ -805,24 +883,29 @@ module Lightning =
let! act1Res = ReadExactAsync stream bolt08ActOneLength
match act1Res with
- | Error err -> return Error err
+ | Error err -> return Error <| LNError err
| Ok act1 ->
let act1Result = PeerChannelEncryptor.processActOneWithKey act1 ourNodeSecret initialPeer.ChannelEncryptor
match act1Result with
| Error err ->
- return Error <| StringError { Msg = SPrintF1 "error from DNL: %A" err; During = "processing of their act1" }
+ return Error <| LNError(StringError {
+ Msg = SPrintF1 "error from DNL: %A" err
+ During = "processing of their act1"
+ })
| Ok (act2, pce) ->
let act2, peerWithSentAct2 =
act2, { initialPeer with ChannelEncryptor = pce }
do! stream.WriteAsync(act2, 0, act2.Length) |> Async.AwaitTask
let! act3Res = ReadExactAsync stream bolt08ActThreeLength
match act3Res with
- | Error err -> return Error err
+ | Error err -> return Error <| LNError err
| Ok act3 ->
let act3Result = PeerChannelEncryptor.processActThree act3 peerWithSentAct2.ChannelEncryptor
match act3Result with
| Error err ->
- return Error <| StringError { Msg = SPrintF1 "error from DNL: %A" err; During = "processing of their act3" }
+ return Error <| LNError(StringError {
+ Msg = SPrintF1 "error from DNL: %A" err; During = "processing of their act3"
+ })
| Ok (remoteNodeId, pce) ->
let receivedAct3Peer = { peerWithSentAct2 with ChannelEncryptor = pce }
Infrastructure.LogDebug "Receiving init..."
@@ -830,24 +913,26 @@ module Lightning =
match initRes with
| Error (PeerDisconnected abruptly) ->
ReportDisconnection channelCounterpartyIP remoteNodeId abruptly "receiving init message while accepting"
- return Error (PeerDisconnected abruptly)
- | Error err -> return Error err
+ return Error <| LNError (PeerDisconnected abruptly)
+ | Error err -> return Error <| LNError err
| Ok init ->
match Peer.executeCommand receivedAct3Peer init with
| Error peerError ->
- return Error <| StringError { Msg = SPrintF1 "couldn't parse init: %s" peerError.Message; During = "receiving init" }
+ return Error <| LNError(StringError {
+ Msg = SPrintF1 "couldn't parse init: %s" peerError.Message
+ During = "receiving init"
+ })
| Ok (ReceivedInit (newInit, _) as evt::[]) ->
let peer = Peer.applyEvent receivedAct3Peer evt
- let connection: Connection =
- { Init = newInit; Peer = peer; Client = client }
+ let connection = { Init = newInit; Peer = peer; Client = client }
let! sentInitPeer = Send plainInit connection.Peer stream
Infrastructure.LogDebug "Receiving open_channel..."
let! msgRes = ReadUntilChannelMessage (keyRepo, sentInitPeer, stream)
match msgRes with
| Error (PeerDisconnected abruptly) ->
ReportDisconnection channelCounterpartyIP remoteNodeId abruptly "receiving open_channel"
- return Error (PeerDisconnected abruptly)
- | Error errorMsg -> return Error errorMsg
+ return Error <| LNError (PeerDisconnected abruptly)
+ | Error error -> return Error <| LNError error
| Ok (receivedOpenChanPeer, chanMsg) ->
match chanMsg with
| :? OpenChannel as openChannel ->
@@ -895,8 +980,8 @@ module Lightning =
| Error (PeerDisconnected abruptly) ->
let context = SPrintF2 "receiving funding_created, their open_channel == %A, our accept_channel == %A" openChannel acceptChannel
ReportDisconnection channelCounterpartyIP remoteNodeId abruptly context
- return Error (PeerDisconnected abruptly)
- | Error errorMsg -> return Error errorMsg
+ return Error <| LNError (PeerDisconnected abruptly)
+ | Error error -> return Error <| LNError error
| Ok (receivedFundingCreatedPeer, chanMsg) ->
match chanMsg with
| :? FundingCreated as fundingCreated ->
@@ -918,25 +1003,51 @@ module Lightning =
return Ok ()
| Ok evtList ->
- return Error <| StringError { Msg = SPrintF1 "event was not a single WeAcceptedFundingCreated, it was: %A" evtList; During = "application of their funding_created message" }
+ return Error <| LNError (StringError {
+ Msg = SPrintF1 "event was not a single WeAcceptedFundingCreated, it was: %A" evtList
+ During = "application of their funding_created message"
+ })
| Error channelError ->
- return Error <| StringError { Msg = SPrintF1 "could not apply funding_created: %s" channelError.Message; During = "application of their funding_created message" }
+ return Error <| LNError (StringError {
+ Msg = SPrintF1 "could not apply funding_created: %s" channelError.Message
+ During = "application of their funding_created message"
+ })
| _ ->
- return Error <| StringError { Msg = SPrintF1 "channel message is not funding_created: %s" (chanMsg.GetType().Name); During = "reception of answer to accept_channel" }
+ return Error <| LNError (StringError {
+ Msg = SPrintF1 "channel message is not funding_created: %s" (chanMsg.GetType().Name)
+ During = "reception of answer to accept_channel"
+ })
| Ok evtList ->
- return Error <| StringError { Msg = SPrintF1 "event list was not a single WeAcceptedOpenChannel, it was: %A" evtList; During = "generation of an accept_channel message" }
+ return Error <| LNError (StringError {
+ Msg = SPrintF1 "event list was not a single WeAcceptedOpenChannel, it was: %A" evtList
+ During = "generation of an accept_channel message"
+ })
| Error err ->
- return Error <| StringError { Msg = SPrintF1 "error from DNL: %A" err; During = "generation of an accept_channel message" }
+ return Error <| LNError (StringError {
+ Msg = SPrintF1 "error from DNL: %A" err
+ During = "generation of an accept_channel message"
+ })
| Ok evtList ->
- return Error <| StringError { Msg = SPrintF1 "event was not a single NewInboundChannelStarted, it was: %A" evtList; During = "execution of CreateChannel command" }
+ return Error <| LNError (StringError {
+ Msg = SPrintF1 "event was not a single NewInboundChannelStarted, it was: %A" evtList
+ During = "execution of CreateChannel command"
+ })
| Error channelError ->
- return Error <| StringError { Msg = SPrintF1 "could not execute channel command: %s" channelError.Message; During = "execution of CreateChannel command" }
+ return Error <| LNError (StringError {
+ Msg = SPrintF1 "could not execute channel command: %s" channelError.Message
+ During = "execution of CreateChannel command"
+ })
| _ ->
- return Error <| StringError { Msg = SPrintF1 "channel message is not open_channel: %s" (chanMsg.GetType().Name); During = "reception of open_channel" }
+ return Error <| LNError (StringError {
+ Msg = SPrintF1 "channel message is not open_channel: %s" (chanMsg.GetType().Name)
+ During = "reception of open_channel"
+ })
| Ok _ ->
- return Error <| StringError { Msg = "not one good ReceivedInit event"; During = "reception of init message" }
-
+ return Error <| LNError(StringError {
+ Msg = "not one good ReceivedInit event"
+ During = "reception of init message"
+ })
}
diff --git a/src/GWallet.Backend/UtxoCoin/Lightning/SerializedChannel.fs b/src/GWallet.Backend/UtxoCoin/Lightning/SerializedChannel.fs
index a5c33a44..7de57e89 100644
--- a/src/GWallet.Backend/UtxoCoin/Lightning/SerializedChannel.fs
+++ b/src/GWallet.Backend/UtxoCoin/Lightning/SerializedChannel.fs
@@ -144,15 +144,6 @@ type SerializedChannel = {
settings.Converters.Add commitmentsConverter
settings
- static member ListSavedChannels (): seq<string * int> =
- if SerializedChannel.LightningDir.Exists then
- let files =
- Directory.GetFiles
- ((SerializedChannel.LightningDir.ToString()), SerializedChannel.ChannelFilePrefix + "*" + SerializedChannel.ChannelFileEnding)
- files |> Seq.choose SerializedChannel.ExtractChannelNumber
- else
- Seq.empty
-
member this.SaveSerializedChannel (fileName: string) =
let json = Marshalling.SerializeCustom(this, SerializedChannel.LightningSerializerSettings)
let filePath = Path.Combine (SerializedChannel.LightningDir.FullName, fileName)
@@ -197,3 +188,15 @@ type SerializedChannel = {
(NodeId this.RemoteNodeId)
with State = this.ChanState
}
+
+module ChannelManager =
+
+ let ListSavedChannels (): seq<string * int> =
+ if SerializedChannel.LightningDir.Exists then
+ let files =
+ Directory.GetFiles
+ ((SerializedChannel.LightningDir.ToString()),
+ SerializedChannel.ChannelFilePrefix + "*" + SerializedChannel.ChannelFileEnding)
+ files |> Seq.choose SerializedChannel.ExtractChannelNumber
+ else
+ Seq.empty
diff --git a/src/GWallet.Backend/UtxoCoin/UtxoCoinAccount.fs b/src/GWallet.Backend/UtxoCoin/UtxoCoinAccount.fs
index 0561906f..2385fcd0 100644
--- a/src/GWallet.Backend/UtxoCoin/UtxoCoinAccount.fs
+++ b/src/GWallet.Backend/UtxoCoin/UtxoCoinAccount.fs
@@ -229,8 +229,8 @@ module Account =
return! EstimateFees newTxBuilder feeRate account newInputs tail
}
- let EstimateFeeForDestination (account: IUtxoAccount) (amount: TransferAmount) (destination: IDestination)
- : Async<TransactionMetadata> = async {
+ let private EstimateFeeForDestination (account: IUtxoAccount) (amount: TransferAmount) (destination: IDestination)
+ : Async<TransactionMetadata> = async {
let rec addInputsUntilAmount (utxos: List<UnspentTransactionOutputInfo>)
soFarInSatoshis
amount
@@ -322,6 +322,14 @@ module Account =
return raise <| InsufficientBalanceForFee None
}
+ // move to lightning module?
+ let EstimateChannelOpeningFee (account: IUtxoAccount) (amount: TransferAmount) =
+ let witScriptIdLength = 32
+ // this dummy address is only used for fee estimation
+ let nullScriptId = NBitcoin.WitScriptId (Array.zeroCreate witScriptIdLength)
+ // TODO: pass currency argument in order to support Litecoin+Lightning here
+ let dummyAddr = NBitcoin.BitcoinWitScriptAddress (nullScriptId, Config.BitcoinNet)
+ EstimateFeeForDestination account amount dummyAddr
let EstimateFee (account: IUtxoAccount) (amount: TransferAmount) (destination: string)
: Async<TransactionMetadata> =
diff --git a/src/GWallet.Frontend.Console/GWallet.Frontend.Console.fsproj b/src/GWallet.Frontend.Console/GWallet.Frontend.Console.fsproj
index c3a18875..a23bfa58 100644
--- a/src/GWallet.Frontend.Console/GWallet.Frontend.Console.fsproj
+++ b/src/GWallet.Frontend.Console/GWallet.Frontend.Console.fsproj
@@ -94,9 +94,6 @@ <?xml version="1.0" encoding="utf-8"?>
<Reference Include="Microsoft.Extensions.Logging.Abstractions">
<HintPath>..\..\packages\Microsoft.Extensions.Logging.Abstractions.1.0.0\lib\netstandard1.1\Microsoft.Extensions.Logging.Abstractions.dll</HintPath>
</Reference>
- <Reference Include="NBitcoin">
- <HintPath>..\..\packages\NBitcoin.5.0.13\lib\net461\NBitcoin.dll</HintPath>
- </Reference>
<Reference Include="System.Net.Http" />
<Reference Include="BouncyCastle.Crypto">
<HintPath>..\..\packages\Portable.BouncyCastle.1.8.6\lib\net40\BouncyCastle.Crypto.dll</HintPath>
@@ -104,15 +101,6 @@ <?xml version="1.0" encoding="utf-8"?>
<Reference Include="System.Numerics.Vectors">
<HintPath>..\..\packages\System.Numerics.Vectors.4.4.0\lib\net46\System.Numerics.Vectors.dll</HintPath>
</Reference>
- <Reference Include="DotNetLightning.Core">
- <HintPath>..\..\packages\DotNetLightning.1.1.2-date20200527-1047-git-42c7cb9\lib\netstandard2.0\DotNetLightning.Core.dll</HintPath>
- </Reference>
- <Reference Include="InternalBech32Encoder">
- <HintPath>..\..\packages\DotNetLightning.1.1.2-date20200527-1047-git-42c7cb9\lib\netstandard2.0\InternalBech32Encoder.dll</HintPath>
- </Reference>
- <Reference Include="ResultUtils">
- <HintPath>..\..\packages\DotNetLightning.1.1.2-date20200527-1047-git-42c7cb9\lib\netstandard2.0\ResultUtils.dll</HintPath>
- </Reference>
<Reference Include="System.Runtime">
<HintPath>..\..\packages\System.Runtime.4.1.0\lib\net462\System.Runtime.dll</HintPath>
</Reference>
diff --git a/src/GWallet.Frontend.Console/Program.fs b/src/GWallet.Frontend.Console/Program.fs
index ffac8ff2..98fd7ad6 100644
--- a/src/GWallet.Frontend.Console/Program.fs
+++ b/src/GWallet.Frontend.Console/Program.fs
@@ -6,11 +6,11 @@ open System.Text.RegularExpressions
open System.Text
open FSharp.Core
-open NBitcoin
open GWallet.Backend
open GWallet.Backend.FSharpUtil
open GWallet.Backend.UtxoCoin.Lightning
+open GWallet.Backend.UtxoCoin.Lightning.Lightning
open GWallet.Frontend.Console
let random = Org.BouncyCastle.Security.SecureRandom () :> Random
@@ -292,44 +292,25 @@ let WalletOptions(): unit =
WipeWallet()
| _ -> ()
-type ChannelCreationDetails =
- {
- Seed: NBitcoin.uint256
- AcceptChannel: DotNetLightning.Serialize.Msgs.AcceptChannel
- Channel: DotNetLightning.Channel.Channel
- Connection: Lightning.Connection
- Password: Ref<string>
- }
-
let AskChannelFee (account: UtxoCoin.NormalUtxoAccount)
(channelCapacity: TransferAmount)
(balance: decimal)
(channelCounterpartyIP: System.Net.IPEndPoint)
- (channelCounterpartyPubKey: NBitcoin.PubKey)
+ (channelCounterpartyPubKey: PublicKey)
: Async<Option<ChannelCreationDetails>> =
- let witScriptIdLength = 32
- // this dummy address is only used for fee estimation
- let nullScriptId = NBitcoin.WitScriptId (Array.zeroCreate witScriptIdLength)
- let dummyAddr = NBitcoin.BitcoinWitScriptAddress (nullScriptId, Config.BitcoinNet)
-
Infrastructure.LogDebug "Calling EstimateFee..."
let metadata =
try
- UtxoCoin.Account.EstimateFeeForDestination
- account channelCapacity dummyAddr
+ UtxoCoin.Account.EstimateChannelOpeningFee
+ account channelCapacity
|> Async.RunSynchronously
with
| InsufficientBalanceForFee _ ->
failwith "Estimated fee is too high for the remaining balance, \
use a different account or a different amount."
- let channelKeysSeed, keyRepo, temporaryChannelId = Lightning.GetSeedAndRepo random
- let channelEnvironment: Lightning.ChannelEnvironment =
- {
- Account = account
- NodeIdForResponder = DotNetLightning.Utils.Primitives.NodeId channelCounterpartyPubKey
- KeyRepo = keyRepo
- }
+ let potentialChannel, channelEnvironment =
+ Lightning.GenerateNewPotentialChannelDetails account channelCounterpartyPubKey random
async {
let! connectionBeforeAcceptChannelRes =
Lightning.ConnectAndHandshake channelEnvironment channelCounterpartyIP
@@ -340,38 +321,36 @@ let AskChannelFee (account: UtxoCoin.NormalUtxoAccount)
| Result.Ok connectionBeforeAcceptChannel ->
let passwordRef = ref "DotNetLightning shouldn't ask for password until later when user has
confirmed the funding transaction fee. So this is a placeholder."
- let! acceptChannelRes =
+ let! maybeAcceptChannel =
Lightning.GetAcceptChannel
+ potentialChannel
channelEnvironment
connectionBeforeAcceptChannel
channelCapacity
metadata
(fun _ -> !passwordRef)
balance
- temporaryChannelId
- match acceptChannelRes with
+ match maybeAcceptChannel with
| Result.Error error ->
Console.WriteLine error.Message
return None
- | Result.Ok (acceptChannel, chan, peer) ->
+ | Result.Ok fullChannel ->
Presentation.ShowFee Currency.BTC metadata
+ let confsReq = (fullChannel :> IChannelToBeOpened).ConfirmationsRequired
printfn
"Opening a channel with this party will require %i confirmations (~%i minutes)"
- acceptChannel.MinimumDepth.Value
- (acceptChannel.MinimumDepth.Value * 10u)
+ confsReq
+ (confsReq * 10u)
let accept = UserInteraction.AskYesNo "Do you accept?"
return
if accept then
- let connectionWithNewPeer = { connectionBeforeAcceptChannel with Peer = peer }
- Some
- {
- ChannelCreationDetails.Seed = channelKeysSeed
- AcceptChannel = acceptChannel
- Channel = chan
- Connection = connectionWithNewPeer
- Password = passwordRef
- }
+ {
+ Client = connectionBeforeAcceptChannel.Client
+ Password = passwordRef
+ ChannelInfo = potentialChannel
+ FullChannel = fullChannel
+ } |> Some
else
connectionBeforeAcceptChannel.Client.Dispose()
None
@@ -425,10 +404,11 @@ let rec PerformOperation (numAccounts: int) =
| Operations.Options ->
WalletOptions()
| Operations.OpenChannel ->
+ let currency = Currency.BTC
let btcAccount = Account
.GetAllActiveAccounts()
.OfType<UtxoCoin.NormalUtxoAccount>()
- .Single(fun account -> (account :> IAccount).Currency = Currency.BTC)
+ .Single(fun account -> (account :> IAccount).Currency = currency)
let balance = Account.GetShowableBalance
btcAccount ServerSelectionMode.Fast None
|> Async.RunSynchronously
@@ -436,7 +416,7 @@ let rec PerformOperation (numAccounts: int) =
FSharpUtil.option {
let! balance = OptionFromMaybeCachedBalance balance
let! channelCapacity = UserInteraction.AskAmount btcAccount
- let! ipEndpoint, pubKey = UserInteraction.AskChannelCounterpartyConnectionDetails()
+ let! ipEndpoint, pubKey = UserInteraction.AskChannelCounterpartyConnectionDetails currency
Infrastructure.LogDebug "Getting channel fee..."
let! channelCreationDetails =
AskChannelFee
@@ -461,14 +441,10 @@ let rec PerformOperation (numAccounts: int) =
let txIdRes =
Lightning.ContinueFromAcceptChannelAndSave
btcAccount
- details.Seed
ipEndpoint
- details.AcceptChannel
- details.Channel
- (details.Connection.Client.GetStream())
- details.Connection.Peer
+ details
|> Async.RunSynchronously
- details.Connection.Client.Dispose()
+ details.Client.Dispose()
Some txIdRes
with
| :? InvalidPassword ->
@@ -534,7 +510,7 @@ let rec CheckArchivedAccountsAreEmpty(): bool =
not (archivedAccountsInNeedOfAction.Any())
let private NotReadyReasonToString (reason: Lightning.NotReadyReason): string =
- sprintf "%i out of %i confirmations" reason.CurrentConfirmations.Value reason.NeededConfirmations.Value
+ sprintf "%i out of %i confirmations" reason.CurrentConfirmations reason.NeededConfirmations
let private CheckChannelStatus (path: string, channelFileId: int): Async<seq<string>> =
async {
@@ -560,7 +536,7 @@ let private CheckChannelStatus (path: string, channelFileId: int): Async<seq<str
let private CheckChannelStatuses(): Async<seq<string>> =
async {
- let jobs = SerializedChannel.ListSavedChannels () |> Seq.map CheckChannelStatus
+ let jobs = ChannelManager.ListSavedChannels () |> Seq.map CheckChannelStatus
let! statuses = Async.Parallel jobs
return Seq.collect id statuses
}
diff --git a/src/GWallet.Frontend.Console/UserInteraction.fs b/src/GWallet.Frontend.Console/UserInteraction.fs
index ece21074..f5142589 100644
--- a/src/GWallet.Frontend.Console/UserInteraction.fs
+++ b/src/GWallet.Frontend.Console/UserInteraction.fs
@@ -660,13 +660,13 @@ module UserInteraction =
IPEndPoint(AskChannelCounterpartyIP(), AskChannelCounterpartyPort())
// Throws FormatException
- let private AskChannelCounterpartyPubKey(): NBitcoin.PubKey =
+ let private AskChannelCounterpartyPubKey (currency: Currency): PublicKey =
Console.Write "Channel counterparty public key in hexadecimal notation: "
- let pubkeyHex = Console.ReadLine().Trim()
- NBitcoin.PubKey pubkeyHex
+ let pubKeyHex = Console.ReadLine().Trim()
+ PublicKey(pubKeyHex, currency)
// Throws FormatException
- let private AskChannelCounterpartyQRString(): IPEndPoint * NBitcoin.PubKey =
+ let private AskChannelCounterpartyQRString (currency: Currency): IPEndPoint * PublicKey =
Console.Write "Channel counterparty QR connection string contents: "
let connectionString = Console.ReadLine().Trim()
let atIndex = connectionString.IndexOf "@"
@@ -679,16 +679,16 @@ module UserInteraction =
let ipString, portString = ipPortCombo.[..portSeparatorIndex - 1], ipPortCombo.[portSeparatorIndex + 1..]
let ipAddress = IPAddress.Parse ipString
let port: int = ParsePortString portString
- IPEndPoint(ipAddress, port), NBitcoin.PubKey pubKeyHex
+ IPEndPoint(ipAddress, port), PublicKey(pubKeyHex, currency)
- let AskChannelCounterpartyConnectionDetails(): Option<IPEndPoint * NBitcoin.PubKey> =
+ let AskChannelCounterpartyConnectionDetails (currency: Currency): Option<IPEndPoint * PublicKey> =
let useQRString = AskYesNo "Do you want to supply the channel counterparty connection string as used embedded in QR codes?"
try
if useQRString then
- Some <| AskChannelCounterpartyQRString()
+ Some <| AskChannelCounterpartyQRString currency
else
let ipEndpoint = AskChannelCounterpartyIPAndPort()
- let pubKey = AskChannelCounterpartyPubKey()
+ let pubKey = AskChannelCounterpartyPubKey currency
Some (ipEndpoint, pubKey)
with
| :? FormatException as e ->
diff --git a/src/GWallet.Frontend.Console/packages.config b/src/GWallet.Frontend.Console/packages.config
index 20738d4e..bf2f39b6 100644
--- a/src/GWallet.Frontend.Console/packages.config
+++ b/src/GWallet.Frontend.Console/packages.config
@@ -1,10 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<packages>
<package id="BouncyCastle" version="1.8.5" targetFramework="net461" />
- <package id="DotNetLightning" version="1.1.2-date20200527-1047-git-42c7cb9" targetFramework="net472" />
<package id="FSharp.Core" version="4.7.0" targetFramework="net45" />
<package id="Microsoft.Extensions.Logging.Abstractions" version="1.0.0" targetFramework="net461" />
- <package id="NBitcoin" version="5.0.13" targetFramework="net461" />
<package id="Newtonsoft.Json" version="11.0.2" targetFramework="net46" />
<package id="Portable.BouncyCastle" version="1.8.6" targetFramework="net461" />
<package id="System.Buffers" version="4.5.0" targetFramework="net461" />
--
2.21.0
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment