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
Support batching multiple RPC requests #187
base: main
Are you sure you want to change the base?
Conversation
…wift into feat/186-batch-multiple-rpc-requests
|
||
XCTAssertEqual(simulations2.count, 2) | ||
XCTAssertTrue(simulations2[0].transactionTrace is StarknetInvokeTransactionTrace) | ||
XCTAssertTrue(simulations2[1].transactionTrace is StarknetDeployAccountTransactionTrace) | ||
} | ||
|
||
func testBatchGetTransactionByHash() async throws { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
- Do we need to include more calls in this test?
- There should be a test for incorrect responses order. Imho it should be placed in a separate class eg.
JsonRpcResponseTests
as it focuses on the responses parsing mechanism.
Wdyt? @DelevoXDG
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
- I think that can be enough
- Yes, should add such test to
JsonRpcResponseTests
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I've added test for incorrect responses order
Also getOrderedRpcResults()
is now a separate internal function. It doesn't affect StarknetBatchRequest
, but simplifies creating test for incorrect order.
…wift into feat/186-batch-multiple-rpc-requests
} | ||
|
||
public func estimateFeeV1(calls: [StarknetCall], nonce: Felt, skipValidate: Bool) async throws -> StarknetFeeEstimate { | ||
let params = StarknetInvokeParamsV1(nonce: nonce, maxFee: .zero) | ||
let signedTransaction = try signV1(calls: calls, params: params, forFeeEstimation: true) | ||
|
||
return try await provider.estimateFee(for: signedTransaction, simulationFlags: skipValidate ? [.skipValidate] : []) | ||
return try await provider.estimateFee(for: signedTransaction, simulationFlags: skipValidate ? [.skipValidate] : []).send()[0] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
should be return [0] here?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, please look at the old implementation of calling estimateFee
. We could directly access value at index 0, because the returned type here was [StarknetFeeEstimate]
. Now it's Request<[StarknetFeeEstimate], EstimateFeeParams>
, therefore first we need to send and then access [0] element.
var orderedRpcResults: [Result<U, StarknetProviderError>?] = Array(repeating: nil, count: rpcPayloads.count) | ||
for rpcResponse in rpcResponses { | ||
if let error = rpcResponse.error { | ||
orderedRpcResults[rpcResponse.id] = .failure(StarknetProviderError.jsonRpcError(error.code, error.message, error.data)) | ||
} else if let result = rpcResponse.result { | ||
orderedRpcResults[rpcResponse.id] = .success(result) | ||
} else { | ||
orderedRpcResults[rpcResponse.id] = .failure(StarknetProviderError.unknownError) | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this seems like it could be really slow for longer response lists. I suppose we need ordering to properly deserialize? is there any other way?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We need to reorder the responses to match the order in which they were sent. This can be achieved with sorting O(nlogn)
or direct placement O(n)
. Second solution was accepted and implemented in JVM SDK, where there is same situation, here.
let result = try await sendBatch(payload: [payload], config: config, receive: [U.self]) | ||
guard let firstResult = result.first else { | ||
throw HttpNetworkProviderError.unknownError | ||
} | ||
return firstResult |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
why cannot we just send single request, why does it have to be sent as a batch?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actually I wanted to reuse sendBatch
to reduce duplicated code. However, sending single request is more valid in the conceptual aspect. I've refactored it - now there are two send
methods with different signatures. They send request with single json payload and array of jsons as payload respectively.
I've also added private methods for error handling to reduce duplicated code.
…wift into feat/186-batch-multiple-rpc-requests
@@ -106,7 +106,7 @@ public class StarknetAccount: StarknetAccountProtocol { | |||
let params = StarknetInvokeParamsV1(nonce: nonce, maxFee: maxFee) | |||
let signedTransaction = try signV1(calls: calls, params: params, forFeeEstimation: false) | |||
|
|||
return try await provider.addInvokeTransaction(signedTransaction) | |||
return try await provider.addInvokeTransaction(signedTransaction).send() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why not return Request
in StarknetAccount
methods as well?
Seems to me we might wanna batch multiple requests created by an account.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Correct, they also use provider methods. I've updated StarknetAccount
methods to return Request
.
@@ -1,6 +1,6 @@ | |||
import Foundation | |||
|
|||
enum TransactionReceiptWrapper: Decodable { | |||
public enum TransactionReceiptWrapper: Decodable { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is this change necessary?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, TransactionReceiptWrapper
is public because it's used in getTransactionReceiptBy()
let rpcPayloads = getBatchRequestRpcPayloads(requests: requests) | ||
let config = getHttpConfiguration() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If we refactor batchRequests<U: Decodable, P: Encodable>(requests: Request<U, P>...)
to just call batchRequests<U: Decodable, P: Encodable>(requests: [Request<U, P>])
, we can get rid of both getBatchRequestRpcPayloads
and getHttpConfiguration
and inline their contents there.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I inlined content of getBatchRequestRpcPayloads
there, but had to keep getHttpConfiguration
, which is also used in buildRequest
.
@@ -5,7 +5,7 @@ import XCTest | |||
final class ProviderTests: XCTestCase { | |||
static var devnetClient: DevnetClientProtocol! | |||
|
|||
var provider: StarknetProviderProtocol! | |||
var provider: StarknetProvider! |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nit:
Maybe we should just add separate provider instance / cast for batch tests?
I don't have a strong opinion on this though. But the idea is to verify provider as a protocol can be used with no issues
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sure, I restored var provider: StarknetProviderProtocol!
, added separate batchProvider
and used it in test for batchRequests
.
do { | ||
let _ = try transactionsResponse[1].get().transaction.hash | ||
XCTFail("Fetching transaction with nonexistent hash should fail") | ||
} catch {} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We can catch an error and verify if it's correct one
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sure, I've added check for error type and error message 👍
|
||
XCTAssertEqual(simulations2.count, 2) | ||
XCTAssertTrue(simulations2[0].transactionTrace is StarknetInvokeTransactionTrace) | ||
XCTAssertTrue(simulations2[1].transactionTrace is StarknetDeployAccountTransactionTrace) | ||
} | ||
|
||
func testBatchGetTransactionByHash() async throws { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
- I think that can be enough
- Yes, should add such test to
JsonRpcResponseTests
Co-authored-by: Maksim Zdobnikau <43750648+DelevoXDG@users.noreply.github.com>
….com/software-mansion/starknet.swift into feat/186-batch-multiple-rpc-requests
let nonce = try await account.getNonce() | ||
let feeEstimate = try await account.estimateFeeV1(call: call, nonce: nonce) | ||
let nonce = try await account.getNonce().send() | ||
let feeEstimate = try await account.estimateFeeV1(call: call, nonce: nonce).send()[0] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The change of estimate fee return type resulted in having to access element [0]
every time we use this metod. Unfortunately there isn't any other way, because first we need to send the request and the access its result.
@@ -30,16 +31,17 @@ final class ProviderTests: XCTestCase { | |||
} | |||
|
|||
provider = makeStarknetProvider(url: Self.devnetClient.rpcUrl) | |||
batchProvider = makeStarknetProvider(url: Self.devnetClient.rpcUrl) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nit: could the name variable name be improved?
do { | ||
let _ = try transactionsResponse[1].get().transaction.hash | ||
XCTFail("Fetching transaction with nonexistent hash should fail") | ||
} catch let error as StarknetProviderError { | ||
switch error { | ||
case let .jsonRpcError(_, message, _): | ||
XCTAssertEqual(message, "Transaction hash not found", "Unexpected error message received") | ||
default: | ||
XCTFail("Expected JsonRpcError but received \(error)") | ||
} | ||
} catch { | ||
XCTFail("Error was not a StarknetProviderError. Received error type: \(type(of: error))") | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't really like the fact that there is double nesting here, but I don't any other way to access jsonRpcError
message.
All requested changes have been addressed, I've also added few remarks about newly applied changes. |
Describe your changes
Suport batching multiple RPC requests
StarknetRequest
andStarknetBatchRequest
classesStarknetProvider.batchRequests()
Linked issues
Closes #186
Breaking changes
StarknetAccount
executeV1(...) async throws -> StarknetInvokeTransactionResponse
->executeV1(...) async throws -> StarknetRequest<StarknetInvokeTransactionResponse, AddInvokeTransactionParams>
executeV3(...) async throws -> StarknetInvokeTransactionResponse
->executeV3(...) async throws -> StarknetRequest<StarknetInvokeTransactionResponse, AddInvokeTransactionParams>
executeV1(...) async throws -> StarknetInvokeTransactionResponse
->executeV1(...) async throws -> StarknetRequest<StarknetInvokeTransactionResponse, AddInvokeTransactionParams>
executeV3(...) async throws -> StarknetInvokeTransactionResponse
->executeV3(...) async throws -> StarknetRequest<StarknetInvokeTransactionResponse, AddInvokeTransactionParams>
estimateFeeV1(...) async throws -> StarknetFeeEstimate
->estimateFeeV1(...) async throws -> StarknetRequest<[StarknetFeeEstimate], EstimateFeeParams>
estimateFeeV3(...) async throws -> StarknetFeeEstimate
->estimateFeeV3(...) async throws -> StarknetRequest<[StarknetFeeEstimate], EstimateFeeParams>
estimateDeployAccountFeeV1(...) async throws -> StarknetFeeEstimate
->estimateDeployAccountFeeV1(...) async throws -> StarknetRequest<[StarknetFeeEstimate], EstimateFeeParams>
estimateDeployAccountFeeV3(...) async throws -> StarknetFeeEstimate
->estimateDeployAccountFeeV3(...) async throws -> StarknetRequest<[StarknetFeeEstimate], EstimateFeeParams>
getNonce() async throws -> Felt
->getNonce() async throws -> StarknetRequest<Felt, GetNonceParams>