Add gettxfeerate, settxfeerate, unsettxfeerate implementations

This commit is contained in:
ghubstan 2020-11-26 18:36:15 -03:00
parent abe7160608
commit 0b0f9f1120
No known key found for this signature in database
GPG key ID: E35592D6800A861E
5 changed files with 179 additions and 1 deletions

View file

@ -31,6 +31,7 @@ import bisq.proto.grpc.GetPaymentAccountFormRequest;
import bisq.proto.grpc.GetPaymentAccountsRequest;
import bisq.proto.grpc.GetPaymentMethodsRequest;
import bisq.proto.grpc.GetTradeRequest;
import bisq.proto.grpc.GetTxFeeRateRequest;
import bisq.proto.grpc.GetUnusedBsqAddressRequest;
import bisq.proto.grpc.GetVersionRequest;
import bisq.proto.grpc.KeepFundsRequest;
@ -39,9 +40,11 @@ import bisq.proto.grpc.OfferInfo;
import bisq.proto.grpc.RegisterDisputeAgentRequest;
import bisq.proto.grpc.RemoveWalletPasswordRequest;
import bisq.proto.grpc.SendBsqRequest;
import bisq.proto.grpc.SetTxFeeRatePreferenceRequest;
import bisq.proto.grpc.SetWalletPasswordRequest;
import bisq.proto.grpc.TakeOfferRequest;
import bisq.proto.grpc.UnlockWalletRequest;
import bisq.proto.grpc.UnsetTxFeeRatePreferenceRequest;
import bisq.proto.grpc.WithdrawFundsRequest;
import protobuf.PaymentAccount;
@ -68,6 +71,7 @@ import java.util.List;
import lombok.extern.slf4j.Slf4j;
import static bisq.cli.CurrencyFormat.formatTxFeeRate;
import static bisq.cli.CurrencyFormat.toSatoshis;
import static bisq.cli.NegativeNumberOptions.hasNegativeNumberOptions;
import static bisq.cli.TableFormat.*;
@ -106,6 +110,9 @@ public class CliMain {
getfundingaddresses,
getunusedbsqaddress,
sendbsq,
gettxfeerate,
settxfeerate,
unsettxfeerate,
lockwallet,
unlockwallet,
removewalletpassword,
@ -266,6 +273,36 @@ public class CliMain {
out.printf("%.2f BSQ sent to %s%n", amount, address);
return;
}
case gettxfeerate: {
var request = GetTxFeeRateRequest.newBuilder().build();
var reply = walletsService.getTxFeeRate(request);
out.println(formatTxFeeRate(reply.getTxFeeRateInfo()));
return;
}
case settxfeerate: {
if (nonOptionArgs.size() < 2)
throw new IllegalArgumentException("no tx fee rate specified");
long txFeeRate;
try {
txFeeRate = Long.parseLong(nonOptionArgs.get(2));
} catch (NumberFormatException e) {
throw new IllegalArgumentException(format("'%s' is not a number", nonOptionArgs.get(2)));
}
var request = SetTxFeeRatePreferenceRequest.newBuilder()
.setTxFeeRatePreference(txFeeRate)
.build();
var reply = walletsService.setTxFeeRatePreference(request);
out.println(formatTxFeeRate(reply.getTxFeeRateInfo()));
return;
}
case unsettxfeerate: {
var request = UnsetTxFeeRatePreferenceRequest.newBuilder().build();
var reply = walletsService.unsetTxFeeRatePreference(request);
out.println(formatTxFeeRate(reply.getTxFeeRateInfo()));
return;
}
case createoffer: {
if (nonOptionArgs.size() < 9)
throw new IllegalArgumentException("incorrect parameter count,"
@ -626,6 +663,9 @@ public class CliMain {
stream.format(rowFormat, "getfundingaddresses", "", "Get BTC funding addresses");
stream.format(rowFormat, "getunusedbsqaddress", "", "Get unused BSQ address");
stream.format(rowFormat, "sendbsq", "address, amount", "Send BSQ");
stream.format(rowFormat, "gettxfeerate", "", "Get current tx fee rate in sats/byte");
stream.format(rowFormat, "settxfeerate", "satoshis (per byte)", "Set custom tx fee rate in sats/byte");
stream.format(rowFormat, "unsettxfeerate", "", "Unset custom tx fee rate");
stream.format(rowFormat, "createoffer", "payment acct id, buy | sell, currency code, \\", "Create and place an offer");
stream.format(rowFormat, "", "amount (btc), min amount, use mkt based price, \\", "");
stream.format(rowFormat, "", "fixed price (btc) | mkt price margin (%), security deposit (%) \\", "");

View file

@ -17,6 +17,8 @@
package bisq.cli;
import bisq.proto.grpc.TxFeeRateInfo;
import com.google.common.annotations.VisibleForTesting;
import java.text.DecimalFormat;
@ -36,6 +38,7 @@ public class CurrencyFormat {
static final BigDecimal SATOSHI_DIVISOR = new BigDecimal(100000000);
static final DecimalFormat BTC_FORMAT = new DecimalFormat("###,##0.00000000");
static final DecimalFormat BTC_TX_FEE_FORMAT = new DecimalFormat("###,##0.00");
static final BigDecimal BSQ_SATOSHI_DIVISOR = new BigDecimal(100);
static final DecimalFormat BSQ_FORMAT = new DecimalFormat("###,###,###,##0.00");
@ -50,6 +53,23 @@ public class CurrencyFormat {
return BSQ_FORMAT.format(BigDecimal.valueOf(sats).divide(BSQ_SATOSHI_DIVISOR));
}
public static String formatTxFeeRate(TxFeeRateInfo txFeeRateInfo) {
String stdTxFeeRate = formatTxFeeRate(txFeeRateInfo.getStdTxFeeRate());
String customTxFeeRate = txFeeRateInfo.getCustomTxFeeRate() < 0
? formatTxFeeRate(txFeeRateInfo.getCustomTxFeeRate())
: null;
String formatString;
if (customTxFeeRate == null)
formatString = String.format("tx fee rate: %s sats/byte", stdTxFeeRate);
else
formatString = String.format("custom tx fee rate: %s sats/byte, network rate: %s sats/byte",
customTxFeeRate,
stdTxFeeRate);
return formatString;
}
static String formatAmountRange(long minAmount, long amount) {
return minAmount != amount
? formatSatoshis(minAmount) + " - " + formatSatoshis(amount)
@ -85,4 +105,9 @@ public class CurrencyFormat {
throw new IllegalArgumentException(format("'%s' is not a number", btc));
}
}
@SuppressWarnings("BigDecimalMethodWithoutRoundingCalled")
private static String formatTxFeeRate(long sats) {
return BTC_TX_FEE_FORMAT.format(BigDecimal.valueOf(sats).divide(SATOSHI_DIVISOR));
}
}

View file

@ -19,6 +19,7 @@ package bisq.core.api;
import bisq.core.api.model.AddressBalanceInfo;
import bisq.core.api.model.BalancesInfo;
import bisq.core.api.model.TxFeeRateInfo;
import bisq.core.btc.wallet.TxBroadcaster;
import bisq.core.monetary.Price;
import bisq.core.offer.Offer;
@ -246,6 +247,18 @@ public class CoreApi {
walletsService.sendBsq(address, amount, callback);
}
public TxFeeRateInfo getTxFeeRate() {
return walletsService.getTxFeeRate();
}
public TxFeeRateInfo setTxFeeRatePreference(long txFeeRate) {
return walletsService.setTxFeeRatePreference(txFeeRate);
}
public TxFeeRateInfo unsetTxFeeRatePreference() {
return walletsService.unsetTxFeeRatePreference();
}
public void setWalletPassword(String password, String newPassword) {
walletsService.setWalletPassword(password, newPassword);
}

View file

@ -21,6 +21,7 @@ import bisq.core.api.model.AddressBalanceInfo;
import bisq.core.api.model.BalancesInfo;
import bisq.core.api.model.BsqBalanceInfo;
import bisq.core.api.model.BtcBalanceInfo;
import bisq.core.api.model.TxFeeRateInfo;
import bisq.core.btc.Balances;
import bisq.core.btc.exceptions.BsqChangeBelowDustException;
import bisq.core.btc.exceptions.TransactionVerificationException;
@ -32,6 +33,8 @@ import bisq.core.btc.wallet.BsqWalletService;
import bisq.core.btc.wallet.BtcWalletService;
import bisq.core.btc.wallet.TxBroadcaster;
import bisq.core.btc.wallet.WalletsManager;
import bisq.core.provider.fee.FeeService;
import bisq.core.user.Preferences;
import bisq.core.util.coin.BsqFormatter;
import bisq.common.Timer;
@ -54,6 +57,8 @@ 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;
@ -74,6 +79,8 @@ class CoreWalletsService {
private final BsqTransferService bsqTransferService;
private final BsqFormatter bsqFormatter;
private final BtcWalletService btcWalletService;
private final FeeService feeService;
private final Preferences preferences;
@Nullable
private Timer lockTimer;
@ -87,13 +94,17 @@ class CoreWalletsService {
BsqWalletService bsqWalletService,
BsqTransferService bsqTransferService,
BsqFormatter bsqFormatter,
BtcWalletService btcWalletService) {
BtcWalletService btcWalletService,
FeeService feeService,
Preferences preferences) {
this.balances = balances;
this.walletsManager = walletsManager;
this.bsqWalletService = bsqWalletService;
this.bsqTransferService = bsqTransferService;
this.bsqFormatter = bsqFormatter;
this.btcWalletService = btcWalletService;
this.feeService = feeService;
this.preferences = preferences;
}
@Nullable
@ -185,6 +196,37 @@ class CoreWalletsService {
}
}
TxFeeRateInfo getTxFeeRate() {
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);
}
Coin stdTxFeeRate = feeService.getTxFeePerVbyte();
Coin customTxFeeRate = preferences.isUseCustomWithdrawalTxFee()
? Coin.valueOf(preferences.getWithdrawalTxFeeInVbytes())
: Coin.NEGATIVE_SATOSHI;
return new TxFeeRateInfo(stdTxFeeRate.value, customTxFeeRate.value);
}
TxFeeRateInfo setTxFeeRatePreference(long txFeeRate) {
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();
}
TxFeeRateInfo unsetTxFeeRatePreference() {
preferences.setUseCustomWithdrawalTxFee(false);
return getTxFeeRate();
}
int getNumConfirmationsForMostRecentTransaction(String addressString) {
Address address = getAddressEntry(addressString).getAddress();
TransactionConfidence confidence = btcWalletService.getConfidenceForAddress(address);

View file

@ -19,6 +19,7 @@ package bisq.daemon.grpc;
import bisq.core.api.CoreApi;
import bisq.core.api.model.AddressBalanceInfo;
import bisq.core.api.model.TxFeeRateInfo;
import bisq.core.btc.exceptions.TxBroadcastException;
import bisq.core.btc.wallet.TxBroadcaster;
@ -28,6 +29,8 @@ import bisq.proto.grpc.GetBalancesReply;
import bisq.proto.grpc.GetBalancesRequest;
import bisq.proto.grpc.GetFundingAddressesReply;
import bisq.proto.grpc.GetFundingAddressesRequest;
import bisq.proto.grpc.GetTxFeeRateReply;
import bisq.proto.grpc.GetTxFeeRateRequest;
import bisq.proto.grpc.GetUnusedBsqAddressReply;
import bisq.proto.grpc.GetUnusedBsqAddressRequest;
import bisq.proto.grpc.LockWalletReply;
@ -36,10 +39,14 @@ import bisq.proto.grpc.RemoveWalletPasswordReply;
import bisq.proto.grpc.RemoveWalletPasswordRequest;
import bisq.proto.grpc.SendBsqReply;
import bisq.proto.grpc.SendBsqRequest;
import bisq.proto.grpc.SetTxFeeRatePreferenceReply;
import bisq.proto.grpc.SetTxFeeRatePreferenceRequest;
import bisq.proto.grpc.SetWalletPasswordReply;
import bisq.proto.grpc.SetWalletPasswordRequest;
import bisq.proto.grpc.UnlockWalletReply;
import bisq.proto.grpc.UnlockWalletRequest;
import bisq.proto.grpc.UnsetTxFeeRatePreferenceReply;
import bisq.proto.grpc.UnsetTxFeeRatePreferenceRequest;
import bisq.proto.grpc.WalletsGrpc;
import io.grpc.Status;
@ -163,6 +170,57 @@ class GrpcWalletsService extends WalletsGrpc.WalletsImplBase {
}
}
@Override
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();
} catch (IllegalStateException cause) {
var ex = new StatusRuntimeException(Status.UNKNOWN.withDescription(cause.getMessage()));
responseObserver.onError(ex);
throw ex;
}
}
@Override
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();
} catch (IllegalStateException cause) {
var ex = new StatusRuntimeException(Status.UNKNOWN.withDescription(cause.getMessage()));
responseObserver.onError(ex);
throw ex;
}
}
@Override
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();
} 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) {