Merge pull request #4539 from ghubstan/refactor-bisqdaemonmain

Refactor API & add registerdisputeagent method to CLI
This commit is contained in:
sqrrm 2020-09-22 11:37:18 +02:00 committed by GitHub
commit dee92670c5
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
18 changed files with 317 additions and 143 deletions

View file

@ -48,14 +48,14 @@
run ./bisq-cli --password="xyz" getversion
[ "$status" -eq 0 ]
echo "actual output: $output" >&2
[ "$output" = "1.3.8" ]
[ "$output" = "1.3.9" ]
}
@test "test getversion" {
run ./bisq-cli --password=xyz getversion
[ "$status" -eq 0 ]
echo "actual output: $output" >&2
[ "$output" = "1.3.8" ]
[ "$output" = "1.3.9" ]
}
@test "test setwalletpassword \"a b c\"" {
@ -166,15 +166,15 @@
[ "$output" = "Error: address bogus not found in wallet" ]
}
@test "test createpaymentacct PerfectMoneyDummy (missing nbr, ccy params)" {
run ./bisq-cli --password=xyz createpaymentacct PerfectMoneyDummy
@test "test createpaymentacct PerfectMoneyDummy (missing name, nbr, ccy params)" {
run ./bisq-cli --password=xyz createpaymentacct PERFECT_MONEY
[ "$status" -eq 1 ]
echo "actual output: $output" >&2
[ "$output" = "Error: incorrect parameter count, expecting account name, account number, currency code" ]
[ "$output" = "Error: incorrect parameter count, expecting payment method id, account name, account number, currency code" ]
}
@test "test createpaymentacct PerfectMoneyDummy 0123456789 USD" {
run ./bisq-cli --password=xyz createpaymentacct PerfectMoneyDummy 0123456789 USD
@test "test createpaymentacct PERFECT_MONEY PerfectMoneyDummy 0123456789 USD" {
run ./bisq-cli --password=xyz createpaymentacct PERFECT_MONEY PerfectMoneyDummy 0123456789 USD
[ "$status" -eq 0 ]
}

View file

@ -25,6 +25,7 @@ import bisq.proto.grpc.GetOffersRequest;
import bisq.proto.grpc.GetPaymentAccountsRequest;
import bisq.proto.grpc.GetVersionRequest;
import bisq.proto.grpc.LockWalletRequest;
import bisq.proto.grpc.RegisterDisputeAgentRequest;
import bisq.proto.grpc.RemoveWalletPasswordRequest;
import bisq.proto.grpc.SetWalletPasswordRequest;
import bisq.proto.grpc.UnlockWalletRequest;
@ -69,7 +70,8 @@ public class CliMain {
lockwallet,
unlockwallet,
removewalletpassword,
setwalletpassword
setwalletpassword,
registerdisputeagent
}
public static void main(String[] args) {
@ -114,9 +116,9 @@ public class CliMain {
}
var methodName = nonOptionArgs.get(0);
final Method method;
Method method;
try {
method = Method.valueOf(methodName);
method = getMethodFromCmd(methodName);
} catch (IllegalArgumentException ex) {
throw new IllegalArgumentException(format("'%s' is not a supported method", methodName));
}
@ -128,6 +130,7 @@ public class CliMain {
throw new IllegalArgumentException("missing required 'password' option");
GrpcStubs grpcStubs = new GrpcStubs(host, port, password);
var disputeAgentsService = grpcStubs.disputeAgentsService;
var versionService = grpcStubs.versionService;
var offersService = grpcStubs.offersService;
var paymentAccountsService = grpcStubs.paymentAccountsService;
@ -166,34 +169,38 @@ public class CliMain {
}
case getoffers: {
if (nonOptionArgs.size() < 3)
throw new IllegalArgumentException("incorrect parameter count, expecting direction (buy|sell), currency code");
throw new IllegalArgumentException("incorrect parameter count,"
+ " expecting direction (buy|sell), currency code");
var direction = nonOptionArgs.get(1);
var fiatCurrency = nonOptionArgs.get(2);
var request = GetOffersRequest.newBuilder()
.setDirection(direction)
.setFiatCurrencyCode(fiatCurrency)
.setCurrencyCode(fiatCurrency)
.build();
var reply = offersService.getOffers(request);
out.println(formatOfferTable(reply.getOffersList(), fiatCurrency));
return;
}
case createpaymentacct: {
if (nonOptionArgs.size() < 4)
if (nonOptionArgs.size() < 5)
throw new IllegalArgumentException(
"incorrect parameter count, expecting account name, account number, currency code");
"incorrect parameter count, expecting payment method id,"
+ " account name, account number, currency code");
var accountName = nonOptionArgs.get(1);
var accountNumber = nonOptionArgs.get(2);
var fiatCurrencyCode = nonOptionArgs.get(3);
var paymentMethodId = nonOptionArgs.get(1);
var accountName = nonOptionArgs.get(2);
var accountNumber = nonOptionArgs.get(3);
var currencyCode = nonOptionArgs.get(4);
var request = CreatePaymentAccountRequest.newBuilder()
.setPaymentMethodId(paymentMethodId)
.setAccountName(accountName)
.setAccountNumber(accountNumber)
.setFiatCurrencyCode(fiatCurrencyCode).build();
.setCurrencyCode(currencyCode).build();
paymentAccountsService.createPaymentAccount(request);
out.println(format("payment account %s saved", accountName));
out.printf("payment account %s saved", accountName);
return;
}
case getpaymentaccts: {
@ -232,7 +239,8 @@ public class CliMain {
if (nonOptionArgs.size() < 2)
throw new IllegalArgumentException("no password specified");
var request = RemoveWalletPasswordRequest.newBuilder().setPassword(nonOptionArgs.get(1)).build();
var request = RemoveWalletPasswordRequest.newBuilder()
.setPassword(nonOptionArgs.get(1)).build();
walletsService.removeWalletPassword(request);
out.println("wallet decrypted");
return;
@ -241,7 +249,8 @@ public class CliMain {
if (nonOptionArgs.size() < 2)
throw new IllegalArgumentException("no password specified");
var requestBuilder = SetWalletPasswordRequest.newBuilder().setPassword(nonOptionArgs.get(1));
var requestBuilder = SetWalletPasswordRequest.newBuilder()
.setPassword(nonOptionArgs.get(1));
var hasNewPassword = nonOptionArgs.size() == 3;
if (hasNewPassword)
requestBuilder.setNewPassword(nonOptionArgs.get(2));
@ -249,6 +258,19 @@ public class CliMain {
out.println("wallet encrypted" + (hasNewPassword ? " with new password" : ""));
return;
}
case registerdisputeagent: {
if (nonOptionArgs.size() < 3)
throw new IllegalArgumentException(
"incorrect parameter count, expecting dispute agent type, registration key");
var disputeAgentType = nonOptionArgs.get(1);
var registrationKey = nonOptionArgs.get(2);
var requestBuilder = RegisterDisputeAgentRequest.newBuilder()
.setDisputeAgentType(disputeAgentType).setRegistrationKey(registrationKey);
disputeAgentsService.registerDisputeAgent(requestBuilder.build());
out.println(disputeAgentType + " registered");
return;
}
default: {
throw new RuntimeException(format("unhandled method '%s'", method));
}
@ -260,6 +282,13 @@ public class CliMain {
}
}
private static Method getMethodFromCmd(String methodName) {
// TODO if we use const type for enum we need add some mapping. Even if we don't
// change now it is handy to have flexibility in case we change internal code
// and don't want to break user commands.
return Method.valueOf(methodName.toLowerCase());
}
private static void printHelp(OptionParser parser, PrintStream stream) {
try {
stream.println("Bisq RPC Client");

View file

@ -139,8 +139,14 @@ public class CoreApi {
// PaymentAccounts
///////////////////////////////////////////////////////////////////////////////////////////
public void createPaymentAccount(String accountName, String accountNumber, String fiatCurrencyCode) {
paymentAccountsService.createPaymentAccount(accountName, accountNumber, fiatCurrencyCode);
public void createPaymentAccount(String paymentMethodId,
String accountName,
String accountNumber,
String currencyCode) {
paymentAccountsService.createPaymentAccount(paymentMethodId,
accountName,
accountNumber,
currencyCode);
}
public Set<PaymentAccount> getPaymentAccounts() {

View file

@ -68,7 +68,7 @@ class CoreDisputeAgentsService {
this.languageCodes = Arrays.asList("de", "en", "es", "fr");
}
public void registerDisputeAgent(String disputeAgentType, String registrationKey) {
void registerDisputeAgent(String disputeAgentType, String registrationKey) {
if (!p2PService.isBootstrapped())
throw new IllegalStateException("p2p service is not bootstrapped yet");

View file

@ -58,7 +58,7 @@ class CoreOffersService {
this.user = user;
}
public List<Offer> getOffers(String direction, String fiatCurrencyCode) {
List<Offer> getOffers(String direction, String fiatCurrencyCode) {
List<Offer> offers = offerBookService.getOffers().stream()
.filter(o -> {
var offerOfWantedDirection = o.getDirection().name().equalsIgnoreCase(direction);
@ -77,7 +77,7 @@ class CoreOffersService {
return offers;
}
public void createOffer(String currencyCode,
void createOffer(String currencyCode,
String directionAsString,
long priceAsLong,
boolean useMarketBasedPrice,
@ -111,7 +111,7 @@ class CoreOffersService {
resultHandler);
}
public void createOffer(String offerId,
void createOffer(String offerId,
String currencyCode,
OfferPayload.Direction direction,
Price price,

View file

@ -33,6 +33,9 @@ import java.util.Set;
import lombok.extern.slf4j.Slf4j;
import static bisq.core.payment.payload.PaymentMethod.*;
import static com.google.common.base.Preconditions.checkNotNull;
@Slf4j
class CorePaymentAccountsService {
@ -49,17 +52,19 @@ class CorePaymentAccountsService {
this.user = user;
}
public void createPaymentAccount(String accountName, String accountNumber, String fiatCurrencyCode) {
// Create and persist a PerfectMoney dummy payment account. There is no guard
// against creating accounts with duplicate names & numbers, only the uuid and
// creation date are unique.
PaymentMethod dummyPaymentMethod = PaymentMethod.getDummyPaymentMethod(PaymentMethod.PERFECT_MONEY_ID);
PaymentAccount paymentAccount = PaymentAccountFactory.getPaymentAccount(dummyPaymentMethod);
paymentAccount.init();
paymentAccount.setAccountName(accountName);
((PerfectMoneyAccount) paymentAccount).setAccountNr(accountNumber);
paymentAccount.setSingleTradeCurrency(new FiatCurrency(fiatCurrencyCode.toUpperCase()));
user.addPaymentAccount(paymentAccount);
void createPaymentAccount(String paymentMethodId,
String accountName,
String accountNumber,
String currencyCode) {
PaymentAccount paymentAccount = getNewPaymentAccount(paymentMethodId,
accountName,
accountNumber,
currencyCode);
// TODO not sure if there is more to do at account creation.
// Need to check all the flow when its created from UI.
user.addPaymentAccountIfNotExists(paymentAccount);
// Don't do this on mainnet until thoroughly tested.
if (config.baseCurrencyNetwork.isRegtest())
@ -68,7 +73,64 @@ class CorePaymentAccountsService {
log.info("Payment account {} saved", paymentAccount.getId());
}
public Set<PaymentAccount> getPaymentAccounts() {
Set<PaymentAccount> getPaymentAccounts() {
return user.getPaymentAccounts();
}
private PaymentAccount getNewPaymentAccount(String paymentMethodId,
String accountName,
String accountNumber,
String currencyCode) {
PaymentAccount paymentAccount = null;
PaymentMethod paymentMethod = getPaymentMethodById(paymentMethodId);
switch (paymentMethod.getId()) {
case UPHOLD_ID:
case MONEY_BEAM_ID:
case POPMONEY_ID:
case REVOLUT_ID:
//noinspection DuplicateBranchesInSwitch
log.error("PaymentMethod {} not supported yet.", paymentMethod);
break;
case PERFECT_MONEY_ID:
// Create and persist a PerfectMoney dummy payment account. There is no
// guard against creating accounts with duplicate names & numbers, only
// the uuid and creation date are unique.
paymentAccount = PaymentAccountFactory.getPaymentAccount(paymentMethod);
paymentAccount.init();
paymentAccount.setAccountName(accountName);
((PerfectMoneyAccount) paymentAccount).setAccountNr(accountNumber);
paymentAccount.setSingleTradeCurrency(new FiatCurrency(currencyCode));
break;
case SEPA_ID:
case SEPA_INSTANT_ID:
case FASTER_PAYMENTS_ID:
case NATIONAL_BANK_ID:
case SAME_BANK_ID:
case SPECIFIC_BANKS_ID:
case JAPAN_BANK_ID:
case ALI_PAY_ID:
case WECHAT_PAY_ID:
case SWISH_ID:
case CLEAR_X_CHANGE_ID:
case CHASE_QUICK_PAY_ID:
case INTERAC_E_TRANSFER_ID:
case US_POSTAL_MONEY_ORDER_ID:
case MONEY_GRAM_ID:
case WESTERN_UNION_ID:
case CASH_DEPOSIT_ID:
case HAL_CASH_ID:
case F2F_ID:
case PROMPT_PAY_ID:
case ADVANCED_CASH_ID:
default:
log.error("PaymentMethod {} not supported yet.", paymentMethod);
break;
}
checkNotNull(paymentAccount,
"Could not create payment account with paymentMethodId "
+ paymentMethodId + ".");
return paymentAccount;
}
}

View file

@ -71,7 +71,7 @@ class CoreWalletsService {
this.btcWalletService = btcWalletService;
}
public long getAvailableBalance() {
long getAvailableBalance() {
verifyWalletsAreAvailable();
verifyEncryptedWalletIsUnlocked();
@ -82,27 +82,26 @@ class CoreWalletsService {
return balance.getValue();
}
public long getAddressBalance(String addressString) {
long getAddressBalance(String addressString) {
Address address = getAddressEntry(addressString).getAddress();
return btcWalletService.getBalanceForAddress(address).value;
}
public AddressBalanceInfo getAddressBalanceInfo(String addressString) {
AddressBalanceInfo getAddressBalanceInfo(String addressString) {
var satoshiBalance = getAddressBalance(addressString);
var numConfirmations = getNumConfirmationsForMostRecentTransaction(addressString);
return new AddressBalanceInfo(addressString, satoshiBalance, numConfirmations);
}
public List<AddressBalanceInfo> getFundingAddresses() {
List<AddressBalanceInfo> getFundingAddresses() {
verifyWalletsAreAvailable();
verifyEncryptedWalletIsUnlocked();
// Create a new funding address if none exists.
if (btcWalletService.getAvailableAddressEntries().size() == 0)
if (btcWalletService.getAvailableAddressEntries().isEmpty())
btcWalletService.getFreshAddressEntry();
List<String> addressStrings =
btcWalletService
List<String> addressStrings = btcWalletService
.getAvailableAddressEntries()
.stream()
.map(AddressEntry::getAddressString)
@ -113,8 +112,7 @@ class CoreWalletsService {
// this::getAddressBalance cannot return null.
var balances = memoize(this::getAddressBalance);
boolean noAddressHasZeroBalance =
addressStrings.stream()
boolean noAddressHasZeroBalance = addressStrings.stream()
.allMatch(addressString -> balances.getUnchecked(addressString) != 0);
if (noAddressHasZeroBalance) {
@ -129,13 +127,13 @@ class CoreWalletsService {
.collect(Collectors.toList());
}
public int getNumConfirmationsForMostRecentTransaction(String addressString) {
int getNumConfirmationsForMostRecentTransaction(String addressString) {
Address address = getAddressEntry(addressString).getAddress();
TransactionConfidence confidence = btcWalletService.getConfidenceForAddress(address);
return confidence == null ? 0 : confidence.getDepthInBlocks();
}
public void setWalletPassword(String password, String newPassword) {
void setWalletPassword(String password, String newPassword) {
verifyWalletsAreAvailable();
KeyCrypterScrypt keyCrypterScrypt = getKeyCrypterScrypt();
@ -165,7 +163,7 @@ class CoreWalletsService {
walletsManager.backupWallets();
}
public void lockWallet() {
void lockWallet() {
if (!walletsManager.areWalletsEncrypted())
throw new IllegalStateException("wallet is not encrypted with a password");
@ -175,7 +173,7 @@ class CoreWalletsService {
tempAesKey = null;
}
public void unlockWallet(String password, long timeout) {
void unlockWallet(String password, long timeout) {
verifyWalletIsAvailableAndEncrypted();
KeyCrypterScrypt keyCrypterScrypt = getKeyCrypterScrypt();
@ -213,7 +211,7 @@ class CoreWalletsService {
// 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) {
void removeWalletPassword(String password) {
verifyWalletIsAvailableAndEncrypted();
KeyCrypterScrypt keyCrypterScrypt = getKeyCrypterScrypt();

View file

@ -793,6 +793,9 @@ public abstract class WalletService {
return maybeAddTxToWallet(transaction.bitcoinSerialize(), wallet, source);
}
public Address getAddress(String addressString) {
return Address.fromBase58(params, addressString);
}
///////////////////////////////////////////////////////////////////////////////////////////
// bisqWalletEventListener

View file

@ -189,6 +189,12 @@ public class User implements PersistedDataHost {
// Collection operations
///////////////////////////////////////////////////////////////////////////////////////////
public void addPaymentAccountIfNotExists(PaymentAccount paymentAccount) {
if (!paymentAccountExists(paymentAccount)) {
addPaymentAccount(paymentAccount);
}
}
public void addPaymentAccount(PaymentAccount paymentAccount) {
paymentAccount.onAddToUser();
@ -493,4 +499,8 @@ public class User implements PersistedDataHost {
public boolean isPaymentAccountImport() {
return isPaymentAccountImport;
}
private boolean paymentAccountExists(PaymentAccount paymentAccount) {
return getPaymentAccountsAsObservable().stream().anyMatch(e -> e.equals(paymentAccount));
}
}

View file

@ -23,6 +23,7 @@ import bisq.core.app.CoreModule;
import bisq.common.UserThread;
import bisq.common.app.AppModule;
import bisq.common.handlers.ResultHandler;
import bisq.common.setup.CommonSetup;
import com.google.common.util.concurrent.ThreadFactoryBuilder;
@ -39,13 +40,15 @@ import bisq.daemon.grpc.GrpcServer;
@Slf4j
public class BisqDaemonMain extends BisqHeadlessAppMain implements BisqSetup.BisqSetupListener {
private GrpcServer grpcServer;
public static void main(String[] args) {
new BisqDaemonMain().execute(args);
}
///////////////////////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////////////
// First synchronous execution tasks
///////////////////////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////////////
@Override
protected void configUserThread() {
@ -70,9 +73,9 @@ public class BisqDaemonMain extends BisqHeadlessAppMain implements BisqSetup.Bis
headlessApp.setGracefulShutDownHandler(this);
}
///////////////////////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////////////
// We continue with a series of synchronous execution tasks
///////////////////////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////////////
@Override
protected AppModule getModule() {
@ -91,7 +94,8 @@ public class BisqDaemonMain extends BisqHeadlessAppMain implements BisqSetup.Bis
// 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
// In headless mode we don't have an async behaviour so we trigger the setup by
// calling onApplicationStarted.
onApplicationStarted();
}
@ -99,7 +103,14 @@ public class BisqDaemonMain extends BisqHeadlessAppMain implements BisqSetup.Bis
protected void onApplicationStarted() {
super.onApplicationStarted();
GrpcServer grpcServer = injector.getInstance(GrpcServer.class);
grpcServer = injector.getInstance(GrpcServer.class);
grpcServer.start();
}
@Override
public void gracefulShutDown(ResultHandler resultHandler) {
super.gracefulShutDown(resultHandler);
grpcServer.shutdown();
}
}

View file

@ -0,0 +1,37 @@
package bisq.daemon.grpc;
import bisq.core.api.CoreApi;
import bisq.core.trade.statistics.TradeStatistics2;
import bisq.proto.grpc.GetTradeStatisticsGrpc;
import bisq.proto.grpc.GetTradeStatisticsReply;
import bisq.proto.grpc.GetTradeStatisticsRequest;
import io.grpc.stub.StreamObserver;
import javax.inject.Inject;
import java.util.stream.Collectors;
class GrpcGetTradeStatisticsService extends GetTradeStatisticsGrpc.GetTradeStatisticsImplBase {
private final CoreApi coreApi;
@Inject
public GrpcGetTradeStatisticsService(CoreApi coreApi) {
this.coreApi = coreApi;
}
@Override
public void getTradeStatistics(GetTradeStatisticsRequest req,
StreamObserver<GetTradeStatisticsReply> responseObserver) {
var tradeStatistics = coreApi.getTradeStatistics().stream()
.map(TradeStatistics2::toProtoTradeStatistics2)
.collect(Collectors.toList());
var reply = GetTradeStatisticsReply.newBuilder().addAllTradeStatistics(tradeStatistics).build();
responseObserver.onNext(reply);
responseObserver.onCompleted();
}
}

View file

@ -52,7 +52,7 @@ class GrpcOffersService extends OffersGrpc.OffersImplBase {
// The client cannot see bisq.core.Offer or its fromProto method.
// We use the lighter weight OfferInfo proto wrapper instead, containing just
// enough fields to view and create offers.
List<OfferInfo> result = coreApi.getOffers(req.getDirection(), req.getFiatCurrencyCode())
List<OfferInfo> result = coreApi.getOffers(req.getDirection(), req.getCurrencyCode())
.stream().map(offer -> new OfferInfo.OfferInfoBuilder()
.withId(offer.getId())
.withDirection(offer.getDirection().name())

View file

@ -45,7 +45,10 @@ class GrpcPaymentAccountsService extends PaymentAccountsGrpc.PaymentAccountsImpl
@Override
public void createPaymentAccount(CreatePaymentAccountRequest req,
StreamObserver<CreatePaymentAccountReply> responseObserver) {
coreApi.createPaymentAccount(req.getAccountName(), req.getAccountNumber(), req.getFiatCurrencyCode());
coreApi.createPaymentAccount(req.getPaymentMethodId(),
req.getAccountName(),
req.getAccountNumber(),
req.getCurrencyCode());
var reply = CreatePaymentAccountReply.newBuilder().build();
responseObserver.onNext(reply);
responseObserver.onCompleted();
@ -54,10 +57,11 @@ class GrpcPaymentAccountsService extends PaymentAccountsGrpc.PaymentAccountsImpl
@Override
public void getPaymentAccounts(GetPaymentAccountsRequest req,
StreamObserver<GetPaymentAccountsReply> responseObserver) {
var tradeStatistics = coreApi.getPaymentAccounts().stream()
var paymentAccounts = coreApi.getPaymentAccounts().stream()
.map(PaymentAccount::toProtoMessage)
.collect(Collectors.toList());
var reply = GetPaymentAccountsReply.newBuilder().addAllPaymentAccounts(tradeStatistics).build();
var reply = GetPaymentAccountsReply.newBuilder()
.addAllPaymentAccounts(paymentAccounts).build();
responseObserver.onNext(reply);
responseObserver.onCompleted();
}

View file

@ -18,30 +18,21 @@
package bisq.daemon.grpc;
import bisq.core.api.CoreApi;
import bisq.core.trade.statistics.TradeStatistics2;
import bisq.common.config.Config;
import bisq.proto.grpc.GetTradeStatisticsGrpc;
import bisq.proto.grpc.GetTradeStatisticsReply;
import bisq.proto.grpc.GetTradeStatisticsRequest;
import bisq.proto.grpc.GetVersionGrpc;
import bisq.proto.grpc.GetVersionReply;
import bisq.proto.grpc.GetVersionRequest;
import io.grpc.Server;
import io.grpc.ServerBuilder;
import io.grpc.stub.StreamObserver;
import javax.inject.Inject;
import javax.inject.Singleton;
import java.io.IOException;
import java.io.UncheckedIOException;
import java.util.stream.Collectors;
import lombok.extern.slf4j.Slf4j;
@Singleton
@Slf4j
public class GrpcServer {
@ -51,19 +42,22 @@ public class GrpcServer {
@Inject
public GrpcServer(Config config,
CoreApi coreApi,
PasswordAuthInterceptor passwordAuthInterceptor,
GrpcDisputeAgentsService disputeAgentsService,
GrpcOffersService offersService,
GrpcPaymentAccountsService paymentAccountsService,
GrpcVersionService versionService,
GrpcGetTradeStatisticsService tradeStatisticsService,
GrpcWalletsService walletsService) {
this.coreApi = coreApi;
this.server = ServerBuilder.forPort(config.apiPort)
.addService(disputeAgentsService)
.addService(new GetVersionService())
.addService(new GetTradeStatisticsService())
.addService(offersService)
.addService(paymentAccountsService)
.addService(tradeStatisticsService)
.addService(versionService)
.addService(walletsService)
.intercept(new PasswordAuthInterceptor(config.apiPassword))
.intercept(passwordAuthInterceptor)
.build();
}
@ -71,36 +65,14 @@ public class GrpcServer {
try {
server.start();
log.info("listening on port {}", server.getPort());
Runtime.getRuntime().addShutdownHook(new Thread(() -> {
server.shutdown();
log.info("shutdown complete");
}));
} catch (IOException ex) {
throw new UncheckedIOException(ex);
}
}
class GetVersionService extends GetVersionGrpc.GetVersionImplBase {
@Override
public void getVersion(GetVersionRequest req, StreamObserver<GetVersionReply> responseObserver) {
var reply = GetVersionReply.newBuilder().setVersion(coreApi.getVersion()).build();
responseObserver.onNext(reply);
responseObserver.onCompleted();
}
}
class GetTradeStatisticsService extends GetTradeStatisticsGrpc.GetTradeStatisticsImplBase {
@Override
public void getTradeStatistics(GetTradeStatisticsRequest req,
StreamObserver<GetTradeStatisticsReply> responseObserver) {
var tradeStatistics = coreApi.getTradeStatistics().stream()
.map(TradeStatistics2::toProtoTradeStatistics2)
.collect(Collectors.toList());
var reply = GetTradeStatisticsReply.newBuilder().addAllTradeStatistics(tradeStatistics).build();
responseObserver.onNext(reply);
responseObserver.onCompleted();
}
public void shutdown() {
log.info("Server shutdown started");
server.shutdown();
log.info("Server shutdown complete");
}
}

View file

@ -0,0 +1,28 @@
package bisq.daemon.grpc;
import bisq.core.api.CoreApi;
import bisq.proto.grpc.GetVersionGrpc;
import bisq.proto.grpc.GetVersionReply;
import bisq.proto.grpc.GetVersionRequest;
import io.grpc.stub.StreamObserver;
import javax.inject.Inject;
class GrpcVersionService extends GetVersionGrpc.GetVersionImplBase {
private final CoreApi coreApi;
@Inject
public GrpcVersionService(CoreApi coreApi) {
this.coreApi = coreApi;
}
@Override
public void getVersion(GetVersionRequest req, StreamObserver<GetVersionReply> responseObserver) {
var reply = GetVersionReply.newBuilder().setVersion(coreApi.getVersion()).build();
responseObserver.onNext(reply);
responseObserver.onCompleted();
}
}

View file

@ -54,11 +54,17 @@ class GrpcWalletsService extends WalletsGrpc.WalletsImplBase {
this.coreApi = coreApi;
}
// TODO we need to support 3 or 4 balance types: available, reserved, lockedInTrade
// and maybe total wallet balance (available+reserved). To not duplicate the methods,
// we should pass an enum type. Enums in proto are a bit cumbersome as they are
// global so you quickly run into namespace conflicts if not always prefixes which
// makes it more verbose. In the core code base we move to the strategy to store the
// enum name and map it. This gives also more flexibility with updates.
@Override
public void getBalance(GetBalanceRequest req, StreamObserver<GetBalanceReply> responseObserver) {
try {
long result = coreApi.getAvailableBalance();
var reply = GetBalanceReply.newBuilder().setBalance(result).build();
long availableBalance = coreApi.getAvailableBalance();
var reply = GetBalanceReply.newBuilder().setBalance(availableBalance).build();
responseObserver.onNext(reply);
responseObserver.onCompleted();
} catch (IllegalStateException cause) {
@ -72,8 +78,9 @@ class GrpcWalletsService extends WalletsGrpc.WalletsImplBase {
public void getAddressBalance(GetAddressBalanceRequest req,
StreamObserver<GetAddressBalanceReply> responseObserver) {
try {
AddressBalanceInfo result = coreApi.getAddressBalanceInfo(req.getAddress());
var reply = GetAddressBalanceReply.newBuilder().setAddressBalanceInfo(result.toProtoMessage()).build();
AddressBalanceInfo balanceInfo = coreApi.getAddressBalanceInfo(req.getAddress());
var reply = GetAddressBalanceReply.newBuilder()
.setAddressBalanceInfo(balanceInfo.toProtoMessage()).build();
responseObserver.onNext(reply);
responseObserver.onCompleted();
} catch (IllegalStateException cause) {
@ -87,10 +94,10 @@ class GrpcWalletsService extends WalletsGrpc.WalletsImplBase {
public void getFundingAddresses(GetFundingAddressesRequest req,
StreamObserver<GetFundingAddressesReply> responseObserver) {
try {
List<AddressBalanceInfo> result = coreApi.getFundingAddresses();
List<AddressBalanceInfo> balanceInfo = coreApi.getFundingAddresses();
var reply = GetFundingAddressesReply.newBuilder()
.addAllAddressBalanceInfo(
result.stream()
balanceInfo.stream()
.map(AddressBalanceInfo::toProtoMessage)
.collect(Collectors.toList()))
.build();

View file

@ -17,12 +17,16 @@
package bisq.daemon.grpc;
import bisq.common.config.Config;
import io.grpc.Metadata;
import io.grpc.ServerCall;
import io.grpc.ServerCallHandler;
import io.grpc.ServerInterceptor;
import io.grpc.StatusRuntimeException;
import javax.inject.Inject;
import static io.grpc.Metadata.ASCII_STRING_MARSHALLER;
import static io.grpc.Metadata.Key;
import static io.grpc.Status.UNAUTHENTICATED;
@ -36,12 +40,13 @@ import static java.lang.String.format;
*/
class PasswordAuthInterceptor implements ServerInterceptor {
public static final String PASSWORD_KEY = "password";
private static final String PASSWORD_KEY = "password";
private final String expectedPasswordValue;
public PasswordAuthInterceptor(String expectedPasswordValue) {
this.expectedPasswordValue = expectedPasswordValue;
@Inject
public PasswordAuthInterceptor(Config config) {
this.expectedPasswordValue = config.apiPassword;
}
@Override

View file

@ -53,7 +53,7 @@ service Offers {
message GetOffersRequest {
string direction = 1;
string fiatCurrencyCode = 2;
string currencyCode = 2;
}
message GetOffersReply {
@ -61,7 +61,7 @@ message GetOffersReply {
}
message CreateOfferRequest {
string currencyCode = 1; // TODO switch order w/ direction field in next PR
string currencyCode = 1;
string direction = 2;
uint64 price = 3;
bool useMarketBasedPrice = 4;
@ -107,9 +107,11 @@ service PaymentAccounts {
}
message CreatePaymentAccountRequest {
string accountName = 1;
string accountNumber = 2;
string fiatCurrencyCode = 3;
string paymentMethodId = 1;
string accountName = 2;
string accountNumber = 3;
// TODO Support all currencies. Maybe add a repeated and if only one is used its a singletonList.
string currencyCode = 4;
}
message CreatePaymentAccountReply {
@ -123,7 +125,7 @@ message GetPaymentAccountsReply {
}
///////////////////////////////////////////////////////////////////////////////////////////
// TradeStatistics
// GetTradeStatistics
///////////////////////////////////////////////////////////////////////////////////////////
service GetTradeStatistics {