Skip to content

Commit

Permalink
Merge pull request #303 from argentlabs/improvement/add_missing_subsc…
Browse files Browse the repository at this point in the history
…ription_method

[ADD] missing logs subscription
  • Loading branch information
DarthMike authored Jan 9, 2023
2 parents 91e3b62 + a9183b5 commit cb69f0d
Show file tree
Hide file tree
Showing 5 changed files with 124 additions and 23 deletions.
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

0 comments on commit cb69f0d

Please sign in to comment.