Merge branch 'dispute-agent-branch' into fix-delayed-payout-tx-issues

# Conflicts:
#	core/src/main/java/bisq/core/trade/protocol/tasks/buyer/BuyerSetupDepositTxListener.java
This commit is contained in:
chimp1984 2020-09-25 15:03:28 -05:00
commit 10bedee803
No known key found for this signature in database
GPG key ID: 9801B4EC591F90E3
47 changed files with 507 additions and 449 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

@ -41,6 +41,7 @@ import lombok.extern.slf4j.Slf4j;
import javax.annotation.Nullable;
import static bisq.apitest.Scaffold.BitcoinCoreApp.bitcoind;
import static bisq.apitest.config.BisqAppConfig.*;
import static java.lang.String.format;
import static java.lang.System.exit;
@ -64,6 +65,10 @@ public class Scaffold {
public static final int EXIT_SUCCESS = 0;
public static final int EXIT_FAILURE = 1;
public enum BitcoinCoreApp {
bitcoind
}
public final ApiTestConfig config;
@Nullable
@ -295,7 +300,7 @@ public class Scaffold {
log.info("Starting supporting apps {}", config.supportingApps.toString());
if (config.hasSupportingApp("bitcoind")) {
if (config.hasSupportingApp(bitcoind.name())) {
BitcoinDaemon bitcoinDaemon = new BitcoinDaemon(config);
bitcoinDaemon.verifyBitcoinPathsExist(true);
bitcoindTask = new SetupTask(bitcoinDaemon, countdownLatch);

View file

@ -24,7 +24,9 @@ import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.ExecutionException;
import java.util.stream.Collectors;
import static java.util.Arrays.stream;
import static java.util.concurrent.TimeUnit.MILLISECONDS;
@ -68,15 +70,21 @@ public class ApiTestCase {
// gRPC service stubs are used by method & scenario tests, but not e2e tests.
private static final Map<BisqAppConfig, GrpcStubs> grpcStubsCache = new HashMap<>();
public static void setUpScaffold(String supportingApps)
public static void setUpScaffold(Enum<?>... supportingApps)
throws InterruptedException, ExecutionException, IOException {
scaffold = new Scaffold(supportingApps).setUp();
scaffold = new Scaffold(stream(supportingApps).map(Enum::name)
.collect(Collectors.joining(",")))
.setUp();
config = scaffold.config;
bitcoinCli = new BitcoinCliHelper((config));
}
public static void setUpScaffold(String[] params)
throws InterruptedException, ExecutionException, IOException {
// Test cases needing to pass more than just an ApiTestConfig
// --supportingApps option will use this setup method, but the
// --supportingApps option will need to be passed too, with its comma
// delimited app list value, e.g., "bitcoind,seednode,arbdaemon".
scaffold = new Scaffold(params).setUp();
config = scaffold.config;
}

View file

@ -27,7 +27,9 @@ import org.junit.jupiter.api.Order;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.TestMethodOrder;
import static bisq.apitest.Scaffold.BitcoinCoreApp.bitcoind;
import static bisq.apitest.config.BisqAppConfig.alicedaemon;
import static bisq.apitest.config.BisqAppConfig.seednode;
import static java.util.concurrent.TimeUnit.MILLISECONDS;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.fail;
@ -41,7 +43,7 @@ public class GetBalanceTest extends MethodTest {
@BeforeAll
public static void setUp() {
try {
setUpScaffold("bitcoind,seednode,alicedaemon");
setUpScaffold(bitcoind, seednode, alicedaemon);
// Have to generate 1 regtest block for alice's wallet to show 10 BTC balance.
bitcoinCli.generateBlocks(1);

View file

@ -41,7 +41,7 @@ public class GetVersionTest extends MethodTest {
@BeforeAll
public static void setUp() {
try {
setUpScaffold(alicedaemon.name());
setUpScaffold(alicedaemon);
} catch (Exception ex) {
fail(ex);
}

View file

@ -29,7 +29,9 @@ import org.junit.jupiter.api.Order;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.TestMethodOrder;
import static bisq.apitest.Scaffold.BitcoinCoreApp.bitcoind;
import static bisq.apitest.config.BisqAppConfig.arbdaemon;
import static bisq.apitest.config.BisqAppConfig.seednode;
import static bisq.common.app.DevEnv.DEV_PRIVILEGE_PRIV_KEY;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertThrows;
@ -42,10 +44,14 @@ import static org.junit.jupiter.api.MethodOrderer.OrderAnnotation;
@TestMethodOrder(OrderAnnotation.class)
public class RegisterDisputeAgentsTest extends MethodTest {
private static final String ARBITRATOR = "arbitrator";
private static final String MEDIATOR = "mediator";
private static final String REFUNDAGENT = "refundagent";
@BeforeAll
public static void setUp() {
try {
setUpScaffold("bitcoind,seednode,arbdaemon");
setUpScaffold(bitcoind, seednode, arbdaemon);
} catch (Exception ex) {
fail(ex);
}
@ -55,7 +61,7 @@ public class RegisterDisputeAgentsTest extends MethodTest {
@Order(1)
public void testRegisterArbitratorShouldThrowException() {
var req =
createRegisterDisputeAgentRequest("arbitrator");
createRegisterDisputeAgentRequest(ARBITRATOR);
Throwable exception = assertThrows(StatusRuntimeException.class, () ->
grpcStubs(arbdaemon).disputeAgentsService.registerDisputeAgent(req));
assertEquals("INVALID_ARGUMENT: arbitrators must be registered in a Bisq UI",
@ -77,7 +83,7 @@ public class RegisterDisputeAgentsTest extends MethodTest {
@Order(3)
public void testInvalidRegistrationKeyArgShouldThrowException() {
var req = RegisterDisputeAgentRequest.newBuilder()
.setDisputeAgentType("refundagent")
.setDisputeAgentType(REFUNDAGENT)
.setRegistrationKey("invalid" + DEV_PRIVILEGE_PRIV_KEY).build();
Throwable exception = assertThrows(StatusRuntimeException.class, () ->
grpcStubs(arbdaemon).disputeAgentsService.registerDisputeAgent(req));
@ -89,7 +95,7 @@ public class RegisterDisputeAgentsTest extends MethodTest {
@Order(4)
public void testRegisterMediator() {
var req =
createRegisterDisputeAgentRequest("mediator");
createRegisterDisputeAgentRequest(MEDIATOR);
grpcStubs(arbdaemon).disputeAgentsService.registerDisputeAgent(req);
}
@ -97,7 +103,7 @@ public class RegisterDisputeAgentsTest extends MethodTest {
@Order(5)
public void testRegisterRefundAgent() {
var req =
createRegisterDisputeAgentRequest("refundagent");
createRegisterDisputeAgentRequest(REFUNDAGENT);
grpcStubs(arbdaemon).disputeAgentsService.registerDisputeAgent(req);
}

View file

@ -25,7 +25,7 @@ public class WalletProtectionTest extends MethodTest {
@BeforeAll
public static void setUp() {
try {
setUpScaffold(alicedaemon.name());
setUpScaffold(alicedaemon);
MILLISECONDS.sleep(2000);
} catch (Exception ex) {
fail(ex);

View file

@ -26,7 +26,9 @@ import org.junit.jupiter.api.Order;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.TestMethodOrder;
import static bisq.apitest.Scaffold.BitcoinCoreApp.bitcoind;
import static bisq.apitest.config.BisqAppConfig.alicedaemon;
import static bisq.apitest.config.BisqAppConfig.seednode;
import static java.util.concurrent.TimeUnit.MILLISECONDS;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.fail;
@ -38,7 +40,7 @@ public class FundWalletScenarioTest extends ScenarioTest {
@BeforeAll
public static void setUp() {
try {
setUpScaffold("bitcoind,seednode,alicedaemon");
setUpScaffold(bitcoind, seednode, alicedaemon);
bitcoinCli.generateBlocks(1);
MILLISECONDS.sleep(1500);
} catch (Exception ex) {

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

@ -161,6 +161,19 @@ public class Utilities {
return getOSName().contains("win");
}
/**
* @return True, if Bisq is running on a virtualized OS within Qubes, false otherwise
*/
public static boolean isQubesOS() {
// For Linux qubes, "os.version" looks like "4.19.132-1.pvops.qubes.x86_64"
// The presence of the "qubes" substring indicates this Linux is running as a qube
// This is the case for all 3 virtualization modes (PV, PVH, HVM)
// In addition, this works for both simple AppVMs, as well as for StandaloneVMs
// TODO This might not work for detecting Qubes virtualization for other OSes
// like Windows
return getOSVersion().contains("qubes");
}
public static boolean isOSX() {
return getOSName().contains("mac") || getOSName().contains("darwin");
}

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

@ -0,0 +1,60 @@
package bisq.core.api;
import bisq.core.btc.Balances;
import bisq.core.btc.setup.WalletsSetup;
import bisq.core.btc.wallet.WalletsManager;
import bisq.core.dao.state.DaoStateService;
import bisq.network.p2p.P2PService;
import bisq.common.config.Config;
import javax.inject.Inject;
import javax.inject.Singleton;
import lombok.extern.slf4j.Slf4j;
@Singleton
@Slf4j
class StatusCheck {
private final Config config;
private final P2PService p2PService;
private final DaoStateService daoStateService;
private final WalletsSetup walletsSetup;
private final WalletsManager walletsManager;
private final Balances balances;
@Inject
public StatusCheck(Config config,
P2PService p2PService,
DaoStateService daoStateService,
WalletsSetup walletsSetup,
WalletsManager walletsManager,
Balances balances) {
this.config = config;
this.p2PService = p2PService;
this.daoStateService = daoStateService;
this.walletsSetup = walletsSetup;
this.walletsManager = walletsManager;
this.balances = balances;
}
public void verifyCanTrade() {
if (!p2PService.isBootstrapped())
throw new IllegalStateException("p2p service is not yet bootstrapped");
if (!daoStateService.isParseBlockChainComplete())
throw new IllegalStateException("dao block chain sync is not yet complete");
if (config.baseCurrencyNetwork.isMainnet()
&& p2PService.getNumConnectedPeers().get() < walletsSetup.getMinBroadcastConnections())
throw new IllegalStateException("not enough connected peers");
if (!walletsManager.areWalletsAvailable())
throw new IllegalStateException("wallet is not yet available");
if (balances.getAvailableBalance().get() == null)
throw new IllegalStateException("balance is not yet available");
}
}

View file

@ -98,6 +98,7 @@ public class BisqHeadlessApp implements HeadlessApp {
bisqSetup.setShowPopupIfInvalidBtcConfigHandler(() -> log.error("onShowPopupIfInvalidBtcConfigHandler"));
bisqSetup.setRevolutAccountsUpdateHandler(revolutAccountList -> log.info("setRevolutAccountsUpdateHandler: revolutAccountList={}", revolutAccountList));
bisqSetup.setOsxKeyLoggerWarningHandler(() -> log.info("setOsxKeyLoggerWarningHandler"));
bisqSetup.setQubesOSInfoHandler(() -> log.info("setQubesOSInfoHandler"));
//TODO move to bisqSetup
corruptedDatabaseFilesHandler.getCorruptedDatabaseFiles().ifPresent(files -> log.warn("getCorruptedDatabaseFiles. files={}", files));

View file

@ -232,6 +232,9 @@ public class BisqSetup {
@Setter
@Nullable
private Runnable osxKeyLoggerWarningHandler;
@Setter
@Nullable
private Runnable qubesOSInfoHandler;
@Getter
final BooleanProperty newVersionAvailableProperty = new SimpleBooleanProperty(false);
@ -357,6 +360,7 @@ public class BisqSetup {
checkCryptoSetup();
checkForCorrectOSArchitecture();
checkOSXVersion();
checkIfRunningOnQubesOS();
}
private void step3() {
@ -678,6 +682,17 @@ public class BisqSetup {
}
}
/**
* If Bisq is running on an OS that is virtualized under Qubes, show info popup with
* link to the Setup Guide. The guide documents what other steps are needed, in
* addition to installing the Linux package (qube sizing, etc)
*/
private void checkIfRunningOnQubesOS() {
if (Utilities.isQubesOS() && qubesOSInfoHandler != null) {
qubesOSInfoHandler.run();
}
}
private void initDomainServices() {
log.info("initDomainServices");

View file

@ -211,7 +211,7 @@ public final class AddressEntryList implements UserThreadMappedPersistableEnvelo
private void maybeAddNewAddressEntry(Transaction tx) {
tx.getOutputs().stream()
.filter(output -> output.isMine(wallet))
.map(output -> output.getAddressFromP2PKHScript(wallet.getNetworkParameters()))
.map(output -> output.getScriptPubKey().getToAddress(wallet.getNetworkParameters()))
.filter(Objects::nonNull)
.filter(this::isAddressNotInEntries)
.map(address -> (DeterministicKey) wallet.findKeyFromPubKeyHash(address.getHash(),

View file

@ -55,8 +55,7 @@ class BtcNodeConverter {
PeerAddress convertOnionHost(BtcNode node) {
// no DNS lookup for onion addresses
String onionAddress = Objects.requireNonNull(node.getOnionAddress());
PeerAddress result = new PeerAddress(onionAddress, node.getPort());
return result;
return new PeerAddress(onionAddress, node.getPort());
}
@Nullable
@ -69,7 +68,7 @@ class BtcNodeConverter {
if (address != null) {
result = create(address, port);
} else {
log.warn("Lookup failed, no address for node", node);
log.warn("Lookup failed, no address for node {}", node);
}
}
return result;
@ -85,7 +84,7 @@ class BtcNodeConverter {
if (address != null) {
result = create(proxy, address, port);
} else {
log.warn("Lookup failed, no address for node", node);
log.warn("Lookup failed, no address for node {}", node);
}
}
return result;

View file

@ -26,7 +26,6 @@ import org.bitcoinj.wallet.DeterministicKeyChain;
import org.bitcoinj.wallet.DeterministicSeed;
import org.bitcoinj.wallet.KeyChainGroupStructure;
import org.bitcoinj.wallet.Protos;
import org.bitcoinj.wallet.UnreadableWalletException;
import com.google.common.collect.ImmutableList;
@ -63,12 +62,12 @@ public class BisqKeyChainFactory extends DefaultKeyChainFactory {
}
@Override
public DeterministicKeyChain makeWatchingKeyChain(Protos.Key key, Protos.Key firstSubKey, DeterministicKey accountKey, boolean isFollowingKey, boolean isMarried, Script.ScriptType outputScriptType) throws UnreadableWalletException {
public DeterministicKeyChain makeWatchingKeyChain(Protos.Key key, Protos.Key firstSubKey, DeterministicKey accountKey, boolean isFollowingKey, boolean isMarried, Script.ScriptType outputScriptType) {
throw new UnsupportedOperationException("Bisq is not supposed to use this");
}
@Override
public DeterministicKeyChain makeSpendingKeyChain(Protos.Key key, Protos.Key firstSubKey, DeterministicKey accountKey, boolean isMarried, Script.ScriptType outputScriptType) throws UnreadableWalletException {
public DeterministicKeyChain makeSpendingKeyChain(Protos.Key key, Protos.Key firstSubKey, DeterministicKey accountKey, boolean isMarried, Script.ScriptType outputScriptType) {
throw new UnsupportedOperationException("Bisq is not supposed to use this");
}
}

View file

@ -20,6 +20,7 @@ package bisq.core.btc.setup;
import bisq.core.btc.nodes.LocalBitcoinNode;
import bisq.core.btc.nodes.ProxySocketFactory;
import bisq.core.btc.wallet.BisqRiskAnalysis;
import bisq.common.config.Config;
import com.google.common.collect.*;
@ -54,9 +55,7 @@ import static com.google.common.base.Preconditions.*;
/**
* <p>Utility class that wraps the boilerplate needed to set up a new SPV bitcoinj app. Instantiate it with a directory
* and file prefix, optionally configure a few things, then use startAsync and optionally awaitRunning. The object will
* construct and configure a {@link BlockChain}, {@link SPVBlockStore}, {@link Wallet} and {@link PeerGroup}. Depending
* on the value of the blockingStartup property, startup will be considered complete once the block chain has fully
* synchronized, so it can take a while.</p>
* construct and configure a {@link BlockChain}, {@link SPVBlockStore}, {@link Wallet} and {@link PeerGroup}.</p>
*
* <p>To add listeners and modify the objects that are constructed, you can either do that by overriding the
* {@link #onSetupCompleted()} method (which will run on a background thread) and make your changes there,
@ -92,16 +91,12 @@ public class WalletConfig extends AbstractIdleService {
protected volatile File vBtcWalletFile;
protected volatile File vBsqWalletFile;
protected boolean useAutoSave = true;
protected PeerAddress[] peerAddresses;
protected DownloadProgressTracker downloadListener;
protected boolean autoStop = true;
protected InputStream checkpoints;
protected boolean blockingStartup = true;
protected String userAgent, version;
protected WalletProtobufSerializer.WalletFactory walletFactory;
@Nullable protected DeterministicSeed restoreFromSeed;
@Nullable protected DeterministicKey restoreFromKey;
@Nullable protected PeerDiscovery discovery;
protected volatile Context context;
@ -124,8 +119,7 @@ public class WalletConfig extends AbstractIdleService {
/**
* Creates a new WalletConfig, with the given {@link Context}. Files will be stored in the given directory.
*/
public WalletConfig(Context context,
File directory, String filePrefix) {
private WalletConfig(Context context, File directory, String filePrefix) {
this.context = context;
this.params = checkNotNull(context.getParams());
this.directory = checkDir(directory);
@ -170,29 +164,15 @@ public class WalletConfig extends AbstractIdleService {
return setPeerNodes(new PeerAddress(params, localHost, params.getPort()));
}
/** If true, the wallet will save itself to disk automatically whenever it changes. */
public WalletConfig setAutoSave(boolean value) {
checkState(state() == State.NEW, "Cannot call after startup");
useAutoSave = value;
return this;
}
/**
* If you want to learn about the sync process, you can provide a listener here. For instance, a
* {@link DownloadProgressTracker} is a good choice. This has no effect unless setBlockingStartup(false) has been called
* too, due to some missing implementation code.
* {@link DownloadProgressTracker} is a good choice.
*/
public WalletConfig setDownloadListener(DownloadProgressTracker listener) {
this.downloadListener = listener;
return this;
}
/** If true, will register a shutdown hook to stop the library. Defaults to true. */
public WalletConfig setAutoStop(boolean autoStop) {
this.autoStop = autoStop;
return this;
}
/**
* If set, the file is expected to contain a checkpoints file calculated with BuildCheckpoints. It makes initial
* block sync faster for new users - please refer to the documentation on the bitcoinj website
@ -205,17 +185,6 @@ public class WalletConfig extends AbstractIdleService {
return this;
}
/**
* If true (the default) then the startup of this service won't be considered complete until the network has been
* brought up, peer connections established and the block chain synchronised. Therefore {@link #awaitRunning()} can
* potentially take a very long time. If false, then startup is considered complete once the network activity
* begins and peer connections/block chain sync will continue in the background.
*/
public WalletConfig setBlockingStartup(boolean blockingStartup) {
this.blockingStartup = blockingStartup;
return this;
}
/**
* Sets the string that will appear in the subver field of the version message.
* @param userAgent A short string that should be the name of your app, e.g. "My Wallet"
@ -227,14 +196,6 @@ public class WalletConfig extends AbstractIdleService {
return this;
}
/**
* Sets a wallet factory which will be used when the kit creates a new wallet.
*/
public WalletConfig setWalletFactory(WalletProtobufSerializer.WalletFactory walletFactory) {
this.walletFactory = walletFactory;
return this;
}
/**
* If a seed is set here then any existing wallet that matches the file name will be renamed to a backup name,
* the chain file will be deleted, and the wallet object will be instantiated with the given seed instead of
@ -248,19 +209,6 @@ public class WalletConfig extends AbstractIdleService {
return this;
}
/**
* If an account key is set here then any existing wallet that matches the file name will be renamed to a backup name,
* the chain file will be deleted, and the wallet object will be instantiated with the given key instead of
* a fresh seed being created. This is intended for restoring a wallet from an account key. To implement restore
* you would shut down the existing appkit, if any, then recreate it with the key given by the user, then start
* up the new kit. The next time your app starts it should work as normal (that is, don't keep calling this each
* time).
*/
public WalletConfig restoreWalletFromKey(DeterministicKey accountKey) {
this.restoreFromKey = accountKey;
return this;
}
/**
* Sets the peer discovery class to use. If none is provided then DNS is used, which is a reasonable default.
*/
@ -269,16 +217,6 @@ public class WalletConfig extends AbstractIdleService {
return this;
}
/**
* <p>Override this to return wallet extensions if any are necessary.</p>
*
* <p>When this is called, chain(), store(), and peerGroup() will return the created objects, however they are not
* initialized/started.</p>
*/
protected List<WalletExtension> provideWalletExtensions() throws Exception {
return ImmutableList.of();
}
/**
* This method is invoked on a background thread after all objects are initialised, but before the peer group
* or block chain download is started. You can tweak the objects configuration here.
@ -287,49 +225,17 @@ public class WalletConfig extends AbstractIdleService {
// Meant to be overridden by subclasses
}
/**
* Tests to see if the spvchain file has an operating system file lock on it. Useful for checking if your app
* is already running. If another copy of your app is running and you start the appkit anyway, an exception will
* be thrown during the startup process. Returns false if the chain file does not exist or is a directory.
*/
public boolean isChainFileLocked() throws IOException {
RandomAccessFile file2 = null;
try {
File file = new File(directory, filePrefix + ".spvchain");
if (!file.exists())
return false;
if (file.isDirectory())
return false;
file2 = new RandomAccessFile(file, "rw");
FileLock lock = file2.getChannel().tryLock();
if (lock == null)
return true;
lock.release();
return false;
} finally {
if (file2 != null)
file2.close();
}
}
@Override
protected void startUp() throws Exception {
// Runs in a separate thread.
Context.propagate(context);
// bitcoinj's WalletAppKit creates the wallet directory if it was not created already,
// but WalletConfig should not do that.
// if (!directory.exists()) {
// if (!directory.mkdirs()) {
// throw new IOException("Could not create directory " + directory.getAbsolutePath());
// }
// }
log.info("Starting up with directory = {}", directory);
try {
File chainFile = new File(directory, filePrefix + ".spvchain");
boolean chainFileExists = chainFile.exists();
String btcPrefix = "_BTC";
vBtcWalletFile = new File(directory, filePrefix + btcPrefix + ".wallet");
boolean shouldReplayWallet = (vBtcWalletFile.exists() && !chainFileExists) || restoreFromSeed != null || restoreFromKey != null;
boolean shouldReplayWallet = (vBtcWalletFile.exists() && !chainFileExists) || restoreFromSeed != null;
vBtcWallet = createOrLoadWallet(shouldReplayWallet, vBtcWalletFile, false);
vBtcWallet.allowSpendingUnconfirmedTransactions();
vBtcWallet.setRiskAnalyzer(new BisqRiskAnalysis.Analyzer());
@ -341,8 +247,8 @@ public class WalletConfig extends AbstractIdleService {
// Initiate Bitcoin network objects (block store, blockchain and peer group)
vStore = new SPVBlockStore(params, chainFile);
if (!chainFileExists || restoreFromSeed != null || restoreFromKey != null) {
if (checkpoints == null && !Utils.isAndroidRuntime()) {
if (!chainFileExists || restoreFromSeed != null) {
if (checkpoints == null) {
checkpoints = CheckpointManager.openStream(params);
}
@ -355,12 +261,6 @@ public class WalletConfig extends AbstractIdleService {
log.info("Clearing the chain file in preparation for restore.");
vStore.clear();
}
} else if (restoreFromKey != null) {
time = restoreFromKey.getCreationTimeSeconds();
if (chainFileExists) {
log.info("Clearing the chain file in preparation for restore.");
vStore.clear();
}
} else {
time = vBtcWallet.getEarliestKeyCreationTime();
}
@ -398,23 +298,12 @@ public class WalletConfig extends AbstractIdleService {
vPeerGroup.addWallet(vBsqWallet);
onSetupCompleted();
if (blockingStartup) {
vPeerGroup.start();
// Make sure we shut down cleanly.
installShutdownHook();
//completeExtensionInitiations(vPeerGroup);
// TODO: Be able to use the provided download listener when doing a blocking startup.
final DownloadProgressTracker listener = new DownloadProgressTracker();
vPeerGroup.startBlockChainDownload(listener);
listener.await();
} else {
Futures.addCallback((ListenableFuture<?>) vPeerGroup.startAsync(), new FutureCallback<Object>() {
@Override
public void onSuccess(@Nullable Object result) {
//completeExtensionInitiations(vPeerGroup);
final DownloadProgressTracker l = downloadListener == null ? new DownloadProgressTracker() : downloadListener;
vPeerGroup.startBlockChainDownload(l);
DownloadProgressTracker tracker = downloadListener == null ? new DownloadProgressTracker() : downloadListener;
vPeerGroup.startBlockChainDownload(tracker);
}
@Override
@ -423,7 +312,6 @@ public class WalletConfig extends AbstractIdleService {
}
}, MoreExecutors.directExecutor());
}
} catch (BlockStoreException e) {
throw new IOException(e);
}
@ -439,9 +327,6 @@ public class WalletConfig extends AbstractIdleService {
} else {
wallet = createWallet(isBsqWallet);
wallet.freshReceiveKey();
for (WalletExtension e : provideWalletExtensions()) {
wallet.addExtension(e);
}
// Currently the only way we can be sure that an extension is aware of its containing wallet is by
// deserializing the extension (see WalletExtension#deserializeWalletExtension(Wallet, byte[]))
@ -450,9 +335,7 @@ public class WalletConfig extends AbstractIdleService {
wallet = loadWallet(false, walletFile, isBsqWallet);
}
if (useAutoSave) {
this.setupAutoSave(wallet, walletFile);
}
return wallet;
}
@ -465,8 +348,7 @@ public class WalletConfig extends AbstractIdleService {
Wallet wallet;
FileInputStream walletStream = new FileInputStream(walletFile);
try {
List<WalletExtension> extensions = provideWalletExtensions();
WalletExtension[] extArray = extensions.toArray(new WalletExtension[extensions.size()]);
WalletExtension[] extArray = new WalletExtension[]{};
Protos.Wallet proto = WalletProtobufSerializer.parseToProto(walletStream);
final WalletProtobufSerializer serializer;
if (walletFactory != null)
@ -492,8 +374,6 @@ public class WalletConfig extends AbstractIdleService {
KeyChainGroup.Builder kcg = KeyChainGroup.builder(params, structure);
if (restoreFromSeed != null) {
kcg.fromSeed(restoreFromSeed, preferredOutputScriptType).build();
} else if (restoreFromKey != null) {
kcg.addChain(DeterministicKeyChain.builder().spend(restoreFromKey).outputScriptType(preferredOutputScriptType).build());
} else {
// new wallet
if (!isBsqWallet) {
@ -512,7 +392,7 @@ public class WalletConfig extends AbstractIdleService {
}
private void maybeMoveOldWalletOutOfTheWay(File walletFile) {
if (restoreFromSeed == null && restoreFromKey == null) return;
if (restoreFromSeed == null) return;
if (!walletFile.exists()) return;
int counter = 1;
File newName;
@ -527,23 +407,6 @@ public class WalletConfig extends AbstractIdleService {
}
}
/*
* As soon as the transaction broadcaster han been created we will pass it to the
* payment channel extensions
*/
// private void completeExtensionInitiations(TransactionBroadcaster transactionBroadcaster) {
// StoredPaymentChannelClientStates clientStoredChannels = (StoredPaymentChannelClientStates)
// vWallet.getExtensions().get(StoredPaymentChannelClientStates.class.getName());
// if(clientStoredChannels != null) {
// clientStoredChannels.setTransactionBroadcaster(transactionBroadcaster);
// }
// StoredPaymentChannelServerStates serverStoredChannels = (StoredPaymentChannelServerStates)
// vWallet.getExtensions().get(StoredPaymentChannelServerStates.class.getName());
// if(serverStoredChannels != null) {
// serverStoredChannels.setTransactionBroadcaster(transactionBroadcaster);
// }
// }
private PeerGroup createPeerGroup() {
PeerGroup peerGroup;
// no proxy case.
@ -573,19 +436,6 @@ public class WalletConfig extends AbstractIdleService {
return peerGroup;
}
private void installShutdownHook() {
if (autoStop) Runtime.getRuntime().addShutdownHook(new Thread("WalletConfig ShutdownHook") {
@Override public void run() {
try {
WalletConfig.this.stopAsync();
WalletConfig.this.awaitTerminated();
} catch (Exception e) {
throw new RuntimeException(e);
}
}
});
}
@Override
protected void shutDown() throws Exception {
// Runs in a separate thread.

View file

@ -249,7 +249,7 @@ public class WalletsSetup {
UserThread.execute(() -> {
addressEntryList.onWalletReady(walletConfig.btcWallet());
timeoutTimer.stop();
setupCompletedHandlers.stream().forEach(Runnable::run);
setupCompletedHandlers.forEach(Runnable::run);
});
// onSetupCompleted in walletAppKit is not the called on the last invocations, so we add a bit of delay
@ -307,11 +307,12 @@ public class WalletsSetup {
}
}
walletConfig.setDownloadListener(downloadListener)
.setBlockingStartup(false);
walletConfig.setDownloadListener(downloadListener);
// If seed is non-null it means we are restoring from backup.
if (seed != null) {
walletConfig.restoreWalletFromSeed(seed);
}
walletConfig.addListener(new Service.Listener() {
@Override

View file

@ -123,7 +123,7 @@ public class BtcWalletService extends WalletService {
@Override
void encryptWallet(KeyCrypterScrypt keyCrypterScrypt, KeyParameter key) {
super.encryptWallet(keyCrypterScrypt, key);
addressEntryList.getAddressEntriesAsListImmutable().stream().forEach(e -> {
addressEntryList.getAddressEntriesAsListImmutable().forEach(e -> {
DeterministicKey keyPair = e.getKeyPair();
if (keyPair.isEncrypted())
e.setDeterministicKey(keyPair.encrypt(keyCrypterScrypt, key));
@ -134,7 +134,7 @@ public class BtcWalletService extends WalletService {
@Override
String getWalletAsString(boolean includePrivKeys) {
StringBuilder sb = new StringBuilder();
getAddressEntryListAsImmutableList().stream().forEach(e -> sb.append(e.toString()).append("\n"));
getAddressEntryListAsImmutableList().forEach(e -> sb.append(e.toString()).append("\n"));
return "Address entry list:\n" +
sb.toString() +
"\n\n" +

View file

@ -38,8 +38,6 @@ import org.bitcoinj.core.Context;
import org.bitcoinj.core.ECKey;
import org.bitcoinj.core.InsufficientMoneyException;
import org.bitcoinj.core.NetworkParameters;
import org.bitcoinj.core.listeners.TransactionConfidenceEventListener;
import org.bitcoinj.script.ScriptException;
import org.bitcoinj.core.Sha256Hash;
import org.bitcoinj.core.Transaction;
import org.bitcoinj.core.TransactionConfidence;
@ -47,12 +45,14 @@ import org.bitcoinj.core.TransactionInput;
import org.bitcoinj.core.TransactionOutput;
import org.bitcoinj.core.VerificationException;
import org.bitcoinj.core.listeners.NewBestBlockListener;
import org.bitcoinj.core.listeners.TransactionConfidenceEventListener;
import org.bitcoinj.crypto.DeterministicKey;
import org.bitcoinj.crypto.KeyCrypter;
import org.bitcoinj.crypto.KeyCrypterScrypt;
import org.bitcoinj.crypto.TransactionSignature;
import org.bitcoinj.script.Script;
import org.bitcoinj.script.ScriptChunk;
import org.bitcoinj.script.ScriptException;
import org.bitcoinj.script.ScriptPattern;
import org.bitcoinj.signers.TransactionSigner;
import org.bitcoinj.utils.Threading;
@ -106,7 +106,6 @@ public abstract class WalletService {
protected final Preferences preferences;
protected final FeeService feeService;
protected final NetworkParameters params;
@SuppressWarnings("deprecation")
protected final BisqWalletListener walletEventListener = new BisqWalletListener();
protected final CopyOnWriteArraySet<AddressConfidenceListener> addressConfidenceListeners = new CopyOnWriteArraySet<>();
protected final CopyOnWriteArraySet<TxConfidenceListener> txConfidenceListeners = new CopyOnWriteArraySet<>();
@ -508,17 +507,17 @@ public abstract class WalletService {
sendRequest.feePerKb = getTxFeeForWithdrawalPerByte().multiply(1000);
sendRequest.aesKey = aesKey;
Wallet.SendResult sendResult = wallet.sendCoins(sendRequest);
printTx("empty wallet", sendResult.tx);
printTx("empty btc wallet", sendResult.tx);
Futures.addCallback(sendResult.broadcastComplete, new FutureCallback<Transaction>() {
@Override
public void onSuccess(Transaction result) {
log.info("emptyWallet onSuccess Transaction=" + result);
log.info("emptyBtcWallet onSuccess Transaction=" + result);
resultHandler.handleResult();
}
@Override
public void onFailure(@NotNull Throwable t) {
log.error("emptyWallet onFailure " + t.toString());
log.error("emptyBtcWallet onFailure " + t.toString());
errorMessageHandler.handleErrorMessage(t.getMessage());
}
}, MoreExecutors.directExecutor());
@ -551,58 +550,10 @@ public abstract class WalletService {
// Wallet delegates to avoid direct access to wallet outside the service class
///////////////////////////////////////////////////////////////////////////////////////////
public void addCoinsReceivedEventListener(WalletCoinsReceivedEventListener listener) {
wallet.addCoinsReceivedEventListener(Threading.USER_THREAD, listener);
}
public void addCoinsSentEventListener(WalletCoinsSentEventListener listener) {
wallet.addCoinsSentEventListener(Threading.USER_THREAD, listener);
}
public void addReorganizeEventListener(WalletReorganizeEventListener listener) {
wallet.addReorganizeEventListener(Threading.USER_THREAD, listener);
}
public void addTransactionConfidenceEventListener(TransactionConfidenceEventListener listener) {
wallet.addTransactionConfidenceEventListener(Threading.USER_THREAD, listener);
}
public void addKeyChainEventListener(KeyChainEventListener listener) {
wallet.addKeyChainEventListener(Threading.USER_THREAD, listener);
}
public void addScriptChangeEventListener(ScriptsChangeEventListener listener) {
wallet.addScriptChangeEventListener(Threading.USER_THREAD, listener);
}
public void addChangeEventListener(WalletChangeEventListener listener) {
wallet.addChangeEventListener(Threading.USER_THREAD, listener);
}
public void removeCoinsReceivedEventListener(WalletCoinsReceivedEventListener listener) {
wallet.removeCoinsReceivedEventListener(listener);
}
public void removeCoinsSentEventListener(WalletCoinsSentEventListener listener) {
wallet.removeCoinsSentEventListener(listener);
}
public void removeReorganizeEventListener(WalletReorganizeEventListener listener) {
wallet.removeReorganizeEventListener(listener);
}
public void removeTransactionConfidenceEventListener(TransactionConfidenceEventListener listener) {
wallet.removeTransactionConfidenceEventListener(listener);
}
public void removeKeyChainEventListener(KeyChainEventListener listener) {
wallet.removeKeyChainEventListener(listener);
}
public void removeScriptChangeEventListener(ScriptsChangeEventListener listener) {
wallet.removeScriptChangeEventListener(listener);
}
public void removeChangeEventListener(WalletChangeEventListener listener) {
wallet.removeChangeEventListener(listener);
}
@ -793,7 +744,6 @@ public abstract class WalletService {
return maybeAddTxToWallet(transaction.bitcoinSerialize(), wallet, source);
}
///////////////////////////////////////////////////////////////////////////////////////////
// bisqWalletEventListener
///////////////////////////////////////////////////////////////////////////////////////////

View file

@ -74,7 +74,7 @@ public abstract class BaseProposalFactory<R extends Proposal> {
R proposal = createProposalWithoutTxId();
proposalValidator.validateDataFields(proposal);
Transaction transaction = createTransaction(proposal);
final Proposal proposalWithTxId = proposal.cloneProposalAndAddTxId(transaction.getTxId().toString());
Proposal proposalWithTxId = proposal.cloneProposalAndAddTxId(transaction.getTxId().toString());
return new ProposalWithTransaction(proposalWithTxId, transaction);
}

View file

@ -388,7 +388,7 @@ public abstract class DisputeManager<T extends DisputeList<? extends DisputeList
// The peer sent us an invalid donation address. We do not return here as we don't want to break
// mediation/arbitration and log only the issue. The dispute agent will run validation as well and will get
// a popup displayed to react.
log.error("Donation address invalid. {}", e.toString());
log.warn("Donation address is invalid. {}", e.toString());
}
if (!isAgent(dispute)) {

View file

@ -1005,7 +1005,8 @@ public abstract class Trade implements Tradable, Model {
log.debug("We set the start for the trade period to {}. Trade started at: {}. Block got mined at: {}",
new Date(startTime), new Date(tradeTime), new Date(blockTime));
} else {
log.debug("depositTx not confirmed yet. We don't start counting remaining trade period yet. txId={}", depositTx.getTxId().toString());
log.debug("depositTx not confirmed yet. We don't start counting remaining trade period yet. txId={}",
depositTx.getTxId().toString());
startTime = now;
}
} else {

View file

@ -291,17 +291,13 @@ public class TradeDataValidation {
}
NetworkParameters params = btcWalletService.getParams();
Address address = output.getAddressFromP2PKHScript(params);
Address address = output.getScriptPubKey().getToAddress(params);
if (address == null) {
// The donation address can be a multisig address as well.
address = output.getAddressFromP2SH(params);
if (address == null) {
errorMsg = "Donation address cannot be resolved (not of type P2PKHScript or P2SH). Output: " + output;
errorMsg = "Donation address cannot be resolved (not of type P2PK nor P2SH nor P2WH). Output: " + output;
log.error(errorMsg);
log.error(delayedPayoutTx.toString());
throw new AddressException(dispute, errorMsg);
}
}
String addressAsString = address.toString();
if (addressConsumer != null) {

View file

@ -58,10 +58,7 @@ public class BuyerSetupDepositTxListener extends TradeTask {
NetworkParameters params = walletService.getParams();
Transaction preparedDepositTx = new Transaction(params, processModel.getPreparedDepositTx());
checkArgument(!preparedDepositTx.getOutputs().isEmpty(), "preparedDepositTx.getOutputs() must not be empty");
//TODO update to new bitcoinj API
Address depositTxAddress = preparedDepositTx.getOutput(0).getAddressFromP2SH(params);
Address depositTxAddress = preparedDepositTx.getOutput(0).getScriptPubKey().getToAddress(params);
TransactionConfidence confidence = walletService.getConfidenceForAddress(depositTxAddress);
if (isVisibleInNetwork(confidence)) {
applyConfidence(confidence);

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

@ -2846,6 +2846,9 @@ popup.info.shutDownWithOpenOffers=Bisq is being shut down, but there are open of
they will be re-published to the P2P network the next time you start Bisq.\n\n\
To keep your offers online, keep Bisq running and make sure this computer remains online too \
(i.e., make sure it doesn't go into standby mode...monitor standby is not a problem).
popup.info.qubesOSSetupInfo=It appears you are running Bisq on Qubes OS. \n\n\
Please make sure your Bisq qube is setup according to our Setup Guide at \
https://bisq.wiki/Running_Bisq_on_Qubes
popup.privateNotification.headline=Important private notification!

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

@ -395,6 +395,15 @@ public class MainViewModel implements ViewModel, BisqSetup.BisqSetupListener {
.show();
}
});
bisqSetup.setQubesOSInfoHandler(() -> {
String key = "qubesOSSetupInfo";
if (preferences.showAgain(key)) {
new Popup().information(Res.get("popup.info.qubesOSSetupInfo"))
.closeButtonText(Res.get("shared.iUnderstand"))
.dontShowAgainId(key)
.show();
}
});
corruptedDatabaseFilesHandler.getCorruptedDatabaseFiles().ifPresent(files -> new Popup()
.warning(Res.get("popup.warning.incompatibleDB", files.toString(), config.appDataDir))

View file

@ -157,7 +157,7 @@ class TransactionAwareTrade implements TransactionAwareTradable {
tx.getOutputs().forEach(txo -> {
if (btcWalletService.isTransactionOutputMine(txo)) {
try {
Address receiverAddress = txo.getAddressFromP2PKHScript(btcWalletService.getParams());
Address receiverAddress = txo.getScriptPubKey().getToAddress(btcWalletService.getParams());
Contract contract = checkNotNull(trade.getContract());
String myPayoutAddressString = contract.isMyRoleBuyer(pubKeyRing) ?
contract.getBuyerPayoutAddressString() :

View file

@ -103,12 +103,6 @@ public class TransactionsView extends ActivatableView<VBox, Void> {
private final TradeDetailsWindow tradeDetailsWindow;
private final OfferDetailsWindow offerDetailsWindow;
private WalletCoinsReceivedEventListener walletCoinsReceivedEventListener;
private WalletCoinsSentEventListener walletCoinsSentEventListener;
private WalletReorganizeEventListener walletReorganizeEventListener;
private TransactionConfidenceEventListener transactionConfidenceEventListener;
private KeyChainEventListener keyChainEventListener;
private ScriptsChangeEventListener scriptsChangeEventListener;
private WalletChangeEventListener walletChangeEventListener;
private EventHandler<KeyEvent> keyEventEventHandler;
@ -173,23 +167,6 @@ public class TransactionsView extends ActivatableView<VBox, Void> {
dateColumn.setSortType(TableColumn.SortType.DESCENDING);
tableView.getSortOrder().add(dateColumn);
walletCoinsReceivedEventListener = (wallet, tx, prevBalance, newBalance) -> {
displayedTransactions.update();
};
walletCoinsSentEventListener = (wallet, tx, prevBalance, newBalance) -> {
displayedTransactions.update();
};
walletReorganizeEventListener = wallet -> {
displayedTransactions.update();
};
transactionConfidenceEventListener = (wallet, tx) -> {
};
keyChainEventListener = keys -> {
displayedTransactions.update();
};
scriptsChangeEventListener = (wallet, scripts, isAddingScripts) -> {
displayedTransactions.update();
};
walletChangeEventListener = wallet -> {
displayedTransactions.update();
};
@ -215,12 +192,6 @@ public class TransactionsView extends ActivatableView<VBox, Void> {
tableView.setItems(sortedDisplayedTransactions);
displayedTransactions.update();
btcWalletService.addCoinsReceivedEventListener(walletCoinsReceivedEventListener);
btcWalletService.addCoinsSentEventListener(walletCoinsSentEventListener);
btcWalletService.addReorganizeEventListener(walletReorganizeEventListener);
btcWalletService.addTransactionConfidenceEventListener(transactionConfidenceEventListener);
btcWalletService.addKeyChainEventListener(keyChainEventListener);
btcWalletService.addScriptChangeEventListener(scriptsChangeEventListener);
btcWalletService.addChangeEventListener(walletChangeEventListener);
scene = root.getScene();
@ -257,12 +228,6 @@ public class TransactionsView extends ActivatableView<VBox, Void> {
protected void deactivate() {
sortedDisplayedTransactions.comparatorProperty().unbind();
displayedTransactions.forEach(TransactionsListItem::cleanup);
btcWalletService.removeCoinsReceivedEventListener(walletCoinsReceivedEventListener);
btcWalletService.removeCoinsSentEventListener(walletCoinsSentEventListener);
btcWalletService.removeReorganizeEventListener(walletReorganizeEventListener);
btcWalletService.removeTransactionConfidenceEventListener(transactionConfidenceEventListener);
btcWalletService.removeKeyChainEventListener(keyChainEventListener);
btcWalletService.removeScriptChangeEventListener(scriptsChangeEventListener);
btcWalletService.removeChangeEventListener(walletChangeEventListener);
if (scene != null)

View file

@ -104,7 +104,9 @@ public class SelectDepositTxWindow extends Overlay<SelectDepositTxWindow> {
});
transactionsComboBox.setItems(FXCollections.observableArrayList(transactions));
transactionsComboBox.setOnAction(event -> {
if (selectHandlerOptional.isPresent()) {
selectHandlerOptional.get().accept(transactionsComboBox.getSelectionModel().getSelectedItem());
}
hide();
});
}

View file

@ -108,6 +108,8 @@ Full node mode:
Lite node mode:
Note: At least one seed node must be running as a full DAO node to support other lite nodes.
`--daoActivated=true --genesisBlockHeight=111 --genesisTxId=30af0050040befd8af25068cc697e418e09c2d8ebd8d411d2240591b9ec203cf --baseCurrencyNetwork=BTC_REGTEST --useDevPrivilegeKeys=true --useLocalhostForP2P=true --nodePort=8888 --appName=bisq-BTC_REGTEST_Bob_dao`
_Don't forget to use different rpcBlockNotificationPorts for different full node instances, otherwise only one node will receive the new block event forwarded to that port._

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 {

View file

@ -14,5 +14,6 @@
<logger name="bisq.common.storage.FileManager" level="WARN"/>
<logger name="com.msopentech.thali.toronionproxy.OnionProxyManagerEventHandler" level="INFO"/>
<logger name="com.neemre.btcdcli4j" level="WARN"/>
</configuration>