mirror of
https://github.com/bisq-network/bisq.git
synced 2024-11-19 09:52:23 +01:00
Adjust grpc & core services to new takeoffer error handling
- GrpcErrorMessageHandler A new ErrorMessageHandler implementation to get around the api specific problem of having to use Task ErrorMessageHandlers that build task error messages for the UI. - GrpcExceptionHandler A new method for working with the ErrorMessageHandler interface. - GrpcTradesService, CoreApi, CoreTradesService: Ajdusted takeoffer error handling to give a failure reason provided by the new GrpcErrorMessageHandler.
This commit is contained in:
parent
067a5c7a08
commit
d0590d93a9
@ -33,6 +33,7 @@ import bisq.core.trade.statistics.TradeStatisticsManager;
|
||||
|
||||
import bisq.common.app.Version;
|
||||
import bisq.common.config.Config;
|
||||
import bisq.common.handlers.ErrorMessageHandler;
|
||||
import bisq.common.handlers.ResultHandler;
|
||||
|
||||
import org.bitcoinj.core.Coin;
|
||||
@ -224,12 +225,14 @@ public class CoreApi {
|
||||
public void takeOffer(String offerId,
|
||||
String paymentAccountId,
|
||||
String takerFeeCurrencyCode,
|
||||
Consumer<Trade> resultHandler) {
|
||||
Consumer<Trade> resultHandler,
|
||||
ErrorMessageHandler errorMessageHandler) {
|
||||
Offer offer = coreOffersService.getOffer(offerId);
|
||||
coreTradesService.takeOffer(offer,
|
||||
paymentAccountId,
|
||||
takerFeeCurrencyCode,
|
||||
resultHandler);
|
||||
resultHandler,
|
||||
errorMessageHandler);
|
||||
}
|
||||
|
||||
public void confirmPaymentStarted(String tradeId) {
|
||||
|
@ -32,6 +32,8 @@ import bisq.core.trade.protocol.SellerProtocol;
|
||||
import bisq.core.user.User;
|
||||
import bisq.core.util.validation.BtcAddressValidator;
|
||||
|
||||
import bisq.common.handlers.ErrorMessageHandler;
|
||||
|
||||
import org.bitcoinj.core.Coin;
|
||||
|
||||
import javax.inject.Inject;
|
||||
@ -86,7 +88,8 @@ class CoreTradesService {
|
||||
void takeOffer(Offer offer,
|
||||
String paymentAccountId,
|
||||
String takerFeeCurrencyCode,
|
||||
Consumer<Trade> resultHandler) {
|
||||
Consumer<Trade> resultHandler,
|
||||
ErrorMessageHandler errorMessageHandler) {
|
||||
coreWalletsService.verifyWalletsAreAvailable();
|
||||
coreWalletsService.verifyEncryptedWalletIsUnlocked();
|
||||
|
||||
@ -114,10 +117,7 @@ class CoreTradesService {
|
||||
useSavingsWallet,
|
||||
coreContext.isApiUser(),
|
||||
resultHandler::accept,
|
||||
errorMessage -> {
|
||||
log.error(errorMessage);
|
||||
throw new IllegalStateException(errorMessage);
|
||||
}
|
||||
errorMessageHandler
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -0,0 +1,104 @@
|
||||
/*
|
||||
* 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.grpc;
|
||||
|
||||
import bisq.common.handlers.ErrorMessageHandler;
|
||||
|
||||
import bisq.proto.grpc.TakeOfferReply;
|
||||
|
||||
import protobuf.AvailabilityResult;
|
||||
|
||||
import io.grpc.stub.StreamObserver;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
|
||||
import lombok.Getter;
|
||||
|
||||
import static bisq.proto.grpc.TradesGrpc.getTakeOfferMethod;
|
||||
|
||||
/**
|
||||
* An implementation of bisq.common.handlers.ErrorMessageHandler that avoids
|
||||
* an exception loop with the UI's bisq.common.taskrunner framework.
|
||||
*
|
||||
* The legacy ErrorMessageHandler is for reporting error messages only to the UI, but
|
||||
* some core api tasks (takeoffer) require one. This implementation works around
|
||||
* the problem of Task ErrorMessageHandlers not throwing exceptions to the gRPC client.
|
||||
*
|
||||
* Extra care is needed because exceptions thrown by an ErrorMessageHandler inside
|
||||
* a Task may be thrown back to the GrpcService object, and if a gRPC ErrorMessageHandler
|
||||
* responded by throwing another exception, the loop may only stop after the gRPC
|
||||
* stream is closed.
|
||||
*
|
||||
* A unique instance should be used for a single gRPC call.
|
||||
*/
|
||||
public class GrpcErrorMessageHandler implements ErrorMessageHandler {
|
||||
|
||||
@Getter
|
||||
private boolean isErrorHandled = false;
|
||||
|
||||
private final String fullMethodName;
|
||||
private final StreamObserver<?> responseObserver;
|
||||
private final GrpcExceptionHandler exceptionHandler;
|
||||
private final Logger log;
|
||||
|
||||
public GrpcErrorMessageHandler(String fullMethodName,
|
||||
StreamObserver<?> responseObserver,
|
||||
GrpcExceptionHandler exceptionHandler,
|
||||
Logger log) {
|
||||
this.fullMethodName = fullMethodName;
|
||||
this.exceptionHandler = exceptionHandler;
|
||||
this.responseObserver = responseObserver;
|
||||
this.log = log;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleErrorMessage(String errorMessage) {
|
||||
// A task runner may call handleErrorMessage(String) more than once.
|
||||
// Throw only one exception if that happens, to avoid looping until the
|
||||
// grpc stream is closed
|
||||
if (!isErrorHandled) {
|
||||
this.isErrorHandled = true;
|
||||
log.error(errorMessage);
|
||||
|
||||
if (isTakeOfferError()) {
|
||||
handleTakeOfferError();
|
||||
} else {
|
||||
exceptionHandler.handleErrorMessage(log,
|
||||
errorMessage,
|
||||
responseObserver);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void handleTakeOfferError() {
|
||||
// Send the AvailabilityResult to the client instead of throwing an exception.
|
||||
// The client should look at the grpc reply object's AvailabilityResult
|
||||
// field if reply.hasTrade = false, and use it give the user a human readable msg.
|
||||
StreamObserver<TakeOfferReply> takeOfferResponseObserver = (StreamObserver<TakeOfferReply>) responseObserver;
|
||||
var availabilityResult = AvailabilityResult.valueOf("MAKER_DENIED_API_USER");
|
||||
var reply = TakeOfferReply.newBuilder()
|
||||
.setAvailabilityResult(availabilityResult)
|
||||
.build();
|
||||
takeOfferResponseObserver.onNext(reply);
|
||||
takeOfferResponseObserver.onCompleted();
|
||||
}
|
||||
|
||||
private boolean isTakeOfferError() {
|
||||
return fullMethodName.equals(getTakeOfferMethod().getFullMethodName());
|
||||
}
|
||||
}
|
@ -24,6 +24,7 @@ import io.grpc.stub.StreamObserver;
|
||||
import javax.inject.Inject;
|
||||
import javax.inject.Singleton;
|
||||
|
||||
import java.util.function.Function;
|
||||
import java.util.function.Predicate;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
@ -68,6 +69,18 @@ class GrpcExceptionHandler {
|
||||
throw grpcStatusRuntimeException;
|
||||
}
|
||||
|
||||
public void handleErrorMessage(Logger log,
|
||||
String errorMessage,
|
||||
StreamObserver<?> responseObserver) {
|
||||
// This is used to wrap Task errors from the ErrorMessageHandler
|
||||
// interface, an interface that is not allowed to throw exceptions.
|
||||
log.error(errorMessage);
|
||||
var grpcStatusRuntimeException = new StatusRuntimeException(
|
||||
UNKNOWN.withDescription(cliStyleErrorMessage.apply(errorMessage)));
|
||||
responseObserver.onError(grpcStatusRuntimeException);
|
||||
throw grpcStatusRuntimeException;
|
||||
}
|
||||
|
||||
private StatusRuntimeException wrapException(Throwable t) {
|
||||
// We want to be careful about what kinds of exception messages we send to the
|
||||
// client. Expected core exceptions should be wrapped in an IllegalStateException
|
||||
@ -87,6 +100,12 @@ class GrpcExceptionHandler {
|
||||
}
|
||||
}
|
||||
|
||||
private final Function<String, String> cliStyleErrorMessage = (e) -> {
|
||||
String[] line = e.split("\\r?\\n");
|
||||
int lastLine = line.length;
|
||||
return line[lastLine - 1].toLowerCase();
|
||||
};
|
||||
|
||||
private Status mapGrpcErrorStatus(Throwable t, String description) {
|
||||
// We default to the UNKNOWN status, except were the mapping of a core api
|
||||
// exception to a gRPC Status is obvious. If we ever use a gRPC reverse-proxy
|
||||
|
@ -90,21 +90,26 @@ class GrpcTradesService extends TradesImplBase {
|
||||
@Override
|
||||
public void takeOffer(TakeOfferRequest req,
|
||||
StreamObserver<TakeOfferReply> responseObserver) {
|
||||
try {
|
||||
coreApi.takeOffer(req.getOfferId(),
|
||||
req.getPaymentAccountId(),
|
||||
req.getTakerFeeCurrencyCode(),
|
||||
trade -> {
|
||||
TradeInfo tradeInfo = toTradeInfo(trade);
|
||||
var reply = TakeOfferReply.newBuilder()
|
||||
.setTrade(tradeInfo.toProtoMessage())
|
||||
.build();
|
||||
responseObserver.onNext(reply);
|
||||
responseObserver.onCompleted();
|
||||
});
|
||||
} catch (Throwable cause) {
|
||||
exceptionHandler.handleException(log, cause, responseObserver);
|
||||
}
|
||||
GrpcErrorMessageHandler errorMessageHandler =
|
||||
new GrpcErrorMessageHandler(getTakeOfferMethod().getFullMethodName(),
|
||||
responseObserver,
|
||||
exceptionHandler,
|
||||
log);
|
||||
coreApi.takeOffer(req.getOfferId(),
|
||||
req.getPaymentAccountId(),
|
||||
req.getTakerFeeCurrencyCode(),
|
||||
trade -> {
|
||||
TradeInfo tradeInfo = toTradeInfo(trade);
|
||||
var reply = TakeOfferReply.newBuilder()
|
||||
.setTrade(tradeInfo.toProtoMessage())
|
||||
.build();
|
||||
responseObserver.onNext(reply);
|
||||
responseObserver.onCompleted();
|
||||
},
|
||||
errorMessage -> {
|
||||
if (!errorMessageHandler.isErrorHandled())
|
||||
errorMessageHandler.handleErrorMessage(errorMessage);
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
|
Loading…
Reference in New Issue
Block a user