Skip to content

Commit

Permalink
Merge pull request #1280 from WalletConnect/feature/rpc-delete-on-res…
Browse files Browse the repository at this point in the history
…olve

[RPCHistory] Delete on resolve
  • Loading branch information
flypaper0 authored Jan 19, 2024
2 parents 3d8e3fa + 7195db9 commit 1a5784e
Show file tree
Hide file tree
Showing 4 changed files with 77 additions and 14 deletions.
6 changes: 3 additions & 3 deletions Sources/WalletConnectNetworking/NetworkingInteractor.swift
Original file line number Diff line number Diff line change
Expand Up @@ -171,9 +171,10 @@ public class NetworkingInteractor: NetworkInteracting {
}

public func respond(topic: String, response: RPCResponse, protocolMethod: ProtocolMethod, envelopeType: Envelope.EnvelopeType) async throws {
try rpcHistory.resolve(response)
try rpcHistory.validate(response)
let message = try serializer.serialize(topic: topic, encodable: response, envelopeType: envelopeType)
try await relayClient.publish(topic: topic, payload: message, tag: protocolMethod.responseConfig.tag, prompt: protocolMethod.responseConfig.prompt, ttl: protocolMethod.responseConfig.ttl)
try rpcHistory.resolve(response)
}

public func respondSuccess(topic: String, requestId: RPCID, protocolMethod: ProtocolMethod, envelopeType: Envelope.EnvelopeType) async throws {
Expand Down Expand Up @@ -216,8 +217,7 @@ public class NetworkingInteractor: NetworkInteracting {

private func handleResponse(topic: String, response: RPCResponse, publishedAt: Date, derivedTopic: String?) {
do {
try rpcHistory.resolve(response)
let record = rpcHistory.get(recordId: response.id!)!
let record = try rpcHistory.resolve(response)
responsePublisherSubject.send((topic, record.request, response, publishedAt, derivedTopic))
} catch {
logger.debug("Handle json rpc response error: \(error)")
Expand Down
42 changes: 36 additions & 6 deletions Sources/WalletConnectUtils/RPCHistory/RPCHistory.swift
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import Foundation

public final class RPCHistory {

public struct Record: Codable {
Expand All @@ -9,7 +11,8 @@ public final class RPCHistory {
public let topic: String
let origin: Origin
public let request: RPCRequest
public var response: RPCResponse?
public let response: RPCResponse?
public var timestamp: Date?
}

enum HistoryError: Error {
Expand All @@ -24,36 +27,43 @@ public final class RPCHistory {

init(keyValueStore: CodableStore<Record>) {
self.storage = keyValueStore

removeOutdated()
}

public func get(recordId: RPCID) -> Record? {
try? storage.get(key: recordId.string)
}

public func set(_ request: RPCRequest, forTopic topic: String, emmitedBy origin: Record.Origin) throws {
public func set(_ request: RPCRequest, forTopic topic: String, emmitedBy origin: Record.Origin, time: TimeProvider = DefaultTimeProvider()) throws {
guard let id = request.id else {
throw HistoryError.unidentifiedRequest
}
guard get(recordId: id) == nil else {
throw HistoryError.requestDuplicateNotAllowed
}
let record = Record(id: id, topic: topic, origin: origin, request: request)
let record = Record(id: id, topic: topic, origin: origin, request: request, response: nil, timestamp: time.currentDate)
storage.set(record, forKey: "\(record.id)")
}

@discardableResult
public func resolve(_ response: RPCResponse) throws -> Record {
let record = try validate(response)
storage.delete(forKey: "\(record.id)")
return record
}

@discardableResult
public func validate(_ response: RPCResponse) throws -> Record {
guard let id = response.id else {
throw HistoryError.unidentifiedResponse
}
guard var record = get(recordId: id) else {
guard let record = get(recordId: id) else {
throw HistoryError.requestMatchingResponseNotFound
}
guard record.response == nil else {
throw HistoryError.responseDuplicateNotAllowed
}
record.response = response
storage.set(record, forKey: "\(record.id)")
return record
}

Expand Down Expand Up @@ -95,3 +105,23 @@ public final class RPCHistory {
storage.getAll().filter { $0.response == nil }
}
}

extension RPCHistory {

func removeOutdated() {
let records = storage.getAll()

let thirtyDays: TimeInterval = 30*86400

for var record in records {
if let timestamp = record.timestamp {
if timestamp.distance(to: Date()) > thirtyDays {
storage.delete(forKey: record.id.string)
}
} else {
record.timestamp = Date()
storage.set(record, forKey: "\(record.id)")
}
}
}
}
12 changes: 12 additions & 0 deletions Sources/WalletConnectUtils/TimeProvider.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import Foundation

public protocol TimeProvider {
var currentDate: Date { get }
}

public struct DefaultTimeProvider: TimeProvider {
public init() {}
public var currentDate: Date {
return Date()
}
}
31 changes: 26 additions & 5 deletions Tests/WalletConnectUtilsTests/RPCHistoryTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -35,10 +35,8 @@ final class RPCHistoryTests: XCTestCase {
try sut.set(requestB, forTopic: String.randomTopic(), emmitedBy: .local)
try sut.resolve(responseA)
try sut.resolve(responseB)
let recordA = sut.get(recordId: requestA.id!)
let recordB = sut.get(recordId: requestB.id!)
XCTAssertEqual(recordA?.response, responseA)
XCTAssertEqual(recordB?.response, responseB)
XCTAssertNil(sut.get(recordId: requestA.id!))
XCTAssertNil(sut.get(recordId: requestB.id!))
}

func testDelete() throws {
Expand Down Expand Up @@ -95,7 +93,7 @@ final class RPCHistoryTests: XCTestCase {
}

func testResolveDuplicateResponse() throws {
let expectedError = RPCHistory.HistoryError.responseDuplicateNotAllowed
let expectedError = RPCHistory.HistoryError.requestMatchingResponseNotFound

let request = RPCRequest.stub()
let responseA = RPCResponse(matchingRequest: request, result: true)
Expand All @@ -107,4 +105,27 @@ final class RPCHistoryTests: XCTestCase {
XCTAssertEqual(expectedError, error as? RPCHistory.HistoryError)
}
}

func testRemoveOutdated() throws {
let request1 = RPCRequest.stub()
let request2 = RPCRequest.stub()

let time1 = TestTimeProvider(currentDate: .distantPast)
let time2 = TestTimeProvider(currentDate: Date())

try sut.set(request1, forTopic: .randomTopic(), emmitedBy: .local, time: time1)
try sut.set(request2, forTopic: .randomTopic(), emmitedBy: .local, time: time2)

XCTAssertEqual(sut.get(recordId: request1.id!)?.request, request1)
XCTAssertEqual(sut.get(recordId: request2.id!)?.request, request2)

sut.removeOutdated()

XCTAssertEqual(sut.get(recordId: request1.id!)?.request, nil)
XCTAssertEqual(sut.get(recordId: request2.id!)?.request, request2)
}

struct TestTimeProvider: TimeProvider {
var currentDate: Date
}
}

0 comments on commit 1a5784e

Please sign in to comment.