Skip to content

Commit 189ef58

Browse files
Add network/fees stub api to rest-java (#12576)
Add a stub REST API endpoint for fee estimation to enable early SDK integration. * Remove gRPC stub implementation and protobuf definitions * Add POST /api/v1/network/fees endpoint to OpenAPI specification * Implement stub endpoint returning test data in NetworkController * Add test for fee estimation endpoint --------- Signed-off-by: martingeorgiev1 <[email protected]> Signed-off-by: Steven Sheehy <[email protected]> Co-authored-by: Steven Sheehy <[email protected]>
1 parent 1495261 commit 189ef58

File tree

9 files changed

+402
-236
lines changed

9 files changed

+402
-236
lines changed

grpc/src/main/java/org/hiero/mirror/grpc/controller/NetworkController.java

Lines changed: 0 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -3,19 +3,10 @@
33
package org.hiero.mirror.grpc.controller;
44

55
import com.google.protobuf.ByteString;
6-
import com.google.protobuf.InvalidProtocolBufferException;
76
import com.hedera.mirror.api.proto.AddressBookQuery;
8-
import com.hedera.mirror.api.proto.Fee.EstimateMode;
9-
import com.hedera.mirror.api.proto.Fee.FeeEstimate;
10-
import com.hedera.mirror.api.proto.Fee.FeeEstimateQuery;
11-
import com.hedera.mirror.api.proto.Fee.FeeEstimateResponse;
12-
import com.hedera.mirror.api.proto.Fee.FeeExtra;
13-
import com.hedera.mirror.api.proto.Fee.NetworkFee;
147
import com.hedera.mirror.api.proto.ReactorNetworkServiceGrpc;
158
import com.hederahashgraph.api.proto.java.NodeAddress;
169
import com.hederahashgraph.api.proto.java.ServiceEndpoint;
17-
import com.hederahashgraph.api.proto.java.SignedTransaction;
18-
import io.grpc.Status;
1910
import java.net.InetAddress;
2011
import java.net.UnknownHostException;
2112
import lombok.CustomLog;
@@ -35,45 +26,8 @@
3526
@RequiredArgsConstructor
3627
public class NetworkController extends ReactorNetworkServiceGrpc.NetworkServiceImplBase {
3728

38-
static final String INVALID_TRANSACTION = "Invalid Transaction.signedTransactionBytes";
39-
static final FeeEstimateResponse STUB_RESPONSE = stubResponse();
40-
4129
private final NetworkService networkService;
4230

43-
private static FeeEstimateResponse stubResponse() {
44-
final var feeExtra = FeeExtra.newBuilder()
45-
.setCharged(1)
46-
.setCount(2)
47-
.setFeePerUnit(10_000L)
48-
.setIncluded(1)
49-
.setName("Test data")
50-
.setSubtotal(10_000L);
51-
final var feeEstimate = FeeEstimate.newBuilder().setBase(100_000L).addExtras(feeExtra);
52-
return FeeEstimateResponse.newBuilder()
53-
.addNotes("This API is not yet implemented and only returns stubbed test data")
54-
.setMode(EstimateMode.STATE)
55-
.setNetwork(NetworkFee.newBuilder().setMultiplier(2).setSubtotal(220_000L))
56-
.setNode(feeEstimate)
57-
.setService(feeEstimate)
58-
.setTotal(440_000L)
59-
.build();
60-
}
61-
62-
@Override
63-
public Mono<FeeEstimateResponse> getFeeEstimate(FeeEstimateQuery request) {
64-
try {
65-
var bytes = request.getTransaction().getSignedTransactionBytes();
66-
SignedTransaction.parseFrom(bytes);
67-
} catch (InvalidProtocolBufferException e) {
68-
final var error = Status.INVALID_ARGUMENT
69-
.augmentDescription(INVALID_TRANSACTION)
70-
.asRuntimeException();
71-
return Mono.error(error);
72-
}
73-
74-
return Mono.just(STUB_RESPONSE);
75-
}
76-
7731
@Override
7832
public Flux<NodeAddress> getNodes(Mono<AddressBookQuery> request) {
7933
return request.map(this::toFilter)

grpc/src/test/java/org/hiero/mirror/grpc/controller/NetworkControllerTest.java

Lines changed: 0 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,10 @@
66

77
import com.google.protobuf.ByteString;
88
import com.hedera.mirror.api.proto.AddressBookQuery;
9-
import com.hedera.mirror.api.proto.Fee.FeeEstimateQuery;
109
import com.hedera.mirror.api.proto.ReactorNetworkServiceGrpc;
1110
import com.hederahashgraph.api.proto.java.FileID;
1211
import com.hederahashgraph.api.proto.java.NodeAddress;
1312
import com.hederahashgraph.api.proto.java.ServiceEndpoint;
14-
import com.hederahashgraph.api.proto.java.Transaction;
1513
import io.grpc.Status;
1614
import io.grpc.StatusRuntimeException;
1715
import java.net.InetAddress;
@@ -26,7 +24,6 @@
2624
import org.hiero.mirror.common.domain.addressbook.AddressBookEntry;
2725
import org.hiero.mirror.common.domain.addressbook.AddressBookServiceEndpoint;
2826
import org.hiero.mirror.common.domain.entity.EntityId;
29-
import org.hiero.mirror.common.util.DomainUtils;
3027
import org.hiero.mirror.grpc.GrpcIntegrationTest;
3128
import org.hiero.mirror.grpc.util.ProtoUtil;
3229
import org.junit.jupiter.api.Test;
@@ -45,29 +42,6 @@ final class NetworkControllerTest extends GrpcIntegrationTest {
4542
@GrpcClient("local")
4643
private ReactorNetworkServiceGrpc.ReactorNetworkServiceStub reactiveService;
4744

48-
@Test
49-
void getFeeEstimate() {
50-
final var query = FeeEstimateQuery.newBuilder().build();
51-
StepVerifier.withVirtualTime(() -> reactiveService.getFeeEstimate(Mono.just(query)))
52-
.thenAwait(WAIT)
53-
.consumeNextWith(n -> assertThat(n).isEqualTo(NetworkController.STUB_RESPONSE))
54-
.expectComplete()
55-
.verify(WAIT);
56-
}
57-
58-
@Test
59-
void getFeeEstimateInvalidTransaction() {
60-
final var query = FeeEstimateQuery.newBuilder()
61-
.setTransaction(Transaction.newBuilder()
62-
.setSignedTransactionBytes(DomainUtils.fromBytes(domainBuilder.bytes(50))))
63-
.build();
64-
StepVerifier.withVirtualTime(() -> reactiveService.getFeeEstimate(Mono.just(query)))
65-
.thenAwait(WAIT)
66-
.expectErrorSatisfies(
67-
t -> assertException(t, Status.Code.INVALID_ARGUMENT, NetworkController.INVALID_TRANSACTION))
68-
.verify(WAIT);
69-
}
70-
7145
@Test
7246
void getNodesMissingFileId() {
7347
final var query = AddressBookQuery.newBuilder().build();

protobuf/src/main/proto/com/hedera/mirror/api/proto/fee.proto

Lines changed: 0 additions & 151 deletions
This file was deleted.

protobuf/src/main/proto/com/hedera/mirror/api/proto/network_service.proto

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@ package com.hedera.mirror.api.proto;
77
option java_multiple_files = true; // Required for the reactor-grpc generator to work correctly
88
option java_package = "com.hedera.mirror.api.proto";
99

10-
import "com/hedera/mirror/api/proto/fee.proto";
1110
import "services/basic_types.proto";
1211

1312
/**
@@ -22,11 +21,6 @@ message AddressBookQuery {
2221
* Provides cross network APIs like address book queries
2322
*/
2423
service NetworkService {
25-
/**
26-
* Query to estimate the fees when submitting a transaction to the network.
27-
*/
28-
rpc getFeeEstimate(FeeEstimateQuery) returns (FeeEstimateResponse);
29-
3024
/**
3125
* Query for an address book and return its nodes. The nodes are returned in ascending order by node ID. The
3226
* response is not guaranteed to be a byte-for-byte equivalent to the NodeAddress in the Hedera file on

rest-java/src/main/java/org/hiero/mirror/restjava/config/RestJavaConfiguration.java

Lines changed: 40 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,11 @@
22

33
package org.hiero.mirror.restjava.config;
44

5+
import com.google.protobuf.ExtensionRegistry;
6+
import com.google.protobuf.Message;
57
import jakarta.annotation.PostConstruct;
8+
import java.io.IOException;
9+
import java.util.ArrayList;
610
import lombok.RequiredArgsConstructor;
711
import org.hiero.mirror.restjava.jooq.DomainRecordMapperProvider;
812
import org.springframework.boot.autoconfigure.jooq.DefaultConfigurationCustomizer;
@@ -11,6 +15,10 @@
1115
import org.springframework.context.annotation.Bean;
1216
import org.springframework.context.annotation.Configuration;
1317
import org.springframework.format.support.FormattingConversionService;
18+
import org.springframework.http.HttpInputMessage;
19+
import org.springframework.http.MediaType;
20+
import org.springframework.http.converter.HttpMessageNotReadableException;
21+
import org.springframework.http.converter.protobuf.ProtobufHttpMessageConverter;
1422
import org.springframework.web.filter.ShallowEtagHeaderFilter;
1523

1624
@Configuration
@@ -19,6 +27,12 @@ class RestJavaConfiguration {
1927

2028
private final FormattingConversionService mvcConversionService;
2129

30+
@PostConstruct
31+
void initialize() {
32+
// Register application converters to use case-insensitive string to enum converter.
33+
ApplicationConversionService.addApplicationConverters(mvcConversionService);
34+
}
35+
2236
@Bean
2337
DefaultConfigurationCustomizer configurationCustomizer(DomainRecordMapperProvider domainRecordMapperProvider) {
2438
return c -> c.set(domainRecordMapperProvider).settings().withRenderSchema(false);
@@ -31,9 +45,31 @@ FilterRegistrationBean<ShallowEtagHeaderFilter> etagFilter() {
3145
return filterRegistrationBean;
3246
}
3347

34-
@PostConstruct
35-
void initialize() {
36-
// Register application converters to use case-insensitive string to enum converter.
37-
ApplicationConversionService.addApplicationConverters(mvcConversionService);
48+
@Bean
49+
ProtobufHttpMessageConverter protobufHttpMessageConverter() {
50+
final var protobufMediaType = new MediaType("application", "protobuf");
51+
final var extensionRegistry = ExtensionRegistry.newInstance();
52+
53+
final var converter = new ProtobufHttpMessageConverter() {
54+
@Override
55+
protected Message readInternal(Class<? extends Message> clazz, HttpInputMessage inputMessage)
56+
throws IOException, HttpMessageNotReadableException {
57+
final var message = super.readInternal(clazz, inputMessage);
58+
final var contentType = inputMessage.getHeaders().getContentType();
59+
60+
if (protobufMediaType.isCompatibleWith(contentType)) {
61+
return message.toBuilder()
62+
.mergeFrom(inputMessage.getBody(), extensionRegistry)
63+
.build();
64+
}
65+
66+
return message;
67+
}
68+
};
69+
70+
final var mediaTypes = new ArrayList<>(converter.getSupportedMediaTypes());
71+
mediaTypes.add(protobufMediaType);
72+
converter.setSupportedMediaTypes(mediaTypes);
73+
return converter;
3874
}
3975
}

0 commit comments

Comments
 (0)