Merge pull request #3888 from cbeams/grpc-poc

Introduce gRPC API proof of concept
This commit is contained in:
sqrrm 2020-01-20 16:19:40 +01:00 committed by GitHub
commit 92466f96eb
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
18 changed files with 1055 additions and 51 deletions

View File

@ -33,7 +33,7 @@
<emptyLine />
<package name="bisq.asset" withSubpackages="true" static="false" />
<emptyLine />
<package name="io.bisq.generated" withSubpackages="true" static="false" />
<package name="io.grpc" withSubpackages="true" static="false" />
<emptyLine />
<package name="com.google.protobuf" withSubpackages="true" static="false" />
<emptyLine />

View File

@ -38,6 +38,7 @@ configure(subprojects) {
fontawesomefxVersion = '8.0.0'
fontawesomefxCommonsVersion = '9.1.2'
fontawesomefxMaterialdesignfontVersion = '2.0.26-9.1.2'
grpcVersion = '1.25.0'
guavaVersion = '20.0'
guiceVersion = '4.2.2'
hamcrestVersion = '1.3'
@ -59,7 +60,8 @@ configure(subprojects) {
lombokVersion = '1.18.2'
mockitoVersion = '3.0.0'
netlayerVersion = '0.6.5.2'
protobufVersion = '3.9.1'
protobufVersion = '3.10.0'
protocVersion = protobufVersion
pushyVersion = '0.13.2'
qrgenVersion = '1.3'
sarxosVersion = '0.3.12'
@ -86,7 +88,9 @@ configure(subprojects) {
}
configure([project(':desktop'),
configure([project(':cli'),
project(':daemon'),
project(':desktop'),
project(':monitor'),
project(':relay'),
project(':seednode'),
@ -166,7 +170,7 @@ configure(project(':common')) {
protobuf {
protoc {
artifact = "com.google.protobuf:protoc:$protobufVersion"
artifact = "com.google.protobuf:protoc:$protocVersion"
}
}
@ -175,7 +179,7 @@ configure(project(':common')) {
compile "org.openjfx:javafx-base:$javafxVersion:$os"
compile "org.openjfx:javafx-graphics:$javafxVersion:$os"
compile "com.google.protobuf:protobuf-java:$protobufVersion"
compile 'com.google.code.gson:gson:2.7'
compile 'com.google.code.gson:gson:2.8.5'
compile "org.springframework:spring-core:$springVersion"
compile "org.slf4j:slf4j-api:$slf4jVersion"
compile "ch.qos.logback:logback-core:$logbackVersion"
@ -225,6 +229,8 @@ configure(project(':p2p')) {
configure(project(':core')) {
apply plugin: 'com.google.protobuf'
dependencies {
compile project(':assets')
compile project(':p2p')
@ -250,7 +256,20 @@ configure(project(':core')) {
compile("com.fasterxml.jackson.core:jackson-databind:$jacksonVersion") {
exclude(module: 'jackson-annotations')
}
implementation "com.google.protobuf:protobuf-java:$protobufVersion"
implementation("io.grpc:grpc-protobuf:$grpcVersion") {
exclude(module: 'guava')
exclude(module: 'animal-sniffer-annotations')
}
implementation("io.grpc:grpc-stub:$grpcVersion") {
exclude(module: 'guava')
exclude(module: 'animal-sniffer-annotations')
}
compileOnly "javax.annotation:javax.annotation-api:1.2"
runtimeOnly ("io.grpc:grpc-netty-shaded:$grpcVersion") {
exclude(module: 'guava')
exclude(module: 'animal-sniffer-annotations')
}
compileOnly "org.projectlombok:lombok:$lombokVersion"
annotationProcessor "org.projectlombok:lombok:$lombokVersion"
@ -262,11 +281,47 @@ configure(project(':core')) {
testAnnotationProcessor "org.projectlombok:lombok:$lombokVersion"
}
protobuf {
protoc {
artifact = "com.google.protobuf:protoc:${protocVersion}"
}
plugins {
grpc {
artifact = "io.grpc:protoc-gen-grpc-java:${grpcVersion}"
}
}
generateProtoTasks {
all()*.plugins { grpc {} }
}
}
sourceSets.main.java.srcDirs += [
'build/generated/source/proto/main/grpc',
'build/generated/source/proto/main/java'
]
test {
systemProperty 'jdk.attach.allowAttachSelf', true
}
}
configure(project(':cli')) {
mainClassName = 'bisq.cli.app.BisqCliMain'
dependencies {
compile project(':core')
implementation("io.grpc:grpc-core:$grpcVersion") {
exclude(module: 'guava')
exclude(module: 'animal-sniffer-annotations')
}
implementation("io.grpc:grpc-stub:$grpcVersion") {
exclude(module: 'guava')
exclude(module: 'animal-sniffer-annotations')
}
compileOnly "org.projectlombok:lombok:$lombokVersion"
annotationProcessor "org.projectlombok:lombok:$lombokVersion"
}
}
configure(project(':desktop')) {
apply plugin: 'com.github.johnrengelman.shadow'
@ -402,3 +457,14 @@ configure(project(':statsnode')) {
annotationProcessor "org.projectlombok:lombok:$lombokVersion"
}
}
configure(project(':daemon')) {
mainClassName = 'bisq.daemon.app.BisqDaemonMain'
dependencies {
compile project(':core')
compileOnly "org.projectlombok:lombok:$lombokVersion"
compileOnly "javax.annotation:javax.annotation-api:1.2"
annotationProcessor "org.projectlombok:lombok:$lombokVersion"
}
}

View File

@ -0,0 +1,296 @@
/*
* This file is part of Bisq.
*
* Bisq is free software: you can redistribute it and/or modify it
* under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or (at
* your option) any later version.
*
* Bisq is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
* License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Bisq. If not, see <http://www.gnu.org/licenses/>.
*/
package bisq.cli.app;
import bisq.core.grpc.GetBalanceGrpc;
import bisq.core.grpc.GetBalanceRequest;
import bisq.core.grpc.GetOffersGrpc;
import bisq.core.grpc.GetOffersRequest;
import bisq.core.grpc.GetPaymentAccountsGrpc;
import bisq.core.grpc.GetPaymentAccountsRequest;
import bisq.core.grpc.GetTradeStatisticsGrpc;
import bisq.core.grpc.GetTradeStatisticsRequest;
import bisq.core.grpc.GetVersionGrpc;
import bisq.core.grpc.GetVersionRequest;
import bisq.core.grpc.PlaceOfferGrpc;
import bisq.core.grpc.PlaceOfferRequest;
import bisq.core.grpc.StopServerGrpc;
import bisq.core.grpc.StopServerRequest;
import bisq.core.payment.PaymentAccount;
import bisq.core.proto.network.CoreNetworkProtoResolver;
import bisq.core.proto.persistable.CorePersistenceProtoResolver;
import io.grpc.ManagedChannel;
import io.grpc.ManagedChannelBuilder;
import io.grpc.StatusRuntimeException;
import org.bitcoinj.core.Coin;
import java.time.Clock;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Scanner;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
import lombok.extern.slf4j.Slf4j;
import static com.google.common.base.Preconditions.checkArgument;
import static java.lang.String.format;
/**
* gRPC client.
*
* FIXME We get warning 'DEBUG io.grpc.netty.shaded.io.netty.util.internal.PlatformDependent0 - direct buffer constructor: unavailable
* java.lang.UnsupportedOperationException: Reflective setAccessible(true) disabled' which is
* related to Java 10 changes. Requests are working but we should find out why we get that warning
*/
@Slf4j
public class BisqCliMain {
private final ManagedChannel channel;
private final GetVersionGrpc.GetVersionBlockingStub getVersionStub;
private final GetBalanceGrpc.GetBalanceBlockingStub getBalanceStub;
private final StopServerGrpc.StopServerBlockingStub stopServerStub;
private final GetTradeStatisticsGrpc.GetTradeStatisticsBlockingStub getTradeStatisticsStub;
private final GetOffersGrpc.GetOffersBlockingStub getOffersStub;
private final GetPaymentAccountsGrpc.GetPaymentAccountsBlockingStub getPaymentAccountsStub;
private final PlaceOfferGrpc.PlaceOfferBlockingStub placeOfferBlockingStub;
private final CorePersistenceProtoResolver corePersistenceProtoResolver;
public static void main(String[] args) throws Exception {
new BisqCliMain("localhost", 8888);
}
private BisqCliMain(String host, int port) {
this(ManagedChannelBuilder.forAddress(host, port)
// Channels are secure by default (via SSL/TLS). For the example we disable TLS to avoid
// needing certificates.
.usePlaintext(true).build());
// Simple input scanner
// TODO use some more sophisticated input processing with validation....
try (Scanner scanner = new Scanner(System.in);) {
while (true) {
long startTs = System.currentTimeMillis();
String[] tokens = scanner.nextLine().split(" ");
if (tokens.length == 0) {
return;
}
String command = tokens[0];
List<String> params = new ArrayList<>();
if (tokens.length > 1) {
params.addAll(Arrays.asList(tokens));
params.remove(0);
}
String result = "";
switch (command) {
case "getVersion":
result = getVersion();
break;
case "getBalance":
result = Coin.valueOf(getBalance()).toFriendlyString();
break;
case "getTradeStatistics":
List<bisq.core.trade.statistics.TradeStatistics2> tradeStatistics = getTradeStatistics().stream()
.map(bisq.core.trade.statistics.TradeStatistics2::fromProto)
.collect(Collectors.toList());
result = tradeStatistics.toString();
break;
case "getOffers":
List<bisq.core.offer.Offer> offers = getOffers().stream()
.map(bisq.core.offer.Offer::fromProto)
.collect(Collectors.toList());
result = offers.toString();
break;
case "getPaymentAccounts":
List<PaymentAccount> paymentAccounts = getPaymentAccounts().stream()
.map(proto -> PaymentAccount.fromProto(proto, corePersistenceProtoResolver))
.collect(Collectors.toList());
result = paymentAccounts.toString();
break;
case "placeOffer":
// test input: placeOffer CNY BUY 750000000 true -0.2251 1000000 500000 0.15 5a972121-c30a-4b0e-b519-b17b63795d16
// payment accountId and currency need to be adopted
// We expect 9 params
// TODO add basic input validation
try {
checkArgument(params.size() == 9);
String currencyCode = params.get(0);
String directionAsString = params.get(1);
long priceAsLong = Long.parseLong(params.get(2));
boolean useMarketBasedPrice = Boolean.parseBoolean(params.get(3));
double marketPriceMargin = Double.parseDouble(params.get(4));
long amountAsLong = Long.parseLong(params.get(5));
long minAmountAsLong = Long.parseLong(params.get(6));
double buyerSecurityDeposit = Double.parseDouble(params.get(7));
String paymentAccountId = params.get(8);
boolean success = placeOffer(currencyCode,
directionAsString,
priceAsLong,
useMarketBasedPrice,
marketPriceMargin,
amountAsLong,
minAmountAsLong,
buyerSecurityDeposit,
paymentAccountId);
result = String.valueOf(success);
break;
} catch (Throwable t) {
log.error(t.toString(), t);
break;
}
case "stop":
result = "Shut down client";
try {
shutdown();
} catch (InterruptedException e) {
log.error(e.toString(), e);
}
break;
case "stopServer":
stopServer();
result = "Server stopped";
break;
default:
result = format("Unknown command '%s'", command);
}
// First response is rather slow (300 ms) but following responses are fast (3-5 ms).
log.info("Request took: {} ms", System.currentTimeMillis() - startTs);
log.info(result);
}
}
}
/**
* Construct client for accessing server using the existing channel.
*/
private BisqCliMain(ManagedChannel channel) {
this.channel = channel;
getVersionStub = GetVersionGrpc.newBlockingStub(channel);
getBalanceStub = GetBalanceGrpc.newBlockingStub(channel);
getTradeStatisticsStub = GetTradeStatisticsGrpc.newBlockingStub(channel);
getOffersStub = GetOffersGrpc.newBlockingStub(channel);
getPaymentAccountsStub = GetPaymentAccountsGrpc.newBlockingStub(channel);
placeOfferBlockingStub = PlaceOfferGrpc.newBlockingStub(channel);
stopServerStub = StopServerGrpc.newBlockingStub(channel);
CoreNetworkProtoResolver coreNetworkProtoResolver = new CoreNetworkProtoResolver(Clock.systemDefaultZone());
//TODO
corePersistenceProtoResolver = new CorePersistenceProtoResolver(null, coreNetworkProtoResolver, null, null);
}
private String getVersion() {
GetVersionRequest request = GetVersionRequest.newBuilder().build();
try {
return getVersionStub.getVersion(request).getVersion();
} catch (StatusRuntimeException e) {
return "RPC failed: " + e.getStatus();
}
}
private long getBalance() {
GetBalanceRequest request = GetBalanceRequest.newBuilder().build();
try {
return getBalanceStub.getBalance(request).getBalance();
} catch (StatusRuntimeException e) {
log.warn("RPC failed: {}", e.getStatus());
return -1;
}
}
private List<protobuf.TradeStatistics2> getTradeStatistics() {
GetTradeStatisticsRequest request = GetTradeStatisticsRequest.newBuilder().build();
try {
return getTradeStatisticsStub.getTradeStatistics(request).getTradeStatisticsList();
} catch (StatusRuntimeException e) {
log.warn("RPC failed: {}", e.getStatus());
return null;
}
}
private List<protobuf.Offer> getOffers() {
GetOffersRequest request = GetOffersRequest.newBuilder().build();
try {
return getOffersStub.getOffers(request).getOffersList();
} catch (StatusRuntimeException e) {
log.warn("RPC failed: {}", e.getStatus());
return null;
}
}
private List<protobuf.PaymentAccount> getPaymentAccounts() {
GetPaymentAccountsRequest request = GetPaymentAccountsRequest.newBuilder().build();
try {
return getPaymentAccountsStub.getPaymentAccounts(request).getPaymentAccountsList();
} catch (StatusRuntimeException e) {
log.warn("RPC failed: {}", e.getStatus());
return null;
}
}
private boolean placeOffer(String currencyCode,
String directionAsString,
long priceAsLong,
boolean useMarketBasedPrice,
double marketPriceMargin,
long amountAsLong,
long minAmountAsLong,
double buyerSecurityDeposit,
String paymentAccountId) {
PlaceOfferRequest request = PlaceOfferRequest.newBuilder()
.setCurrencyCode(currencyCode)
.setDirection(directionAsString)
.setPrice(priceAsLong)
.setUseMarketBasedPrice(useMarketBasedPrice)
.setMarketPriceMargin(marketPriceMargin)
.setAmount(amountAsLong)
.setMinAmount(minAmountAsLong)
.setBuyerSecurityDeposit(buyerSecurityDeposit)
.setPaymentAccountId(paymentAccountId)
.build();
try {
return placeOfferBlockingStub.placeOffer(request).getResult();
} catch (StatusRuntimeException e) {
log.warn("RPC failed: {}", e.getStatus());
return false;
}
}
private void stopServer() {
StopServerRequest request = StopServerRequest.newBuilder().build();
try {
stopServerStub.stopServer(request);
} catch (StatusRuntimeException e) {
log.warn("RPC failed: {}", e.getStatus());
}
}
private void shutdown() throws InterruptedException {
channel.shutdown().awaitTermination(1, TimeUnit.SECONDS);
System.exit(0);
}
}

View File

@ -18,8 +18,6 @@
package bisq.core.app;
public class AppOptionKeys {
public static final String DESKTOP_WITH_HTTP_API = "desktopWithHttpApi";
public static final String DESKTOP_WITH_GRPC_API = "desktopWithGrpcApi";
public static final String APP_NAME_KEY = "appName";
public static final String USER_DATA_DIR_KEY = "userDataDir";
public static final String APP_DATA_DIR_KEY = "appDataDir";

View File

@ -186,8 +186,6 @@ public class BisqEnvironment extends StandardEnvironment {
@Setter
protected boolean isBitcoinLocalhostNodeRunning;
@Getter
protected String desktopWithHttpApi, desktopWithGrpcApi;
@Getter
protected List<String> bannedSeedNodes, bannedBtcNodes, bannedPriceRelayNodes;
protected final String btcNodes, seedNodes, ignoreDevMsg, useDevPrivilegeKeys, useDevMode, useTorForBtc, rpcUser, rpcPassword,
@ -219,8 +217,6 @@ public class BisqEnvironment extends StandardEnvironment {
appDataDir = getProperty(commandLineProperties, AppOptionKeys.APP_DATA_DIR_KEY, appDataDir(userDataDir, appName));
staticAppDataDir = appDataDir;
desktopWithHttpApi = getProperty(commandLineProperties, AppOptionKeys.DESKTOP_WITH_HTTP_API, "false");
desktopWithGrpcApi = getProperty(commandLineProperties, AppOptionKeys.DESKTOP_WITH_GRPC_API, "false");
ignoreDevMsg = getProperty(commandLineProperties, AppOptionKeys.IGNORE_DEV_MSG_KEY, "");
useDevPrivilegeKeys = getProperty(commandLineProperties, AppOptionKeys.USE_DEV_PRIVILEGE_KEYS, "");
referralId = getProperty(commandLineProperties, AppOptionKeys.REFERRAL_ID, "");
@ -398,8 +394,6 @@ public class BisqEnvironment extends StandardEnvironment {
setProperty(NetworkOptionKeys.SEND_MSG_THROTTLE_SLEEP, sendMsgThrottleSleep);
setProperty(AppOptionKeys.APP_DATA_DIR_KEY, appDataDir);
setProperty(AppOptionKeys.DESKTOP_WITH_HTTP_API, desktopWithHttpApi);
setProperty(AppOptionKeys.DESKTOP_WITH_GRPC_API, desktopWithGrpcApi);
setProperty(AppOptionKeys.IGNORE_DEV_MSG_KEY, ignoreDevMsg);
setProperty(AppOptionKeys.USE_DEV_PRIVILEGE_KEYS, useDevPrivilegeKeys);
setProperty(AppOptionKeys.REFERRAL_ID, referralId);

View File

@ -446,16 +446,6 @@ public abstract class BisqExecutable implements GracefulShutDownHandler, BisqSet
.withRequiredArg()
.ofType(boolean.class);
parser.accepts(AppOptionKeys.DESKTOP_WITH_HTTP_API,
format("If set to true Bisq Desktop starts with Http API (default: %s)", "false"))
.withRequiredArg()
.ofType(boolean.class);
parser.accepts(AppOptionKeys.DESKTOP_WITH_GRPC_API,
format("If set to true Bisq Desktop starts with gRPC API (default: %s)", "false"))
.withRequiredArg()
.ofType(boolean.class);
parser.accepts(AppOptionKeys.USE_DEV_PRIVILEGE_KEYS,
format("If that is true all the privileged features which requires a private key " +
"to enable it are overridden by a dev key pair (This is for developers only!) (default: %s)", "false"))

View File

@ -17,8 +17,6 @@
package bisq.core.app;
import bisq.core.CoreModule;
import bisq.common.UserThread;
import bisq.common.app.AppModule;
import bisq.common.app.Version;

View File

@ -15,11 +15,9 @@
* along with Bisq. If not, see <http://www.gnu.org/licenses/>.
*/
package bisq.core;
package bisq.core.app;
import bisq.core.alert.AlertModule;
import bisq.core.app.AppOptionKeys;
import bisq.core.app.BisqEnvironment;
import bisq.core.btc.BitcoinModule;
import bisq.core.dao.DaoModule;
import bisq.core.filter.FilterModule;

View File

@ -0,0 +1,203 @@
/*
* This file is part of Bisq.
*
* Bisq is free software: you can redistribute it and/or modify it
* under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or (at
* your option) any later version.
*
* Bisq is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
* License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Bisq. If not, see <http://www.gnu.org/licenses/>.
*/
package bisq.core.grpc;
import bisq.core.offer.Offer;
import bisq.core.payment.PaymentAccount;
import bisq.core.trade.handlers.TransactionResultHandler;
import bisq.core.trade.statistics.TradeStatistics2;
import io.grpc.Server;
import io.grpc.ServerBuilder;
import io.grpc.stub.StreamObserver;
import java.io.IOException;
import java.util.List;
import java.util.stream.Collectors;
import lombok.extern.slf4j.Slf4j;
/**
* gRPC server. Gets a instance of BisqFacade passed to access data from the running Bisq instance.
*/
@Slf4j
public class BisqGrpcServer {
private Server server;
private static BisqGrpcServer instance;
private static CoreApi coreApi;
///////////////////////////////////////////////////////////////////////////////////////////
// Services
///////////////////////////////////////////////////////////////////////////////////////////
static class GetVersionImpl extends GetVersionGrpc.GetVersionImplBase {
@Override
public void getVersion(GetVersionRequest req, StreamObserver<GetVersionReply> responseObserver) {
GetVersionReply reply = GetVersionReply.newBuilder().setVersion(coreApi.getVersion()).build();
responseObserver.onNext(reply);
responseObserver.onCompleted();
}
}
static class GetBalanceImpl extends GetBalanceGrpc.GetBalanceImplBase {
@Override
public void getBalance(GetBalanceRequest req, StreamObserver<GetBalanceReply> responseObserver) {
GetBalanceReply reply = GetBalanceReply.newBuilder().setBalance(coreApi.getAvailableBalance()).build();
responseObserver.onNext(reply);
responseObserver.onCompleted();
}
}
static class GetTradeStatisticsImpl extends GetTradeStatisticsGrpc.GetTradeStatisticsImplBase {
@Override
public void getTradeStatistics(GetTradeStatisticsRequest req,
StreamObserver<GetTradeStatisticsReply> responseObserver) {
List<protobuf.TradeStatistics2> tradeStatistics = coreApi.getTradeStatistics().stream()
.map(TradeStatistics2::toProtoTradeStatistics2)
.collect(Collectors.toList());
GetTradeStatisticsReply reply = GetTradeStatisticsReply.newBuilder().addAllTradeStatistics(tradeStatistics).build();
responseObserver.onNext(reply);
responseObserver.onCompleted();
}
}
static class GetOffersImpl extends GetOffersGrpc.GetOffersImplBase {
@Override
public void getOffers(GetOffersRequest req, StreamObserver<GetOffersReply> responseObserver) {
List<protobuf.Offer> tradeStatistics = coreApi.getOffers().stream()
.map(Offer::toProtoMessage)
.collect(Collectors.toList());
GetOffersReply reply = GetOffersReply.newBuilder().addAllOffers(tradeStatistics).build();
responseObserver.onNext(reply);
responseObserver.onCompleted();
}
}
static class GetPaymentAccountsImpl extends GetPaymentAccountsGrpc.GetPaymentAccountsImplBase {
@Override
public void getPaymentAccounts(GetPaymentAccountsRequest req,
StreamObserver<GetPaymentAccountsReply> responseObserver) {
List<protobuf.PaymentAccount> tradeStatistics = coreApi.getPaymentAccounts().stream()
.map(PaymentAccount::toProtoMessage)
.collect(Collectors.toList());
GetPaymentAccountsReply reply = GetPaymentAccountsReply.newBuilder().addAllPaymentAccounts(tradeStatistics).build();
responseObserver.onNext(reply);
responseObserver.onCompleted();
}
}
static class PlaceOfferImpl extends PlaceOfferGrpc.PlaceOfferImplBase {
@Override
public void placeOffer(PlaceOfferRequest req, StreamObserver<PlaceOfferReply> responseObserver) {
TransactionResultHandler resultHandler = transaction -> {
PlaceOfferReply reply = PlaceOfferReply.newBuilder().setResult(true).build();
responseObserver.onNext(reply);
responseObserver.onCompleted();
};
coreApi.placeOffer(
req.getCurrencyCode(),
req.getDirection(),
req.getPrice(),
req.getUseMarketBasedPrice(),
req.getMarketPriceMargin(),
req.getAmount(),
req.getMinAmount(),
req.getBuyerSecurityDeposit(),
req.getPaymentAccountId(),
resultHandler);
}
}
static class StopServerImpl extends StopServerGrpc.StopServerImplBase {
@Override
public void stopServer(StopServerRequest req, StreamObserver<StopServerReply> responseObserver) {
StopServerReply reply = StopServerReply.newBuilder().build();
responseObserver.onNext(reply);
responseObserver.onCompleted();
instance.stop();
}
}
///////////////////////////////////////////////////////////////////////////////////////////
// Constructor
///////////////////////////////////////////////////////////////////////////////////////////
public BisqGrpcServer(CoreApi coreApi) {
instance = this;
BisqGrpcServer.coreApi = coreApi;
try {
start();
} catch (IOException e) {
log.error(e.toString(), e);
}
}
///////////////////////////////////////////////////////////////////////////////////////////
// API
///////////////////////////////////////////////////////////////////////////////////////////
public void stop() {
if (server != null) {
server.shutdown();
}
}
///////////////////////////////////////////////////////////////////////////////////////////
// Private
///////////////////////////////////////////////////////////////////////////////////////////
private void start() throws IOException {
// TODO add to options
int port = 8888;
// Config services
server = ServerBuilder.forPort(port)
.addService(new GetVersionImpl())
.addService(new GetBalanceImpl())
.addService(new GetTradeStatisticsImpl())
.addService(new GetOffersImpl())
.addService(new GetPaymentAccountsImpl())
.addService(new PlaceOfferImpl())
.addService(new StopServerImpl())
.build()
.start();
log.info("Server started, listening on " + port);
Runtime.getRuntime().addShutdownHook(new Thread(() -> {
// Use stderr here since the logger may have been reset by its JVM shutdown hook.
log.error("*** shutting down gRPC server since JVM is shutting down");
BisqGrpcServer.this.stop();
log.error("*** server shut down");
}));
}
}

View File

@ -0,0 +1,163 @@
/*
* This file is part of Bisq.
*
* Bisq is free software: you can redistribute it and/or modify it
* under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or (at
* your option) any later version.
*
* Bisq is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
* License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Bisq. If not, see <http://www.gnu.org/licenses/>.
*/
package bisq.core.grpc;
import bisq.core.btc.Balances;
import bisq.core.monetary.Price;
import bisq.core.offer.CreateOfferService;
import bisq.core.offer.Offer;
import bisq.core.offer.OfferBookService;
import bisq.core.offer.OfferPayload;
import bisq.core.offer.OpenOfferManager;
import bisq.core.payment.PaymentAccount;
import bisq.core.presentation.BalancePresentation;
import bisq.core.trade.handlers.TransactionResultHandler;
import bisq.core.trade.statistics.TradeStatistics2;
import bisq.core.trade.statistics.TradeStatisticsManager;
import bisq.core.user.User;
import bisq.common.app.Version;
import org.bitcoinj.core.Coin;
import javax.inject.Inject;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
import lombok.extern.slf4j.Slf4j;
/**
* Provides high level interface to functionality of core Bisq features.
* E.g. useful for different APIs to access data of different domains of Bisq.
*/
@Slf4j
public class CoreApi {
private final Balances balances;
private final BalancePresentation balancePresentation;
private final OfferBookService offerBookService;
private final TradeStatisticsManager tradeStatisticsManager;
private final CreateOfferService createOfferService;
private final OpenOfferManager openOfferManager;
private final User user;
@Inject
public CoreApi(Balances balances,
BalancePresentation balancePresentation,
OfferBookService offerBookService,
TradeStatisticsManager tradeStatisticsManager,
CreateOfferService createOfferService,
OpenOfferManager openOfferManager,
User user) {
this.balances = balances;
this.balancePresentation = balancePresentation;
this.offerBookService = offerBookService;
this.tradeStatisticsManager = tradeStatisticsManager;
this.createOfferService = createOfferService;
this.openOfferManager = openOfferManager;
this.user = user;
}
public String getVersion() {
return Version.VERSION;
}
public long getAvailableBalance() {
return balances.getAvailableBalance().get().getValue();
}
public String getAvailableBalanceAsString() {
return balancePresentation.getAvailableBalance().get();
}
public List<TradeStatistics2> getTradeStatistics() {
return new ArrayList<>(tradeStatisticsManager.getObservableTradeStatisticsSet());
}
public List<Offer> getOffers() {
return offerBookService.getOffers();
}
public Set<PaymentAccount> getPaymentAccounts() {
return user.getPaymentAccounts();
}
public void placeOffer(String currencyCode,
String directionAsString,
long priceAsLong,
boolean useMarketBasedPrice,
double marketPriceMargin,
long amountAsLong,
long minAmountAsLong,
double buyerSecurityDeposit,
String paymentAccountId,
TransactionResultHandler resultHandler) {
String offerId = createOfferService.getRandomOfferId();
OfferPayload.Direction direction = OfferPayload.Direction.valueOf(directionAsString);
Price price = Price.valueOf(currencyCode, priceAsLong);
Coin amount = Coin.valueOf(amountAsLong);
Coin minAmount = Coin.valueOf(minAmountAsLong);
PaymentAccount paymentAccount = user.getPaymentAccount(paymentAccountId);
// We don't support atm funding from external wallet to keep it simple
boolean useSavingsWallet = true;
placeOffer(offerId,
currencyCode,
direction,
price,
useMarketBasedPrice,
marketPriceMargin,
amount,
minAmount,
buyerSecurityDeposit,
paymentAccount,
useSavingsWallet,
resultHandler);
}
public void placeOffer(String offerId,
String currencyCode,
OfferPayload.Direction direction,
Price price,
boolean useMarketBasedPrice,
double marketPriceMargin,
Coin amount,
Coin minAmount,
double buyerSecurityDeposit,
PaymentAccount paymentAccount,
boolean useSavingsWallet,
TransactionResultHandler resultHandler) {
Offer offer = createOfferService.createAndGetOffer(offerId,
direction,
currencyCode,
amount,
minAmount,
price,
useMarketBasedPrice,
marketPriceMargin,
buyerSecurityDeposit,
paymentAccount);
openOfferManager.placeOffer(offer,
buyerSecurityDeposit,
useSavingsWallet,
resultHandler,
log::error);
}
}

View File

@ -0,0 +1,148 @@
/*
* This file is part of Bisq.
*
* Bisq is free software: you can redistribute it and/or modify it
* under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or (at
* your option) any later version.
*
* Bisq is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
* License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Bisq. If not, see <http://www.gnu.org/licenses/>.
*/
syntax = "proto3";
package io.bisq.protobuffer;
// FIXME: IntelliJ does not recognize the import but the compiler does
import "pb.proto";
option java_package = "bisq.core.grpc";
option java_multiple_files = true;
///////////////////////////////////////////////////////////////////////////////////////////
// Version
///////////////////////////////////////////////////////////////////////////////////////////
service GetVersion {
rpc GetVersion (GetVersionRequest) returns (GetVersionReply) {
}
}
message GetVersionRequest {
}
message GetVersionReply {
string version = 1;
}
///////////////////////////////////////////////////////////////////////////////////////////
// Balance
///////////////////////////////////////////////////////////////////////////////////////////
service GetBalance {
rpc GetBalance (GetBalanceRequest) returns (GetBalanceReply) {
}
}
message GetBalanceRequest {
}
message GetBalanceReply {
uint64 balance = 1;
}
///////////////////////////////////////////////////////////////////////////////////////////
// TradeStatistics
///////////////////////////////////////////////////////////////////////////////////////////
service GetTradeStatistics {
rpc GetTradeStatistics (GetTradeStatisticsRequest) returns (GetTradeStatisticsReply) {
}
}
message GetTradeStatisticsRequest {
}
// FIXME: IntelliJ does not recognize the imported TradeStatistics2 but the compiler does
message GetTradeStatisticsReply {
repeated TradeStatistics2 TradeStatistics = 1;
}
///////////////////////////////////////////////////////////////////////////////////////////
// Offer
///////////////////////////////////////////////////////////////////////////////////////////
service GetOffers {
rpc GetOffers (GetOffersRequest) returns (GetOffersReply) {
}
}
message GetOffersRequest {
}
// FIXME: IntelliJ does not recognize the imported Offer but the compiler does
message GetOffersReply {
repeated Offer offers = 1;
}
///////////////////////////////////////////////////////////////////////////////////////////
// PaymentAccount
///////////////////////////////////////////////////////////////////////////////////////////
service GetPaymentAccounts {
rpc GetPaymentAccounts (GetPaymentAccountsRequest) returns (GetPaymentAccountsReply) {
}
}
message GetPaymentAccountsRequest {
}
// FIXME: IntelliJ does not recognize the imported PaymentAccount but the compiler does
message GetPaymentAccountsReply {
repeated PaymentAccount paymentAccounts = 1;
}
///////////////////////////////////////////////////////////////////////////////////////////
// PlaceOffer
///////////////////////////////////////////////////////////////////////////////////////////
service PlaceOffer {
rpc PlaceOffer (PlaceOfferRequest) returns (PlaceOfferReply) {
}
}
message PlaceOfferRequest {
string currencyCode = 1;
string direction = 2;
uint64 price = 3;
bool useMarketBasedPrice = 4;
double marketPriceMargin = 5;
uint64 amount = 6;
uint64 minAmount = 7;
double buyerSecurityDeposit = 8;
string paymentAccountId = 9;
}
message PlaceOfferReply {
bool result = 1;
}
///////////////////////////////////////////////////////////////////////////////////////////
// StopServer
///////////////////////////////////////////////////////////////////////////////////////////
service StopServer {
rpc StopServer (StopServerRequest) returns (StopServerReply) {
}
}
message StopServerRequest {
}
message StopServerReply {
}

View File

@ -0,0 +1,23 @@
/*
* This file is part of Bisq.
*
* Bisq is free software: you can redistribute it and/or modify it
* under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or (at
* your option) any later version.
*
* Bisq is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
* License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Bisq. If not, see <http://www.gnu.org/licenses/>.
*/
package bisq.daemon.app;
import bisq.core.app.BisqHeadlessApp;
public class BisqDaemon extends BisqHeadlessApp {
}

View File

@ -0,0 +1,111 @@
/*
* This file is part of Bisq.
*
* Bisq is free software: you can redistribute it and/or modify it
* under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or (at
* your option) any later version.
*
* Bisq is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
* License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Bisq. If not, see <http://www.gnu.org/licenses/>.
*/
package bisq.daemon.app;
import bisq.core.app.BisqExecutable;
import bisq.core.app.BisqHeadlessAppMain;
import bisq.core.app.BisqSetup;
import bisq.core.app.CoreModule;
import bisq.core.grpc.BisqGrpcServer;
import bisq.core.grpc.CoreApi;
import bisq.common.UserThread;
import bisq.common.app.AppModule;
import bisq.common.setup.CommonSetup;
import com.google.common.util.concurrent.ThreadFactoryBuilder;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadFactory;
import lombok.extern.slf4j.Slf4j;
@Slf4j
public class BisqDaemonMain extends BisqHeadlessAppMain implements BisqSetup.BisqSetupListener {
public static void main(String[] args) throws Exception {
if (BisqExecutable.setupInitialOptionParser(args)) {
// For some reason the JavaFX launch process results in us losing the thread context class loader: reset it.
// In order to work around a bug in JavaFX 8u25 and below, you must include the following code as the first line of your realMain method:
Thread.currentThread().setContextClassLoader(BisqDaemonMain.class.getClassLoader());
new BisqDaemonMain().execute(args);
}
}
///////////////////////////////////////////////////////////////////////////////////////////
// First synchronous execution tasks
///////////////////////////////////////////////////////////////////////////////////////////
@Override
protected void configUserThread() {
final ThreadFactory threadFactory = new ThreadFactoryBuilder()
.setNameFormat(this.getClass().getSimpleName())
.setDaemon(true)
.build();
UserThread.setExecutor(Executors.newSingleThreadExecutor(threadFactory));
}
@Override
protected void launchApplication() {
headlessApp = new BisqDaemon();
CommonSetup.setup(BisqDaemonMain.this.headlessApp);
UserThread.execute(this::onApplicationLaunched);
}
@Override
protected void onApplicationLaunched() {
super.onApplicationLaunched();
headlessApp.setGracefulShutDownHandler(this);
}
///////////////////////////////////////////////////////////////////////////////////////////
// We continue with a series of synchronous execution tasks
///////////////////////////////////////////////////////////////////////////////////////////
@Override
protected AppModule getModule() {
return new CoreModule(bisqEnvironment);
}
@Override
protected void applyInjector() {
super.applyInjector();
headlessApp.setInjector(injector);
}
@Override
protected void startApplication() {
// We need to be in user thread! We mapped at launchApplication already...
headlessApp.startApplication();
// In headless mode we don't have an async behaviour so we trigger the setup by calling onApplicationStarted
onApplicationStarted();
}
@Override
protected void onApplicationStarted() {
super.onApplicationStarted();
CoreApi coreApi = injector.getInstance(CoreApi.class);
new BisqGrpcServer(coreApi);
}
}

View File

@ -0,0 +1,16 @@
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<appender name="CONSOLE_APPENDER" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>%highlight(%d{MMM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{30}: %msg %xEx%n)</pattern>
</encoder>
</appender>
<root level="TRACE">
<appender-ref ref="CONSOLE_APPENDER"/>
</root>
<logger name="com.neemre.btcdcli4j" level="WARN"/>
<logger name="io.grpc.netty" level="WARN"/>
</configuration>

View File

@ -44,11 +44,6 @@ public class BisqAppMain extends BisqExecutable {
super("Bisq Desktop", "bisq-desktop", Version.VERSION);
}
/* @Nullable
private BisqHttpApiServer bisqHttpApiServer;*/
/* @Nullable
private BisqGrpcServer bisqGrpcServer;
*/
public static void main(String[] args) throws Exception {
if (BisqExecutable.setupInitialOptionParser(args)) {
// For some reason the JavaFX launch process results in us losing the thread context class loader: reset it.
@ -135,20 +130,11 @@ public class BisqAppMain extends BisqExecutable {
protected void onApplicationStarted() {
super.onApplicationStarted();
/* if (runWithHttpApi()) {
bisqHttpApiServer = new BisqHttpApiServer();
}*/
/*
if (runWithGrpcApi()) {
bisqGrpcServer = new BisqGrpcServer();
}*/
}
private boolean runWithHttpApi() {
return bisqEnvironment.getDesktopWithHttpApi().toLowerCase().equals("true");
}
private boolean runWithGrpcApi() {
return bisqEnvironment.getDesktopWithGrpcApi().toLowerCase().equals("true");
CoreApi coreApi = injector.getInstance(CoreApi.class);
bisqGrpcServer = new BisqGrpcServer(coreApi);
}
*/
}
}

View File

@ -19,7 +19,7 @@ package bisq.desktop.app;
import bisq.desktop.DesktopModule;
import bisq.core.CoreModule;
import bisq.core.app.CoreModule;
import bisq.common.app.AppModule;

View File

@ -11,7 +11,6 @@
//
// See https://github.com/signalapp/gradle-witness#using-witness for further details.
dependencyVerification {
verify = [
'aopalliance:aopalliance:0addec670fedcd3f113c5c8091d783280d23f75e3acb841b61a9cdb079376a08',
@ -32,11 +31,14 @@ dependencyVerification {
'com.github.bisq-network.bitcoinj:bitcoinj-core:f979c2187e61ee3b08dd4cbfc49a149734cff64c045d29ed112f2e12f34068a3',
'com.github.ravn:jsocks:3c71600af027b2b6d4244e4ad14d98ff2352a379410daebefff5d8cd48d742a4',
'com.github.sarxos:webcam-capture:d960b7ea8ec3ddf2df0725ef214c3fccc9699ea7772df37f544e1f8e4fd665f6',
'com.google.android:annotations:ba734e1e84c09d615af6a09d33034b4f0442f8772dec120efb376d86a565ae15',
'com.google.api.grpc:proto-google-common-protos:bd60cd7a423b00fb824c27bdd0293aaf4781be1daba6ed256311103fb4b84108',
'com.google.code.findbugs:jsr305:766ad2a0783f2687962c8ad74ceecc38a28b9f72a2d085ee438b7813e928d0c7',
'com.google.code.gson:gson:2d43eb5ea9e133d2ee2405cc14f5ee08951b8361302fdd93494a3a997b508d32',
'com.google.code.gson:gson:233a0149fc365c9f6edbd683cfe266b19bdc773be98eabdaf6b3c924b48e7d81',
'com.google.errorprone:error_prone_annotations:ec59f1b702d9afc09e8c3929f5c42777dec623a6ea2731ac694332c7d7680f5a',
'com.google.guava:guava:36a666e3b71ae7f0f0dca23654b67e086e6c93d192f60ba5dfd5519db6c288c8',
'com.google.inject:guice:d258ff1bd9b8b527872f8402648226658ad3149f1f40e74b0566d69e7e042fbc',
'com.google.protobuf:protobuf-java:5a1e5c225791eccd3d67a598922e637406190c90155fb97f38e4eab29719324d',
'com.google.protobuf:protobuf-java:161d7d61a8cb3970891c299578702fd079646e032329d6c2cabf998d191437c9',
'com.google.zxing:core:11aae8fd974ab25faa8208be50468eb12349cd239e93e7c797377fa13e381729',
'com.google.zxing:javase:0ec23e2ec12664ddd6347c8920ad647bb3b9da290f897a88516014b56cc77eb9',
'com.googlecode.jcsv:jcsv:73ca7d715e90c8d2c2635cc284543b038245a34f70790660ed590e157b8714a2',
@ -53,6 +55,16 @@ dependencyVerification {
'de.jensd:fontawesomefx-materialdesignfont:dbad8dfdd1c85e298d5bbae25b2399aec9e85064db57b2427d10f3815aa98752',
'de.jensd:fontawesomefx:73bacc991a0a6f5cf0f911767c8db161e0949dbca61e8371eb4342e3da96887b',
'io.github.microutils:kotlin-logging:4992504fd3c6ecdf9ed10874b9508e758bb908af9e9d7af19a61e9afb6b7e27a',
'io.grpc:grpc-api:a269094009588213ab5386a6fb92426b8056a130b2653d3b4e59e971f2f1ef08',
'io.grpc:grpc-context:f4c8f878c320f6fb56c1c14692618f6df8253314b556176e32727afbc5921a73',
'io.grpc:grpc-core:d67fa113fd9cc45a02710f9c41dda9c15191448c14e9e96fcc21839a41345d4c',
'io.grpc:grpc-netty-shaded:9edfd45da473d2efbb5683fc3eaf1857e82d2148033d82dd558a7ac38731ea33',
'io.grpc:grpc-protobuf-lite:9ba9aaa3e6997a04c707793c25e3ec88c6bad86f8d6f6b8b7a1a0c33ea2429d8',
'io.grpc:grpc-protobuf:454dae7e246dac25526ed5b795d97a5dafedd3cc2042cfc810f02051d7d3e3cb',
'io.grpc:grpc-stub:1532e291c0e9fd8230a6416c8ebbd902d99c7e2760241ae638ea761aa3dd5f43',
'io.opencensus:opencensus-api:8e2cb0f6391d8eb0a1bcd01e7748883f0033b1941754f4ed3f19d2c3e4276fc8',
'io.opencensus:opencensus-contrib-grpc-metrics:29fc79401082301542cab89d7054d2f0825f184492654c950020553ef4ff0ef8',
'io.perfmark:perfmark-api:b734ba2149712409a44eabdb799f64768578fee0defe1418bb108fe32ea43e1a',
'javax.inject:javax.inject:91c77044a50c481636c32d916fd89c9118a72195390452c81065080f957de7ff',
'net.glxn:qrgen:c85d9d8512d91e8ad11fe56259a7825bd50ce0245447e236cf168d1b17591882',
'net.jcip:jcip-annotations:be5805392060c71474bf6c9a67a099471274d30b83eef84bfc4e0889a4f1dcc0',

View File

@ -2,6 +2,8 @@ include 'assets'
include 'common'
include 'p2p'
include 'core'
include 'cli'
include 'daemon'
include 'desktop'
include 'monitor'
include 'pricenode'