Use ListenableFuture and callback when requesting tx fee

This change fixes the blocking problem in the fee rate request api.
Also redefined the TxFeeRateInfo.

- Redefined grpc.proto message TxFeeRateInfo, added
  lastFeeServiceRequestTs field. (CLI user may want to know
  TS of last fee request.)

- Adjusted TxFeeRateInfo proto wrapper.

- Adjusted CurrencyFormat and BtcTxFeeRateTest to new TxFeeRateInfo.

- Added @Getter annotation to FeeService.  (CLI user may want to know
  TS of last fee request).

- Pass resultHandler from GrpcWalletsService through CoreApi, to
  CoreWalletsService's tx fee rate api methods.
This commit is contained in:
ghubstan 2020-12-01 21:10:47 -03:00
parent faf030fbc5
commit 987d89319e
No known key found for this signature in database
GPG key ID: E35592D6800A861E
8 changed files with 107 additions and 61 deletions

View file

@ -45,7 +45,7 @@ public class BtcTxFeeRateTest extends MethodTest {
log.debug("{} -> Fee rate with no preference: {}", testName(testInfo), txFeeRateInfo);
assertFalse(txFeeRateInfo.isUseCustomTxFeeRate());
assertTrue(txFeeRateInfo.getStdTxFeeRate() > 0);
assertTrue(txFeeRateInfo.getFeeServiceRate() > 0);
}
@Test
@ -56,7 +56,7 @@ public class BtcTxFeeRateTest extends MethodTest {
assertTrue(txFeeRateInfo.isUseCustomTxFeeRate());
assertEquals(10, txFeeRateInfo.getCustomTxFeeRate());
assertTrue(txFeeRateInfo.getStdTxFeeRate() > 0);
assertTrue(txFeeRateInfo.getFeeServiceRate() > 0);
}
@Test
@ -66,7 +66,7 @@ public class BtcTxFeeRateTest extends MethodTest {
log.debug("{} -> Fee rate with no preference: {}", testName(testInfo), txFeeRateInfo);
assertFalse(txFeeRateInfo.isUseCustomTxFeeRate());
assertTrue(txFeeRateInfo.getStdTxFeeRate() > 0);
assertTrue(txFeeRateInfo.getFeeServiceRate() > 0);
}
@AfterAll

View file

@ -57,10 +57,10 @@ public class CurrencyFormat {
if (txFeeRateInfo.getUseCustomTxFeeRate())
return format("custom tx fee rate: %s sats/byte, network rate: %s sats/byte",
formatFeeSatoshis(txFeeRateInfo.getCustomTxFeeRate()),
formatFeeSatoshis(txFeeRateInfo.getStdTxFeeRate()));
formatFeeSatoshis(txFeeRateInfo.getFeeServiceRate()));
else
return format("tx fee rate: %s sats/byte",
formatFeeSatoshis(txFeeRateInfo.getStdTxFeeRate()));
formatFeeSatoshis(txFeeRateInfo.getFeeServiceRate()));
}
static String formatAmountRange(long minAmount, long amount) {

View file

@ -31,6 +31,7 @@ import bisq.core.trade.statistics.TradeStatistics3;
import bisq.core.trade.statistics.TradeStatisticsManager;
import bisq.common.app.Version;
import bisq.common.handlers.ResultHandler;
import org.bitcoinj.core.Coin;
@ -247,16 +248,21 @@ public class CoreApi {
walletsService.sendBsq(address, amount, callback);
}
public TxFeeRateInfo getTxFeeRate() {
return walletsService.getTxFeeRate();
public void getTxFeeRate(ResultHandler resultHandler) {
walletsService.getTxFeeRate(resultHandler);
}
public TxFeeRateInfo setTxFeeRatePreference(long txFeeRate) {
return walletsService.setTxFeeRatePreference(txFeeRate);
public void setTxFeeRatePreference(long txFeeRate,
ResultHandler resultHandler) {
walletsService.setTxFeeRatePreference(txFeeRate, resultHandler);
}
public TxFeeRateInfo unsetTxFeeRatePreference() {
return walletsService.unsetTxFeeRatePreference();
public void unsetTxFeeRatePreference(ResultHandler resultHandler) {
walletsService.unsetTxFeeRatePreference(resultHandler);
}
public TxFeeRateInfo getMostRecentTxFeeRateInfo() {
return walletsService.getMostRecentTxFeeRateInfo();
}
public void setWalletPassword(String password, String newPassword) {

View file

@ -39,6 +39,8 @@ import bisq.core.util.coin.BsqFormatter;
import bisq.common.Timer;
import bisq.common.UserThread;
import bisq.common.handlers.ResultHandler;
import bisq.common.util.Utilities;
import org.bitcoinj.core.Address;
import org.bitcoinj.core.Coin;
@ -52,13 +54,16 @@ import javax.inject.Inject;
import com.google.common.cache.CacheBuilder;
import com.google.common.cache.CacheLoader;
import com.google.common.cache.LoadingCache;
import com.google.common.util.concurrent.FutureCallback;
import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.ListenableFuture;
import com.google.common.util.concurrent.ListeningExecutorService;
import com.google.common.util.concurrent.MoreExecutors;
import org.bouncycastle.crypto.params.KeyParameter;
import java.util.List;
import java.util.Optional;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import java.util.function.Function;
import java.util.stream.Collectors;
@ -88,6 +93,8 @@ class CoreWalletsService {
@Nullable
private KeyParameter tempAesKey;
private final ListeningExecutorService executor = Utilities.getSingleThreadListeningExecutor("CoreWalletsService");
@Inject
public CoreWalletsService(Balances balances,
WalletsManager walletsManager,
@ -196,32 +203,50 @@ class CoreWalletsService {
}
}
TxFeeRateInfo getTxFeeRate() {
void getTxFeeRate(ResultHandler resultHandler) {
try {
CompletableFuture<Void> feeRequestFuture = CompletableFuture.runAsync(feeService::requestFees);
feeRequestFuture.get(); // Block until async fee request is complete.
} catch (InterruptedException | ExecutionException e) {
throw new IllegalStateException("could not request fees from fee service.", e);
}
ListenableFuture<Void> future = (ListenableFuture<Void>) executor.submit(() -> feeService.requestFees());
Futures.addCallback(future, new FutureCallback<>() {
@Override
public void onSuccess(@Nullable Void ignored) {
resultHandler.handleResult();
}
return new TxFeeRateInfo(feeService.getTxFeePerVbyte().value,
preferences.getWithdrawalTxFeeInVbytes(),
preferences.isUseCustomWithdrawalTxFee());
@Override
public void onFailure(Throwable t) {
log.error("", t);
throw new IllegalStateException("could not request fees from fee service", t);
}
}, MoreExecutors.directExecutor());
} catch (Exception ex) {
log.error("", ex);
throw new IllegalStateException("could not request fees from fee service", ex);
}
}
TxFeeRateInfo setTxFeeRatePreference(long txFeeRate) {
void setTxFeeRatePreference(long txFeeRate,
ResultHandler resultHandler) {
if (txFeeRate <= 0)
throw new IllegalStateException("cannot create transactions without fees");
preferences.setUseCustomWithdrawalTxFee(true);
Coin satsPerByte = Coin.valueOf(txFeeRate);
preferences.setWithdrawalTxFeeInVbytes(satsPerByte.value);
return getTxFeeRate();
getTxFeeRate(resultHandler);
}
TxFeeRateInfo unsetTxFeeRatePreference() {
void unsetTxFeeRatePreference(ResultHandler resultHandler) {
preferences.setUseCustomWithdrawalTxFee(false);
return getTxFeeRate();
getTxFeeRate(resultHandler);
}
TxFeeRateInfo getMostRecentTxFeeRateInfo() {
return new TxFeeRateInfo(
preferences.isUseCustomWithdrawalTxFee(),
preferences.getWithdrawalTxFeeInVbytes(),
feeService.getTxFeePerVbyte().value,
feeService.getLastRequest());
}
int getNumConfirmationsForMostRecentTransaction(String addressString) {

View file

@ -26,16 +26,19 @@ import lombok.Getter;
@Getter
public class TxFeeRateInfo implements Payload {
private final long stdTxFeeRate;
private final long customTxFeeRate;
private final boolean useCustomTxFeeRate;
private final long customTxFeeRate;
private final long feeServiceRate;
private final long lastFeeServiceRequestTs;
public TxFeeRateInfo(long stdTxFeeRate,
public TxFeeRateInfo(boolean useCustomTxFeeRate,
long customTxFeeRate,
boolean useCustomTxFeeRate) {
this.stdTxFeeRate = stdTxFeeRate;
this.customTxFeeRate = customTxFeeRate;
long feeServiceRate,
long lastFeeServiceRequestTs) {
this.useCustomTxFeeRate = useCustomTxFeeRate;
this.customTxFeeRate = customTxFeeRate;
this.feeServiceRate = feeServiceRate;
this.lastFeeServiceRequestTs = lastFeeServiceRequestTs;
}
//////////////////////////////////////////////////////////////////////////////////////
@ -45,25 +48,28 @@ public class TxFeeRateInfo implements Payload {
@Override
public bisq.proto.grpc.TxFeeRateInfo toProtoMessage() {
return bisq.proto.grpc.TxFeeRateInfo.newBuilder()
.setStdTxFeeRate(stdTxFeeRate)
.setCustomTxFeeRate(customTxFeeRate)
.setUseCustomTxFeeRate(useCustomTxFeeRate)
.setCustomTxFeeRate(customTxFeeRate)
.setFeeServiceRate(feeServiceRate)
.setLastFeeServiceRequestTs(lastFeeServiceRequestTs)
.build();
}
@SuppressWarnings("unused")
public static TxFeeRateInfo fromProto(bisq.proto.grpc.TxFeeRateInfo proto) {
return new TxFeeRateInfo(proto.getStdTxFeeRate(),
return new TxFeeRateInfo(proto.getUseCustomTxFeeRate(),
proto.getCustomTxFeeRate(),
proto.getUseCustomTxFeeRate());
proto.getFeeServiceRate(),
proto.getLastFeeServiceRequestTs());
}
@Override
public String toString() {
return "TxFeeRateInfo{"
+ "stdTxFeeRate=" + stdTxFeeRate + " sats/byte"
+ ", customTxFeeRate=" + customTxFeeRate + " sats/byte"
+ ", useCustomTxFeeRate=" + useCustomTxFeeRate
+ "}";
return "TxFeeRateInfo{" + "\n" +
" useCustomTxFeeRate=" + useCustomTxFeeRate + "\n" +
", customTxFeeRate=" + customTxFeeRate + "sats/byte" + "\n" +
", feeServiceRate=" + feeServiceRate + "sats/byte" + "\n" +
", lastFeeServiceRequestTs=" + lastFeeServiceRequestTs + "\n" +
'}';
}
}

View file

@ -45,6 +45,7 @@ import java.time.Instant;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import lombok.Getter;
import lombok.extern.slf4j.Slf4j;
import org.jetbrains.annotations.NotNull;
@ -96,6 +97,7 @@ public class FeeService {
private final IntegerProperty feeUpdateCounter = new SimpleIntegerProperty(0);
private long txFeePerVbyte = BTC_DEFAULT_TX_FEE;
private Map<String, Long> timeStampMap;
@Getter
private long lastRequest;
private long minFeePerVByte;
private long epochInSecondAtLastRequest;

View file

@ -174,12 +174,14 @@ class GrpcWalletsService extends WalletsGrpc.WalletsImplBase {
public void getTxFeeRate(GetTxFeeRateRequest req,
StreamObserver<GetTxFeeRateReply> responseObserver) {
try {
TxFeeRateInfo txFeeRateInfo = coreApi.getTxFeeRate();
var reply = GetTxFeeRateReply.newBuilder()
.setTxFeeRateInfo(txFeeRateInfo.toProtoMessage())
.build();
responseObserver.onNext(reply);
responseObserver.onCompleted();
coreApi.getTxFeeRate(() -> {
TxFeeRateInfo txFeeRateInfo = coreApi.getMostRecentTxFeeRateInfo();
var reply = GetTxFeeRateReply.newBuilder()
.setTxFeeRateInfo(txFeeRateInfo.toProtoMessage())
.build();
responseObserver.onNext(reply);
responseObserver.onCompleted();
});
} catch (IllegalStateException cause) {
var ex = new StatusRuntimeException(Status.UNKNOWN.withDescription(cause.getMessage()));
responseObserver.onError(ex);
@ -191,12 +193,14 @@ class GrpcWalletsService extends WalletsGrpc.WalletsImplBase {
public void setTxFeeRatePreference(SetTxFeeRatePreferenceRequest req,
StreamObserver<SetTxFeeRatePreferenceReply> responseObserver) {
try {
TxFeeRateInfo txFeeRateInfo = coreApi.setTxFeeRatePreference(req.getTxFeeRatePreference());
var reply = SetTxFeeRatePreferenceReply.newBuilder()
.setTxFeeRateInfo(txFeeRateInfo.toProtoMessage())
.build();
responseObserver.onNext(reply);
responseObserver.onCompleted();
coreApi.setTxFeeRatePreference(req.getTxFeeRatePreference(), () -> {
TxFeeRateInfo txFeeRateInfo = coreApi.getMostRecentTxFeeRateInfo();
var reply = SetTxFeeRatePreferenceReply.newBuilder()
.setTxFeeRateInfo(txFeeRateInfo.toProtoMessage())
.build();
responseObserver.onNext(reply);
responseObserver.onCompleted();
});
} catch (IllegalStateException cause) {
var ex = new StatusRuntimeException(Status.UNKNOWN.withDescription(cause.getMessage()));
responseObserver.onError(ex);
@ -208,12 +212,14 @@ class GrpcWalletsService extends WalletsGrpc.WalletsImplBase {
public void unsetTxFeeRatePreference(UnsetTxFeeRatePreferenceRequest req,
StreamObserver<UnsetTxFeeRatePreferenceReply> responseObserver) {
try {
TxFeeRateInfo txFeeRateInfo = coreApi.unsetTxFeeRatePreference();
var reply = UnsetTxFeeRatePreferenceReply.newBuilder()
.setTxFeeRateInfo(txFeeRateInfo.toProtoMessage())
.build();
responseObserver.onNext(reply);
responseObserver.onCompleted();
coreApi.unsetTxFeeRatePreference(() -> {
TxFeeRateInfo txFeeRateInfo = coreApi.getMostRecentTxFeeRateInfo();
var reply = UnsetTxFeeRatePreferenceReply.newBuilder()
.setTxFeeRateInfo(txFeeRateInfo.toProtoMessage())
.build();
responseObserver.onNext(reply);
responseObserver.onCompleted();
});
} catch (IllegalStateException cause) {
var ex = new StatusRuntimeException(Status.UNKNOWN.withDescription(cause.getMessage()));
responseObserver.onError(ex);

View file

@ -438,9 +438,10 @@ message AddressBalanceInfo {
}
message TxFeeRateInfo {
uint64 stdTxFeeRate = 1;
bool useCustomTxFeeRate = 1;
uint64 customTxFeeRate = 2;
bool useCustomTxFeeRate = 3;
uint64 feeServiceRate = 3;
uint64 lastFeeServiceRequestTs = 4;
}
///////////////////////////////////////////////////////////////////////////////////////////