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

Support batching multiple RPC requests #187

Merged
merged 62 commits into from
Jun 12, 2024

Conversation

franciszekjob
Copy link
Collaborator

@franciszekjob franciszekjob commented Apr 25, 2024

Describe your changes

Suport batching multiple RPC requests

  • add StarknetRequest and StarknetBatchRequest structs
  • add StarknetProvider.batchRequests() accepting both list and variadic parameters
  • add JsonRpcParams enum and extension
  • add batch-related tests

Linked issues

Closes #186, #145

Breaking changes

  • This issue contains breaking changes
  • StarknetAccount
    • executeV1(...) async throws -> StarknetInvokeTransactionResponse -> executeV1(...) -> StarknetRequest<StarknetInvokeTransactionResponse>
    • executeV3(...) async throws -> StarknetInvokeTransactionResponse -> executeV3(...) -> StarknetRequest<StarknetInvokeTransactionResponse>
    • executeV1(...) async throws -> StarknetInvokeTransactionResponse -> executeV1(...) -> StarknetRequest<StarknetInvokeTransactionResponse>
    • executeV3(...) async throws -> StarknetInvokeTransactionResponse -> executeV3(...) -> StarknetRequest<StarknetInvokeTransactionResponse>
    • estimateFeeV1(...) async throws -> StarknetFeeEstimate -> estimateFeeV1(...) -> StarknetRequest<[StarknetFeeEstimate]>
    • estimateFeeV3(...) async throws -> StarknetFeeEstimate -> estimateFeeV3(...) -> StarknetRequest<[StarknetFeeEstimate]>
    • estimateDeployAccountFeeV1(...) async throws -> StarknetFeeEstimate -> estimateDeployAccountFeeV1(...) -> StarknetRequest<[StarknetFeeEstimate]>
    • estimateDeployAccountFeeV3(...) async throws -> StarknetFeeEstimate -> estimateDeployAccountFeeV3(...) -> StarknetRequest<[StarknetFeeEstimate]>
    • getNonce() async throws -> Felt -> getNonce()-> StarknetRequest<Felt>
  • StarknetProvider
    • specVersion() async throws -> String -> specVersion() -> StarknetRequest<String>
    • callContract(...) async throws -> [Felt] -> callContract(...) -> StarknetRequest<[Felt]>
    • estimateMessageFee(...) async throws -> StarknetFeeEstimate -> estimateMessageFee(...) -> StarknetRequest<StarknetFeeEstimate>
    • getNonce() async throws -> Felt -> getNonce() -> StarknetRequest<Felt>
    • addInvokeTransaction(...) async throws -> StarknetInvokeTransactionResponse -> addInvokeTransaction(...) -> StarknetRequest<StarknetInvokeTransactionResponse>
    • add addDeployAccountTransaction(...) async throws -> StarknetDeployAccountResponse -> addDeployAccountTransaction(...) -> StarknetRequest<StarknetDeployAccountResponse>
    • getClassHashAt(...) async throws -> Felt -> getClassHashAt(...) -> StarknetRequest<Felt>
    • getBlockNumber() async throws -> UInt64 -> getBlockNumber() -> StarknetRequest<UInt64>
    • getBlockHashAndNumber() async throws -> StarknetBlockHashAndNumber -> getBlockHashAndNumber() -> StarknetRequest<StarknetBlockHashAndNumber>
    • getEvents(...) async throws -> StarknetGetEventsResponse -> getEvents(...) -> StarknetRequest<StarknetGetEventsResponse>
    • getTransactionBy(...) async throws -> any StarknetTransaction -> getTransactionBy(...) -> StarknetRequest<StarknetTransaction>
    • getTransactionReceiptBy(...) async throws -> any StarknetTransactionReceipt -> getTransactionReceiptBy(...) -> StarknetRequest<StarknetTransactionReceipt>
    • getTransactionStatusBy(...) async throws -> StarknetGetTransactionStatusResponse -> getTransactionStatusBy(...) async throws -> StarknetRequest<StarknetGetTransactionStatusResponse>
    • getChainId() async throws -> StarknetChainId -> getChainId() -> StarknetRequest<StarknetChainId>
    • simulateTransactions(...) async throws -> [StarknetSimulatedTransaction] -> simulateTransactions(...) -> StarknetRequest<[StarknetSimulatedTransaction]>


XCTAssertEqual(simulations2.count, 2)
XCTAssertTrue(simulations2[0].transactionTrace is StarknetInvokeTransactionTrace)
XCTAssertTrue(simulations2[1].transactionTrace is StarknetDeployAccountTransactionTrace)
}

func testBatchGetTransactionByHash() async throws {
Copy link
Collaborator Author

@franciszekjob franciszekjob May 10, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  1. Do we need to include more calls in this test?
  2. 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

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  1. I think that can be enough
  2. Yes, should add such test to JsonRpcResponseTests

Copy link
Collaborator Author

@franciszekjob franciszekjob May 16, 2024

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.

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]
Copy link
Collaborator Author

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)
Copy link
Collaborator Author

@franciszekjob franciszekjob May 16, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: could the variable name be improved?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we might wanna add batch to the StarknetProviderProtocol, WDYT?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yup, I added it to the protocol 👍

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If that's the case, can we just go ahead and use provider instead of batchProvider?

Comment on lines +421 to +433
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))")
}
Copy link
Collaborator Author

@franciszekjob franciszekjob May 16, 2024

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, maybe there's other way to access jsonRpcError message?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I understand the concern, but afaik we can't have some kind of property that is shared amongst all enums, enums in swift just work differently. So we'll have to stick to what's possible here 😅

@franciszekjob
Copy link
Collaborator Author

franciszekjob commented May 16, 2024

All requested changes have been addressed, I've also added few remarks about newly applied changes.

}

if let paramsResourceBounds = params.resourceBounds {
resourceBounds = paramsResourceBounds
} else {
let feeEstimate = try await estimateFeeV3(calls: calls, nonce: nonce)
let feeEstimate = try await estimateFeeV3(calls: calls, nonce: nonce).send()[0]
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are you sure getting the first estimate is a valid approach?

Copy link
Collaborator Author

@franciszekjob franciszekjob Jun 3, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It has been discussed in previous thread
#187 (comment)

Sources/Starknet/Accounts/StarknetAccount.swift Outdated Show resolved Hide resolved
Sources/Starknet/Network/HttpNetworkProvider.swift Outdated Show resolved Hide resolved
Comment on lines 3 to 4
public typealias EmptySequence = [String]
public struct EmptyParams: Encodable {}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It could be theoretically achieved, if we decrease params' level of abstraction.

Can we do that? I think it would considerably simplify the codebase (no unnecessary generis, no public param structs), but if that's too much effort it can be done in a separate PR.

Copy link
Collaborator

@DelevoXDG DelevoXDG left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Mostly ok, few minor comments to be addressed before merging

Comment on lines +421 to +433
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))")
}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I understand the concern, but afaik we can't have some kind of property that is shared amongst all enums, enums in swift just work differently. So we'll have to stick to what's possible here 😅

Sources/Starknet/Network/StarknetRequest.swift Outdated Show resolved Hide resolved
Sources/Starknet/Network/StarknetBatchRequest.swift Outdated Show resolved Hide resolved
}
}

func getOrderedRpcResults<U: Decodable>(rpcResponses: [JsonRpcResponse<U>], count: Int) -> [Result<U, StarknetProviderError>] {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit:

We can rename this to prepareOrderedRpcResults or orderRpcResults.

Also, I think we can just inline this.

Copy link
Collaborator Author

@franciszekjob franciszekjob Jun 11, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I renamed it to orderRpcResults. It wasn't inlined on purpose - thanks to keeping it in separate function, ordering can be tested in testBatchResponseWithIncorrectOrder

@@ -30,16 +31,17 @@ final class ProviderTests: XCTestCase {
}

provider = makeStarknetProvider(url: Self.devnetClient.rpcUrl)
batchProvider = makeStarknetProvider(url: Self.devnetClient.rpcUrl)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we might wanna add batch to the StarknetProviderProtocol, WDYT?

franciszekjob and others added 6 commits June 11, 2024 19:29
Co-authored-by: Maksim Zdobnikau <43750648+DelevoXDG@users.noreply.github.com>
Co-authored-by: Maksim Zdobnikau <43750648+DelevoXDG@users.noreply.github.com>
Co-authored-by: Maksim Zdobnikau <43750648+DelevoXDG@users.noreply.github.com>
Copy link
Collaborator

@DelevoXDG DelevoXDG left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM overall, can be merged after addressing the comments

Sources/Starknet/Network/StarknetBatchRequest.swift Outdated Show resolved Hide resolved
Sources/Starknet/Network/StarknetBatchRequest.swift Outdated Show resolved Hide resolved
@@ -30,16 +31,17 @@ final class ProviderTests: XCTestCase {
}

provider = makeStarknetProvider(url: Self.devnetClient.rpcUrl)
batchProvider = makeStarknetProvider(url: Self.devnetClient.rpcUrl)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If that's the case, can we just go ahead and use provider instead of batchProvider?

@franciszekjob franciszekjob merged commit a5c820a into main Jun 12, 2024
1 check passed
@franciszekjob franciszekjob deleted the feat/186-batch-multiple-rpc-requests branch June 12, 2024 15:10
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Support batching multiple RPC requests
3 participants