Skip to content

Commit

Permalink
[Electric-Coin-Company#1028] Runtime switch of lightwalletd servers
Browse files Browse the repository at this point in the history
- prototype of the solution implemented

[Electric-Coin-Company#1028] Runtime switch of lightwalletd servers

- error handling done
- localized all new texts
- custom server resolved with all possible parsing states
- persistency of selected server done

[Electric-Coin-Company#1028] Runtime switch of lightwalletd servers (Electric-Coin-Company#1044)

- changelog update

[Electric-Coin-Company#1028] Runtime switch of lightwalletd servers (Electric-Coin-Company#1044)

- Unfortunately the compiler has a bug so Circular reference error is not possible to solve, Apple fixed reported issue from October 2023 last week so we should expect fix in Xcode 15.3, beta is released but still no fix. Until that moment I moved placeholders to the view and will move it back to the stores once the issue is resolved

[Electric-Coin-Company#1028] Runtime switch of lightwalletd servers (Electric-Coin-Company#1044)

- adoption of SDK 2.0.10
  • Loading branch information
LukasKorba committed Feb 13, 2024
1 parent f7f5bcf commit 0faa8d1
Show file tree
Hide file tree
Showing 28 changed files with 526 additions and 100 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Expand Up @@ -13,6 +13,7 @@ directly impact users rather than highlighting other crucial architectural updat

### Added
- Pending values (changes) at the Balances tab.
- Choose a Server feature: available at settings, pre-defined servers + custom server setup.

### Fixed
- Failed transactions are no longer at the top of the transaction history but mixed with the transactions around the time it failed.
Expand Down
20 changes: 18 additions & 2 deletions modules/Package.swift
Expand Up @@ -44,6 +44,7 @@ let package = Package(
.library(name: "SecItem", targets: ["SecItem"]),
.library(name: "SecurityWarning", targets: ["SecurityWarning"]),
.library(name: "SendFlow", targets: ["SendFlow"]),
.library(name: "ServerSetup", targets: ["ServerSetup"]),
.library(name: "Settings", targets: ["Settings"]),
.library(name: "SupportDataGenerator", targets: ["SupportDataGenerator"]),
.library(name: "SyncProgress", targets: ["SyncProgress"]),
Expand All @@ -62,11 +63,11 @@ let package = Package(
.library(name: "ZcashSDKEnvironment", targets: ["ZcashSDKEnvironment"])
],
dependencies: [
.package(url: "https://github.com/pointfreeco/swift-composable-architecture", from: "1.4.2"),
.package(url: "https://github.com/pointfreeco/swift-composable-architecture", from: "1.7.0"),
.package(url: "https://github.com/pointfreeco/swift-case-paths", from: "1.1.0"),
.package(url: "https://github.com/pointfreeco/swift-url-routing", from: "0.6.0"),
.package(url: "https://github.com/zcash-hackworks/MnemonicSwift", from: "2.2.4"),
.package(url: "https://github.com/zcash/ZcashLightClientKit", from: "2.0.9"),
.package(url: "https://github.com/zcash/ZcashLightClientKit", from: "2.0.10"),
.package(url: "https://github.com/firebase/firebase-ios-sdk", from: "10.17.0")
],
targets: [
Expand Down Expand Up @@ -468,6 +469,19 @@ let package = Package(
],
path: "Sources/Features/SendFlow"
),
.target(
name: "ServerSetup",
dependencies: [
"Generated",
"SDKSynchronizer",
"UIComponents",
"UserDefaults",
"ZcashSDKEnvironment",
.product(name: "ComposableArchitecture", package: "swift-composable-architecture"),
.product(name: "ZcashLightClientKit", package: "ZcashLightClientKit")
],
path: "Sources/Features/ServerSetup"
),
.target(
name: "Settings",
dependencies: [
Expand All @@ -480,6 +494,7 @@ let package = Package(
"RecoveryPhraseDisplay",
"RestoreWalletStorage",
"SDKSynchronizer",
"ServerSetup",
"SupportDataGenerator",
"UIComponents",
"WalletStorage",
Expand Down Expand Up @@ -628,6 +643,7 @@ let package = Package(
.target(
name: "ZcashSDKEnvironment",
dependencies: [
"UserDefaults",
.product(name: "ZcashLightClientKit", package: "ZcashLightClientKit"),
.product(name: "ComposableArchitecture", package: "swift-composable-architecture")
],
Expand Down
Expand Up @@ -41,4 +41,6 @@ public struct SDKSynchronizerClient {
public let shieldFunds: (UnifiedSpendingKey, Memo, Zatoshi) async throws -> TransactionState

public var wipe: () -> AnyPublisher<Void, Error>?

public var switchToEndpoint: (LightWalletEndpoint) async throws -> Void
}
Expand Up @@ -103,7 +103,10 @@ extension SDKSynchronizerClient {
latestBlockHeight: try await SDKSynchronizerClient.latestBlockHeight(synchronizer: synchronizer)
)
},
wipe: { synchronizer.wipe() }
wipe: { synchronizer.wipe() },
switchToEndpoint: { endpoint in
try await synchronizer.switchTo(endpoint: endpoint)
}
)
}
}
Expand Down
Expand Up @@ -29,7 +29,8 @@ extension SDKSynchronizerClient: TestDependencyKey {
getSaplingAddress: XCTUnimplemented("\(Self.self).getSaplingAddress", placeholder: nil),
sendTransaction: XCTUnimplemented("\(Self.self).sendTransaction", placeholder: .placeholder()),
shieldFunds: XCTUnimplemented("\(Self.self).shieldFunds", placeholder: .placeholder()),
wipe: XCTUnimplemented("\(Self.self).wipe")
wipe: XCTUnimplemented("\(Self.self).wipe"),
switchToEndpoint: XCTUnimplemented("\(Self.self).switchToEndpoint")
)
}

Expand All @@ -50,7 +51,8 @@ extension SDKSynchronizerClient {
getSaplingAddress: { _ in return nil },
sendTransaction: { _, _, _, _ in return .placeholder() },
shieldFunds: { _, _, _ in return .placeholder() },
wipe: { Empty<Void, Error>().eraseToAnyPublisher() }
wipe: { Empty<Void, Error>().eraseToAnyPublisher() },
switchToEndpoint: { _ in }
)

public static let mock = Self.mocked()
Expand Down Expand Up @@ -169,7 +171,8 @@ extension SDKSynchronizerClient {
zecAmount: Zatoshi(10)
)
},
wipe: @escaping () -> AnyPublisher<Void, Error>? = { Fail(error: "Error").eraseToAnyPublisher() }
wipe: @escaping () -> AnyPublisher<Void, Error>? = { Fail(error: "Error").eraseToAnyPublisher() },
switchToEndpoint: @escaping (LightWalletEndpoint) async throws -> Void = { _ in }
) -> SDKSynchronizerClient {
SDKSynchronizerClient(
stateStream: stateStream,
Expand All @@ -187,7 +190,8 @@ extension SDKSynchronizerClient {
getSaplingAddress: getSaplingAddress,
sendTransaction: sendTransaction,
shieldFunds: shieldFunds,
wipe: wipe
wipe: wipe,
switchToEndpoint: switchToEndpoint
)
}
}
Expand Up @@ -8,6 +8,8 @@
import ComposableArchitecture
import ZcashLightClientKit

import UserDefaults

extension DependencyValues {
public var zcashSDKEnvironment: ZcashSDKEnvironment {
get { self[ZcashSDKEnvironment.self] }
Expand All @@ -16,6 +18,85 @@ extension DependencyValues {
}

extension ZcashSDKEnvironment {
public enum Servers: String, CaseIterable, Equatable {
public enum Constants {
public static let udServerKey = "zashi_udServerKey"
public static let udCustomServerKey = "zashi_udCustomServerKey"
}

case mainnet
case naNW
case saNW
case euNW
case aiNW
case custom

public func server() -> String {
switch self {
case .mainnet: return "mainnet.lightwalletd.com:9067"
case .naNW: return "na.lightwalletd.com:443"
case .saNW: return "sa.lightwalletd.com:443"
case .euNW: return "eu.lightwalletd.com:443"
case .aiNW: return "ai.lightwalletd.com:443"
case .custom: return "custom"
}
}

public func lightWalletEndpoint(_ userDefaults: UserDefaultsClient) -> LightWalletEndpoint? {
switch self {
case .mainnet:
return LightWalletEndpoint(
address: "mainnet.lightwalletd.com",
port: 9067,
secure: true,
streamingCallTimeoutInMillis: ZcashSDKConstants.streamingCallTimeoutInMillis
)
case .naNW, .saNW, .euNW, .aiNW:
return LightWalletEndpoint(
address: String(self.server().dropLast(4)),
port: 443,
secure: true,
streamingCallTimeoutInMillis: ZcashSDKConstants.streamingCallTimeoutInMillis
)
case .custom:
let udKey = ZcashSDKEnvironment.Servers.Constants.udCustomServerKey
if let storedCustomServer = userDefaults.objectForKey(udKey) as? String{
// remove http:// or https:// from the input if present
var input = storedCustomServer

if input.contains("https://") {
input = String(input.dropFirst(8))
} else if input.contains("http://") {
input = String(input.dropFirst(7))
}

let split = input.split(separator: ":")

if let portString = split.last, let port = Int(portString) {
var host = ""

if split.count == 2, let first = split.first {
host = String(first)
} else if split.count == 3, let first = split.first {
let second = split[1]

host = "\(String(first))\(String(second))"
}

return LightWalletEndpoint(
address: host,
port: port,
secure: true,
streamingCallTimeoutInMillis: ZcashSDKConstants.streamingCallTimeoutInMillis
)
}
}

return nil
}
}
}

public enum ZcashSDKConstants {
static let endpointMainnetAddress = "mainnet.lightwalletd.com"
static let endpointTestnetAddress = "lightwalletd.testnet.electriccoin.co"
Expand Down
Expand Up @@ -12,7 +12,26 @@ extension ZcashSDKEnvironment: DependencyKey {
public static let liveValue = Self(
latestCheckpoint: { network in BlockHeight.ofLatestCheckpoint(network: network) },
endpoint: { network in
LightWalletEndpoint(
// In case of mainnet network we may have stored server as a user action in advanced settings
if network.networkType == .mainnet {
@Dependency(\.userDefaults) var userDefaults

let udKey = ZcashSDKEnvironment.Servers.Constants.udServerKey

if let storedServerRaw = userDefaults.objectForKey(udKey) as? String,
let storedServer = ZcashSDKEnvironment.Servers(rawValue: storedServerRaw) {
if let endpoint = storedServer.lightWalletEndpoint(userDefaults) {
// Some endpoint is set by a user so we initialize the SDK with this one
return endpoint
} else {
// Endpoint failed, fallback to hardcoded mainnet
userDefaults.setValue(ZcashSDKEnvironment.Servers.mainnet.rawValue, udKey)
}
}
}

// Hardcoded endpoint
return LightWalletEndpoint(
address: Self.endpoint(for: network),
port: ZcashSDKConstants.endpointPort,
secure: true,
Expand Down
Expand Up @@ -37,10 +37,10 @@ public struct BalanceBreakdownReducer: Reducer {
public var shieldedBalance: Zatoshi
public var totalBalance: Zatoshi
public var syncProgressState: SyncProgressReducer.State
public var transparentBalance: Balance
public var transparentBalance: Zatoshi

public var isShieldableBalanceAvailable: Bool {
transparentBalance.data.verified.amount >= autoShieldingThreshold.amount
transparentBalance.amount >= autoShieldingThreshold.amount
}

public var isShieldingButtonDisabled: Bool {
Expand All @@ -57,7 +57,7 @@ public struct BalanceBreakdownReducer: Reducer {
shieldedBalance: Zatoshi,
syncProgressState: SyncProgressReducer.State,
totalBalance: Zatoshi,
transparentBalance: Balance
transparentBalance: Zatoshi
) {
self.autoShieldingThreshold = autoShieldingThreshold
self.changePending = changePending
Expand Down Expand Up @@ -164,11 +164,11 @@ public struct BalanceBreakdownReducer: Reducer {
return .none

case .synchronizerStateChanged(let latestState):
state.shieldedBalance = latestState.accountBalances.saplingBalance.spendableValue
state.totalBalance = latestState.accountBalances.saplingBalance.total()
state.transparentBalance = latestState.transparentBalance.redacted
state.changePending = latestState.accountBalances.saplingBalance.changePendingConfirmation
state.pendingTransactions = latestState.accountBalances.saplingBalance.valuePendingSpendability
state.shieldedBalance = latestState.accountBalance?.saplingBalance.spendableValue ?? .zero
state.totalBalance = latestState.accountBalance?.saplingBalance.total() ?? .zero
state.transparentBalance = latestState.accountBalance?.unshielded ?? .zero
state.changePending = latestState.accountBalance?.saplingBalance.changePendingConfirmation ?? .zero
state.pendingTransactions = latestState.accountBalance?.saplingBalance.valuePendingSpendability ?? .zero
return .none

case .syncProgress:
Expand Down Expand Up @@ -205,7 +205,7 @@ extension BalanceBreakdownReducer.State {
shieldedBalance: .zero,
syncProgressState: .initial,
totalBalance: .zero,
transparentBalance: Balance.zero
transparentBalance: .zero
)

public static let initial = BalanceBreakdownReducer.State(
Expand All @@ -216,7 +216,7 @@ extension BalanceBreakdownReducer.State {
shieldedBalance: .zero,
syncProgressState: .initial,
totalBalance: .zero,
transparentBalance: Balance.zero
transparentBalance: .zero
)
}

Expand Down
Expand Up @@ -188,7 +188,7 @@ extension BalanceBreakdownView {
Spacer()

ZatoshiRepresentationView(
balance: viewStore.transparentBalance.data.verified,
balance: viewStore.transparentBalance,
fontName: FontFamily.Archivo.semiBold.name,
mostSignificantFontSize: 16,
leastSignificantFontSize: 8,
Expand Down Expand Up @@ -275,7 +275,7 @@ extension BalanceBreakdownView {
syncStatusMessage: "Syncing"
),
totalBalance: Zatoshi(25_234_778),
transparentBalance: Balance(WalletBalance(verified: Zatoshi(25_234_778), total: Zatoshi(35_814_169)))
transparentBalance: Zatoshi(25_234_778)
)
) {
BalanceBreakdownReducer(networkType: .testnet)
Expand Down
4 changes: 2 additions & 2 deletions modules/Sources/Features/Home/HomeStore.swift
Expand Up @@ -193,8 +193,8 @@ public struct HomeReducer: Reducer {
}

state.synchronizerStatusSnapshot = snapshot
state.shieldedBalance = latestState.accountBalances.saplingBalance.spendableValue
state.totalBalance = latestState.accountBalances.saplingBalance.total()
state.shieldedBalance = latestState.accountBalance?.saplingBalance.spendableValue ?? .zero
state.totalBalance = latestState.accountBalance?.saplingBalance.total() ?? .zero

switch snapshot.syncStatus {
case .error(let error):
Expand Down
4 changes: 2 additions & 2 deletions modules/Sources/Features/SendFlow/SendFlowStore.swift
Expand Up @@ -262,8 +262,8 @@ public struct SendFlowReducer: Reducer {
return .none

case .synchronizerStateChanged(let latestState):
state.shieldedBalance = latestState.accountBalances.saplingBalance.spendableValue
state.totalBalance = latestState.accountBalances.saplingBalance.total()
state.shieldedBalance = latestState.accountBalance?.saplingBalance.spendableValue ?? .zero
state.totalBalance = latestState.accountBalance?.saplingBalance.total() ?? .zero
state.transactionAmountInputState.maxValue = state.shieldedBalance.amount.redacted
return .none

Expand Down

0 comments on commit 0faa8d1

Please sign in to comment.