-
Notifications
You must be signed in to change notification settings - Fork 0
Add support for aggregator API keys #17
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
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change | ||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
@@ -0,0 +1,20 @@ | ||||||||||||||||||
package org.unicitylabs.sdk.jsonrpc; | ||||||||||||||||||
|
||||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The RateLimitExceededException class is missing a Javadoc comment. Add documentation explaining when this exception is thrown and the purpose of the retryAfterSeconds field.
Suggested change
Copilot uses AI. Check for mistakes. Positive FeedbackNegative Feedback |
||||||||||||||||||
public class RateLimitExceededException extends RuntimeException { | ||||||||||||||||||
|
||||||||||||||||||
private final int retryAfterSeconds; | ||||||||||||||||||
|
||||||||||||||||||
public RateLimitExceededException(String message, int retryAfterSeconds) { | ||||||||||||||||||
super(message); | ||||||||||||||||||
this.retryAfterSeconds = retryAfterSeconds; | ||||||||||||||||||
} | ||||||||||||||||||
|
||||||||||||||||||
public RateLimitExceededException(String message, int retryAfterSeconds, Throwable cause) { | ||||||||||||||||||
super(message, cause); | ||||||||||||||||||
this.retryAfterSeconds = retryAfterSeconds; | ||||||||||||||||||
} | ||||||||||||||||||
|
||||||||||||||||||
public int getRetryAfterSeconds() { | ||||||||||||||||||
return retryAfterSeconds; | ||||||||||||||||||
} | ||||||||||||||||||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
package org.unicitylabs.sdk.jsonrpc; | ||
|
||
/** | ||
* Exception thrown when an API request is unauthorized (HTTP 401). | ||
* This typically occurs when an API key is missing or invalid. | ||
*/ | ||
public class UnauthorizedException extends RuntimeException { | ||
|
||
public UnauthorizedException(String message) { | ||
super(message); | ||
} | ||
|
||
public UnauthorizedException(String message, Throwable cause) { | ||
super(message, cause); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change | ||||||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
@@ -0,0 +1,152 @@ | ||||||||||||||||||||||
package org.unicitylabs.sdk; | ||||||||||||||||||||||
|
||||||||||||||||||||||
import com.fasterxml.jackson.core.JsonProcessingException; | ||||||||||||||||||||||
import okhttp3.mockwebserver.Dispatcher; | ||||||||||||||||||||||
import okhttp3.mockwebserver.MockResponse; | ||||||||||||||||||||||
import okhttp3.mockwebserver.MockWebServer; | ||||||||||||||||||||||
import okhttp3.mockwebserver.RecordedRequest; | ||||||||||||||||||||||
import com.fasterxml.jackson.databind.ObjectMapper; | ||||||||||||||||||||||
import com.fasterxml.jackson.databind.JsonNode; | ||||||||||||||||||||||
import org.jetbrains.annotations.Nullable; | ||||||||||||||||||||||
|
||||||||||||||||||||||
import java.io.IOException; | ||||||||||||||||||||||
import java.util.Set; | ||||||||||||||||||||||
import java.util.HashSet; | ||||||||||||||||||||||
import java.util.UUID; | ||||||||||||||||||||||
|
||||||||||||||||||||||
public class MockAggregatorServer { | ||||||||||||||||||||||
|
||||||||||||||||||||||
private final MockWebServer server; | ||||||||||||||||||||||
private final ObjectMapper objectMapper; | ||||||||||||||||||||||
private final Set<String> protectedMethods; | ||||||||||||||||||||||
private volatile boolean simulateRateLimit = false; | ||||||||||||||||||||||
private volatile int rateLimitRetryAfter = 60; | ||||||||||||||||||||||
private volatile String expectedApiKey = null; | ||||||||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The simulateRateLimit flag is reset after one use but rateLimitRetryAfter persists. Consider resetting rateLimitRetryAfter to its default value when simulateRateLimit is reset to avoid unexpected behavior in subsequent tests.
Suggested change
Copilot uses AI. Check for mistakes. Positive FeedbackNegative Feedback |
||||||||||||||||||||||
|
||||||||||||||||||||||
public MockAggregatorServer() { | ||||||||||||||||||||||
this.server = new MockWebServer(); | ||||||||||||||||||||||
this.objectMapper = new ObjectMapper(); | ||||||||||||||||||||||
this.protectedMethods = new HashSet<>(); | ||||||||||||||||||||||
this.protectedMethods.add("submit_commitment"); | ||||||||||||||||||||||
|
||||||||||||||||||||||
server.setDispatcher(new Dispatcher() { | ||||||||||||||||||||||
@Override | ||||||||||||||||||||||
public MockResponse dispatch(RecordedRequest request) { | ||||||||||||||||||||||
return handleRequest(request); | ||||||||||||||||||||||
} | ||||||||||||||||||||||
}); | ||||||||||||||||||||||
} | ||||||||||||||||||||||
|
||||||||||||||||||||||
public void start() throws IOException { | ||||||||||||||||||||||
server.start(); | ||||||||||||||||||||||
} | ||||||||||||||||||||||
|
||||||||||||||||||||||
public void shutdown() throws IOException { | ||||||||||||||||||||||
server.shutdown(); | ||||||||||||||||||||||
} | ||||||||||||||||||||||
|
||||||||||||||||||||||
public String getUrl() { | ||||||||||||||||||||||
return server.url("/").toString(); | ||||||||||||||||||||||
} | ||||||||||||||||||||||
|
||||||||||||||||||||||
public RecordedRequest takeRequest() throws InterruptedException { | ||||||||||||||||||||||
return server.takeRequest(); | ||||||||||||||||||||||
} | ||||||||||||||||||||||
|
||||||||||||||||||||||
public void simulateRateLimitForNextRequest(int retryAfterSeconds) { | ||||||||||||||||||||||
this.simulateRateLimit = true; | ||||||||||||||||||||||
this.rateLimitRetryAfter = retryAfterSeconds; | ||||||||||||||||||||||
} | ||||||||||||||||||||||
|
||||||||||||||||||||||
public void setExpectedApiKey(String apiKey) { | ||||||||||||||||||||||
this.expectedApiKey = apiKey; | ||||||||||||||||||||||
} | ||||||||||||||||||||||
|
||||||||||||||||||||||
private MockResponse handleRequest(RecordedRequest request) { | ||||||||||||||||||||||
try { | ||||||||||||||||||||||
if (simulateRateLimit) { | ||||||||||||||||||||||
simulateRateLimit = false; // Reset for next request | ||||||||||||||||||||||
return new MockResponse() | ||||||||||||||||||||||
.setResponseCode(429) | ||||||||||||||||||||||
.setHeader("Retry-After", String.valueOf(rateLimitRetryAfter)) | ||||||||||||||||||||||
.setBody("Too Many Requests"); | ||||||||||||||||||||||
} | ||||||||||||||||||||||
|
||||||||||||||||||||||
String method = extractJsonRpcMethod(request); | ||||||||||||||||||||||
|
||||||||||||||||||||||
if (protectedMethods.contains(method) && expectedApiKey != null && !hasValidApiKey(request)) { | ||||||||||||||||||||||
return new MockResponse() | ||||||||||||||||||||||
.setResponseCode(401) | ||||||||||||||||||||||
.setHeader("WWW-Authenticate", "Bearer") | ||||||||||||||||||||||
.setBody("Unauthorized"); | ||||||||||||||||||||||
} | ||||||||||||||||||||||
|
||||||||||||||||||||||
return generateSuccessResponse(method); | ||||||||||||||||||||||
|
||||||||||||||||||||||
} catch (Exception e) { | ||||||||||||||||||||||
return new MockResponse() | ||||||||||||||||||||||
.setResponseCode(400) | ||||||||||||||||||||||
.setBody("Bad Request"); | ||||||||||||||||||||||
} | ||||||||||||||||||||||
} | ||||||||||||||||||||||
|
||||||||||||||||||||||
private boolean hasValidApiKey(RecordedRequest request) { | ||||||||||||||||||||||
String authHeader = request.getHeader("Authorization"); | ||||||||||||||||||||||
if (authHeader != null && authHeader.startsWith("Bearer ")) { | ||||||||||||||||||||||
String providedKey = authHeader.substring(7); | ||||||||||||||||||||||
return expectedApiKey.equals(providedKey); | ||||||||||||||||||||||
} | ||||||||||||||||||||||
return false; | ||||||||||||||||||||||
} | ||||||||||||||||||||||
|
||||||||||||||||||||||
private @Nullable String extractJsonRpcMethod(RecordedRequest request) throws JsonProcessingException { | ||||||||||||||||||||||
if (!"POST".equals(request.getMethod())) { | ||||||||||||||||||||||
return null; | ||||||||||||||||||||||
} | ||||||||||||||||||||||
JsonNode jsonRequest = objectMapper.readTree(request.getBody().readUtf8()); | ||||||||||||||||||||||
return jsonRequest.has("method") ? jsonRequest.get("method").asText() : null; | ||||||||||||||||||||||
} | ||||||||||||||||||||||
|
||||||||||||||||||||||
private MockResponse generateSuccessResponse(String method) { | ||||||||||||||||||||||
String responseBody; | ||||||||||||||||||||||
String id = UUID.randomUUID().toString(); | ||||||||||||||||||||||
|
||||||||||||||||||||||
switch (method != null ? method : "") { | ||||||||||||||||||||||
case "submit_commitment": | ||||||||||||||||||||||
responseBody = String.format( | ||||||||||||||||||||||
"{\n" + | ||||||||||||||||||||||
" \"jsonrpc\": \"2.0\",\n" + | ||||||||||||||||||||||
" \"result\": {\n" + | ||||||||||||||||||||||
" \"status\": \"SUCCESS\"\n" + | ||||||||||||||||||||||
" },\n" + | ||||||||||||||||||||||
" \"id\": \"%s\"\n" + | ||||||||||||||||||||||
"}", id); | ||||||||||||||||||||||
break; | ||||||||||||||||||||||
|
||||||||||||||||||||||
case "get_block_height": | ||||||||||||||||||||||
responseBody = String.format( | ||||||||||||||||||||||
"{\n" + | ||||||||||||||||||||||
" \"jsonrpc\": \"2.0\",\n" + | ||||||||||||||||||||||
" \"result\": {\n" + | ||||||||||||||||||||||
" \"blockNumber\": \"67890\"\n" + | ||||||||||||||||||||||
" },\n" + | ||||||||||||||||||||||
" \"id\": \"%s\"\n" + | ||||||||||||||||||||||
"}", id); | ||||||||||||||||||||||
break; | ||||||||||||||||||||||
|
||||||||||||||||||||||
default: | ||||||||||||||||||||||
responseBody = String.format( | ||||||||||||||||||||||
"{\n" + | ||||||||||||||||||||||
" \"jsonrpc\": \"2.0\",\n" + | ||||||||||||||||||||||
" \"result\": \"OK\",\n" + | ||||||||||||||||||||||
" \"id\": \"%s\"\n" + | ||||||||||||||||||||||
"}", id); | ||||||||||||||||||||||
break; | ||||||||||||||||||||||
} | ||||||||||||||||||||||
|
||||||||||||||||||||||
return new MockResponse() | ||||||||||||||||||||||
.setResponseCode(200) | ||||||||||||||||||||||
.setHeader("Content-Type", "application/json") | ||||||||||||||||||||||
.setBody(responseBody); | ||||||||||||||||||||||
} | ||||||||||||||||||||||
} |
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 message could be put together inside RateLimitExceededException constructor