Refactor API & add registerdisputeagent method to CLI

This commit contains most of the changes suggested by @chimp1984 in
his api-suggestions branch.  See
961703ecea

A new 'registerdisputeagent' method was also added to MainCli, finishing
the work to support registration of mediators and refund agents on
arbitration daemons running in regtest mode.  This method cannot be
used to register dispute agents on mainnet;  users will see an error
msg if they try.
This commit is contained in:
ghubstan 2020-09-18 11:33:16 -03:00
parent b22ab6a1fa
commit e2bd89f39a
No known key found for this signature in database
GPG Key ID: E35592D6800A861E
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,16 +77,16 @@ class CoreOffersService {
return offers;
}
public void createOffer(String currencyCode,
String directionAsString,
long priceAsLong,
boolean useMarketBasedPrice,
double marketPriceMargin,
long amountAsLong,
long minAmountAsLong,
double buyerSecurityDeposit,
String paymentAccountId,
TransactionResultHandler resultHandler) {
void createOffer(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);
@ -111,18 +111,18 @@ class CoreOffersService {
resultHandler);
}
public void createOffer(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) {
void createOffer(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) {
Coin useDefaultTxFee = Coin.ZERO;
Offer offer = createOfferService.createAndGetOffer(offerId,
direction,

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,40 +82,38 @@ 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
.getAvailableAddressEntries()
.stream()
.map(AddressEntry::getAddressString)
.collect(Collectors.toList());
List<String> addressStrings = btcWalletService
.getAvailableAddressEntries()
.stream()
.map(AddressEntry::getAddressString)
.collect(Collectors.toList());
// getAddressBalance is memoized, because we'll map it over addresses twice.
// To get the balances, we'll be using .getUnchecked, because we know that
// this::getAddressBalance cannot return null.
var balances = memoize(this::getAddressBalance);
boolean noAddressHasZeroBalance =
addressStrings.stream()
.allMatch(addressString -> balances.getUnchecked(addressString) != 0);
boolean noAddressHasZeroBalance = addressStrings.stream()
.allMatch(addressString -> balances.getUnchecked(addressString) != 0);
if (noAddressHasZeroBalance) {
var newZeroBalanceAddress = btcWalletService.getFreshAddressEntry();
@ -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

@ -734,6 +734,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;
public 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;
public 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 {