# Title `v1.2.0-pre0` - Network Robustness, Fog 1.2.0, Apple Silicon/M1 & Mac Catalyst # Description Added a way for implementing apps to pass in there own `HttpRequester` which can be useful for network robustness. Subspec `LibMobileCoin` now supports Apple Silicon/M1 and Mac Catalyst. Code changes to support Fog v1.2.0. #### `pre1` Changes - Uses the `v1.2.0-pre1` version of `LibMobileCoin` which supports a higher version of `GRPC`. - Added bridging headers - Updated Gemfile/Makefile to use the latest `cocoapods` version `1.11.2` #### Future Work Fix the `docs` steps in the circleci build process. Will require `jazzy` related fixes and testing on Xcode 11. # Changes ## Network Robustness Adds a separate `HTTP` networking architecture. An object conforming to `protocol HttpRequester` can be provided to the `NetworkConfig` object when the `MobileCoinClient` is created. Then the `TransportProtocolOption` can be changed from `grpc` to `http`. > NOTE: This branch will not run because it depends on changes in other submodules that have not yet been merged. ### `HttpRequester` Implementing apps/frameworks should provide an object conforming to this protocol. Our `RestApiRequester` wraps around an `HttpRequester` to communicate with with our services using `protobuf`s. ``` Sources/Network/HttpConnection/HttpRequester.swift ``` ### Attested & Auth Connection Wrappers `HTTP` versions of the Auth & Attested wrapper classes. These protocol and their default implementations handle the logic paths needed for authentication/attestation and re-authentication/attestation: > Can become generic ``` Sources/Network/HttpConnection/ArbitraryHttpConnection.swift Sources/Network/HttpConnection/AttestedHttpConnection.swift ``` ### Networking Protocols Separate code-paths for `HTTP` versions of the networking protocols: ``` Sources/Network/HttpConnection/HttpCallable/AttestedHttpCallable.swift Sources/Network/HttpConnection/HttpCallable/AuthHttpCallable.swift Sources/Network/HttpConnection/HttpCallable/AuthHttpCallableClientWrapper.swift Sources/Network/HttpConnection/HttpCallable/HttpCallable.swift ``` `HTTP` "interfaces" that closely mimic functionality from `GRPC`. Allows us to re-use more of our existing patterns. ``` Sources/Network/HttpConnection/HTTPInterface/HTTPCallOptions.swift Sources/Network/HttpConnection/HTTPInterface/HTTPClient.swift Sources/Network/HttpConnection/HTTPInterface/HTTPClientCall.swift Sources/Network/HttpConnection/HTTPInterface/HTTPMethod.swift Sources/Network/HttpConnection/HTTPInterface/HTTPResponse.swift Sources/Network/HttpConnection/HTTPInterface/HTTPStatus.swift Sources/Network/HttpConnection/HTTPInterface/HTTPUnaryCall.swift ``` ### `HTTP` Connection Implementations Wrapper classes that interface directly with `protoc-swift` generated `.swift` files for our protobuf models. ``` Sources/Network/HttpConnection/HttpConnection.swift Sources/Network/HttpConnection/HttpConnections/BlockchainHttpConnection.swift Sources/Network/HttpConnection/HttpConnections/ConsensusHttpConnection.swift Sources/Network/HttpConnection/HttpConnections/FogBlockHttpConnection.swift Sources/Network/HttpConnection/HttpConnections/FogKeyImageHttpConnection.swift Sources/Network/HttpConnection/HttpConnections/FogMerkleProofHttpConnection.swift Sources/Network/HttpConnection/HttpConnections/FogReportHttpConnection.swift Sources/Network/HttpConnection/HttpConnections/FogUntrustedTxOutHttpConnection.swift Sources/Network/HttpConnection/HttpConnections/FogViewHttpConnection.swift ``` ### `HTTP` versions of `protoc-swift` generated models The GRPC versions of these are generated by `protoc-swift`. The HTTP versions were edited by hand to work with the `HTTP` Connections implementations. > **This could be automated preferably with a `protoc-swift` plugin template but also `sed`/`VIM` if needed.** ``` Sources/Network/HttpConnection/HttpConnections/Http Proto Generated/attest.http.swift Sources/Network/HttpConnection/HttpConnections/Http Proto Generated/consensus_client.http.swift Sources/Network/HttpConnection/HttpConnections/Http Proto Generated/consensus_common.http.swift Sources/Network/HttpConnection/HttpConnections/Http Proto Generated/ledger.http.swift Sources/Network/HttpConnection/HttpConnections/Http Proto Generated/report.http.swift Sources/Network/HttpConnection/HttpConnections/Http Proto Generated/view.http.swift ``` ## Fog Updates The latest version of `fog` changes the name of a protobuf `FogLedger_Block` -> `FogLedger_BlockData`. ### Compressed Commitment The `TxOut` compressed commitment is no longer sent in the protobuf message because it can be reconstructed with its constituent parts (+ the user's `view_private_key`) We now reconstruct the commitment in several places whereas before it was being returned from the decoded protobuf message. Lastly some function signatures into `LibMobileCoin` were updated to adjust to the changes. ## Miscellaneous New MrEnclave values Support for Apple Silicon/M1 & Mac Catalyst ## Unit Tests One unit test was removed. It creates a TxOut and tries to unmask the value with an **incorrect** private view key. The return value should be 'nil' but is noise. It will require a change in the rust code and should be implemented in a future release. Some objects were re-serialized to match the new TxOut structure. Credentials are not required for `consensus` so this has been changed in the `NetworkConfig` Tests can be run with `TransportProtocol == .http` by changing the default value in `NetworkConfig`
257 lines
11 KiB
Swift
257 lines
11 KiB
Swift
//
|
|
// Copyright (c) 2020-2021 MobileCoin. All rights reserved.
|
|
//
|
|
|
|
// swiftlint:disable todo
|
|
|
|
import LibMobileCoin
|
|
@testable import MobileCoin
|
|
import XCTest
|
|
|
|
class FogViewConnectionIntTests: XCTestCase {
|
|
func testEnclaveRequest() throws {
|
|
let fogViewConnection = try createFogViewConnection()
|
|
|
|
let expect = expectation(description: "Making Fog View enclave request")
|
|
fogViewConnection.query(
|
|
requestAad: FogView_QueryRequestAAD(),
|
|
request: FogView_QueryRequest()
|
|
) {
|
|
guard let response = $0.successOrFulfill(expectation: expect) else { return }
|
|
|
|
print("highestProcessedBlockCount: \(response.highestProcessedBlockCount)")
|
|
print("lastKnownBlockCount: \(response.lastKnownBlockCount)")
|
|
print("lastKnownBlockCumulativeTxoCount: \(response.lastKnownBlockCumulativeTxoCount)")
|
|
|
|
XCTAssertGreaterThan(response.highestProcessedBlockCount, 0)
|
|
XCTAssertGreaterThan(response.rngs.count, 0)
|
|
XCTAssertEqual(response.txOutSearchResults.count, 0)
|
|
XCTAssertGreaterThan(response.lastKnownBlockCount, 0)
|
|
XCTAssertGreaterThan(response.lastKnownBlockCumulativeTxoCount, 0)
|
|
|
|
expect.fulfill()
|
|
}
|
|
waitForExpectations(timeout: 20)
|
|
}
|
|
|
|
func testEnclaveRequestReturnsTxOuts() throws {
|
|
let accountKey = try IntegrationTestFixtures.createAccountKey()
|
|
let fogViewConnection = try createFogViewConnection()
|
|
|
|
let expect = expectation(description: "Making Fog View enclave request")
|
|
|
|
func fetchRngRecords(success: @escaping (FogView_RngRecord) -> Void) {
|
|
fogViewConnection.query(
|
|
requestAad: FogView_QueryRequestAAD(),
|
|
request: FogView_QueryRequest()
|
|
) {
|
|
guard let response = $0.successOrFulfill(expectation: expect) else { return }
|
|
|
|
XCTAssertGreaterThan(response.highestProcessedBlockCount, 0)
|
|
XCTAssertGreaterThan(response.rngs.count, 0)
|
|
XCTAssertEqual(response.txOutSearchResults.count, 0)
|
|
XCTAssertGreaterThan(response.lastKnownBlockCount, 0)
|
|
XCTAssertGreaterThan(response.lastKnownBlockCumulativeTxoCount, 0)
|
|
guard !response.rngs.isEmpty else { expect.fulfill(); return }
|
|
|
|
success(response.rngs[0])
|
|
}
|
|
}
|
|
|
|
fetchRngRecords { rngRecord in
|
|
XCTAssertNoThrowOrFulfill(expectation: expect, evaluating: {
|
|
let fogRngKey = FogRngKey(rngRecord.pubkey)
|
|
let rng = try FogRng.make(fogRngKey: fogRngKey, accountKey: accountKey).get()
|
|
|
|
var request = FogView_QueryRequest()
|
|
request.getTxos = rng.outputs(count: 5)
|
|
fogViewConnection.query(requestAad: FogView_QueryRequestAAD(), request: request) {
|
|
guard let response = $0.successOrFulfill(expectation: expect) else { return }
|
|
|
|
XCTAssertGreaterThan(response.highestProcessedBlockCount, 0)
|
|
XCTAssertEqual(response.txOutSearchResults.count, 5)
|
|
for txOutResult in response.txOutSearchResults {
|
|
XCTAssert(request.getTxos.contains(txOutResult.searchKey))
|
|
}
|
|
XCTAssertEqual(response.txOutSearchResults.first?.resultCodeEnum, .found)
|
|
XCTAssertGreaterThan(response.lastKnownBlockCount, 0)
|
|
XCTAssertGreaterThan(response.lastKnownBlockCumulativeTxoCount, 0)
|
|
|
|
expect.fulfill()
|
|
}
|
|
})
|
|
}
|
|
|
|
waitForExpectations(timeout: 20)
|
|
}
|
|
|
|
func testEnclaveRequestReturnsNotFoundForFakeData() throws {
|
|
let fogViewConnection = try createFogViewConnection()
|
|
|
|
let expect = expectation(description: "Making Fog View enclave request")
|
|
|
|
var request = FogView_QueryRequest()
|
|
let fakeSearchKey = Data(repeating: 1, count: 16)
|
|
request.getTxos = [fakeSearchKey]
|
|
fogViewConnection.query(requestAad: FogView_QueryRequestAAD(), request: request) {
|
|
guard let response = $0.successOrFulfill(expectation: expect) else { return }
|
|
|
|
XCTAssertGreaterThan(response.highestProcessedBlockCount, 0)
|
|
XCTAssertEqual(response.txOutSearchResults.count, 1)
|
|
if let result = response.txOutSearchResults.first {
|
|
XCTAssertEqual(Array(result.searchKey), Array(fakeSearchKey))
|
|
XCTAssertEqual(result.resultCodeEnum, .notFound)
|
|
}
|
|
XCTAssertGreaterThan(response.lastKnownBlockCount, 0)
|
|
XCTAssertGreaterThan(response.lastKnownBlockCumulativeTxoCount, 0)
|
|
|
|
expect.fulfill()
|
|
}
|
|
waitForExpectations(timeout: 20)
|
|
}
|
|
|
|
func testEnclaveRequestReturnsBadSearchKeyForEmptySearchKey() throws {
|
|
let fogViewConnection = try createFogViewConnection()
|
|
|
|
let expect = expectation(description: "Making Fog View enclave request")
|
|
|
|
var request = FogView_QueryRequest()
|
|
let invalidSearchKey = Data(repeating: 0, count: 16)
|
|
request.getTxos = [invalidSearchKey]
|
|
fogViewConnection.query(requestAad: FogView_QueryRequestAAD(), request: request) {
|
|
guard let response = $0.successOrFulfill(expectation: expect) else { return }
|
|
|
|
XCTAssertGreaterThan(response.highestProcessedBlockCount, 0)
|
|
XCTAssertEqual(response.txOutSearchResults.count, 1)
|
|
if let result = response.txOutSearchResults.first {
|
|
XCTAssertEqual(Array(result.searchKey), Array(invalidSearchKey))
|
|
XCTAssertEqual(result.resultCodeEnum, .badSearchKey)
|
|
}
|
|
XCTAssertGreaterThan(response.lastKnownBlockCount, 0)
|
|
XCTAssertGreaterThan(response.lastKnownBlockCumulativeTxoCount, 0)
|
|
|
|
expect.fulfill()
|
|
}
|
|
waitForExpectations(timeout: 20)
|
|
}
|
|
|
|
func testEnclaveRequestReturnsBadSearchKeyForInvalidSearchKey() throws {
|
|
let fogViewConnection = try createFogViewConnection()
|
|
|
|
let expect = expectation(description: "Making Fog View enclave request")
|
|
|
|
var request = FogView_QueryRequest()
|
|
let invalidSearchKey = Data(repeating: 0, count: 0)
|
|
request.getTxos = [invalidSearchKey]
|
|
fogViewConnection.query(requestAad: FogView_QueryRequestAAD(), request: request) {
|
|
guard let response = $0.successOrFulfill(expectation: expect) else { return }
|
|
|
|
XCTAssertGreaterThan(response.highestProcessedBlockCount, 0)
|
|
XCTAssertEqual(response.txOutSearchResults.count, 1)
|
|
if let result = response.txOutSearchResults.first {
|
|
// TODO: this can be re-enabled once Fog returns the exact search key used
|
|
// XCTAssertEqual(Array(result.searchKey), Array(invalidSearchKey))
|
|
XCTAssertEqual(result.resultCodeEnum, .badSearchKey)
|
|
}
|
|
XCTAssertGreaterThan(response.lastKnownBlockCount, 0)
|
|
XCTAssertGreaterThan(response.lastKnownBlockCumulativeTxoCount, 0)
|
|
|
|
expect.fulfill()
|
|
}
|
|
waitForExpectations(timeout: 20)
|
|
}
|
|
|
|
func testEnclaveRequestReturnsBadSearchKeyForTooShortSearchKey() throws {
|
|
let fogViewConnection = try createFogViewConnection()
|
|
|
|
let expect = expectation(description: "Making Fog View enclave request")
|
|
|
|
var request = FogView_QueryRequest()
|
|
let invalidSearchKey = Data(repeating: 1, count: 8)
|
|
request.getTxos = [invalidSearchKey]
|
|
fogViewConnection.query(requestAad: FogView_QueryRequestAAD(), request: request) {
|
|
guard let response = $0.successOrFulfill(expectation: expect) else { return }
|
|
|
|
XCTAssertGreaterThan(response.highestProcessedBlockCount, 0)
|
|
XCTAssertEqual(response.txOutSearchResults.count, 1)
|
|
if let result = response.txOutSearchResults.first {
|
|
// TODO: this can be re-enabled once Fog returns the exact search key used
|
|
// XCTAssertEqual(Array(result.searchKey), Array(invalidSearchKey))
|
|
XCTAssertEqual(result.resultCodeEnum, .badSearchKey)
|
|
}
|
|
XCTAssertGreaterThan(response.lastKnownBlockCount, 0)
|
|
XCTAssertGreaterThan(response.lastKnownBlockCumulativeTxoCount, 0)
|
|
|
|
expect.fulfill()
|
|
}
|
|
waitForExpectations(timeout: 20)
|
|
}
|
|
|
|
func testEnclaveRequestReturnsBadSearchKeyForTooLongSearchKey() throws {
|
|
let fogViewConnection = try createFogViewConnection()
|
|
|
|
let expect = expectation(description: "Making Fog View enclave request")
|
|
|
|
var request = FogView_QueryRequest()
|
|
let invalidSearchKey = Data(repeating: 1, count: 32)
|
|
request.getTxos = [invalidSearchKey]
|
|
fogViewConnection.query(requestAad: FogView_QueryRequestAAD(), request: request) {
|
|
guard let response = $0.successOrFulfill(expectation: expect) else { return }
|
|
|
|
XCTAssertGreaterThan(response.highestProcessedBlockCount, 0)
|
|
XCTAssertEqual(response.txOutSearchResults.count, 1)
|
|
if let result = response.txOutSearchResults.first {
|
|
// TODO: this can be re-enabled once Fog returns the exact search key used
|
|
// XCTAssertEqual(Array(result.searchKey), Array(invalidSearchKey))
|
|
XCTAssertEqual(result.resultCodeEnum, .badSearchKey)
|
|
}
|
|
XCTAssertGreaterThan(response.lastKnownBlockCount, 0)
|
|
XCTAssertGreaterThan(response.lastKnownBlockCumulativeTxoCount, 0)
|
|
|
|
expect.fulfill()
|
|
}
|
|
waitForExpectations(timeout: 20)
|
|
}
|
|
|
|
func testInvalidCredentialsReturnsAuthorizationFailure() throws {
|
|
try XCTSkipUnless(IntegrationTestFixtures.network.fogRequiresCredentials)
|
|
|
|
let expect = expectation(description: "Making Fog View enclave request")
|
|
try createFogViewConnectionWithInvalidCredentials().query(
|
|
requestAad: FogView_QueryRequestAAD(),
|
|
request: FogView_QueryRequest()
|
|
) {
|
|
guard let error = $0.failureOrFulfill(expectation: expect) else { return }
|
|
|
|
switch error {
|
|
case .authorizationFailure:
|
|
break
|
|
default:
|
|
XCTFail("error of type \(type(of: error)), \(error)")
|
|
}
|
|
expect.fulfill()
|
|
}
|
|
waitForExpectations(timeout: 20)
|
|
}
|
|
}
|
|
|
|
extension FogViewConnectionIntTests {
|
|
func createFogViewConnection() throws -> FogViewConnection {
|
|
let networkConfig = try IntegrationTestFixtures.createNetworkConfig()
|
|
return createFogViewConnection(networkConfig: networkConfig)
|
|
}
|
|
|
|
func createFogViewConnectionWithInvalidCredentials() throws -> FogViewConnection {
|
|
let networkConfig = try IntegrationTestFixtures.createNetworkConfigWithInvalidCredentials()
|
|
return createFogViewConnection(networkConfig: networkConfig)
|
|
}
|
|
|
|
func createFogViewConnection(networkConfig: NetworkConfig) -> FogViewConnection {
|
|
FogViewConnection(
|
|
config: networkConfig.fogView,
|
|
channelManager: GrpcChannelManager(),
|
|
httpRequester: TestHttpRequester(),
|
|
targetQueue: DispatchQueue.main)
|
|
}
|
|
}
|