Compare commits

...

5 Commits

Author SHA1 Message Date
Kyle Fleming
431bd74c29 [tmp] 2021-06-30 10:59:37 -10:00
Kyle Fleming
6acaf5bf83 Move thread safety of DefaultServiceProvider for better encapsulation 2021-06-29 20:06:27 -10:00
Kyle Fleming
a35ac627a9 Move Connection objects to GrpcConnection namespace 2021-06-29 20:06:27 -10:00
Kyle Fleming
b4dd03d96b Minor tweaks 2021-06-29 20:06:27 -10:00
Kyle Fleming
c6b7a04694 Use local LibMobileCoin 2021-06-25 15:45:11 -10:00
50 changed files with 1665 additions and 866 deletions

View File

@ -26,8 +26,8 @@ target 'Example' do
# pod 'MobileCoin', git: 'https://github.com/mobilecoinofficial/MobileCoin-Swift.git'
# pod 'MobileCoin/Core', git: 'https://github.com/mobilecoinofficial/MobileCoin-Swift.git', testspecs: ['Tests', 'IntegrationTests']
pod 'LibMobileCoin', binary: true
# pod 'LibMobileCoin', path: '../Vendor/libmobilecoin-ios-artifacts'
# pod 'LibMobileCoin', binary: true
pod 'LibMobileCoin', path: '../Vendor/libmobilecoin-ios-artifacts'
# pod 'LibMobileCoin', podspec: '../Vendor/libmobilecoin-ios-artifacts/LibMobileCoin.podspec'
# pod 'LibMobileCoin', git: 'https://github.com/mobilecoinofficial/libmobilecoin-ios-artifacts.git'

View File

@ -104,7 +104,7 @@ PODS:
DEPENDENCIES:
- gRPC-Swift
- Keys (from `Pods/CocoaPodsKeys`)
- LibMobileCoin
- LibMobileCoin (from `../Vendor/libmobilecoin-ios-artifacts`)
- MobileCoin (from `..`)
- MobileCoin/Core (from `..`)
- MobileCoin/Core/IntegrationTests (from `..`)
@ -124,7 +124,6 @@ SPEC REPOS:
- CNIOLinux
- CNIOWindows
- gRPC-Swift
- LibMobileCoin
- Logging
- SwiftLint
- SwiftNIO
@ -142,6 +141,8 @@ SPEC REPOS:
EXTERNAL SOURCES:
Keys:
:path: Pods/CocoaPodsKeys
LibMobileCoin:
:path: "../Vendor/libmobilecoin-ios-artifacts"
MobileCoin:
:path: ".."
@ -172,6 +173,6 @@ SPEC CHECKSUMS:
SwiftNIOTransportServices: 896c9a4ac98698d32aa2feea7657ade219ae80bb
SwiftProtobuf: 3320217e9d8fb75f36b40282e78c482640fd75dd
PODFILE CHECKSUM: 004d4412cc1559f32bc126f553599fcfcaac859f
PODFILE CHECKSUM: a0d081de36bb0e26c2f240e65e5e63f9f111ec19
COCOAPODS: 1.9.3

View File

@ -22,11 +22,11 @@ final class FogReportManager {
completion: @escaping (Result<Report_ReportResponse, ConnectionError>) -> Void
) {
logger.info("reportUrl: \(reportUrl.url)")
let reportService = serviceProvider.fogReportService(for: reportUrl)
self.inner.accessAsync {
let reportServer = $0.reportServer(for: reportUrl)
reportServer.reports(reportService: reportService, completion: completion)
serviceProvider.fogReportService(for: reportUrl) { reportService in
self.inner.accessAsync {
let reportServer = $0.reportServer(for: reportUrl)
reportServer.reports(reportService: reportService, completion: completion)
}
}
}
@ -36,14 +36,14 @@ final class FogReportManager {
completion: @escaping (Result<Report_ReportResponse, ConnectionError>) -> Void
) {
logger.info("reportUrl: \(reportUrl.url), reportParams: \(reportParams)")
let reportService = serviceProvider.fogReportService(for: reportUrl)
self.inner.accessAsync {
let reportServer = $0.reportServer(for: reportUrl)
reportServer.reports(
reportService: reportService,
reportParams: reportParams,
completion: completion)
serviceProvider.fogReportService(for: reportUrl) { reportService in
self.inner.accessAsync {
let reportServer = $0.reportServer(for: reportUrl)
reportServer.reports(
reportService: reportService,
reportParams: reportParams,
completion: completion)
}
}
}
}

View File

@ -19,9 +19,8 @@ final class FogResolverManager {
) {
self.serialQueue = DispatchQueue(label: "com.mobilecoin.\(Self.self)", target: targetQueue)
self.reportAttestation = fogReportAttestation
self.reportManager = FogReportManager(
serviceProvider: serviceProvider,
targetQueue: targetQueue)
self.reportManager =
FogReportManager(serviceProvider: serviceProvider, targetQueue: targetQueue)
}
func fogResolver(

View File

@ -22,7 +22,6 @@ public final class MobileCoinClient {
}
private let accountLock: ReadWriteDispatchLock<Account>
private let inner: SerialDispatchLock<Inner>
private let serialQueue: DispatchQueue
private let callbackQueue: DispatchQueue
@ -30,6 +29,8 @@ public final class MobileCoinClient {
private let mixinSelectionStrategy: MixinSelectionStrategy
private let fogQueryScalingStrategy: FogQueryScalingStrategy
private let serviceProvider: ServiceProvider
private let fogResolverManager: FogResolverManager
private let feeFetcher: BlockchainFeeFetcher
init(accountKey: AccountKeyWithFog, config: Config) {
@ -45,16 +46,12 @@ public final class MobileCoinClient {
self.mixinSelectionStrategy = config.mixinSelectionStrategy
self.fogQueryScalingStrategy = config.fogQueryScalingStrategy
let serviceProvider =
self.serviceProvider =
DefaultServiceProvider(networkConfig: config.networkConfig, targetQueue: serialQueue)
let fogResolverManager = FogResolverManager(
self.fogResolverManager = FogResolverManager(
fogReportAttestation: config.networkConfig.fogReportAttestation,
serviceProvider: serviceProvider,
targetQueue: serialQueue)
let inner = Inner(serviceProvider: serviceProvider, fogResolverManager: fogResolverManager)
self.inner = .init(inner, targetQueue: serialQueue)
self.feeFetcher = BlockchainFeeFetcher(
blockchainService: serviceProvider.blockchainService,
minimumFeeCacheTTL: config.minimumFeeCacheTTL,
@ -69,29 +66,31 @@ public final class MobileCoinClient {
accountLock.readSync { $0.cachedAccountActivity }
}
public func setTransportProtocol(_ transportProtocol: TransportProtocol) {
serviceProvider.setTransportProtocolOption(transportProtocol.option)
}
public func setConsensusBasicAuthorization(username: String, password: String) {
let credentials = BasicCredentials(username: username, password: password)
inner.accessAsync { $0.serviceProvider.setConsensusAuthorization(credentials: credentials) }
serviceProvider.setConsensusAuthorization(credentials: credentials)
}
public func setFogBasicAuthorization(username: String, password: String) {
let credentials = BasicCredentials(username: username, password: password)
inner.accessAsync { $0.serviceProvider.setFogUserAuthorization(credentials: credentials) }
serviceProvider.setFogUserAuthorization(credentials: credentials)
}
public func updateBalance(completion: @escaping (Result<Balance, ConnectionError>) -> Void) {
inner.accessAsync {
Account.BalanceUpdater(
account: self.accountLock,
fogViewService: $0.serviceProvider.fogViewService,
fogKeyImageService: $0.serviceProvider.fogKeyImageService,
fogBlockService: $0.serviceProvider.fogBlockService,
fogQueryScalingStrategy: self.fogQueryScalingStrategy,
targetQueue: self.serialQueue
).updateBalance { result in
self.callbackQueue.async {
completion(result)
}
Account.BalanceUpdater(
account: accountLock,
fogViewService: serviceProvider.fogViewService,
fogKeyImageService: serviceProvider.fogKeyImageService,
fogBlockService: serviceProvider.fogBlockService,
fogQueryScalingStrategy: fogQueryScalingStrategy,
targetQueue: serialQueue
).updateBalance { result in
self.callbackQueue.async {
completion(result)
}
}
}
@ -142,19 +141,17 @@ public final class MobileCoinClient {
Result<(transaction: Transaction, receipt: Receipt), TransactionPreparationError>
) -> Void
) {
inner.accessAsync {
Account.TransactionOperations(
account: self.accountLock,
fogMerkleProofService: $0.serviceProvider.fogMerkleProofService,
fogResolverManager: $0.fogResolverManager,
feeFetcher: self.feeFetcher,
txOutSelectionStrategy: self.txOutSelectionStrategy,
mixinSelectionStrategy: self.mixinSelectionStrategy,
targetQueue: self.serialQueue
).prepareTransaction(to: recipient, amount: amount, fee: fee) { result in
self.callbackQueue.async {
completion(result)
}
Account.TransactionOperations(
account: accountLock,
fogMerkleProofService: serviceProvider.fogMerkleProofService,
fogResolverManager: fogResolverManager,
feeFetcher: feeFetcher,
txOutSelectionStrategy: txOutSelectionStrategy,
mixinSelectionStrategy: mixinSelectionStrategy,
targetQueue: serialQueue
).prepareTransaction(to: recipient, amount: amount, fee: fee) { result in
self.callbackQueue.async {
completion(result)
}
}
}
@ -167,19 +164,17 @@ public final class MobileCoinClient {
Result<(transaction: Transaction, receipt: Receipt), TransactionPreparationError>
) -> Void
) {
inner.accessAsync {
Account.TransactionOperations(
account: self.accountLock,
fogMerkleProofService: $0.serviceProvider.fogMerkleProofService,
fogResolverManager: $0.fogResolverManager,
feeFetcher: self.feeFetcher,
txOutSelectionStrategy: self.txOutSelectionStrategy,
mixinSelectionStrategy: self.mixinSelectionStrategy,
targetQueue: self.serialQueue
).prepareTransaction(to: recipient, amount: amount, feeLevel: feeLevel) { result in
self.callbackQueue.async {
completion(result)
}
Account.TransactionOperations(
account: accountLock,
fogMerkleProofService: serviceProvider.fogMerkleProofService,
fogResolverManager: fogResolverManager,
feeFetcher: feeFetcher,
txOutSelectionStrategy: txOutSelectionStrategy,
mixinSelectionStrategy: mixinSelectionStrategy,
targetQueue: serialQueue
).prepareTransaction(to: recipient, amount: amount, feeLevel: feeLevel) { result in
self.callbackQueue.async {
completion(result)
}
}
}
@ -189,20 +184,18 @@ public final class MobileCoinClient {
feeLevel: FeeLevel = .minimum,
completion: @escaping (Result<[Transaction], DefragTransactionPreparationError>) -> Void
) {
inner.accessAsync {
Account.TransactionOperations(
account: self.accountLock,
fogMerkleProofService: $0.serviceProvider.fogMerkleProofService,
fogResolverManager: $0.fogResolverManager,
feeFetcher: self.feeFetcher,
txOutSelectionStrategy: self.txOutSelectionStrategy,
mixinSelectionStrategy: self.mixinSelectionStrategy,
targetQueue: self.serialQueue
).prepareDefragmentationStepTransactions(toSendAmount: amount, feeLevel: feeLevel)
{ result in
self.callbackQueue.async {
completion(result)
}
Account.TransactionOperations(
account: accountLock,
fogMerkleProofService: serviceProvider.fogMerkleProofService,
fogResolverManager: fogResolverManager,
feeFetcher: feeFetcher,
txOutSelectionStrategy: txOutSelectionStrategy,
mixinSelectionStrategy: mixinSelectionStrategy,
targetQueue: serialQueue
).prepareDefragmentationStepTransactions(toSendAmount: amount, feeLevel: feeLevel)
{ result in
self.callbackQueue.async {
completion(result)
}
}
}
@ -211,14 +204,12 @@ public final class MobileCoinClient {
_ transaction: Transaction,
completion: @escaping (Result<(), TransactionSubmissionError>) -> Void
) {
inner.accessAsync {
TransactionSubmitter(
consensusService: $0.serviceProvider.consensusService,
feeFetcher: self.feeFetcher
).submitTransaction(transaction) { result in
self.callbackQueue.async {
completion(result)
}
TransactionSubmitter(
consensusService: serviceProvider.consensusService,
feeFetcher: feeFetcher
).submitTransaction(transaction) { result in
self.callbackQueue.async {
completion(result)
}
}
}
@ -227,16 +218,14 @@ public final class MobileCoinClient {
of transaction: Transaction,
completion: @escaping (Result<TransactionStatus, ConnectionError>) -> Void
) {
inner.accessAsync {
TransactionStatusChecker(
account: self.accountLock,
fogUntrustedTxOutService: $0.serviceProvider.fogUntrustedTxOutService,
fogKeyImageService: $0.serviceProvider.fogKeyImageService,
targetQueue: self.serialQueue
).checkStatus(transaction) { result in
self.callbackQueue.async {
completion(result)
}
TransactionStatusChecker(
account: accountLock,
fogUntrustedTxOutService: serviceProvider.fogUntrustedTxOutService,
fogKeyImageService: serviceProvider.fogKeyImageService,
targetQueue: serialQueue
).checkStatus(transaction) { result in
self.callbackQueue.async {
completion(result)
}
}
}
@ -307,18 +296,6 @@ extension MobileCoinClient {
}
}
extension MobileCoinClient {
private struct Inner {
let serviceProvider: ServiceProvider
let fogResolverManager: FogResolverManager
init(serviceProvider: ServiceProvider, fogResolverManager: FogResolverManager) {
self.serviceProvider = serviceProvider
self.fogResolverManager = fogResolverManager
}
}
}
extension MobileCoinClient {
public struct Config {
/// - Returns: `InvalidInputError` when `consensusUrl` or `fogUrl` are not well-formed URLs
@ -368,21 +345,41 @@ extension MobileCoinClient {
self.networkConfig = networkConfig
}
public var transportProtocol: TransportProtocol {
get { networkConfig.transportProtocol }
set { networkConfig.transportProtocol = newValue }
}
public mutating func setConsensusTrustRoots(_ trustRoots: [Data])
-> Result<(), InvalidInputError>
{
Self.parseTrustRoots(trustRootsBytes: trustRoots).map { trustRoots in
networkConfig.consensusTrustRoots = trustRoots
Self.parseTrustRoots(trustRootsBytes: trustRoots).map {
networkConfig.consensusTrustRoots = $0
}
}
public mutating func setFogTrustRoots(_ trustRoots: [Data]) -> Result<(), InvalidInputError>
{
Self.parseTrustRoots(trustRootsBytes: trustRoots).map { trustRoots in
networkConfig.fogTrustRoots = trustRoots
Self.parseTrustRoots(trustRootsBytes: trustRoots).map {
networkConfig.fogTrustRoots = $0
}
}
public mutating func setConsensusBasicAuthorization(username: String, password: String) {
networkConfig.consensusAuthorization =
BasicCredentials(username: username, password: password)
}
public mutating func setFogBasicAuthorization(username: String, password: String) {
networkConfig.fogUserAuthorization =
BasicCredentials(username: username, password: password)
}
public var httpRequester: HttpRequester? {
get { networkConfig.httpRequester }
set { networkConfig.httpRequester = newValue }
}
private static func parseTrustRoots(trustRootsBytes: [Data])
-> Result<[NIOSSLCertificate], InvalidInputError>
{
@ -400,15 +397,5 @@ extension MobileCoinClient {
}
return .success(trustRoots)
}
public mutating func setConsensusBasicAuthorization(username: String, password: String) {
networkConfig.consensusAuthorization =
BasicCredentials(username: username, password: password)
}
public mutating func setFogBasicAuthorization(username: String, password: String) {
networkConfig.fogUserAuthorization =
BasicCredentials(username: username, password: password)
}
}
}

View File

@ -6,25 +6,25 @@ import Foundation
import NIOSSL
protocol AttestedConnectionConfigProtocol: ConnectionConfigProtocol {
var url: MobileCoinUrlProtocol { get }
var attestation: Attestation { get }
var trustRoots: [NIOSSLCertificate]? { get }
var authorization: BasicCredentials? { get }
}
struct AttestedConnectionConfig<Url: MobileCoinUrlProtocol>: AttestedConnectionConfigProtocol {
let urlTyped: Url
let transportProtocolOption: TransportProtocol.Option
let attestation: Attestation
let trustRoots: [NIOSSLCertificate]?
let authorization: BasicCredentials?
init(
url: Url,
transportProtocolOption: TransportProtocol.Option,
attestation: Attestation,
trustRoots: [NIOSSLCertificate]?,
authorization: BasicCredentials?
) {
self.urlTyped = url
self.transportProtocolOption = transportProtocolOption
self.attestation = attestation
self.trustRoots = trustRoots
self.authorization = authorization

View File

@ -3,78 +3,37 @@
//
import Foundation
import GRPC
class ArbitraryConnection {
class ArbitraryConnection<GrpcService, HttpService> {
private let inner: SerialDispatchLock<Inner>
init(url: MobileCoinUrlProtocol, targetQueue: DispatchQueue?) {
let inner = Inner(url: url)
private let connectionOptionWrapperFactory: (TransportProtocol.Option)
-> ConnectionOptionWrapper<GrpcService, HttpService>
init(
connectionOptionWrapperFactory: @escaping (TransportProtocol.Option)
-> ConnectionOptionWrapper<GrpcService, HttpService>,
transportProtocolOption: TransportProtocol.Option,
targetQueue: DispatchQueue?
) {
self.connectionOptionWrapperFactory = connectionOptionWrapperFactory
let connectionOptionWrapper = connectionOptionWrapperFactory(transportProtocolOption)
let inner = Inner(connectionOptionWrapper: connectionOptionWrapper)
self.inner = .init(inner, targetQueue: targetQueue)
}
func performCall<Call: GrpcCallable>(
_ call: Call,
request: Call.Request,
completion: @escaping (Result<Call.Response, ConnectionError>) -> Void
) {
func performCallCallback(callResult: UnaryCallResult<Call.Response>) {
inner.accessAsync {
let result = $0.processResponse(callResult: callResult)
switch result {
case .success:
logger.info("Call complete. url: \($0.url)", logFunction: false)
case .failure(let connectionError):
let errorMessage =
"Connection failure. url: \($0.url), error: \(connectionError)"
switch connectionError {
case .connectionFailure, .serverRateLimited:
logger.warning(errorMessage, logFunction: false)
case .authorizationFailure, .invalidServerResponse,
.attestationVerificationFailed, .outdatedClient:
logger.error(errorMessage, logFunction: false)
}
}
completion(result)
}
}
inner.accessAsync {
logger.info("Performing call... url: \($0.url)", logFunction: false)
let callOptions = $0.requestCallOptions()
call.call(request: request, callOptions: callOptions, completion: performCallCallback)
}
func setTransportProtocolOption(_ transportProtocolOption: TransportProtocol.Option) {
let connectionOptionWrapper = connectionOptionWrapperFactory(transportProtocolOption)
inner.accessAsync { $0.connectionOptionWrapper = connectionOptionWrapper }
}
}
extension ArbitraryConnection {
private struct Inner {
let url: MobileCoinUrlProtocol
private let session: ConnectionSession
var connectionOptionWrapper: ConnectionOptionWrapper<GrpcService, HttpService>
init(url: MobileCoinUrlProtocol) {
self.url = url
self.session = ConnectionSession(url: url)
}
func requestCallOptions() -> CallOptions {
var callOptions = CallOptions()
session.addRequestHeaders(to: &callOptions.customMetadata)
return callOptions
}
func processResponse<Response>(callResult: UnaryCallResult<Response>)
-> Result<Response, ConnectionError>
{
guard callResult.status.isOk, let response = callResult.response else {
return .failure(.connectionFailure(String(describing: callResult.status)))
}
if let initialMetadata = callResult.initialMetadata {
session.processResponse(headers: initialMetadata)
}
return .success(response)
init(connectionOptionWrapper: ConnectionOptionWrapper<GrpcService, HttpService>) {
self.connectionOptionWrapper = connectionOptionWrapper
}
}
}

View File

@ -2,363 +2,53 @@
// Copyright (c) 2020-2021 MobileCoin. All rights reserved.
//
// swiftlint:disable closure_body_length cyclomatic_complexity function_body_length
// swiftlint:disable multiline_function_chains operator_usage_whitespace
import Foundation
import GRPC
import LibMobileCoin
enum AttestedConnectionError: Error {
case connectionError(ConnectionError)
case attestationFailure(String = String())
}
extension AttestedConnectionError: CustomStringConvertible {
var description: String {
"Attested connection error: " + {
switch self {
case .connectionError(let connectionError):
return "\(connectionError)"
case .attestationFailure(let reason):
return "Attestation failure\(!reason.isEmpty ? ": \(reason)" : "")"
}
}()
}
}
class AttestedConnection {
private let inner: SerialCallbackLock<Inner>
init(
client: AttestableGrpcClient,
config: AttestedConnectionConfigProtocol,
targetQueue: DispatchQueue?,
rng: (@convention(c) (UnsafeMutableRawPointer?) -> UInt64)? = securityRNG,
rngContext: Any? = nil
) {
let inner = Inner(client: client, config: config, rng: rng, rngContext: rngContext)
self.inner = .init(inner, targetQueue: targetQueue)
}
func setAuthorization(credentials: BasicCredentials) {
inner.priorityAccessAsync {
$0.setAuthorization(credentials: credentials)
}
}
func performAttestedCall<Call: AttestedGrpcCallable>(
_ call: Call,
request: Call.InnerRequest,
completion: @escaping (Result<Call.InnerResponse, ConnectionError>) -> Void
) where Call.InnerRequestAad == (), Call.InnerResponseAad == () {
performAttestedCall(call, requestAad: (), request: request, completion: completion)
}
func performAttestedCall<Call: AttestedGrpcCallable>(
_ call: Call,
requestAad: Call.InnerRequestAad,
request: Call.InnerRequest,
completion: @escaping (Result<Call.InnerResponse, ConnectionError>) -> Void
) where Call.InnerResponseAad == () {
performAttestedCall(call, requestAad: requestAad, request: request) {
completion($0.map { $0.response })
}
}
func performAttestedCall<Call: AttestedGrpcCallable>(
_ call: Call,
request: Call.InnerRequest,
completion: @escaping (
Result<(responseAad: Call.InnerResponseAad, response: Call.InnerResponse),
ConnectionError>
) -> Void
) where Call.InnerRequestAad == () {
performAttestedCall(call, requestAad: (), request: request, completion: completion)
}
func performAttestedCall<Call: AttestedGrpcCallable>(
_ call: Call,
requestAad: Call.InnerRequestAad,
request: Call.InnerRequest,
completion: @escaping (
Result<(responseAad: Call.InnerResponseAad, response: Call.InnerResponse),
ConnectionError>
) -> Void
) {
inner.accessAsync(block: { inner, callback in
inner.performAttestedCallWithAuth(
call,
requestAad: requestAad,
request: request,
completion: callback)
}, completion: completion)
}
}
extension AttestedConnection {
// Note: Because `SerialCallbackLock` is being used to wrap `AttestedConnection.Inner`, calls
// to `AttestedConnection.Inner` have exclusive access (other calls will be queued up) until the
// executing call invokes the completion handler that returns control back to
// `AttestedConnection`, at which point the block passed to the async `SerialCallbackLock`
// method that invoked the call to inner will complete and the next async `SerialCallbackLock`
// access block will execute.
//
// This means that calls to `AttestedConnection.Inner` can assume thread-safety until the call
// invokes the completion handler.
private struct Inner {
private let url: MobileCoinUrlProtocol
private let session: ConnectionSession
private let client: AttestableGrpcClient
private let attestAke: AttestAke
private let responderId: String
private let attestationVerifier: AttestationVerifier
private let rng: (@convention(c) (UnsafeMutableRawPointer?) -> UInt64)?
private let rngContext: Any?
init(
client: AttestableGrpcClient,
config: AttestedConnectionConfigProtocol,
rng: (@convention(c) (UnsafeMutableRawPointer?) -> UInt64)? = securityRNG,
rngContext: Any? = nil
) {
self.url = config.url
self.session = ConnectionSession(config: config)
self.client = client
self.attestAke = AttestAke()
self.responderId = config.url.responderId
self.attestationVerifier = AttestationVerifier(attestation: config.attestation)
self.rng = rng
self.rngContext = rngContext
}
func setAuthorization(credentials: BasicCredentials) {
session.authorizationCredentials = credentials
}
func performAttestedCallWithAuth<Call: AttestedGrpcCallable>(
_ call: Call,
requestAad: Call.InnerRequestAad,
request: Call.InnerRequest,
completion: @escaping (
Result<(responseAad: Call.InnerResponseAad, response: Call.InnerResponse),
ConnectionError>
) -> Void
) {
doPerformAttestedCallWithAuth(
call,
requestAad: requestAad,
request: request,
attestAkeCipher: attestAke.cipher.map { ($0, freshCipher: false) },
completion: completion)
}
private func doPerformAttestedCallWithAuth<Call: AttestedGrpcCallable>(
_ call: Call,
requestAad: Call.InnerRequestAad,
request: Call.InnerRequest,
attestAkeCipher: (AttestAke.Cipher, freshCipher: Bool)?,
completion: @escaping (
Result<(responseAad: Call.InnerResponseAad, response: Call.InnerResponse),
ConnectionError>
) -> Void
) {
if let (attestAkeCipher, freshCipher) = attestAkeCipher {
logger.info(
"Performing attested call... url: \(self.url)",
logFunction: false)
doPerformAttestedCall(
call,
requestAad: requestAad,
request: request,
attestAkeCipher: attestAkeCipher
) {
switch $0 {
case .success(let response):
logger.info(
"Attested call successful. url: \(self.url)",
logFunction: false)
completion(.success(response))
case .failure(.connectionError(let connectionError)):
let errorMessage = "Connection failure while performing attested call. " +
"url: \(self.url), error: \(connectionError)"
switch connectionError {
case .connectionFailure, .serverRateLimited:
logger.warning(errorMessage, logFunction: false)
case .authorizationFailure, .invalidServerResponse,
.attestationVerificationFailed, .outdatedClient:
logger.error(errorMessage, logFunction: false)
}
completion(.failure(connectionError))
case .failure(.attestationFailure):
self.attestAke.deattest()
if freshCipher {
let errorMessage =
"Attestation failure with fresh auth. url: \(self.url)"
logger.warning(errorMessage, logFunction: false)
completion(.failure(.invalidServerResponse(errorMessage)))
} else {
logger.info(
"Attestation failure using cached auth, reattesting... url: " +
"\(self.url)",
logFunction: false)
self.doPerformAttestedCallWithAuth(
call,
requestAad: requestAad,
request: request,
attestAkeCipher: nil,
completion: completion)
}
}
}
} else {
logger.info(
"Peforming attestation... url: \(url)",
logFunction: false)
doPerformAuthCall {
switch $0 {
case .success(let attestAkeCipher):
logger.info(
"Attestation successful. url: \(self.url)",
logFunction: false)
self.doPerformAttestedCallWithAuth(
call,
requestAad: requestAad,
request: request,
attestAkeCipher: (attestAkeCipher, freshCipher: true),
completion: completion)
case .failure(let connectionError):
let errorMessage = "Connection failure while performing attestation. " +
"url: \(self.url), error: \(connectionError)"
switch connectionError {
case .connectionFailure, .serverRateLimited:
logger.warning(errorMessage, logFunction: false)
case .authorizationFailure, .invalidServerResponse,
.attestationVerificationFailed, .outdatedClient:
logger.error(errorMessage, logFunction: false)
}
completion(.failure(connectionError))
}
}
}
}
private func doPerformAuthCall(
completion: @escaping (Result<AttestAke.Cipher, ConnectionError>) -> Void
) {
let request = attestAke.authBeginRequest(
responderId: responderId,
rng: rng,
rngContext: rngContext)
doPerformCall(
AuthGrpcCallableWrapper(authCallable: client.authCallable),
request: request
) {
completion(
$0.mapError {
switch $0 {
case .connectionError(let connectionError):
return connectionError
case .attestationFailure:
self.attestAke.deattest()
return .invalidServerResponse(
"Attestation failure during auth. url: \(self.url)")
}
}.flatMap { response in
self.attestAke.authEnd(
authResponse: response,
attestationVerifier: self.attestationVerifier
).mapError {
switch $0 {
case .invalidInput(let reason):
return .invalidServerResponse(reason)
case .attestationVerificationFailed(let reason):
return .attestationVerificationFailed(reason)
}
}
})
}
}
private func doPerformAttestedCall<Call: AttestedGrpcCallable>(
_ call: Call,
requestAad: Call.InnerRequestAad,
request: Call.InnerRequest,
attestAkeCipher: AttestAke.Cipher,
completion: @escaping (
Result<(responseAad: Call.InnerResponseAad, response: Call.InnerResponse),
AttestedConnectionError>
) -> Void
) {
guard let processedRequest =
call.processRequest(
requestAad: requestAad,
request: request,
attestAkeCipher: attestAkeCipher)
.mapError({ _ in .attestationFailure() })
.successOr(completion: completion)
else { return }
doPerformCall(call, request: processedRequest) {
completion($0.flatMap { response in
call.processResponse(response: response, attestAkeCipher: attestAkeCipher)
})
}
}
private func doPerformCall<Call: GrpcCallable>(
_ call: Call,
request: Call.Request,
completion: @escaping (Result<Call.Response, AttestedConnectionError>) -> Void
) {
let callOptions = requestCallOptions()
call.call(request: request, callOptions: callOptions) {
completion(self.processResponse(callResult: $0))
}
}
private func requestCallOptions() -> CallOptions {
var callOptions = CallOptions()
session.addRequestHeaders(to: &callOptions.customMetadata)
return callOptions
}
private func processResponse<Response>(callResult: UnaryCallResult<Response>)
-> Result<Response, AttestedConnectionError>
{
// Basic credential authorization failure
guard callResult.status.code != .unauthenticated else {
return .failure(.connectionError(.authorizationFailure("url: \(url)")))
}
// Attestation failure, reattest
guard callResult.status.code != .permissionDenied else {
return .failure(.attestationFailure())
}
guard callResult.status.isOk, let response = callResult.response else {
return .failure(.connectionError(
.connectionFailure("url: \(url), status: \(callResult.status)")))
}
if let initialMetadata = callResult.initialMetadata {
session.processResponse(headers: initialMetadata)
}
return .success(response)
}
}
}
//class AttestedConnection<Service> {
// private let inner: SerialDispatchLock<Inner>
//
// init(connectionOptionWrapper: ConnectionOptionWrapper<Service>, targetQueue: DispatchQueue?) {
// let inner = Inner(connectionOptionWrapper: connectionOptionWrapper)
// self.inner = .init(inner, targetQueue: targetQueue)
// }
//
// func setConnectionOptionWrapper(_ connectionOptionWrapper: ConnectionOptionWrapper<Service>) {
// inner.accessAsync { $0.connectionOptionWrapper = connectionOptionWrapper }
// }
//
// func setAuthorization(credentials: BasicCredentials) {
// inner.accessAsync { $0.setAuthorization(credentials: credentials) }
// }
//}
//
//extension AttestedConnection {
// private struct Inner {
// var connectionOptionWrapper: ConnectionOptionWrapper<ServiceType> {
// didSet {
// if let credentials = authorizationCredentials {
// switch connectionOptionWrapper {
// case .grpc(grpcService: let grpcService):
// grpcService.setAuthorization(credentials: credentials)
// case .http(httpService: let httpService):
// httpService.setAuthorization(credentials: credentials)
// }
// }
// }
// }
// private var authorizationCredentials: BasicCredentials?
//
// init(connectionOptionWrapper: ConnectionOptionWrapper<ServiceType>) {
// self.connectionOptionWrapper = connectionOptionWrapper
// }
//
// mutating func setAuthorization(credentials: BasicCredentials) {
// self.authorizationCredentials = credentials
// switch connectionOptionWrapper {
// case .grpc(grpcService: let grpcService):
// grpcService.setAuthorization(credentials: credentials)
// case .http(httpService: let httpService):
// httpService.setAuthorization(credentials: credentials)
// }
// }
// }
//}

View File

@ -3,100 +3,64 @@
//
import Foundation
import GRPC
class Connection {
class Connection<GrpcService: ConnectionProtocol, HttpService: ConnectionProtocol> {
private let inner: SerialDispatchLock<Inner>
init(config: ConnectionConfigProtocol, targetQueue: DispatchQueue?) {
let inner = Inner(config: config)
private let connectionOptionWrapperFactory: (TransportProtocol.Option)
-> ConnectionOptionWrapper<GrpcService, HttpService>
init(
connectionOptionWrapperFactory: @escaping (TransportProtocol.Option)
-> ConnectionOptionWrapper<GrpcService, HttpService>,
transportProtocolOption: TransportProtocol.Option,
targetQueue: DispatchQueue?
) {
self.connectionOptionWrapperFactory = connectionOptionWrapperFactory
let connectionOptionWrapper = connectionOptionWrapperFactory(transportProtocolOption)
let inner = Inner(connectionOptionWrapper: connectionOptionWrapper)
self.inner = .init(inner, targetQueue: targetQueue)
}
func setTransportProtocolOption(_ transportProtocolOption: TransportProtocol.Option) {
let connectionOptionWrapper = connectionOptionWrapperFactory(transportProtocolOption)
inner.accessAsync { $0.connectionOptionWrapper = connectionOptionWrapper }
}
func setAuthorization(credentials: BasicCredentials) {
inner.accessAsync {
$0.setAuthorization(credentials: credentials)
}
}
func performCall<Call: GrpcCallable>(
_ call: Call,
request: Call.Request,
completion: @escaping (Result<Call.Response, ConnectionError>) -> Void
) {
func performCallCallback(callResult: UnaryCallResult<Call.Response>) {
inner.accessAsync {
let result = $0.processResponse(callResult: callResult)
switch result {
case .success:
logger.info("Call complete. url: \($0.url)", logFunction: false)
case .failure(let connectionError):
let errorMessage =
"Connection failure. url: \($0.url), error: \(connectionError)"
switch connectionError {
case .connectionFailure, .serverRateLimited:
logger.warning(errorMessage, logFunction: false)
case .authorizationFailure, .invalidServerResponse,
.attestationVerificationFailed, .outdatedClient:
logger.error(errorMessage, logFunction: false)
}
}
completion(result)
}
}
inner.accessAsync {
logger.info("Performing call... url: \($0.url)", logFunction: false)
let callOptions = $0.requestCallOptions()
call.call(request: request, callOptions: callOptions, completion: performCallCallback)
}
}
func performCall<Call: GrpcCallable>(
_ call: Call,
completion: @escaping (Result<Call.Response, ConnectionError>) -> Void
) where Call.Request == () {
performCall(call, request: (), completion: completion)
inner.accessAsync { $0.setAuthorization(credentials: credentials) }
}
}
extension Connection {
private struct Inner {
let url: MobileCoinUrlProtocol
private let session: ConnectionSession
init(config: ConnectionConfigProtocol) {
self.url = config.url
self.session = ConnectionSession(config: config)
var connectionOptionWrapper: ConnectionOptionWrapper<GrpcService, HttpService> {
didSet {
if let credentials = authorizationCredentials {
switch connectionOptionWrapper {
case .grpc(grpcService: let grpcService):
grpcService.setAuthorization(credentials: credentials)
case .http(httpService: let httpService):
httpService.setAuthorization(credentials: credentials)
}
}
}
}
func setAuthorization(credentials: BasicCredentials) {
session.authorizationCredentials = credentials
private var authorizationCredentials: BasicCredentials?
init(connectionOptionWrapper: ConnectionOptionWrapper<GrpcService, HttpService>) {
self.connectionOptionWrapper = connectionOptionWrapper
}
func requestCallOptions() -> CallOptions {
var callOptions = CallOptions()
session.addRequestHeaders(to: &callOptions.customMetadata)
return callOptions
}
func processResponse<Response>(callResult: UnaryCallResult<Response>)
-> Result<Response, ConnectionError>
{
guard callResult.status.code != .unauthenticated else {
return .failure(.authorizationFailure("url: \(url)"))
mutating func setAuthorization(credentials: BasicCredentials) {
self.authorizationCredentials = credentials
switch connectionOptionWrapper {
case .grpc(grpcService: let grpcService):
grpcService.setAuthorization(credentials: credentials)
case .http(httpService: let httpService):
httpService.setAuthorization(credentials: credentials)
}
guard callResult.status.isOk, let response = callResult.response else {
return .failure(.connectionFailure("url: \(url), status: \(callResult.status)"))
}
if let initialMetadata = callResult.initialMetadata {
session.processResponse(headers: initialMetadata)
}
return .success(response)
}
}
}

View File

@ -0,0 +1,10 @@
//
// Copyright (c) 2020-2021 MobileCoin. All rights reserved.
//
import Foundation
enum ConnectionOptionWrapper<GrpcService, HttpService> {
case grpc(grpcService: GrpcService)
case http(httpService: HttpService)
}

View File

@ -0,0 +1,9 @@
//
// Copyright (c) 2020-2021 MobileCoin. All rights reserved.
//
import Foundation
protocol ConnectionProtocol {
func setAuthorization(credentials: BasicCredentials)
}

View File

@ -7,39 +7,42 @@ import GRPC
import LibMobileCoin
import SwiftProtobuf
final class BlockchainConnection: Connection, BlockchainService {
private let client: ConsensusCommon_BlockchainAPIClient
final class BlockchainConnection:
Connection<BlockchainGrpcConnection, BlockchainHttpConnection>, BlockchainService
{
private let config: ConnectionConfig<ConsensusUrl>
private let channelManager: GrpcChannelManager
private let targetQueue: DispatchQueue?
init(
config: ConnectionConfig<ConsensusUrl>,
channelManager: GrpcChannelManager,
targetQueue: DispatchQueue?
) {
let channel = channelManager.channel(for: config)
self.client = ConsensusCommon_BlockchainAPIClient(channel: channel)
super.init(config: config, targetQueue: targetQueue)
self.config = config
self.channelManager = channelManager
self.targetQueue = targetQueue
super.init(
connectionOptionWrapperFactory: { transportProtocolOption in
switch transportProtocolOption {
case .grpc:
return .grpc(
grpcService: BlockchainGrpcConnection(
config: config,
channelManager: channelManager,
targetQueue: targetQueue))
case .http:
return .http(httpService: BlockchainHttpConnection())
}
},
transportProtocolOption: config.transportProtocolOption,
targetQueue: targetQueue)
}
func getLastBlockInfo(
completion:
@escaping (Result<ConsensusCommon_LastBlockInfoResponse, ConnectionError>) -> Void
) {
performCall(GetLastBlockInfoCall(client: client), completion: completion)
}
}
extension BlockchainConnection {
private struct GetLastBlockInfoCall: GrpcCallable {
let client: ConsensusCommon_BlockchainAPIClient
func call(
request: (),
callOptions: CallOptions?,
completion: @escaping (UnaryCallResult<ConsensusCommon_LastBlockInfoResponse>) -> Void
) {
let unaryCall =
client.getLastBlockInfo(Google_Protobuf_Empty(), callOptions: callOptions)
unaryCall.callResult.whenSuccess(completion)
}
}
}

View File

@ -6,8 +6,14 @@ import Foundation
import GRPC
import LibMobileCoin
final class ConsensusConnection: AttestedConnection, ConsensusService {
private let client: ConsensusClient_ConsensusClientAPIClient
final class ConsensusConnection:
Connection<ConsensusGrpcConnection, ConsensusHttpConnection>, ConsensusService
{
private let config: AttestedConnectionConfig<ConsensusUrl>
private let channelManager: GrpcChannelManager
private let targetQueue: DispatchQueue?
private let rng: (@convention(c) (UnsafeMutableRawPointer?) -> UInt64)?
private let rngContext: Any?
init(
config: AttestedConnectionConfig<ConsensusUrl>,
@ -16,43 +22,34 @@ final class ConsensusConnection: AttestedConnection, ConsensusService {
rng: (@convention(c) (UnsafeMutableRawPointer?) -> UInt64)? = securityRNG,
rngContext: Any? = nil
) {
let channel = channelManager.channel(for: config)
self.client = ConsensusClient_ConsensusClientAPIClient(channel: channel)
self.config = config
self.channelManager = channelManager
self.targetQueue = targetQueue
self.rng = rng
self.rngContext = rngContext
super.init(
client: Attest_AttestedApiClient(channel: channel),
config: config,
targetQueue: targetQueue,
rng: rng,
rngContext: rngContext)
connectionOptionWrapperFactory: { transportProtocolOption in
switch transportProtocolOption {
case .grpc:
return .grpc(
grpcService: ConsensusGrpcConnection(
config: config,
channelManager: channelManager,
targetQueue: targetQueue,
rng: rng,
rngContext: rngContext))
case .http:
return .http(httpService: ConsensusHttpConnection())
}
},
transportProtocolOption: config.transportProtocolOption,
targetQueue: targetQueue)
}
func proposeTx(
_ tx: External_Tx,
completion: @escaping (Result<ConsensusCommon_ProposeTxResponse, ConnectionError>) -> Void
) {
performAttestedCall(
ProposeTxCall(client: client),
request: tx,
completion: completion)
}
}
extension ConsensusConnection {
private struct ProposeTxCall: AttestedGrpcCallable {
typealias InnerRequest = External_Tx
typealias InnerResponse = ConsensusCommon_ProposeTxResponse
let client: ConsensusClient_ConsensusClientAPIClient
func call(
request: Attest_Message,
callOptions: CallOptions?,
completion: @escaping (UnaryCallResult<ConsensusCommon_ProposeTxResponse>) -> Void
) {
let unaryCall = client.clientTxPropose(request, callOptions: callOptions)
unaryCall.callResult.whenSuccess(completion)
}
}
}
extension Attest_AttestedApiClient: AuthGrpcCallableClient {}

View File

@ -6,38 +6,42 @@ import Foundation
import GRPC
import LibMobileCoin
final class FogBlockConnection: Connection, FogBlockService {
private let client: FogLedger_FogBlockAPIClient
final class FogBlockConnection:
Connection<FogBlockGrpcConnection, FogBlockHttpConnection>, FogBlockService
{
private let config: ConnectionConfig<FogUrl>
private let channelManager: GrpcChannelManager
private let targetQueue: DispatchQueue?
init(
config: ConnectionConfig<FogUrl>,
channelManager: GrpcChannelManager,
targetQueue: DispatchQueue?
) {
let channel = channelManager.channel(for: config)
self.client = FogLedger_FogBlockAPIClient(channel: channel)
super.init(config: config, targetQueue: targetQueue)
self.config = config
self.channelManager = channelManager
self.targetQueue = targetQueue
super.init(
connectionOptionWrapperFactory: { transportProtocolOption in
switch transportProtocolOption {
case .grpc:
return .grpc(
grpcService: FogBlockGrpcConnection(
config: config,
channelManager: channelManager,
targetQueue: targetQueue))
case .http:
return .http(httpService: FogBlockHttpConnection())
}
},
transportProtocolOption: config.transportProtocolOption,
targetQueue: targetQueue)
}
func getBlocks(
request: FogLedger_BlockRequest,
completion: @escaping (Result<FogLedger_BlockResponse, ConnectionError>) -> Void
) {
performCall(GetBlocksCall(client: client), request: request, completion: completion)
}
}
extension FogBlockConnection {
private struct GetBlocksCall: GrpcCallable {
let client: FogLedger_FogBlockAPIClient
func call(
request: FogLedger_BlockRequest,
callOptions: CallOptions?,
completion: @escaping (UnaryCallResult<FogLedger_BlockResponse>) -> Void
) {
let unaryCall = client.getBlocks(request, callOptions: callOptions)
unaryCall.callResult.whenSuccess(completion)
}
}
}

View File

@ -6,8 +6,14 @@ import Foundation
import GRPC
import LibMobileCoin
final class FogKeyImageConnection: AttestedConnection, FogKeyImageService {
private let client: FogLedger_FogKeyImageAPIClient
final class FogKeyImageConnection:
Connection<FogKeyImageGrpcConnection, FogKeyImageHttpConnection>, FogKeyImageService
{
private let config: AttestedConnectionConfig<FogUrl>
private let channelManager: GrpcChannelManager
private let targetQueue: DispatchQueue?
private let rng: (@convention(c) (UnsafeMutableRawPointer?) -> UInt64)?
private let rngContext: Any?
init(
config: AttestedConnectionConfig<FogUrl>,
@ -16,43 +22,34 @@ final class FogKeyImageConnection: AttestedConnection, FogKeyImageService {
rng: (@convention(c) (UnsafeMutableRawPointer?) -> UInt64)? = securityRNG,
rngContext: Any? = nil
) {
let channel = channelManager.channel(for: config)
self.client = FogLedger_FogKeyImageAPIClient(channel: channel)
self.config = config
self.channelManager = channelManager
self.targetQueue = targetQueue
self.rng = rng
self.rngContext = rngContext
super.init(
client: self.client,
config: config,
targetQueue: targetQueue,
rng: rng,
rngContext: rngContext)
connectionOptionWrapperFactory: { transportProtocolOption in
switch transportProtocolOption {
case .grpc:
return .grpc(
grpcService: FogKeyImageGrpcConnection(
config: config,
channelManager: channelManager,
targetQueue: targetQueue,
rng: rng,
rngContext: rngContext))
case .http:
return .http(httpService: FogKeyImageHttpConnection())
}
},
transportProtocolOption: config.transportProtocolOption,
targetQueue: targetQueue)
}
func checkKeyImages(
request: FogLedger_CheckKeyImagesRequest,
completion: @escaping (Result<FogLedger_CheckKeyImagesResponse, ConnectionError>) -> Void
) {
performAttestedCall(
CheckKeyImagesCall(client: client),
request: request,
completion: completion)
}
}
extension FogKeyImageConnection {
private struct CheckKeyImagesCall: AttestedGrpcCallable {
typealias InnerRequest = FogLedger_CheckKeyImagesRequest
typealias InnerResponse = FogLedger_CheckKeyImagesResponse
let client: FogLedger_FogKeyImageAPIClient
func call(
request: Attest_Message,
callOptions: CallOptions?,
completion: @escaping (UnaryCallResult<Attest_Message>) -> Void
) {
let unaryCall = client.checkKeyImages(request, callOptions: callOptions)
unaryCall.callResult.whenSuccess(completion)
}
}
}
extension FogLedger_FogKeyImageAPIClient: AuthGrpcCallableClient {}

View File

@ -6,8 +6,14 @@ import Foundation
import GRPC
import LibMobileCoin
final class FogMerkleProofConnection: AttestedConnection, FogMerkleProofService {
private let client: FogLedger_FogMerkleProofAPIClient
final class FogMerkleProofConnection:
Connection<FogMerkleProofGrpcConnection, FogMerkleProofHttpConnection>, FogMerkleProofService
{
private let config: AttestedConnectionConfig<FogUrl>
private let channelManager: GrpcChannelManager
private let targetQueue: DispatchQueue?
private let rng: (@convention(c) (UnsafeMutableRawPointer?) -> UInt64)?
private let rngContext: Any?
init(
config: AttestedConnectionConfig<FogUrl>,
@ -16,43 +22,34 @@ final class FogMerkleProofConnection: AttestedConnection, FogMerkleProofService
rng: (@convention(c) (UnsafeMutableRawPointer?) -> UInt64)? = securityRNG,
rngContext: Any? = nil
) {
let channel = channelManager.channel(for: config)
self.client = FogLedger_FogMerkleProofAPIClient(channel: channel)
self.config = config
self.channelManager = channelManager
self.targetQueue = targetQueue
self.rng = rng
self.rngContext = rngContext
super.init(
client: self.client,
config: config,
targetQueue: targetQueue,
rng: rng,
rngContext: rngContext)
connectionOptionWrapperFactory: { transportProtocolOption in
switch transportProtocolOption {
case .grpc:
return .grpc(
grpcService: FogMerkleProofGrpcConnection(
config: config,
channelManager: channelManager,
targetQueue: targetQueue,
rng: rng,
rngContext: rngContext))
case .http:
return .http(httpService: FogMerkleProofHttpConnection())
}
},
transportProtocolOption: config.transportProtocolOption,
targetQueue: targetQueue)
}
func getOutputs(
request: FogLedger_GetOutputsRequest,
completion: @escaping (Result<FogLedger_GetOutputsResponse, ConnectionError>) -> Void
) {
performAttestedCall(
GetOutputsCall(client: client),
request: request,
completion: completion)
}
}
extension FogMerkleProofConnection {
private struct GetOutputsCall: AttestedGrpcCallable {
typealias InnerRequest = FogLedger_GetOutputsRequest
typealias InnerResponse = FogLedger_GetOutputsResponse
let client: FogLedger_FogMerkleProofAPIClient
func call(
request: Attest_Message,
callOptions: CallOptions?,
completion: @escaping (UnaryCallResult<Attest_Message>) -> Void
) {
let unaryCall = client.getOutputs(request, callOptions: callOptions)
unaryCall.callResult.whenSuccess(completion)
}
}
}
extension FogLedger_FogMerkleProofAPIClient: AuthGrpcCallableClient {}

View File

@ -6,34 +6,43 @@ import Foundation
import GRPC
import LibMobileCoin
final class FogReportConnection: ArbitraryConnection, FogReportService {
private let client: Report_ReportAPIClient
final class FogReportConnection:
ArbitraryConnection<FogReportGrpcConnection, FogReportHttpConnection>, FogReportService
{
private let url: FogUrl
private let channelManager: GrpcChannelManager
private let targetQueue: DispatchQueue?
init(url: FogUrl, channelManager: GrpcChannelManager, targetQueue: DispatchQueue?) {
let channel = channelManager.channel(for: url)
self.client = Report_ReportAPIClient(channel: channel)
super.init(url: url, targetQueue: targetQueue)
init(
url: FogUrl,
transportProtocolOption: TransportProtocol.Option,
channelManager: GrpcChannelManager,
targetQueue: DispatchQueue?
) {
self.url = url
self.channelManager = channelManager
self.targetQueue = targetQueue
super.init(
connectionOptionWrapperFactory: { transportProtocolOption in
switch transportProtocolOption {
case .grpc:
return .grpc(
grpcService: FogReportGrpcConnection(
url: url,
channelManager: channelManager,
targetQueue: targetQueue))
case .http:
return .http(httpService: FogReportHttpConnection())
}
},
transportProtocolOption: transportProtocolOption,
targetQueue: targetQueue)
}
func getReports(
request: Report_ReportRequest,
completion: @escaping (Result<Report_ReportResponse, ConnectionError>) -> Void
) {
performCall(GetReportsCall(client: client), request: request, completion: completion)
}
}
extension FogReportConnection {
private struct GetReportsCall: GrpcCallable {
let client: Report_ReportAPIClient
func call(
request: Report_ReportRequest,
callOptions: CallOptions?,
completion: @escaping (UnaryCallResult<Report_ReportResponse>) -> Void
) {
let unaryCall = client.getReports(request, callOptions: callOptions)
unaryCall.callResult.whenSuccess(completion)
}
}
}

View File

@ -6,38 +6,43 @@ import Foundation
import GRPC
import LibMobileCoin
final class FogUntrustedTxOutConnection: Connection, FogUntrustedTxOutService {
private let client: FogLedger_FogUntrustedTxOutApiClient
final class FogUntrustedTxOutConnection:
Connection<FogUntrustedTxOutGrpcConnection, FogUntrustedTxOutHttpConnection>,
FogUntrustedTxOutService
{
private let config: ConnectionConfig<FogUrl>
private let channelManager: GrpcChannelManager
private let targetQueue: DispatchQueue?
init(
config: ConnectionConfig<FogUrl>,
channelManager: GrpcChannelManager,
targetQueue: DispatchQueue?
) {
let channel = channelManager.channel(for: config)
self.client = FogLedger_FogUntrustedTxOutApiClient(channel: channel)
super.init(config: config, targetQueue: targetQueue)
self.config = config
self.channelManager = channelManager
self.targetQueue = targetQueue
super.init(
connectionOptionWrapperFactory: { transportProtocolOption in
switch transportProtocolOption {
case .grpc:
return .grpc(
grpcService: FogUntrustedTxOutGrpcConnection(
config: config,
channelManager: channelManager,
targetQueue: targetQueue))
case .http:
return .http(httpService: FogUntrustedTxOutHttpConnection())
}
},
transportProtocolOption: config.transportProtocolOption,
targetQueue: targetQueue)
}
func getTxOuts(
request: FogLedger_TxOutRequest,
completion: @escaping (Result<FogLedger_TxOutResponse, ConnectionError>) -> Void
) {
performCall(GetTxOutsCall(client: client), request: request, completion: completion)
}
}
extension FogUntrustedTxOutConnection {
private struct GetTxOutsCall: GrpcCallable {
let client: FogLedger_FogUntrustedTxOutApiClient
func call(
request: FogLedger_TxOutRequest,
callOptions: CallOptions?,
completion: @escaping (UnaryCallResult<FogLedger_TxOutResponse>) -> Void
) {
let unaryCall = client.getTxOuts(request, callOptions: callOptions)
unaryCall.callResult.whenSuccess(completion)
}
}
}

View File

@ -6,8 +6,14 @@ import Foundation
import GRPC
import LibMobileCoin
final class FogViewConnection: AttestedConnection, FogViewService {
private let client: FogView_FogViewAPIClient
final class FogViewConnection:
Connection<FogViewGrpcConnection, FogViewHttpConnection>, FogViewService
{
private let config: AttestedConnectionConfig<FogUrl>
private let channelManager: GrpcChannelManager
private let targetQueue: DispatchQueue?
private let rng: (@convention(c) (UnsafeMutableRawPointer?) -> UInt64)?
private let rngContext: Any?
init(
config: AttestedConnectionConfig<FogUrl>,
@ -16,14 +22,29 @@ final class FogViewConnection: AttestedConnection, FogViewService {
rng: (@convention(c) (UnsafeMutableRawPointer?) -> UInt64)? = securityRNG,
rngContext: Any? = nil
) {
let channel = channelManager.channel(for: config)
self.client = FogView_FogViewAPIClient(channel: channel)
self.config = config
self.channelManager = channelManager
self.targetQueue = targetQueue
self.rng = rng
self.rngContext = rngContext
super.init(
client: self.client,
config: config,
targetQueue: targetQueue,
rng: rng,
rngContext: rngContext)
connectionOptionWrapperFactory: { transportProtocolOption in
switch transportProtocolOption {
case .grpc:
return .grpc(
grpcService: FogViewGrpcConnection(
config: config,
channelManager: channelManager,
targetQueue: targetQueue,
rng: rng,
rngContext: rngContext))
case .http:
return .http(httpService: FogViewHttpConnection())
}
},
transportProtocolOption: config.transportProtocolOption,
targetQueue: targetQueue)
}
func query(
@ -31,31 +52,11 @@ final class FogViewConnection: AttestedConnection, FogViewService {
request: FogView_QueryRequest,
completion: @escaping (Result<FogView_QueryResponse, ConnectionError>) -> Void
) {
performAttestedCall(
EnclaveRequestCall(client: client),
requestAad: requestAad,
request: request,
completion: completion)
}
}
extension FogViewConnection {
private struct EnclaveRequestCall: AttestedGrpcCallable {
typealias InnerRequestAad = FogView_QueryRequestAAD
typealias InnerRequest = FogView_QueryRequest
typealias InnerResponse = FogView_QueryResponse
let client: FogView_FogViewAPIClient
func call(
request: Attest_Message,
callOptions: CallOptions?,
completion: @escaping (UnaryCallResult<Attest_Message>) -> Void
) {
let unaryCall = client.query(request, callOptions: callOptions)
unaryCall.callResult.whenSuccess(completion)
switch connectionOption {
case .grpc(let grpcConnection):
grpcConnection.query(requestAad: requestAad, request: request, completion: completion)
case .http(let httpConnection):
httpConnection.query(requestAad: requestAad, request: request, completion: completion)
}
}
}
extension FogView_FogViewAPIClient: AuthGrpcCallableClient {}

View File

@ -7,17 +7,25 @@ import NIOSSL
protocol ConnectionConfigProtocol {
var url: MobileCoinUrlProtocol { get }
var transportProtocolOption: TransportProtocol.Option { get }
var trustRoots: [NIOSSLCertificate]? { get }
var authorization: BasicCredentials? { get }
}
struct ConnectionConfig<Url: MobileCoinUrlProtocol>: ConnectionConfigProtocol {
let urlTyped: Url
let transportProtocolOption: TransportProtocol.Option
let trustRoots: [NIOSSLCertificate]?
let authorization: BasicCredentials?
init(url: Url, trustRoots: [NIOSSLCertificate]?, authorization: BasicCredentials?) {
init(
url: Url,
transportProtocolOption: TransportProtocol.Option,
trustRoots: [NIOSSLCertificate]?,
authorization: BasicCredentials?
) {
self.urlTyped = url
self.transportProtocolOption = transportProtocolOption
self.trustRoots = trustRoots
self.authorization = authorization
}

View File

@ -0,0 +1,80 @@
//
// Copyright (c) 2020-2021 MobileCoin. All rights reserved.
//
import Foundation
import GRPC
class ArbitraryGrpcConnection {
private let inner: SerialDispatchLock<Inner>
init(url: MobileCoinUrlProtocol, targetQueue: DispatchQueue?) {
let inner = Inner(url: url)
self.inner = .init(inner, targetQueue: targetQueue)
}
func performCall<Call: GrpcCallable>(
_ call: Call,
request: Call.Request,
completion: @escaping (Result<Call.Response, ConnectionError>) -> Void
) {
func performCallCallback(callResult: UnaryCallResult<Call.Response>) {
inner.accessAsync {
let result = $0.processResponse(callResult: callResult)
switch result {
case .success:
logger.info("Call complete. url: \($0.url)", logFunction: false)
case .failure(let connectionError):
let errorMessage =
"Connection failure. url: \($0.url), error: \(connectionError)"
switch connectionError {
case .connectionFailure, .serverRateLimited:
logger.warning(errorMessage, logFunction: false)
case .authorizationFailure, .invalidServerResponse,
.attestationVerificationFailed, .outdatedClient:
logger.error(errorMessage, logFunction: false)
}
}
completion(result)
}
}
inner.accessAsync {
logger.info("Performing call... url: \($0.url)", logFunction: false)
let callOptions = $0.requestCallOptions()
call.call(request: request, callOptions: callOptions, completion: performCallCallback)
}
}
}
extension ArbitraryGrpcConnection {
private struct Inner {
let url: MobileCoinUrlProtocol
private let session: ConnectionSession
init(url: MobileCoinUrlProtocol) {
self.url = url
self.session = ConnectionSession(url: url)
}
func requestCallOptions() -> CallOptions {
var callOptions = CallOptions()
session.addRequestHeaders(to: &callOptions.customMetadata)
return callOptions
}
func processResponse<Response>(callResult: UnaryCallResult<Response>)
-> Result<Response, ConnectionError>
{
guard callResult.status.isOk, let response = callResult.response else {
return .failure(.connectionFailure(String(describing: callResult.status)))
}
if let initialMetadata = callResult.initialMetadata {
session.processResponse(headers: initialMetadata)
}
return .success(response)
}
}
}

View File

@ -0,0 +1,364 @@
//
// Copyright (c) 2020-2021 MobileCoin. All rights reserved.
//
// swiftlint:disable closure_body_length cyclomatic_complexity function_body_length
// swiftlint:disable multiline_function_chains operator_usage_whitespace
import Foundation
import GRPC
import LibMobileCoin
enum AttestedGrpcConnectionError: Error {
case connectionError(ConnectionError)
case attestationFailure(String = String())
}
extension AttestedGrpcConnectionError: CustomStringConvertible {
var description: String {
"Attested connection error: " + {
switch self {
case .connectionError(let connectionError):
return "\(connectionError)"
case .attestationFailure(let reason):
return "Attestation failure\(!reason.isEmpty ? ": \(reason)" : "")"
}
}()
}
}
class AttestedGrpcConnection: ConnectionProtocol {
private let inner: SerialCallbackLock<Inner>
init(
client: AttestableGrpcClient,
config: AttestedConnectionConfigProtocol,
targetQueue: DispatchQueue?,
rng: (@convention(c) (UnsafeMutableRawPointer?) -> UInt64)? = securityRNG,
rngContext: Any? = nil
) {
let inner = Inner(client: client, config: config, rng: rng, rngContext: rngContext)
self.inner = .init(inner, targetQueue: targetQueue)
}
func setAuthorization(credentials: BasicCredentials) {
inner.priorityAccessAsync {
$0.setAuthorization(credentials: credentials)
}
}
func performAttestedCall<Call: AttestedGrpcCallable>(
_ call: Call,
request: Call.InnerRequest,
completion: @escaping (Result<Call.InnerResponse, ConnectionError>) -> Void
) where Call.InnerRequestAad == (), Call.InnerResponseAad == () {
performAttestedCall(call, requestAad: (), request: request, completion: completion)
}
func performAttestedCall<Call: AttestedGrpcCallable>(
_ call: Call,
requestAad: Call.InnerRequestAad,
request: Call.InnerRequest,
completion: @escaping (Result<Call.InnerResponse, ConnectionError>) -> Void
) where Call.InnerResponseAad == () {
performAttestedCall(call, requestAad: requestAad, request: request) {
completion($0.map { $0.response })
}
}
func performAttestedCall<Call: AttestedGrpcCallable>(
_ call: Call,
request: Call.InnerRequest,
completion: @escaping (
Result<(responseAad: Call.InnerResponseAad, response: Call.InnerResponse),
ConnectionError>
) -> Void
) where Call.InnerRequestAad == () {
performAttestedCall(call, requestAad: (), request: request, completion: completion)
}
func performAttestedCall<Call: AttestedGrpcCallable>(
_ call: Call,
requestAad: Call.InnerRequestAad,
request: Call.InnerRequest,
completion: @escaping (
Result<(responseAad: Call.InnerResponseAad, response: Call.InnerResponse),
ConnectionError>
) -> Void
) {
inner.accessAsync(block: { inner, callback in
inner.performAttestedCallWithAuth(
call,
requestAad: requestAad,
request: request,
completion: callback)
}, completion: completion)
}
}
extension AttestedGrpcConnection {
// Note: Because `SerialCallbackLock` is being used to wrap `AttestedConnection.Inner`, calls
// to `AttestedConnection.Inner` have exclusive access (other calls will be queued up) until the
// executing call invokes the completion handler that returns control back to
// `AttestedConnection`, at which point the block passed to the async `SerialCallbackLock`
// method that invoked the call to inner will complete and the next async `SerialCallbackLock`
// access block will execute.
//
// This means that calls to `AttestedConnection.Inner` can assume thread-safety until the call
// invokes the completion handler.
private struct Inner {
private let url: MobileCoinUrlProtocol
private let session: ConnectionSession
private let client: AttestableGrpcClient
private let attestAke: AttestAke
private let responderId: String
private let attestationVerifier: AttestationVerifier
private let rng: (@convention(c) (UnsafeMutableRawPointer?) -> UInt64)?
private let rngContext: Any?
init(
client: AttestableGrpcClient,
config: AttestedConnectionConfigProtocol,
rng: (@convention(c) (UnsafeMutableRawPointer?) -> UInt64)? = securityRNG,
rngContext: Any? = nil
) {
self.url = config.url
self.session = ConnectionSession(config: config)
self.client = client
self.attestAke = AttestAke()
self.responderId = config.url.responderId
self.attestationVerifier = AttestationVerifier(attestation: config.attestation)
self.rng = rng
self.rngContext = rngContext
}
func setAuthorization(credentials: BasicCredentials) {
session.authorizationCredentials = credentials
}
func performAttestedCallWithAuth<Call: AttestedGrpcCallable>(
_ call: Call,
requestAad: Call.InnerRequestAad,
request: Call.InnerRequest,
completion: @escaping (
Result<(responseAad: Call.InnerResponseAad, response: Call.InnerResponse),
ConnectionError>
) -> Void
) {
doPerformAttestedCallWithAuth(
call,
requestAad: requestAad,
request: request,
attestAkeCipher: attestAke.cipher.map { ($0, freshCipher: false) },
completion: completion)
}
private func doPerformAttestedCallWithAuth<Call: AttestedGrpcCallable>(
_ call: Call,
requestAad: Call.InnerRequestAad,
request: Call.InnerRequest,
attestAkeCipher: (AttestAke.Cipher, freshCipher: Bool)?,
completion: @escaping (
Result<(responseAad: Call.InnerResponseAad, response: Call.InnerResponse),
ConnectionError>
) -> Void
) {
if let (attestAkeCipher, freshCipher) = attestAkeCipher {
logger.info(
"Performing attested call... url: \(self.url)",
logFunction: false)
doPerformAttestedCall(
call,
requestAad: requestAad,
request: request,
attestAkeCipher: attestAkeCipher
) {
switch $0 {
case .success(let response):
logger.info(
"Attested call successful. url: \(self.url)",
logFunction: false)
completion(.success(response))
case .failure(.connectionError(let connectionError)):
let errorMessage = "Connection failure while performing attested call. " +
"url: \(self.url), error: \(connectionError)"
switch connectionError {
case .connectionFailure, .serverRateLimited:
logger.warning(errorMessage, logFunction: false)
case .authorizationFailure, .invalidServerResponse,
.attestationVerificationFailed, .outdatedClient:
logger.error(errorMessage, logFunction: false)
}
completion(.failure(connectionError))
case .failure(.attestationFailure):
self.attestAke.deattest()
if freshCipher {
let errorMessage =
"Attestation failure with fresh auth. url: \(self.url)"
logger.warning(errorMessage, logFunction: false)
completion(.failure(.invalidServerResponse(errorMessage)))
} else {
logger.info(
"Attestation failure using cached auth, reattesting... url: " +
"\(self.url)",
logFunction: false)
self.doPerformAttestedCallWithAuth(
call,
requestAad: requestAad,
request: request,
attestAkeCipher: nil,
completion: completion)
}
}
}
} else {
logger.info(
"Peforming attestation... url: \(url)",
logFunction: false)
doPerformAuthCall {
switch $0 {
case .success(let attestAkeCipher):
logger.info(
"Attestation successful. url: \(self.url)",
logFunction: false)
self.doPerformAttestedCallWithAuth(
call,
requestAad: requestAad,
request: request,
attestAkeCipher: (attestAkeCipher, freshCipher: true),
completion: completion)
case .failure(let connectionError):
let errorMessage = "Connection failure while performing attestation. " +
"url: \(self.url), error: \(connectionError)"
switch connectionError {
case .connectionFailure, .serverRateLimited:
logger.warning(errorMessage, logFunction: false)
case .authorizationFailure, .invalidServerResponse,
.attestationVerificationFailed, .outdatedClient:
logger.error(errorMessage, logFunction: false)
}
completion(.failure(connectionError))
}
}
}
}
private func doPerformAuthCall(
completion: @escaping (Result<AttestAke.Cipher, ConnectionError>) -> Void
) {
let request = attestAke.authBeginRequest(
responderId: responderId,
rng: rng,
rngContext: rngContext)
doPerformCall(
AuthGrpcCallableWrapper(authCallable: client.authCallable),
request: request
) {
completion(
$0.mapError {
switch $0 {
case .connectionError(let connectionError):
return connectionError
case .attestationFailure:
self.attestAke.deattest()
return .invalidServerResponse(
"Attestation failure during auth. url: \(self.url)")
}
}.flatMap { response in
self.attestAke.authEnd(
authResponse: response,
attestationVerifier: self.attestationVerifier
).mapError {
switch $0 {
case .invalidInput(let reason):
return .invalidServerResponse(reason)
case .attestationVerificationFailed(let reason):
return .attestationVerificationFailed(reason)
}
}
})
}
}
private func doPerformAttestedCall<Call: AttestedGrpcCallable>(
_ call: Call,
requestAad: Call.InnerRequestAad,
request: Call.InnerRequest,
attestAkeCipher: AttestAke.Cipher,
completion: @escaping (
Result<(responseAad: Call.InnerResponseAad, response: Call.InnerResponse),
AttestedGrpcConnectionError>
) -> Void
) {
guard let processedRequest =
call.processRequest(
requestAad: requestAad,
request: request,
attestAkeCipher: attestAkeCipher)
.mapError({ _ in .attestationFailure() })
.successOr(completion: completion)
else { return }
doPerformCall(call, request: processedRequest) {
completion($0.flatMap { response in
call.processResponse(response: response, attestAkeCipher: attestAkeCipher)
})
}
}
private func doPerformCall<Call: GrpcCallable>(
_ call: Call,
request: Call.Request,
completion: @escaping (Result<Call.Response, AttestedGrpcConnectionError>) -> Void
) {
let callOptions = requestCallOptions()
call.call(request: request, callOptions: callOptions) {
completion(self.processResponse(callResult: $0))
}
}
private func requestCallOptions() -> CallOptions {
var callOptions = CallOptions()
session.addRequestHeaders(to: &callOptions.customMetadata)
return callOptions
}
private func processResponse<Response>(callResult: UnaryCallResult<Response>)
-> Result<Response, AttestedGrpcConnectionError>
{
// Basic credential authorization failure
guard callResult.status.code != .unauthenticated else {
return .failure(.connectionError(.authorizationFailure("url: \(url)")))
}
// Attestation failure, reattest
guard callResult.status.code != .permissionDenied else {
return .failure(.attestationFailure())
}
guard callResult.status.isOk, let response = callResult.response else {
return .failure(.connectionError(
.connectionFailure("url: \(url), status: \(callResult.status)")))
}
if let initialMetadata = callResult.initialMetadata {
session.processResponse(headers: initialMetadata)
}
return .success(response)
}
}
}

View File

@ -39,7 +39,8 @@ protocol AttestedGrpcCallable: GrpcCallable {
func processResponse(
response: Response,
attestAkeCipher: AttestAke.Cipher
) -> Result<(responseAad: InnerResponseAad, response: InnerResponse), AttestedConnectionError>
) -> Result<(responseAad: InnerResponseAad, response: InnerResponse),
AttestedGrpcConnectionError>
}
extension AttestedGrpcCallable where InnerRequestAad == (), InnerRequest == Request {
@ -54,7 +55,8 @@ extension AttestedGrpcCallable where InnerRequestAad == (), InnerRequest == Requ
extension AttestedGrpcCallable where InnerResponseAad == (), InnerResponse == Response {
func processResponse(response: Response, attestAkeCipher: AttestAke.Cipher)
-> Result<(responseAad: InnerResponseAad, response: InnerResponse), AttestedConnectionError>
-> Result<(responseAad: InnerResponseAad, response: InnerResponse),
AttestedGrpcConnectionError>
{
.success((responseAad: (), response: response))
}
@ -85,7 +87,9 @@ extension AttestedGrpcCallable
func processResponse(
response: Attest_Message,
attestAkeCipher: AttestAke.Cipher
) -> Result<(responseAad: InnerResponseAad, response: InnerResponse), AttestedConnectionError> {
) -> Result<(responseAad: InnerResponseAad, response: InnerResponse),
AttestedGrpcConnectionError>
{
guard response.aad == Data() else {
return .failure(.connectionError(.invalidServerResponse(
"\(Self.self) received unexpected aad: " +
@ -133,7 +137,9 @@ extension AttestedGrpcCallable
func processResponse(
response: Attest_Message,
attestAkeCipher: AttestAke.Cipher
) -> Result<(responseAad: InnerResponseAad, response: InnerResponse), AttestedConnectionError> {
) -> Result<(responseAad: InnerResponseAad, response: InnerResponse),
AttestedGrpcConnectionError>
{
guard let responseAad = try? InnerResponseAad(serializedData: response.aad) else {
return .failure(.connectionError(.invalidServerResponse(
"Failed to deserialized attested message aad into \(InnerResponseAad.self). aad: " +

View File

@ -0,0 +1,102 @@
//
// Copyright (c) 2020-2021 MobileCoin. All rights reserved.
//
import Foundation
import GRPC
class GrpcConnection: ConnectionProtocol {
private let inner: SerialDispatchLock<Inner>
init(config: ConnectionConfigProtocol, targetQueue: DispatchQueue?) {
let inner = Inner(config: config)
self.inner = .init(inner, targetQueue: targetQueue)
}
func setAuthorization(credentials: BasicCredentials) {
inner.accessAsync {
$0.setAuthorization(credentials: credentials)
}
}
func performCall<Call: GrpcCallable>(
_ call: Call,
request: Call.Request,
completion: @escaping (Result<Call.Response, ConnectionError>) -> Void
) {
func performCallCallback(callResult: UnaryCallResult<Call.Response>) {
inner.accessAsync {
let result = $0.processResponse(callResult: callResult)
switch result {
case .success:
logger.info("Call complete. url: \($0.url)", logFunction: false)
case .failure(let connectionError):
let errorMessage =
"Connection failure. url: \($0.url), error: \(connectionError)"
switch connectionError {
case .connectionFailure, .serverRateLimited:
logger.warning(errorMessage, logFunction: false)
case .authorizationFailure, .invalidServerResponse,
.attestationVerificationFailed, .outdatedClient:
logger.error(errorMessage, logFunction: false)
}
}
completion(result)
}
}
inner.accessAsync {
logger.info("Performing call... url: \($0.url)", logFunction: false)
let callOptions = $0.requestCallOptions()
call.call(request: request, callOptions: callOptions, completion: performCallCallback)
}
}
func performCall<Call: GrpcCallable>(
_ call: Call,
completion: @escaping (Result<Call.Response, ConnectionError>) -> Void
) where Call.Request == () {
performCall(call, request: (), completion: completion)
}
}
extension GrpcConnection {
private struct Inner {
let url: MobileCoinUrlProtocol
private let session: ConnectionSession
init(config: ConnectionConfigProtocol) {
self.url = config.url
self.session = ConnectionSession(config: config)
}
func setAuthorization(credentials: BasicCredentials) {
session.authorizationCredentials = credentials
}
func requestCallOptions() -> CallOptions {
var callOptions = CallOptions()
session.addRequestHeaders(to: &callOptions.customMetadata)
return callOptions
}
func processResponse<Response>(callResult: UnaryCallResult<Response>)
-> Result<Response, ConnectionError>
{
guard callResult.status.code != .unauthenticated else {
return .failure(.authorizationFailure("url: \(url)"))
}
guard callResult.status.isOk, let response = callResult.response else {
return .failure(.connectionFailure("url: \(url), status: \(callResult.status)"))
}
if let initialMetadata = callResult.initialMetadata {
session.processResponse(headers: initialMetadata)
}
return .success(response)
}
}
}

View File

@ -0,0 +1,45 @@
//
// Copyright (c) 2020-2021 MobileCoin. All rights reserved.
//
import Foundation
import GRPC
import LibMobileCoin
import SwiftProtobuf
final class BlockchainGrpcConnection: GrpcConnection, BlockchainService {
private let client: ConsensusCommon_BlockchainAPIClient
init(
config: ConnectionConfig<ConsensusUrl>,
channelManager: GrpcChannelManager,
targetQueue: DispatchQueue?
) {
let channel = channelManager.channel(for: config)
self.client = ConsensusCommon_BlockchainAPIClient(channel: channel)
super.init(config: config, targetQueue: targetQueue)
}
func getLastBlockInfo(
completion:
@escaping (Result<ConsensusCommon_LastBlockInfoResponse, ConnectionError>) -> Void
) {
performCall(GetLastBlockInfoCall(client: client), completion: completion)
}
}
extension BlockchainGrpcConnection {
private struct GetLastBlockInfoCall: GrpcCallable {
let client: ConsensusCommon_BlockchainAPIClient
func call(
request: (),
callOptions: CallOptions?,
completion: @escaping (UnaryCallResult<ConsensusCommon_LastBlockInfoResponse>) -> Void
) {
let unaryCall =
client.getLastBlockInfo(Google_Protobuf_Empty(), callOptions: callOptions)
unaryCall.callResult.whenSuccess(completion)
}
}
}

View File

@ -0,0 +1,58 @@
//
// Copyright (c) 2020-2021 MobileCoin. All rights reserved.
//
import Foundation
import GRPC
import LibMobileCoin
final class ConsensusGrpcConnection: AttestedGrpcConnection, ConsensusService {
private let client: ConsensusClient_ConsensusClientAPIClient
init(
config: AttestedConnectionConfig<ConsensusUrl>,
channelManager: GrpcChannelManager,
targetQueue: DispatchQueue?,
rng: (@convention(c) (UnsafeMutableRawPointer?) -> UInt64)? = securityRNG,
rngContext: Any? = nil
) {
let channel = channelManager.channel(for: config)
self.client = ConsensusClient_ConsensusClientAPIClient(channel: channel)
super.init(
client: Attest_AttestedApiClient(channel: channel),
config: config,
targetQueue: targetQueue,
rng: rng,
rngContext: rngContext)
}
func proposeTx(
_ tx: External_Tx,
completion: @escaping (Result<ConsensusCommon_ProposeTxResponse, ConnectionError>) -> Void
) {
performAttestedCall(
ProposeTxCall(client: client),
request: tx,
completion: completion)
}
}
extension ConsensusGrpcConnection {
private struct ProposeTxCall: AttestedGrpcCallable {
typealias InnerRequest = External_Tx
typealias InnerResponse = ConsensusCommon_ProposeTxResponse
let client: ConsensusClient_ConsensusClientAPIClient
func call(
request: Attest_Message,
callOptions: CallOptions?,
completion: @escaping (UnaryCallResult<ConsensusCommon_ProposeTxResponse>) -> Void
) {
let unaryCall = client.clientTxPropose(request, callOptions: callOptions)
unaryCall.callResult.whenSuccess(completion)
}
}
}
extension Attest_AttestedApiClient: AuthGrpcCallableClient {}

View File

@ -0,0 +1,43 @@
//
// Copyright (c) 2020-2021 MobileCoin. All rights reserved.
//
import Foundation
import GRPC
import LibMobileCoin
final class FogBlockGrpcConnection: GrpcConnection, FogBlockService {
private let client: FogLedger_FogBlockAPIClient
init(
config: ConnectionConfig<FogUrl>,
channelManager: GrpcChannelManager,
targetQueue: DispatchQueue?
) {
let channel = channelManager.channel(for: config)
self.client = FogLedger_FogBlockAPIClient(channel: channel)
super.init(config: config, targetQueue: targetQueue)
}
func getBlocks(
request: FogLedger_BlockRequest,
completion: @escaping (Result<FogLedger_BlockResponse, ConnectionError>) -> Void
) {
performCall(GetBlocksCall(client: client), request: request, completion: completion)
}
}
extension FogBlockGrpcConnection {
private struct GetBlocksCall: GrpcCallable {
let client: FogLedger_FogBlockAPIClient
func call(
request: FogLedger_BlockRequest,
callOptions: CallOptions?,
completion: @escaping (UnaryCallResult<FogLedger_BlockResponse>) -> Void
) {
let unaryCall = client.getBlocks(request, callOptions: callOptions)
unaryCall.callResult.whenSuccess(completion)
}
}
}

View File

@ -0,0 +1,58 @@
//
// Copyright (c) 2020-2021 MobileCoin. All rights reserved.
//
import Foundation
import GRPC
import LibMobileCoin
final class FogKeyImageGrpcConnection: AttestedGrpcConnection, FogKeyImageService {
private let client: FogLedger_FogKeyImageAPIClient
init(
config: AttestedConnectionConfig<FogUrl>,
channelManager: GrpcChannelManager,
targetQueue: DispatchQueue?,
rng: (@convention(c) (UnsafeMutableRawPointer?) -> UInt64)? = securityRNG,
rngContext: Any? = nil
) {
let channel = channelManager.channel(for: config)
self.client = FogLedger_FogKeyImageAPIClient(channel: channel)
super.init(
client: self.client,
config: config,
targetQueue: targetQueue,
rng: rng,
rngContext: rngContext)
}
func checkKeyImages(
request: FogLedger_CheckKeyImagesRequest,
completion: @escaping (Result<FogLedger_CheckKeyImagesResponse, ConnectionError>) -> Void
) {
performAttestedCall(
CheckKeyImagesCall(client: client),
request: request,
completion: completion)
}
}
extension FogKeyImageGrpcConnection {
private struct CheckKeyImagesCall: AttestedGrpcCallable {
typealias InnerRequest = FogLedger_CheckKeyImagesRequest
typealias InnerResponse = FogLedger_CheckKeyImagesResponse
let client: FogLedger_FogKeyImageAPIClient
func call(
request: Attest_Message,
callOptions: CallOptions?,
completion: @escaping (UnaryCallResult<Attest_Message>) -> Void
) {
let unaryCall = client.checkKeyImages(request, callOptions: callOptions)
unaryCall.callResult.whenSuccess(completion)
}
}
}
extension FogLedger_FogKeyImageAPIClient: AuthGrpcCallableClient {}

View File

@ -0,0 +1,58 @@
//
// Copyright (c) 2020-2021 MobileCoin. All rights reserved.
//
import Foundation
import GRPC
import LibMobileCoin
final class FogMerkleProofGrpcConnection: AttestedGrpcConnection, FogMerkleProofService {
private let client: FogLedger_FogMerkleProofAPIClient
init(
config: AttestedConnectionConfig<FogUrl>,
channelManager: GrpcChannelManager,
targetQueue: DispatchQueue?,
rng: (@convention(c) (UnsafeMutableRawPointer?) -> UInt64)? = securityRNG,
rngContext: Any? = nil
) {
let channel = channelManager.channel(for: config)
self.client = FogLedger_FogMerkleProofAPIClient(channel: channel)
super.init(
client: self.client,
config: config,
targetQueue: targetQueue,
rng: rng,
rngContext: rngContext)
}
func getOutputs(
request: FogLedger_GetOutputsRequest,
completion: @escaping (Result<FogLedger_GetOutputsResponse, ConnectionError>) -> Void
) {
performAttestedCall(
GetOutputsCall(client: client),
request: request,
completion: completion)
}
}
extension FogMerkleProofGrpcConnection {
private struct GetOutputsCall: AttestedGrpcCallable {
typealias InnerRequest = FogLedger_GetOutputsRequest
typealias InnerResponse = FogLedger_GetOutputsResponse
let client: FogLedger_FogMerkleProofAPIClient
func call(
request: Attest_Message,
callOptions: CallOptions?,
completion: @escaping (UnaryCallResult<Attest_Message>) -> Void
) {
let unaryCall = client.getOutputs(request, callOptions: callOptions)
unaryCall.callResult.whenSuccess(completion)
}
}
}
extension FogLedger_FogMerkleProofAPIClient: AuthGrpcCallableClient {}

View File

@ -0,0 +1,39 @@
//
// Copyright (c) 2020-2021 MobileCoin. All rights reserved.
//
import Foundation
import GRPC
import LibMobileCoin
final class FogReportGrpcConnection: ArbitraryGrpcConnection, FogReportService {
private let client: Report_ReportAPIClient
init(url: FogUrl, channelManager: GrpcChannelManager, targetQueue: DispatchQueue?) {
let channel = channelManager.channel(for: url)
self.client = Report_ReportAPIClient(channel: channel)
super.init(url: url, targetQueue: targetQueue)
}
func getReports(
request: Report_ReportRequest,
completion: @escaping (Result<Report_ReportResponse, ConnectionError>) -> Void
) {
performCall(GetReportsCall(client: client), request: request, completion: completion)
}
}
extension FogReportGrpcConnection {
private struct GetReportsCall: GrpcCallable {
let client: Report_ReportAPIClient
func call(
request: Report_ReportRequest,
callOptions: CallOptions?,
completion: @escaping (UnaryCallResult<Report_ReportResponse>) -> Void
) {
let unaryCall = client.getReports(request, callOptions: callOptions)
unaryCall.callResult.whenSuccess(completion)
}
}
}

View File

@ -0,0 +1,43 @@
//
// Copyright (c) 2020-2021 MobileCoin. All rights reserved.
//
import Foundation
import GRPC
import LibMobileCoin
final class FogUntrustedTxOutGrpcConnection: GrpcConnection, FogUntrustedTxOutService {
private let client: FogLedger_FogUntrustedTxOutApiClient
init(
config: ConnectionConfig<FogUrl>,
channelManager: GrpcChannelManager,
targetQueue: DispatchQueue?
) {
let channel = channelManager.channel(for: config)
self.client = FogLedger_FogUntrustedTxOutApiClient(channel: channel)
super.init(config: config, targetQueue: targetQueue)
}
func getTxOuts(
request: FogLedger_TxOutRequest,
completion: @escaping (Result<FogLedger_TxOutResponse, ConnectionError>) -> Void
) {
performCall(GetTxOutsCall(client: client), request: request, completion: completion)
}
}
extension FogUntrustedTxOutGrpcConnection {
private struct GetTxOutsCall: GrpcCallable {
let client: FogLedger_FogUntrustedTxOutApiClient
func call(
request: FogLedger_TxOutRequest,
callOptions: CallOptions?,
completion: @escaping (UnaryCallResult<FogLedger_TxOutResponse>) -> Void
) {
let unaryCall = client.getTxOuts(request, callOptions: callOptions)
unaryCall.callResult.whenSuccess(completion)
}
}
}

View File

@ -0,0 +1,61 @@
//
// Copyright (c) 2020-2021 MobileCoin. All rights reserved.
//
import Foundation
import GRPC
import LibMobileCoin
final class FogViewGrpcConnection: AttestedGrpcConnection, FogViewService {
private let client: FogView_FogViewAPIClient
init(
config: AttestedConnectionConfig<FogUrl>,
channelManager: GrpcChannelManager,
targetQueue: DispatchQueue?,
rng: (@convention(c) (UnsafeMutableRawPointer?) -> UInt64)? = securityRNG,
rngContext: Any? = nil
) {
let channel = channelManager.channel(for: config)
self.client = FogView_FogViewAPIClient(channel: channel)
super.init(
client: self.client,
config: config,
targetQueue: targetQueue,
rng: rng,
rngContext: rngContext)
}
func query(
requestAad: FogView_QueryRequestAAD,
request: FogView_QueryRequest,
completion: @escaping (Result<FogView_QueryResponse, ConnectionError>) -> Void
) {
performAttestedCall(
EnclaveRequestCall(client: client),
requestAad: requestAad,
request: request,
completion: completion)
}
}
extension FogViewGrpcConnection {
private struct EnclaveRequestCall: AttestedGrpcCallable {
typealias InnerRequestAad = FogView_QueryRequestAAD
typealias InnerRequest = FogView_QueryRequest
typealias InnerResponse = FogView_QueryResponse
let client: FogView_FogViewAPIClient
func call(
request: Attest_Message,
callOptions: CallOptions?,
completion: @escaping (UnaryCallResult<Attest_Message>) -> Void
) {
let unaryCall = client.query(request, callOptions: callOptions)
unaryCall.callResult.whenSuccess(completion)
}
}
}
extension FogView_FogViewAPIClient: AuthGrpcCallableClient {}

View File

@ -0,0 +1,14 @@
//
// Copyright (c) 2020-2021 MobileCoin. All rights reserved.
//
import Foundation
import LibMobileCoin
final class BlockchainHttpConnection: ConnectionProtocol, BlockchainService {
func getLastBlockInfo(
completion:
@escaping (Result<ConsensusCommon_LastBlockInfoResponse, ConnectionError>) -> Void
) {
}
}

View File

@ -0,0 +1,14 @@
//
// Copyright (c) 2020-2021 MobileCoin. All rights reserved.
//
import Foundation
import LibMobileCoin
final class ConsensusHttpConnection: ConnectionProtocol, ConsensusService {
func proposeTx(
_ tx: External_Tx,
completion: @escaping (Result<ConsensusCommon_ProposeTxResponse, ConnectionError>) -> Void
) {
}
}

View File

@ -0,0 +1,14 @@
//
// Copyright (c) 2020-2021 MobileCoin. All rights reserved.
//
import Foundation
import LibMobileCoin
final class FogBlockHttpConnection: ConnectionProtocol, FogBlockService {
func getBlocks(
request: FogLedger_BlockRequest,
completion: @escaping (Result<FogLedger_BlockResponse, ConnectionError>) -> Void
) {
}
}

View File

@ -0,0 +1,14 @@
//
// Copyright (c) 2020-2021 MobileCoin. All rights reserved.
//
import Foundation
import LibMobileCoin
final class FogKeyImageHttpConnection: ConnectionProtocol, FogKeyImageService {
func checkKeyImages(
request: FogLedger_CheckKeyImagesRequest,
completion: @escaping (Result<FogLedger_CheckKeyImagesResponse, ConnectionError>) -> Void
) {
}
}

View File

@ -0,0 +1,14 @@
//
// Copyright (c) 2020-2021 MobileCoin. All rights reserved.
//
import Foundation
import LibMobileCoin
final class FogMerkleProofHttpConnection: ConnectionProtocol, FogMerkleProofService {
func getOutputs(
request: FogLedger_GetOutputsRequest,
completion: @escaping (Result<FogLedger_GetOutputsResponse, ConnectionError>) -> Void
) {
}
}

View File

@ -0,0 +1,14 @@
//
// Copyright (c) 2020-2021 MobileCoin. All rights reserved.
//
import Foundation
import LibMobileCoin
final class FogReportHttpConnection: FogReportService {
func getReports(
request: Report_ReportRequest,
completion: @escaping (Result<Report_ReportResponse, ConnectionError>) -> Void
) {
}
}

View File

@ -0,0 +1,14 @@
//
// Copyright (c) 2020-2021 MobileCoin. All rights reserved.
//
import Foundation
import LibMobileCoin
final class FogUntrustedTxOutHttpConnection: ConnectionProtocol, FogUntrustedTxOutService {
func getTxOuts(
request: FogLedger_TxOutRequest,
completion: @escaping (Result<FogLedger_TxOutResponse, ConnectionError>) -> Void
) {
}
}

View File

@ -0,0 +1,15 @@
//
// Copyright (c) 2020-2021 MobileCoin. All rights reserved.
//
import Foundation
import LibMobileCoin
final class FogViewHttpConnection: ConnectionProtocol, FogViewService {
func query(
requestAad: FogView_QueryRequestAAD,
request: FogView_QueryRequest,
completion: @escaping (Result<FogView_QueryResponse, ConnectionError>) -> Void
) {
}
}

View File

@ -0,0 +1,16 @@
//
// Copyright (c) 2020-2021 MobileCoin. All rights reserved.
//
// swiftlint:disable multiline_parameters_brackets
import Foundation
public protocol HttpRequester {
func request(
url: URL,
method: String,
headers: [String: String]?,
body: Data?,
completion: @escaping (Result<Void, Error>) -> Void)
}

View File

@ -21,12 +21,16 @@ struct NetworkConfig {
private let attestation: AttestationConfig
var transportProtocol: TransportProtocol = .grpc
var consensusTrustRoots: [NIOSSLCertificate]?
var fogTrustRoots: [NIOSSLCertificate]?
var consensusAuthorization: BasicCredentials?
var fogUserAuthorization: BasicCredentials?
var httpRequester: HttpRequester?
init(consensusUrl: ConsensusUrl, fogUrl: FogUrl, attestation: AttestationConfig) {
self.consensusUrl = consensusUrl
self.fogUrl = fogUrl

View File

@ -5,8 +5,7 @@
import Foundation
final class DefaultServiceProvider: ServiceProvider {
private let targetQueue: DispatchQueue?
private let channelManager = GrpcChannelManager()
private let inner: SerialDispatchLock<Inner>
private let consensus: ConsensusConnection
private let blockchain: BlockchainConnection
@ -16,10 +15,12 @@ final class DefaultServiceProvider: ServiceProvider {
private let block: FogBlockConnection
private let untrustedTxOut: FogUntrustedTxOutConnection
private var reportUrlToReportConnection: [GrpcChannelConfig: FogReportConnection] = [:]
init(networkConfig: NetworkConfig, targetQueue: DispatchQueue?) {
self.targetQueue = targetQueue
let channelManager = GrpcChannelManager()
let inner = Inner(channelManager: channelManager, targetQueue: targetQueue)
self.inner = .init(inner, targetQueue: targetQueue)
self.consensus = ConsensusConnection(
config: networkConfig.consensus,
channelManager: channelManager,
@ -56,19 +57,26 @@ final class DefaultServiceProvider: ServiceProvider {
var fogMerkleProofService: FogMerkleProofService { merkleProof }
var fogKeyImageService: FogKeyImageService { keyImage }
var fogBlockService: FogBlockService { block }
var fogUntrustedTxOutService: FogUntrustedTxOutConnection { untrustedTxOut }
var fogUntrustedTxOutService: FogUntrustedTxOutService { untrustedTxOut }
func fogReportService(for fogReportUrl: FogUrl) -> FogReportService {
let config = GrpcChannelConfig(url: fogReportUrl)
guard let reportConnection = reportUrlToReportConnection[config] else {
let reportConnection = FogReportConnection(
url: fogReportUrl,
channelManager: channelManager,
targetQueue: targetQueue)
reportUrlToReportConnection[config] = reportConnection
return reportConnection
func fogReportService(
for fogReportUrl: FogUrl,
completion: @escaping (FogReportService) -> Void
) {
inner.accessAsync { completion($0.fogReportService(for: fogReportUrl)) }
}
func setTransportProtocolOption(_ transportProtocolOption: TransportProtocol.Option) {
inner.accessAsync {
$0.setTransportProtocolOption(transportProtocolOption)
self.consensus.setTransportProtocolOption(transportProtocolOption)
self.blockchain.setTransportProtocolOption(transportProtocolOption)
self.view.setTransportProtocolOption(transportProtocolOption)
self.merkleProof.setTransportProtocolOption(transportProtocolOption)
self.keyImage.setTransportProtocolOption(transportProtocolOption)
self.block.setTransportProtocolOption(transportProtocolOption)
self.untrustedTxOut.setTransportProtocolOption(transportProtocolOption)
}
return reportConnection
}
func setConsensusAuthorization(credentials: BasicCredentials) {
@ -84,3 +92,41 @@ final class DefaultServiceProvider: ServiceProvider {
untrustedTxOut.setAuthorization(credentials: credentials)
}
}
extension DefaultServiceProvider {
private struct Inner {
private let targetQueue: DispatchQueue?
private let channelManager: GrpcChannelManager
private var reportUrlToReportConnection: [GrpcChannelConfig: FogReportConnection] = [:]
private(set) var transportProtocolOption: TransportProtocol.Option
init(channelManager: GrpcChannelManager, targetQueue: DispatchQueue?) {
self.targetQueue = targetQueue
self.channelManager = channelManager
}
mutating func fogReportService(for fogReportUrl: FogUrl) -> FogReportService {
let config = GrpcChannelConfig(url: fogReportUrl)
guard let reportConnection = reportUrlToReportConnection[config] else {
let reportConnection = FogReportConnection(
url: fogReportUrl,
transportProtocolOption: transportProtocolOption,
channelManager: channelManager,
targetQueue: targetQueue)
reportUrlToReportConnection[config] = reportConnection
return reportConnection
}
return reportConnection
}
mutating func setTransportProtocolOption(
_ transportProtocolOption: TransportProtocol.Option
) {
self.transportProtocolOption = transportProtocolOption
for reportConnection in reportUrlToReportConnection.values {
reportConnection.setTransportProtocolOption(transportProtocolOption)
}
}
}
}

View File

@ -12,9 +12,13 @@ protocol ServiceProvider {
var fogMerkleProofService: FogMerkleProofService { get }
var fogKeyImageService: FogKeyImageService { get }
var fogBlockService: FogBlockService { get }
var fogUntrustedTxOutService: FogUntrustedTxOutConnection { get }
var fogUntrustedTxOutService: FogUntrustedTxOutService { get }
func fogReportService(for fogReportUrl: FogUrl) -> FogReportService
func fogReportService(
for fogReportUrl: FogUrl,
completion: @escaping (FogReportService) -> Void)
func setTransportProtocolOption(_ transportProtocolOption: TransportProtocol.Option)
func setConsensusAuthorization(credentials: BasicCredentials)
func setFogUserAuthorization(credentials: BasicCredentials)

View File

@ -0,0 +1,19 @@
//
// Copyright (c) 2020-2021 MobileCoin. All rights reserved.
//
import Foundation
public struct TransportProtocol {
public static let grpc = TransportProtocol(option: .grpc)
public static let http = TransportProtocol(option: .http)
let option: Option
}
extension TransportProtocol {
enum Option {
case grpc
case http
}
}