Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[ADD] missing logs subscription #303

Merged
merged 1 commit into from
Jan 9, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 38 additions & 2 deletions web3sTests/Client/EthereumClientTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -483,7 +483,7 @@ class EthereumWebSocketClientTests: EthereumClientTests {
await waitForExpectations(timeout: 5, handler: nil)

XCTAssertNotEqual(subscription.id, "")
XCTAssertEqual(subscription.type, .pendingTransactions)
XCTAssertEqual(subscription.type, .newPendingTransactions)
} catch {
XCTFail("Expected subscription but failed \(error).")
}
Expand Down Expand Up @@ -512,6 +512,31 @@ class EthereumWebSocketClientTests: EthereumClientTests {
}
}

func testWebSocketLogs() async {
do {
guard let client = client as? EthereumWebSocketClient else {
XCTFail("Expected client to be EthereumWebSocketClient")
return
}

var expectation: XCTestExpectation? = self.expectation(description: "Logs")
let type = EthereumSubscriptionType.logs(nil)
let subscription = try await client.logs { log in
print(log)
expectation?.fulfill()
expectation = nil
}

// we need a high timeout as new block might take a while
await waitForExpectations(timeout: 2500, handler: nil)

XCTAssertNotEqual(subscription.id, "")
XCTAssertEqual(subscription.type, type)
} catch {
XCTFail("Expected subscription but failed \(error).")
}
}

func testWebSocketSubscribe() async {
do {
guard let client = client as? EthereumWebSocketClient else {
Expand All @@ -521,14 +546,20 @@ class EthereumWebSocketClientTests: EthereumClientTests {
client.delegate = self

delegateExpectation = expectation(description: "onNewPendingTransaction delegate call")
var subscription = try await client.subscribe(type: .pendingTransactions)
var subscription = try await client.subscribe(type: .newPendingTransactions)
await waitForExpectations(timeout: 10)
_ = try await client.unsubscribe(subscription)

delegateExpectation = expectation(description: "onNewBlockHeader delegate call")
subscription = try await client.subscribe(type: .newBlockHeaders)
await waitForExpectations(timeout: 2500)
_ = try await client.unsubscribe(subscription)

delegateExpectation = expectation(description: "onLogs delegate call")
let type = EthereumSubscriptionType.logs(nil)
subscription = try await client.subscribe(type: type)
await waitForExpectations(timeout: 2500)
_ = try await client.unsubscribe(subscription)
} catch {
XCTFail("Expected subscription but failed \(error).")
}
Expand Down Expand Up @@ -561,4 +592,9 @@ extension EthereumWebSocketClientTests: EthereumWebSocketClientDelegate {
delegateExpectation?.fulfill()
delegateExpectation = nil
}

func onLog(subscription: EthereumSubscription, log: EthereumLog) {
delegateExpectation?.fulfill()
delegateExpectation = nil
}
}
6 changes: 6 additions & 0 deletions web3swift/src/Client/EthereumClientProtocol.swift
Original file line number Diff line number Diff line change
Expand Up @@ -109,13 +109,17 @@ public protocol EthereumClientProtocol: AnyObject {
func newBlockHeaders(onSubscribe: @escaping (Result<EthereumSubscription, EthereumClientError>) -> Void, onData: @escaping (EthereumHeader) -> Void)
func newBlockHeaders(onData: @escaping (EthereumHeader) -> Void) async throws -> EthereumSubscription

func logs(logsParams: LogsParams?, onSubscribe: @escaping (Result<EthereumSubscription, EthereumClientError>) -> Void, onData: @escaping (EthereumLog) -> Void)
func logs(logsParams: LogsParams?, onData: @escaping (EthereumLog) -> Void) async throws -> EthereumSubscription

func syncing(onSubscribe: @escaping (Result<EthereumSubscription, EthereumClientError>) -> Void, onData: @escaping (EthereumSyncStatus) -> Void)
func syncing(onData: @escaping (EthereumSyncStatus) -> Void) async throws -> EthereumSubscription
}

public protocol EthereumWebSocketClientDelegate: AnyObject {
func onNewPendingTransaction(subscription: EthereumSubscription, txHash: String)
func onNewBlockHeader(subscription: EthereumSubscription, header: EthereumHeader)
func onLog(subscription: EthereumSubscription, log: EthereumLog)
func onSyncing(subscription: EthereumSubscription, sync: EthereumSyncStatus)
func onWebSocketReconnect()
}
Expand All @@ -125,6 +129,8 @@ public protocol EthereumClientProtocol: AnyObject {

func onNewBlockHeader(subscription: EthereumSubscription, header: EthereumHeader) {}

func onLog(subscription: EthereumSubscription, log: EthereumLog) {}

func onSyncing(subscription: EthereumSubscription, sync: EthereumSyncStatus) {}

func onWebSocketReconnect() {}
Expand Down
39 changes: 34 additions & 5 deletions web3swift/src/Client/EthereumWebSocketClient.swift
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,7 @@
extension EthereumWebSocketClient: EthereumClientWebSocketProtocol {
public func subscribe(type: EthereumSubscriptionType) async throws -> EthereumSubscription {
do {
let data = try await networkProvider.send(method: "eth_subscribe", params: [type.method, type.params].compactMap { $0 }, receive: String.self)
let data = try await networkProvider.send(method: "eth_subscribe", params: type.params.compactMap { $0 }, receive: String.self)
if let resDataString = data as? String {
let subscription = EthereumSubscription(type: type, id: resDataString)
provider.addSubscription(subscription, callback: { _ in })
Expand Down Expand Up @@ -123,9 +123,9 @@

public func pendingTransactions(onData: @escaping (String) -> Void) async throws -> EthereumSubscription {
do {
let data = try await networkProvider.send(method: "eth_subscribe", params: [EthereumSubscriptionType.pendingTransactions.method], receive: String.self)
let data = try await networkProvider.send(method: "eth_subscribe", params: EthereumSubscriptionType.newPendingTransactions.params, receive: String.self)
if let resDataString = data as? String {
let subscription = EthereumSubscription(type: .pendingTransactions, id: resDataString)
let subscription = EthereumSubscription(type: .newPendingTransactions, id: resDataString)
provider.addSubscription(subscription, callback: { object in
onData(object as! String)
})
Expand All @@ -140,7 +140,7 @@

public func newBlockHeaders(onData: @escaping (EthereumHeader) -> Void) async throws -> EthereumSubscription {
do {
let data = try await networkProvider.send(method: "eth_subscribe", params: [EthereumSubscriptionType.newBlockHeaders.method], receive: String.self)
let data = try await networkProvider.send(method: "eth_subscribe", params: EthereumSubscriptionType.newBlockHeaders.params, receive: String.self)
if let resDataString = data as? String {
let subscription = EthereumSubscription(type: .newBlockHeaders, id: resDataString)
provider.addSubscription(subscription, callback: { object in
Expand All @@ -155,9 +155,27 @@
}
}

public func logs(logsParams: LogsParams? = nil, onData: @escaping (EthereumLog) -> Void) async throws -> EthereumSubscription {
do {
let type: EthereumSubscriptionType = .logs(logsParams)
let data = try await networkProvider.send(method: "eth_subscribe", params: type.params, receive: String.self)
if let resDataString = data as? String {
let subscription = EthereumSubscription(type: type, id: resDataString)
provider.addSubscription(subscription, callback: { object in
onData(object as! EthereumLog)
})
return subscription
} else {
throw EthereumClientError.unexpectedReturnValue
}
} catch {
throw failureHandler(error)
}
}

public func syncing(onData: @escaping (EthereumSyncStatus) -> Void) async throws -> EthereumSubscription {
do {
let data = try await networkProvider.send(method: "eth_subscribe", params: [EthereumSubscriptionType.syncing.method], receive: String.self)
let data = try await networkProvider.send(method: "eth_subscribe", params: EthereumSubscriptionType.syncing.params, receive: String.self)
if let resDataString = data as? String {
let subscription = EthereumSubscription(type: .syncing, id: resDataString)
provider.addSubscription(subscription, callback: { object in
Expand Down Expand Up @@ -218,6 +236,17 @@
}
}

public func logs(logsParams: LogsParams? = nil, onSubscribe: @escaping (Result<EthereumSubscription, EthereumClientError>) -> Void, onData: @escaping (EthereumLog) -> Void) {
Task {
do {
let result = try await logs(logsParams: logsParams, onData: onData)
onSubscribe(.success(result))
} catch {
failureHandler(error, completionHandler: onSubscribe)
}
}
}

public func syncing(onSubscribe: @escaping (Result<EthereumSubscription, EthereumClientError>) -> Void, onData: @escaping (EthereumSyncStatus) -> Void) {
Task {
do {
Expand Down
55 changes: 40 additions & 15 deletions web3swift/src/Client/Models/EthereumSubscription.swift
Original file line number Diff line number Diff line change
Expand Up @@ -7,31 +7,56 @@ import Foundation

public enum EthereumSubscriptionType: Equatable, Hashable {
case newBlockHeaders
case pendingTransactions
case logs(LogsParams?)
case newPendingTransactions
case syncing

var method: String {
var params: [EthereumSubscriptionParamElement] {
switch self {
case .newBlockHeaders:
return "newHeads"
case .pendingTransactions:
return "newPendingTransactions"
return [.method("newHeads")]
case let .logs(params):
return [.method("logs"), .logsParams(params ?? .init(address: nil, topics: nil))]
case .newPendingTransactions:
return [.method("newPendingTransactions")]
case .syncing:
return "syncing"
return [.method("syncing")]
}
}

struct LogParams: Encodable {
let address: [EthereumAddress]
let topics: [String?]
}

var params: String? {
nil
}
}

public struct EthereumSubscription: Hashable {
let type: EthereumSubscriptionType
let id: String
}

public enum EthereumSubscriptionParamElement: Encodable {
case method(String)
case logsParams(LogsParams)

public func encode(to encoder: Encoder) throws {
var container = encoder.singleValueContainer()
switch self {
case let .method(x):
try container.encode(x)
case let .logsParams(x):
try container.encode(x)
}
}
}

// MARK: - ParamClass
public struct LogsParams: Codable, Equatable, Hashable {
public let address: EthereumAddress?
public let topics: [String]?

enum CodingKeys: String, CodingKey {
case address = "address"
case topics = "topics"
}

public init(address: EthereumAddress?, topics: [String]?) {
self.address = address
self.topics = topics
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -331,7 +331,7 @@
self.delegate?.onNewBlockHeader(subscription: subscription.key, header: response.params.result)
subscription.value(response.params.result)
}
case .pendingTransactions:
case .newPendingTransactions:
if let data = string.data(using: .utf8), let response = try? JSONDecoder().decode(JSONRPCSubscriptionResponse<String>.self, from: data) {
self.delegate?.onNewPendingTransaction(subscription: subscription.key, txHash: response.params.result)
subscription.value(response.params.result)
Expand All @@ -341,6 +341,11 @@
self.delegate?.onSyncing(subscription: subscription.key, sync: response.params.result)
subscription.value(response.params.result)
}
case .logs:
if let data = string.data(using: .utf8), let response = try? JSONDecoder().decode(JSONRPCSubscriptionResponse<EthereumLog>.self, from: data) {
self.delegate?.onLog(subscription: subscription.key, log: response.params.result)
subscription.value(response.params.result)
}
}
}

Expand Down