diff --git a/src/GWallet.Backend/UtxoCoin/ElectrumClient.fs b/src/GWallet.Backend/UtxoCoin/ElectrumClient.fs index 14bca3c32..9bef434a8 100644 --- a/src/GWallet.Backend/UtxoCoin/ElectrumClient.fs +++ b/src/GWallet.Backend/UtxoCoin/ElectrumClient.fs @@ -51,7 +51,7 @@ module ElectrumClient = | { Encrypted = false; Protocol = Tcp port } -> Init electrumServer.ServerInfo.NetworkPath port - let GetBalance (scriptHash: string) (stratumServer: Async) = async { + let GetBalances (scriptHashes: List) (stratumServer: Async) = async { // FIXME: we should rather implement this method in terms of: // - querying all unspent transaction outputs (X) -> block heights included // - querying transaction history (Y) -> block heights included @@ -67,8 +67,31 @@ module ElectrumClient = // [ see https://www.youtube.com/watch?v=hjYCXOyDy7Y&feature=youtu.be&t=1171 for more information ] // * -> although that would be fixing only half of the problem, we also need proof of completeness let! stratumClient = stratumServer - let! balanceResult = stratumClient.BlockchainScriptHashGetBalance scriptHash - return balanceResult.Result + let rec innerGetBalances (scriptHashes: List) (result: BlockchainScriptHashGetBalanceInnerResult) = + async { + match scriptHashes with + | scriptHash::otherScriptHashes -> + let! balanceHash = stratumClient.BlockchainScriptHashGetBalance scriptHash + + return! + innerGetBalances + otherScriptHashes + { + result with + Unconfirmed = result.Unconfirmed + balanceHash.Result.Unconfirmed + Confirmed = result.Confirmed + balanceHash.Result.Confirmed + } + | [] -> + return result + } + + return! + innerGetBalances + scriptHashes + { + Unconfirmed = 0L + Confirmed = 0L + } } let GetUnspentTransactionOutputs scriptHash (stratumServer: Async) = async { diff --git a/src/GWallet.Backend/UtxoCoin/UtxoCoinAccount.fs b/src/GWallet.Backend/UtxoCoin/UtxoCoinAccount.fs index ec56c1a2c..eaed4a503 100644 --- a/src/GWallet.Backend/UtxoCoin/UtxoCoinAccount.fs +++ b/src/GWallet.Backend/UtxoCoin/UtxoCoinAccount.fs @@ -71,12 +71,20 @@ module Account = // TODO: measure how long does it take to get the script hash and if it's too long, cache it at app startup? BitcoinAddress.Create(publicAddress, GetNetwork currency) |> GetElectrumScriptHashFromAddress - let internal GetPublicAddressFromPublicKey currency (publicKey: PubKey) = + let internal GetSegwitP2SHPublicAddressFromPublicKey currency (publicKey: PubKey) = + publicKey + .GetScriptPubKey(ScriptPubKeyType.SegwitP2SH) + .GetDestinationAddress(GetNetwork currency) + .ToString() + + let internal GetNativeSegwitPublicAddressFromPublicKey currency (publicKey: PubKey) = publicKey .GetScriptPubKey(ScriptPubKeyType.Segwit) - .Hash - .GetAddress(GetNetwork currency) + .GetDestinationAddress(GetNetwork currency) .ToString() + + let internal GetPublicAddressFromPublicKey = + GetNativeSegwitPublicAddressFromPublicKey let internal GetPublicAddressFromNormalAccountFile (currency: Currency) (accountFile: FileRepresentation): string = let pubKey = PubKey(accountFile.Name) @@ -137,11 +145,17 @@ module Account = (mode: ServerSelectionMode) (cancelSourceOption: Option) : Async = - let scriptHashHex = GetElectrumScriptHashFromPublicAddress account.Currency account.PublicAddress + let scriptHashesHex = + [ + GetNativeSegwitPublicAddressFromPublicKey account.Currency account.PublicKey + |> GetElectrumScriptHashFromPublicAddress account.Currency + GetSegwitP2SHPublicAddressFromPublicKey account.Currency account.PublicKey + |> GetElectrumScriptHashFromPublicAddress account.Currency + ] let querySettings = QuerySettings.Balance(mode,(BalanceMatchWithCacheOrInitialBalance account.PublicAddress account.Currency)) - let balanceJob = ElectrumClient.GetBalance scriptHashHex + let balanceJob = ElectrumClient.GetBalances scriptHashesHex Server.Query account.Currency querySettings balanceJob cancelSourceOption let private GetBalancesFromServer (account: IUtxoAccount) @@ -174,9 +188,21 @@ module Account = let txHash = uint256 inputOutpointInfo.TransactionHash let scriptPubKeyInBytes = NBitcoin.DataEncoders.Encoders.Hex.DecodeData inputOutpointInfo.DestinationInHex let scriptPubKey = Script(scriptPubKeyInBytes) + // We convert the scriptPubKey to address temporarily to compare it with + // our own addresses, we could compare scriptPubKeys directly but we would + // need functions that return scriptPubKey of our addresses instead of a + // string. + let sourceAddress = scriptPubKey.GetDestinationAddress(GetNetwork account.Currency).ToString() let coin = Coin(txHash, uint32 inputOutpointInfo.OutputIndex, Money(inputOutpointInfo.ValueInSatoshis), scriptPubKey) - coin.ToScriptCoin account.PublicKey.WitHash.ScriptPubKey :> ICoin + if sourceAddress = GetSegwitP2SHPublicAddressFromPublicKey account.Currency account.PublicKey then + coin.ToScriptCoin(account.PublicKey.WitHash.ScriptPubKey) :> ICoin + elif sourceAddress = GetNativeSegwitPublicAddressFromPublicKey account.Currency account.PublicKey then + coin :> ICoin + else + //We filter utxos based on scriptPubKey when retrieving from electrum + //so this is unreachable. + failwith "Unreachable: unrecognized scriptPubKey" let private CreateTransactionAndCoinsToBeSigned (account: IUtxoAccount) (transactionInputs: List) @@ -292,9 +318,28 @@ module Account = else newAcc,tail - let job = GetElectrumScriptHashFromPublicAddress account.Currency account.PublicAddress - |> ElectrumClient.GetUnspentTransactionOutputs - let! utxos = Server.Query account.Currency (QuerySettings.Default ServerSelectionMode.Fast) job None + let currency = account.Currency + + let getUtxos (publicAddress: string) = + async { + let job = GetElectrumScriptHashFromPublicAddress currency publicAddress + |> ElectrumClient.GetUnspentTransactionOutputs + + return! Server.Query currency (QuerySettings.Default ServerSelectionMode.Fast) job None + } + + let! utxos = + async { + let! nativeSegwitUtxos = + GetNativeSegwitPublicAddressFromPublicKey currency account.PublicKey + |> getUtxos + + let! legacySegwitUtxos = + GetSegwitP2SHPublicAddressFromPublicKey currency account.PublicKey + |> getUtxos + + return Array.concat [ nativeSegwitUtxos; legacySegwitUtxos ] + } if not (utxos.Any()) then failwith "No UTXOs found!"