From 6b2c386a7c8e6fa8d3f28b5d1fdbf8d680176109 Mon Sep 17 00:00:00 2001 From: ghubstan <36207203+ghubstan@users.noreply.github.com> Date: Sat, 27 Feb 2021 21:47:52 -0300 Subject: [PATCH] Fix call rate metering interceptor bug The gRPC interceptor was not using the correct method/ratemeter map key, and failing to find a rate meter for the server call. - Fix the rate meter key lookup bug. - Disable most strict, default call rate meters in tests. Made an adjustment to the test harness' scaffold setup so an interceptor testing or disabling config file is always picked up by bob and alice daemons. - Set arbitration daemon's registerDisputeAgent rate @ 10/second, so it does not interfere with the test harness. (There is no pre-existing arb node appDataDir before that daemon starts.) Note: The test harness cannot install the custom rate metering file in an arb daemon's appDataDir before it starts because there is no dao-setup file for that node type. TODO: Adjust api simulation scripts to interceptor bug fix. --- .../test/java/bisq/apitest/ApiTestCase.java | 48 +++++++++++++++--- .../CallRateMeteringInterceptorTest.java | 50 +------------------ .../java/bisq/apitest/method/MethodTest.java | 4 ++ .../bisq/apitest/scenario/StartupTest.java | 3 +- .../daemon/grpc/GrpcDisputeAgentsService.java | 6 +-- .../CallRateMeteringInterceptor.java | 17 ++++--- 6 files changed, 62 insertions(+), 66 deletions(-) diff --git a/apitest/src/test/java/bisq/apitest/ApiTestCase.java b/apitest/src/test/java/bisq/apitest/ApiTestCase.java index fea32d2e75..dbdbfda763 100644 --- a/apitest/src/test/java/bisq/apitest/ApiTestCase.java +++ b/apitest/src/test/java/bisq/apitest/ApiTestCase.java @@ -17,11 +17,14 @@ package bisq.apitest; +import java.io.File; import java.io.IOException; import java.util.concurrent.ExecutionException; import java.util.stream.Collectors; +import lombok.extern.slf4j.Slf4j; + import javax.annotation.Nullable; import org.junit.jupiter.api.TestInfo; @@ -29,15 +32,19 @@ import org.junit.jupiter.api.TestInfo; import static bisq.apitest.config.BisqAppConfig.alicedaemon; import static bisq.apitest.config.BisqAppConfig.arbdaemon; import static bisq.apitest.config.BisqAppConfig.bobdaemon; +import static bisq.common.file.FileUtil.deleteFileIfExists; import static java.net.InetAddress.getLoopbackAddress; import static java.util.Arrays.stream; import static java.util.concurrent.TimeUnit.MILLISECONDS; +import static java.util.concurrent.TimeUnit.SECONDS; import bisq.apitest.config.ApiTestConfig; import bisq.apitest.method.BitcoinCliHelper; import bisq.cli.GrpcClient; +import bisq.daemon.grpc.GrpcVersionService; +import bisq.daemon.grpc.interceptor.GrpcServiceRateMeteringConfig; /** * Base class for all test types: 'method', 'scenario' and 'e2e'. @@ -64,6 +71,7 @@ import bisq.cli.GrpcClient; *
* Initial Bob balances & accounts: 10.0 BTC, 1500000.00 BSQ, USD PerfectMoney dummy */ +@Slf4j public class ApiTestCase { protected static Scaffold scaffold; @@ -79,12 +87,12 @@ public class ApiTestCase { public static void setUpScaffold(Enum>... supportingApps) throws InterruptedException, ExecutionException, IOException { - scaffold = new Scaffold(stream(supportingApps).map(Enum::name) - .collect(Collectors.joining(","))) - .setUp(); - config = scaffold.config; - bitcoinCli = new BitcoinCliHelper((config)); - createGrpcClients(); + String[] params = new String[]{ + "--supportingApps", stream(supportingApps).map(Enum::name).collect(Collectors.joining(",")), + "--callRateMeteringConfigPath", defaultRateMeterInterceptorConfig().getAbsolutePath(), + "--enableBisqDebugging", "false" + }; + setUpScaffold(params); } public static void setUpScaffold(String[] params) @@ -139,4 +147,32 @@ public class ApiTestCase { ? testInfo.getTestMethod().get().getName() : "unknown test name"; } + + protected static File defaultRateMeterInterceptorConfig() { + GrpcServiceRateMeteringConfig.Builder builder = new GrpcServiceRateMeteringConfig.Builder(); + builder.addCallRateMeter(GrpcVersionService.class.getSimpleName(), + "getVersion", + 1, + SECONDS); + // Only GrpcVersionService is @VisibleForTesting, so we hardcode the class names. + builder.addCallRateMeter("GrpcDisputeAgentsService", + "registerDisputeAgent", + 6, // Allow 6/second for test harness setup + tests. + SECONDS); + String[] serviceClassNames = new String[]{ + "GrpcGetTradeStatisticsService", + "GrpcHelpService", + "GrpcOffersService", + "GrpcPaymentAccountsService", + "GrpcPriceService", + "GrpcTradesService", + "GrpcWalletsService" + }; + for (String service : serviceClassNames) { + builder.addCallRateMeter(service, "disabled", 1, MILLISECONDS); + } + File file = builder.build(); + file.deleteOnExit(); + return file; + } } diff --git a/apitest/src/test/java/bisq/apitest/method/CallRateMeteringInterceptorTest.java b/apitest/src/test/java/bisq/apitest/method/CallRateMeteringInterceptorTest.java index ee5e91e139..f7a076a9f9 100644 --- a/apitest/src/test/java/bisq/apitest/method/CallRateMeteringInterceptorTest.java +++ b/apitest/src/test/java/bisq/apitest/method/CallRateMeteringInterceptorTest.java @@ -19,9 +19,6 @@ package bisq.apitest.method; import io.grpc.StatusRuntimeException; -import java.io.File; -import java.io.IOException; - import lombok.extern.slf4j.Slf4j; import org.junit.jupiter.api.AfterAll; @@ -35,19 +32,9 @@ import org.junit.jupiter.api.TestMethodOrder; import static bisq.apitest.Scaffold.BitcoinCoreApp.bitcoind; import static bisq.apitest.config.BisqAppConfig.alicedaemon; -import static bisq.common.file.FileUtil.deleteFileIfExists; -import static java.util.concurrent.TimeUnit.DAYS; -import static java.util.concurrent.TimeUnit.HOURS; -import static java.util.concurrent.TimeUnit.MINUTES; -import static java.util.concurrent.TimeUnit.SECONDS; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertThrows; - - -import bisq.daemon.grpc.GrpcVersionService; -import bisq.daemon.grpc.interceptor.GrpcServiceRateMeteringConfig; - @Disabled @Slf4j @TestMethodOrder(MethodOrderer.OrderAnnotation.class) @@ -55,13 +42,9 @@ public class CallRateMeteringInterceptorTest extends MethodTest { private static final GetVersionTest getVersionTest = new GetVersionTest(); - private static File callRateMeteringConfigFile; - @BeforeAll public static void setUp() { - callRateMeteringConfigFile = buildInterceptorConfigFile(); - startSupportingApps(callRateMeteringConfigFile, - false, + startSupportingApps(false, false, bitcoind, alicedaemon); } @@ -102,37 +85,6 @@ public class CallRateMeteringInterceptorTest extends MethodTest { @AfterAll public static void tearDown() { - try { - deleteFileIfExists(callRateMeteringConfigFile); - } catch (IOException ex) { - log.error(ex.getMessage()); - } tearDownScaffold(); } - - public static File buildInterceptorConfigFile() { - GrpcServiceRateMeteringConfig.Builder builder = new GrpcServiceRateMeteringConfig.Builder(); - builder.addCallRateMeter(GrpcVersionService.class.getSimpleName(), - "getVersion", - 1, - SECONDS); - builder.addCallRateMeter(GrpcVersionService.class.getSimpleName(), - "shouldNotBreakAnything", - 1000, - DAYS); - // Only GrpcVersionService is @VisibleForTesting, so we hardcode the class names. - builder.addCallRateMeter("GrpcOffersService", - "createOffer", - 5, - MINUTES); - builder.addCallRateMeter("GrpcTradesService", - "takeOffer", - 10, - DAYS); - builder.addCallRateMeter("GrpcTradesService", - "withdrawFunds", - 3, - HOURS); - return builder.build(); - } } diff --git a/apitest/src/test/java/bisq/apitest/method/MethodTest.java b/apitest/src/test/java/bisq/apitest/method/MethodTest.java index 84f5eed694..f0634fb2c4 100644 --- a/apitest/src/test/java/bisq/apitest/method/MethodTest.java +++ b/apitest/src/test/java/bisq/apitest/method/MethodTest.java @@ -71,8 +71,11 @@ public class MethodTest extends ApiTestCase { boolean generateBtcBlock, Enum>... supportingApps) { try { + // Disable call rate metering where there is no callRateMeteringConfigFile. + File callRateMeteringConfigFile = defaultRateMeterInterceptorConfig(); setUpScaffold(new String[]{ "--supportingApps", toNameList.apply(supportingApps), + "--callRateMeteringConfigPath", callRateMeteringConfigFile.getAbsolutePath(), "--enableBisqDebugging", "false" }); doPostStartup(registerDisputeAgents, generateBtcBlock); @@ -136,6 +139,7 @@ public class MethodTest extends ApiTestCase { protected static void registerDisputeAgents() { arbClient.registerDisputeAgent(MEDIATOR, DEV_PRIVILEGE_PRIV_KEY); + sleep(1001); // Can call registerdisputeagent 1x per second. arbClient.registerDisputeAgent(REFUND_AGENT, DEV_PRIVILEGE_PRIV_KEY); } diff --git a/apitest/src/test/java/bisq/apitest/scenario/StartupTest.java b/apitest/src/test/java/bisq/apitest/scenario/StartupTest.java index 8c31b3630c..8aa9367511 100644 --- a/apitest/src/test/java/bisq/apitest/scenario/StartupTest.java +++ b/apitest/src/test/java/bisq/apitest/scenario/StartupTest.java @@ -33,7 +33,6 @@ import static bisq.apitest.Scaffold.BitcoinCoreApp.bitcoind; import static bisq.apitest.config.BisqAppConfig.alicedaemon; import static bisq.apitest.config.BisqAppConfig.arbdaemon; import static bisq.apitest.config.BisqAppConfig.seednode; -import static bisq.apitest.method.CallRateMeteringInterceptorTest.buildInterceptorConfigFile; import static bisq.common.file.FileUtil.deleteFileIfExists; import static org.junit.jupiter.api.Assertions.fail; @@ -55,7 +54,7 @@ public class StartupTest extends MethodTest { @BeforeAll public static void setUp() { try { - callRateMeteringConfigFile = buildInterceptorConfigFile(); + callRateMeteringConfigFile = defaultRateMeterInterceptorConfig(); startSupportingApps(callRateMeteringConfigFile, false, false, diff --git a/daemon/src/main/java/bisq/daemon/grpc/GrpcDisputeAgentsService.java b/daemon/src/main/java/bisq/daemon/grpc/GrpcDisputeAgentsService.java index 11f4a63f3f..60413c1c96 100644 --- a/daemon/src/main/java/bisq/daemon/grpc/GrpcDisputeAgentsService.java +++ b/daemon/src/main/java/bisq/daemon/grpc/GrpcDisputeAgentsService.java @@ -59,9 +59,9 @@ class GrpcDisputeAgentsService extends DisputeAgentsGrpc.DisputeAgentsImplBase { return getCustomRateMeteringInterceptor(coreApi.getConfig().appDataDir, this.getClass()) .or(() -> Optional.of(CallRateMeteringInterceptor.valueOf( new HashMap<>() {{ - // You can only register mainnet dispute agents in the UI. - // Do not limit devs' ability to register test agents. - put("registerDisputeAgent", new GrpcCallRateMeter(1, SECONDS)); + // Do not limit devs' ability to test agent registration + // and call validation in regtest arbitration daemons. + put("registerDisputeAgent", new GrpcCallRateMeter(10, SECONDS)); }} ))); } diff --git a/daemon/src/main/java/bisq/daemon/grpc/interceptor/CallRateMeteringInterceptor.java b/daemon/src/main/java/bisq/daemon/grpc/interceptor/CallRateMeteringInterceptor.java index 429ed1e22c..404c093a2f 100644 --- a/daemon/src/main/java/bisq/daemon/grpc/interceptor/CallRateMeteringInterceptor.java +++ b/daemon/src/main/java/bisq/daemon/grpc/interceptor/CallRateMeteringInterceptor.java @@ -27,7 +27,6 @@ import org.apache.commons.lang3.StringUtils; import java.util.HashMap; import java.util.Map; -import java.util.Objects; import java.util.Optional; import lombok.extern.slf4j.Slf4j; @@ -35,6 +34,7 @@ import lombok.extern.slf4j.Slf4j; import static io.grpc.Status.PERMISSION_DENIED; import static java.lang.String.format; import static java.util.stream.Collectors.joining; +import static org.apache.commons.lang3.StringUtils.uncapitalize; @Slf4j public final class CallRateMeteringInterceptor implements ServerInterceptor { @@ -106,11 +106,16 @@ public final class CallRateMeteringInterceptor implements ServerInterceptor { } private String getRateMeterKey(ServerCall, ?> serverCall) { - // Get the rate meter map key from the full rpc service name. The key name - // is hard coded in the Grpc*Service interceptors() method. - String fullServiceName = serverCall.getMethodDescriptor().getServiceName(); - return StringUtils.uncapitalize(Objects.requireNonNull(fullServiceName) - .substring("io.bisq.protobuffer.".length())); + // Get the rate meter map key from the server call method descriptor. The + // returned key (e.g., 'createOffer') is defined in the 'serviceCallRateMeters' + // constructor argument. It is extracted from the gRPC fullMethodName, e.g., + // 'io.bisq.protobuffer.Offers/CreateOffer'. + String fullServiceMethodName = serverCall.getMethodDescriptor().getFullMethodName(); + if (fullServiceMethodName.contains("/")) + return uncapitalize(fullServiceMethodName.split("/")[1]); + else + throw new IllegalStateException("Could not extract rate meter key from " + + fullServiceMethodName + "."); } @Override