mirror of
https://github.com/bisq-network/bisq.git
synced 2024-11-19 01:41:11 +01:00
Merge pull request #4214 from ghubstan/rpc-wallet-protection
Add rpc wallet protection endpoints
This commit is contained in:
commit
2e33c2c87a
@ -17,15 +17,18 @@
|
||||
|
||||
package bisq.cli;
|
||||
|
||||
import bisq.proto.grpc.GetBalanceGrpc;
|
||||
import bisq.proto.grpc.GetBalanceRequest;
|
||||
import bisq.proto.grpc.GetVersionGrpc;
|
||||
import bisq.proto.grpc.GetVersionRequest;
|
||||
import bisq.proto.grpc.LockWalletRequest;
|
||||
import bisq.proto.grpc.RemoveWalletPasswordRequest;
|
||||
import bisq.proto.grpc.SetWalletPasswordRequest;
|
||||
import bisq.proto.grpc.UnlockWalletRequest;
|
||||
import bisq.proto.grpc.WalletGrpc;
|
||||
|
||||
import io.grpc.ManagedChannelBuilder;
|
||||
import io.grpc.StatusRuntimeException;
|
||||
|
||||
import joptsimple.OptionException;
|
||||
import joptsimple.OptionParser;
|
||||
import joptsimple.OptionSet;
|
||||
|
||||
@ -41,6 +44,7 @@ import java.util.concurrent.TimeUnit;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import static java.lang.String.format;
|
||||
import static java.lang.System.err;
|
||||
import static java.lang.System.exit;
|
||||
import static java.lang.System.out;
|
||||
@ -51,15 +55,25 @@ import static java.lang.System.out;
|
||||
@Slf4j
|
||||
public class CliMain {
|
||||
|
||||
private static final int EXIT_SUCCESS = 0;
|
||||
private static final int EXIT_FAILURE = 1;
|
||||
|
||||
private enum Method {
|
||||
getversion,
|
||||
getbalance
|
||||
getbalance,
|
||||
lockwallet,
|
||||
unlockwallet,
|
||||
removewalletpassword,
|
||||
setwalletpassword
|
||||
}
|
||||
|
||||
public static void main(String[] args) {
|
||||
try {
|
||||
run(args);
|
||||
} catch (Throwable t) {
|
||||
err.println("Error: " + t.getMessage());
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
public static void run(String[] args) {
|
||||
var parser = new OptionParser();
|
||||
|
||||
var helpOpt = parser.accepts("help", "Print this help text")
|
||||
@ -77,43 +91,33 @@ public class CliMain {
|
||||
var passwordOpt = parser.accepts("password", "rpc server password")
|
||||
.withRequiredArg();
|
||||
|
||||
OptionSet options = null;
|
||||
try {
|
||||
options = parser.parse(args);
|
||||
} catch (OptionException ex) {
|
||||
err.println("Error: " + ex.getMessage());
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
OptionSet options = parser.parse(args);
|
||||
|
||||
if (options.has(helpOpt)) {
|
||||
printHelp(parser, out);
|
||||
exit(EXIT_SUCCESS);
|
||||
return;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
var nonOptionArgs = (List<String>) options.nonOptionArguments();
|
||||
if (nonOptionArgs.isEmpty()) {
|
||||
printHelp(parser, err);
|
||||
err.println("Error: no method specified");
|
||||
exit(EXIT_FAILURE);
|
||||
throw new IllegalArgumentException("no method specified");
|
||||
}
|
||||
|
||||
var methodName = nonOptionArgs.get(0);
|
||||
Method method = null;
|
||||
final Method method;
|
||||
try {
|
||||
method = Method.valueOf(methodName);
|
||||
} catch (IllegalArgumentException ex) {
|
||||
err.printf("Error: '%s' is not a supported method\n", methodName);
|
||||
exit(EXIT_FAILURE);
|
||||
throw new IllegalArgumentException(format("'%s' is not a supported method", methodName));
|
||||
}
|
||||
|
||||
var host = options.valueOf(hostOpt);
|
||||
var port = options.valueOf(portOpt);
|
||||
var password = options.valueOf(passwordOpt);
|
||||
if (password == null) {
|
||||
err.println("Error: missing required 'password' option");
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
if (password == null)
|
||||
throw new IllegalArgumentException("missing required 'password' option");
|
||||
|
||||
var credentials = new PasswordCallCredentials(password);
|
||||
|
||||
@ -122,43 +126,87 @@ public class CliMain {
|
||||
try {
|
||||
channel.shutdown().awaitTermination(1, TimeUnit.SECONDS);
|
||||
} catch (InterruptedException ex) {
|
||||
ex.printStackTrace(err);
|
||||
exit(EXIT_FAILURE);
|
||||
throw new RuntimeException(ex);
|
||||
}
|
||||
}));
|
||||
|
||||
var versionService = GetVersionGrpc.newBlockingStub(channel).withCallCredentials(credentials);
|
||||
var walletService = WalletGrpc.newBlockingStub(channel).withCallCredentials(credentials);
|
||||
|
||||
try {
|
||||
switch (method) {
|
||||
case getversion: {
|
||||
var stub = GetVersionGrpc.newBlockingStub(channel).withCallCredentials(credentials);
|
||||
var request = GetVersionRequest.newBuilder().build();
|
||||
var version = stub.getVersion(request).getVersion();
|
||||
var version = versionService.getVersion(request).getVersion();
|
||||
out.println(version);
|
||||
exit(EXIT_SUCCESS);
|
||||
return;
|
||||
}
|
||||
case getbalance: {
|
||||
var stub = GetBalanceGrpc.newBlockingStub(channel).withCallCredentials(credentials);
|
||||
var request = GetBalanceRequest.newBuilder().build();
|
||||
var balance = stub.getBalance(request).getBalance();
|
||||
if (balance == -1) {
|
||||
err.println("Error: server is still initializing");
|
||||
exit(EXIT_FAILURE);
|
||||
var reply = walletService.getBalance(request);
|
||||
var satoshiBalance = reply.getBalance();
|
||||
var satoshiDivisor = new BigDecimal(100000000);
|
||||
var btcFormat = new DecimalFormat("###,##0.00000000");
|
||||
@SuppressWarnings("BigDecimalMethodWithoutRoundingCalled")
|
||||
var btcBalance = btcFormat.format(BigDecimal.valueOf(satoshiBalance).divide(satoshiDivisor));
|
||||
out.println(btcBalance);
|
||||
return;
|
||||
}
|
||||
case lockwallet: {
|
||||
var request = LockWalletRequest.newBuilder().build();
|
||||
walletService.lockWallet(request);
|
||||
out.println("wallet locked");
|
||||
return;
|
||||
}
|
||||
case unlockwallet: {
|
||||
if (nonOptionArgs.size() < 2)
|
||||
throw new IllegalArgumentException("no password specified");
|
||||
|
||||
if (nonOptionArgs.size() < 3)
|
||||
throw new IllegalArgumentException("no unlock timeout specified");
|
||||
|
||||
long timeout;
|
||||
try {
|
||||
timeout = Long.parseLong(nonOptionArgs.get(2));
|
||||
} catch (NumberFormatException e) {
|
||||
throw new IllegalArgumentException(format("'%s' is not a number", nonOptionArgs.get(2)));
|
||||
}
|
||||
out.println(formatBalance(balance));
|
||||
exit(EXIT_SUCCESS);
|
||||
var request = UnlockWalletRequest.newBuilder()
|
||||
.setPassword(nonOptionArgs.get(1))
|
||||
.setTimeout(timeout).build();
|
||||
walletService.unlockWallet(request);
|
||||
out.println("wallet unlocked");
|
||||
return;
|
||||
}
|
||||
case removewalletpassword: {
|
||||
if (nonOptionArgs.size() < 2)
|
||||
throw new IllegalArgumentException("no password specified");
|
||||
|
||||
var request = RemoveWalletPasswordRequest.newBuilder().setPassword(nonOptionArgs.get(1)).build();
|
||||
walletService.removeWalletPassword(request);
|
||||
out.println("wallet decrypted");
|
||||
return;
|
||||
}
|
||||
case setwalletpassword: {
|
||||
if (nonOptionArgs.size() < 2)
|
||||
throw new IllegalArgumentException("no password specified");
|
||||
|
||||
var requestBuilder = SetWalletPasswordRequest.newBuilder().setPassword(nonOptionArgs.get(1));
|
||||
var hasNewPassword = nonOptionArgs.size() == 3;
|
||||
if (hasNewPassword)
|
||||
requestBuilder.setNewPassword(nonOptionArgs.get(2));
|
||||
walletService.setWalletPassword(requestBuilder.build());
|
||||
out.println("wallet encrypted" + (hasNewPassword ? " with new password" : ""));
|
||||
return;
|
||||
}
|
||||
default: {
|
||||
err.printf("Error: unhandled method '%s'\n", method);
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
throw new RuntimeException(format("unhandled method '%s'", method));
|
||||
}
|
||||
}
|
||||
} catch (StatusRuntimeException ex) {
|
||||
// This exception is thrown if the client-provided password credentials do not
|
||||
// match the value set on the server. The actual error message is in a nested
|
||||
// exception and we clean it up a bit to make it more presentable.
|
||||
Throwable t = ex.getCause() == null ? ex : ex.getCause();
|
||||
err.println("Error: " + t.getMessage().replace("UNAUTHENTICATED: ", ""));
|
||||
exit(EXIT_FAILURE);
|
||||
// Remove the leading gRPC status code (e.g. "UNKNOWN: ") from the message
|
||||
String message = ex.getMessage().replaceFirst("^[A-Z_]+: ", "");
|
||||
throw new RuntimeException(message, ex);
|
||||
}
|
||||
}
|
||||
|
||||
@ -166,24 +214,22 @@ public class CliMain {
|
||||
try {
|
||||
stream.println("Bisq RPC Client");
|
||||
stream.println();
|
||||
stream.println("Usage: bisq-cli [options] <method>");
|
||||
stream.println("Usage: bisq-cli [options] <method> [params]");
|
||||
stream.println();
|
||||
parser.printHelpOn(stream);
|
||||
stream.println();
|
||||
stream.println("Method Description");
|
||||
stream.println("------ -----------");
|
||||
stream.println("getversion Get server version");
|
||||
stream.println("getbalance Get server wallet balance");
|
||||
stream.format("%-19s%-30s%s%n", "Method", "Params", "Description");
|
||||
stream.format("%-19s%-30s%s%n", "------", "------", "------------");
|
||||
stream.format("%-19s%-30s%s%n", "getversion", "", "Get server version");
|
||||
stream.format("%-19s%-30s%s%n", "getbalance", "", "Get server wallet balance");
|
||||
stream.format("%-19s%-30s%s%n", "lockwallet", "", "Remove wallet password from memory, locking the wallet");
|
||||
stream.format("%-19s%-30s%s%n", "unlockwallet", "password timeout",
|
||||
"Store wallet password in memory for timeout seconds");
|
||||
stream.format("%-19s%-30s%s%n", "setwalletpassword", "password [newpassword]",
|
||||
"Encrypt wallet with password, or set new password on encrypted wallet");
|
||||
stream.println();
|
||||
} catch (IOException ex) {
|
||||
ex.printStackTrace(stream);
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("BigDecimalMethodWithoutRoundingCalled")
|
||||
private static String formatBalance(long satoshis) {
|
||||
var btcFormat = new DecimalFormat("###,##0.00000000");
|
||||
var satoshiDivisor = new BigDecimal(100000000);
|
||||
return btcFormat.format(BigDecimal.valueOf(satoshis).divide(satoshiDivisor));
|
||||
}
|
||||
}
|
||||
|
@ -17,7 +17,6 @@
|
||||
|
||||
package bisq.core.grpc;
|
||||
|
||||
import bisq.core.btc.Balances;
|
||||
import bisq.core.monetary.Price;
|
||||
import bisq.core.offer.CreateOfferService;
|
||||
import bisq.core.offer.Offer;
|
||||
@ -25,7 +24,6 @@ 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;
|
||||
@ -49,8 +47,6 @@ import lombok.extern.slf4j.Slf4j;
|
||||
*/
|
||||
@Slf4j
|
||||
public class CoreApi {
|
||||
private final Balances balances;
|
||||
private final BalancePresentation balancePresentation;
|
||||
private final OfferBookService offerBookService;
|
||||
private final TradeStatisticsManager tradeStatisticsManager;
|
||||
private final CreateOfferService createOfferService;
|
||||
@ -58,15 +54,11 @@ public class CoreApi {
|
||||
private final User user;
|
||||
|
||||
@Inject
|
||||
public CoreApi(Balances balances,
|
||||
BalancePresentation balancePresentation,
|
||||
OfferBookService offerBookService,
|
||||
public CoreApi(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;
|
||||
@ -78,14 +70,6 @@ public class CoreApi {
|
||||
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());
|
||||
}
|
||||
@ -160,4 +144,5 @@ public class CoreApi {
|
||||
resultHandler,
|
||||
log::error);
|
||||
}
|
||||
|
||||
}
|
||||
|
159
core/src/main/java/bisq/core/grpc/CoreWalletService.java
Normal file
159
core/src/main/java/bisq/core/grpc/CoreWalletService.java
Normal file
@ -0,0 +1,159 @@
|
||||
package bisq.core.grpc;
|
||||
|
||||
import bisq.core.btc.Balances;
|
||||
import bisq.core.btc.wallet.WalletsManager;
|
||||
|
||||
import org.bitcoinj.crypto.KeyCrypterScrypt;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
import org.spongycastle.crypto.params.KeyParameter;
|
||||
|
||||
import java.util.Timer;
|
||||
import java.util.TimerTask;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
import static java.util.concurrent.TimeUnit.SECONDS;
|
||||
|
||||
@Slf4j
|
||||
class CoreWalletService {
|
||||
|
||||
private final Balances balances;
|
||||
private final WalletsManager walletsManager;
|
||||
|
||||
@Nullable
|
||||
private TimerTask lockTask;
|
||||
|
||||
@Nullable
|
||||
private KeyParameter tempAesKey;
|
||||
|
||||
@Inject
|
||||
public CoreWalletService(Balances balances, WalletsManager walletsManager) {
|
||||
this.balances = balances;
|
||||
this.walletsManager = walletsManager;
|
||||
}
|
||||
|
||||
public long getAvailableBalance() {
|
||||
if (!walletsManager.areWalletsAvailable())
|
||||
throw new IllegalStateException("wallet is not yet available");
|
||||
|
||||
if (walletsManager.areWalletsEncrypted() && tempAesKey == null)
|
||||
throw new IllegalStateException("wallet is locked");
|
||||
|
||||
var balance = balances.getAvailableBalance().get();
|
||||
if (balance == null)
|
||||
throw new IllegalStateException("balance is not yet available");
|
||||
|
||||
return balance.getValue();
|
||||
}
|
||||
|
||||
public void setWalletPassword(String password, String newPassword) {
|
||||
if (!walletsManager.areWalletsAvailable())
|
||||
throw new IllegalStateException("wallet is not yet available");
|
||||
|
||||
KeyCrypterScrypt keyCrypterScrypt = getKeyCrypterScrypt();
|
||||
|
||||
if (newPassword != null && !newPassword.isEmpty()) {
|
||||
// TODO Validate new password before replacing old password.
|
||||
if (!walletsManager.areWalletsEncrypted())
|
||||
throw new IllegalStateException("wallet is not encrypted with a password");
|
||||
|
||||
KeyParameter aesKey = keyCrypterScrypt.deriveKey(password);
|
||||
if (!walletsManager.checkAESKey(aesKey))
|
||||
throw new IllegalStateException("incorrect old password");
|
||||
|
||||
walletsManager.decryptWallets(aesKey);
|
||||
aesKey = keyCrypterScrypt.deriveKey(newPassword);
|
||||
walletsManager.encryptWallets(keyCrypterScrypt, aesKey);
|
||||
walletsManager.backupWallets();
|
||||
return;
|
||||
}
|
||||
|
||||
if (walletsManager.areWalletsEncrypted())
|
||||
throw new IllegalStateException("wallet is encrypted with a password");
|
||||
|
||||
// TODO Validate new password.
|
||||
KeyParameter aesKey = keyCrypterScrypt.deriveKey(password);
|
||||
walletsManager.encryptWallets(keyCrypterScrypt, aesKey);
|
||||
walletsManager.backupWallets();
|
||||
}
|
||||
|
||||
public void lockWallet() {
|
||||
if (!walletsManager.areWalletsEncrypted())
|
||||
throw new IllegalStateException("wallet is not encrypted with a password");
|
||||
|
||||
if (tempAesKey == null)
|
||||
throw new IllegalStateException("wallet is already locked");
|
||||
|
||||
tempAesKey = null;
|
||||
}
|
||||
|
||||
public void unlockWallet(String password, long timeout) {
|
||||
verifyWalletIsAvailableAndEncrypted();
|
||||
|
||||
KeyCrypterScrypt keyCrypterScrypt = getKeyCrypterScrypt();
|
||||
// The aesKey is also cached for timeout (secs) after being used to decrypt the
|
||||
// wallet, in case the user wants to manually lock the wallet before the timeout.
|
||||
tempAesKey = keyCrypterScrypt.deriveKey(password);
|
||||
|
||||
if (!walletsManager.checkAESKey(tempAesKey))
|
||||
throw new IllegalStateException("incorrect password");
|
||||
|
||||
if (lockTask != null) {
|
||||
// The user is overriding a prior unlock timeout. Cancel the existing
|
||||
// lock TimerTask to prevent it from calling lockWallet() before or after the
|
||||
// new timer task does.
|
||||
lockTask.cancel();
|
||||
// Avoid the synchronized(lock) overhead of an unnecessary lockTask.cancel()
|
||||
// call the next time 'unlockwallet' is called.
|
||||
lockTask = null;
|
||||
}
|
||||
|
||||
lockTask = new TimerTask() {
|
||||
@Override
|
||||
public void run() {
|
||||
if (tempAesKey != null) {
|
||||
// Do not try to lock wallet after timeout if the user has already
|
||||
// done so via 'lockwallet'
|
||||
log.info("Locking wallet after {} second timeout expired.", timeout);
|
||||
tempAesKey = null;
|
||||
}
|
||||
}
|
||||
};
|
||||
Timer timer = new Timer("Lock Wallet Timer");
|
||||
timer.schedule(lockTask, SECONDS.toMillis(timeout));
|
||||
}
|
||||
|
||||
// Provided for automated wallet protection method testing, despite the
|
||||
// security risks exposed by providing users the ability to decrypt their wallets.
|
||||
public void removeWalletPassword(String password) {
|
||||
verifyWalletIsAvailableAndEncrypted();
|
||||
KeyCrypterScrypt keyCrypterScrypt = getKeyCrypterScrypt();
|
||||
|
||||
KeyParameter aesKey = keyCrypterScrypt.deriveKey(password);
|
||||
if (!walletsManager.checkAESKey(aesKey))
|
||||
throw new IllegalStateException("incorrect password");
|
||||
|
||||
walletsManager.decryptWallets(aesKey);
|
||||
walletsManager.backupWallets();
|
||||
}
|
||||
|
||||
// Throws a RuntimeException if wallets are not available or not encrypted.
|
||||
private void verifyWalletIsAvailableAndEncrypted() {
|
||||
if (!walletsManager.areWalletsAvailable())
|
||||
throw new IllegalStateException("wallet is not yet available");
|
||||
|
||||
if (!walletsManager.areWalletsEncrypted())
|
||||
throw new IllegalStateException("wallet is not encrypted with a password");
|
||||
}
|
||||
|
||||
private KeyCrypterScrypt getKeyCrypterScrypt() {
|
||||
KeyCrypterScrypt keyCrypterScrypt = walletsManager.getKeyCrypterScrypt();
|
||||
if (keyCrypterScrypt == null)
|
||||
throw new IllegalStateException("wallet encrypter is not available");
|
||||
return keyCrypterScrypt;
|
||||
}
|
||||
}
|
@ -24,9 +24,6 @@ import bisq.core.trade.statistics.TradeStatistics2;
|
||||
|
||||
import bisq.common.config.Config;
|
||||
|
||||
import bisq.proto.grpc.GetBalanceGrpc;
|
||||
import bisq.proto.grpc.GetBalanceReply;
|
||||
import bisq.proto.grpc.GetBalanceRequest;
|
||||
import bisq.proto.grpc.GetOffersGrpc;
|
||||
import bisq.proto.grpc.GetOffersReply;
|
||||
import bisq.proto.grpc.GetOffersRequest;
|
||||
@ -43,10 +40,14 @@ import bisq.proto.grpc.PlaceOfferGrpc;
|
||||
import bisq.proto.grpc.PlaceOfferReply;
|
||||
import bisq.proto.grpc.PlaceOfferRequest;
|
||||
|
||||
import io.grpc.Server;
|
||||
import io.grpc.ServerBuilder;
|
||||
import io.grpc.stub.StreamObserver;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.UncheckedIOException;
|
||||
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@ -56,32 +57,32 @@ import lombok.extern.slf4j.Slf4j;
|
||||
public class GrpcServer {
|
||||
|
||||
private final CoreApi coreApi;
|
||||
private final int port;
|
||||
private final Server server;
|
||||
|
||||
public GrpcServer(Config config, CoreApi coreApi) {
|
||||
@Inject
|
||||
public GrpcServer(Config config, CoreApi coreApi, GrpcWalletService walletService) {
|
||||
this.coreApi = coreApi;
|
||||
this.port = config.apiPort;
|
||||
this.server = ServerBuilder.forPort(config.apiPort)
|
||||
.addService(new GetVersionService())
|
||||
.addService(new GetTradeStatisticsService())
|
||||
.addService(new GetOffersService())
|
||||
.addService(new GetPaymentAccountsService())
|
||||
.addService(new PlaceOfferService())
|
||||
.addService(walletService)
|
||||
.intercept(new PasswordAuthInterceptor(config.apiPassword))
|
||||
.build();
|
||||
}
|
||||
|
||||
public void start() {
|
||||
try {
|
||||
var server = ServerBuilder.forPort(port)
|
||||
.addService(new GetVersionService())
|
||||
.addService(new GetBalanceService())
|
||||
.addService(new GetTradeStatisticsService())
|
||||
.addService(new GetOffersService())
|
||||
.addService(new GetPaymentAccountsService())
|
||||
.addService(new PlaceOfferService())
|
||||
.intercept(new PasswordAuthInterceptor(config.apiPassword))
|
||||
.build()
|
||||
.start();
|
||||
|
||||
log.info("listening on port {}", port);
|
||||
server.start();
|
||||
log.info("listening on port {}", server.getPort());
|
||||
Runtime.getRuntime().addShutdownHook(new Thread(() -> {
|
||||
server.shutdown();
|
||||
log.info("shutdown complete");
|
||||
}));
|
||||
|
||||
} catch (IOException e) {
|
||||
log.error(e.toString(), e);
|
||||
} catch (IOException ex) {
|
||||
throw new UncheckedIOException(ex);
|
||||
}
|
||||
}
|
||||
|
||||
@ -94,14 +95,6 @@ public class GrpcServer {
|
||||
}
|
||||
}
|
||||
|
||||
class GetBalanceService extends GetBalanceGrpc.GetBalanceImplBase {
|
||||
@Override
|
||||
public void getBalance(GetBalanceRequest req, StreamObserver<GetBalanceReply> responseObserver) {
|
||||
var reply = GetBalanceReply.newBuilder().setBalance(coreApi.getAvailableBalance()).build();
|
||||
responseObserver.onNext(reply);
|
||||
responseObserver.onCompleted();
|
||||
}
|
||||
}
|
||||
|
||||
class GetTradeStatisticsService extends GetTradeStatisticsGrpc.GetTradeStatisticsImplBase {
|
||||
@Override
|
||||
|
103
core/src/main/java/bisq/core/grpc/GrpcWalletService.java
Normal file
103
core/src/main/java/bisq/core/grpc/GrpcWalletService.java
Normal file
@ -0,0 +1,103 @@
|
||||
package bisq.core.grpc;
|
||||
|
||||
import bisq.proto.grpc.GetBalanceReply;
|
||||
import bisq.proto.grpc.GetBalanceRequest;
|
||||
import bisq.proto.grpc.LockWalletReply;
|
||||
import bisq.proto.grpc.LockWalletRequest;
|
||||
import bisq.proto.grpc.RemoveWalletPasswordReply;
|
||||
import bisq.proto.grpc.RemoveWalletPasswordRequest;
|
||||
import bisq.proto.grpc.SetWalletPasswordReply;
|
||||
import bisq.proto.grpc.SetWalletPasswordRequest;
|
||||
import bisq.proto.grpc.UnlockWalletReply;
|
||||
import bisq.proto.grpc.UnlockWalletRequest;
|
||||
import bisq.proto.grpc.WalletGrpc;
|
||||
|
||||
import io.grpc.Status;
|
||||
import io.grpc.StatusRuntimeException;
|
||||
import io.grpc.stub.StreamObserver;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
class GrpcWalletService extends WalletGrpc.WalletImplBase {
|
||||
|
||||
private final CoreWalletService walletService;
|
||||
|
||||
@Inject
|
||||
public GrpcWalletService(CoreWalletService walletService) {
|
||||
this.walletService = walletService;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void getBalance(GetBalanceRequest req, StreamObserver<GetBalanceReply> responseObserver) {
|
||||
try {
|
||||
long result = walletService.getAvailableBalance();
|
||||
var reply = GetBalanceReply.newBuilder().setBalance(result).build();
|
||||
responseObserver.onNext(reply);
|
||||
responseObserver.onCompleted();
|
||||
} catch (IllegalStateException cause) {
|
||||
var ex = new StatusRuntimeException(Status.UNKNOWN.withDescription(cause.getMessage()));
|
||||
responseObserver.onError(ex);
|
||||
throw ex;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setWalletPassword(SetWalletPasswordRequest req,
|
||||
StreamObserver<SetWalletPasswordReply> responseObserver) {
|
||||
try {
|
||||
walletService.setWalletPassword(req.getPassword(), req.getNewPassword());
|
||||
var reply = SetWalletPasswordReply.newBuilder().build();
|
||||
responseObserver.onNext(reply);
|
||||
responseObserver.onCompleted();
|
||||
} catch (IllegalStateException cause) {
|
||||
var ex = new StatusRuntimeException(Status.UNKNOWN.withDescription(cause.getMessage()));
|
||||
responseObserver.onError(ex);
|
||||
throw ex;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeWalletPassword(RemoveWalletPasswordRequest req,
|
||||
StreamObserver<RemoveWalletPasswordReply> responseObserver) {
|
||||
try {
|
||||
walletService.removeWalletPassword(req.getPassword());
|
||||
var reply = RemoveWalletPasswordReply.newBuilder().build();
|
||||
responseObserver.onNext(reply);
|
||||
responseObserver.onCompleted();
|
||||
} catch (IllegalStateException cause) {
|
||||
var ex = new StatusRuntimeException(Status.UNKNOWN.withDescription(cause.getMessage()));
|
||||
responseObserver.onError(ex);
|
||||
throw ex;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void lockWallet(LockWalletRequest req,
|
||||
StreamObserver<LockWalletReply> responseObserver) {
|
||||
try {
|
||||
walletService.lockWallet();
|
||||
var reply = LockWalletReply.newBuilder().build();
|
||||
responseObserver.onNext(reply);
|
||||
responseObserver.onCompleted();
|
||||
} catch (IllegalStateException cause) {
|
||||
var ex = new StatusRuntimeException(Status.UNKNOWN.withDescription(cause.getMessage()));
|
||||
responseObserver.onError(ex);
|
||||
throw ex;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void unlockWallet(UnlockWalletRequest req,
|
||||
StreamObserver<UnlockWalletReply> responseObserver) {
|
||||
try {
|
||||
walletService.unlockWallet(req.getPassword(), req.getTimeout());
|
||||
var reply = UnlockWalletReply.newBuilder().build();
|
||||
responseObserver.onNext(reply);
|
||||
responseObserver.onCompleted();
|
||||
} catch (IllegalStateException cause) {
|
||||
var ex = new StatusRuntimeException(Status.UNKNOWN.withDescription(cause.getMessage()));
|
||||
responseObserver.onError(ex);
|
||||
throw ex;
|
||||
}
|
||||
}
|
||||
}
|
@ -21,7 +21,6 @@ import bisq.core.app.BisqHeadlessAppMain;
|
||||
import bisq.core.app.BisqSetup;
|
||||
import bisq.core.app.CoreModule;
|
||||
import bisq.core.grpc.GrpcServer;
|
||||
import bisq.core.grpc.CoreApi;
|
||||
|
||||
import bisq.common.UserThread;
|
||||
import bisq.common.app.AppModule;
|
||||
@ -98,7 +97,7 @@ public class BisqDaemonMain extends BisqHeadlessAppMain implements BisqSetup.Bis
|
||||
protected void onApplicationStarted() {
|
||||
super.onApplicationStarted();
|
||||
|
||||
CoreApi coreApi = injector.getInstance(CoreApi.class);
|
||||
new GrpcServer(config, coreApi);
|
||||
GrpcServer grpcServer = injector.getInstance(GrpcServer.class);
|
||||
grpcServer.start();
|
||||
}
|
||||
}
|
||||
|
@ -39,22 +39,6 @@ message GetVersionReply {
|
||||
string version = 1;
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
// Balance
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
service GetBalance {
|
||||
rpc GetBalance (GetBalanceRequest) returns (GetBalanceReply) {
|
||||
}
|
||||
}
|
||||
|
||||
message GetBalanceRequest {
|
||||
}
|
||||
|
||||
message GetBalanceReply {
|
||||
uint64 balance = 1;
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
// TradeStatistics
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
@ -127,3 +111,56 @@ message PlaceOfferRequest {
|
||||
message PlaceOfferReply {
|
||||
bool result = 1;
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
// Wallet
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
service Wallet {
|
||||
rpc GetBalance (GetBalanceRequest) returns (GetBalanceReply) {
|
||||
}
|
||||
rpc SetWalletPassword (SetWalletPasswordRequest) returns (SetWalletPasswordReply) {
|
||||
}
|
||||
rpc RemoveWalletPassword (RemoveWalletPasswordRequest) returns (RemoveWalletPasswordReply) {
|
||||
}
|
||||
rpc LockWallet (LockWalletRequest) returns (LockWalletReply) {
|
||||
}
|
||||
rpc UnlockWallet (UnlockWalletRequest) returns (UnlockWalletReply) {
|
||||
}
|
||||
}
|
||||
|
||||
message GetBalanceRequest {
|
||||
}
|
||||
|
||||
message GetBalanceReply {
|
||||
uint64 balance = 1;
|
||||
}
|
||||
|
||||
message SetWalletPasswordRequest {
|
||||
string password = 1;
|
||||
string newPassword = 2;
|
||||
}
|
||||
|
||||
message SetWalletPasswordReply {
|
||||
}
|
||||
|
||||
message RemoveWalletPasswordRequest {
|
||||
string password = 1;
|
||||
}
|
||||
|
||||
message RemoveWalletPasswordReply {
|
||||
}
|
||||
|
||||
message LockWalletRequest {
|
||||
}
|
||||
|
||||
message LockWalletReply {
|
||||
}
|
||||
|
||||
message UnlockWalletRequest {
|
||||
string password = 1;
|
||||
uint64 timeout = 2;
|
||||
}
|
||||
|
||||
message UnlockWalletReply {
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user