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 run ./bisq-cli --password="xyz" getversion
[ "$status" -eq 0 ] [ "$status" -eq 0 ]
echo "actual output: $output" >&2 echo "actual output: $output" >&2
[ "$output" = "1.3.8" ] [ "$output" = "1.3.9" ]
} }
@test "test getversion" { @test "test getversion" {
run ./bisq-cli --password=xyz getversion run ./bisq-cli --password=xyz getversion
[ "$status" -eq 0 ] [ "$status" -eq 0 ]
echo "actual output: $output" >&2 echo "actual output: $output" >&2
[ "$output" = "1.3.8" ] [ "$output" = "1.3.9" ]
} }
@test "test setwalletpassword \"a b c\"" { @test "test setwalletpassword \"a b c\"" {
@ -166,15 +166,15 @@
[ "$output" = "Error: address bogus not found in wallet" ] [ "$output" = "Error: address bogus not found in wallet" ]
} }
@test "test createpaymentacct PerfectMoneyDummy (missing nbr, ccy params)" { @test "test createpaymentacct PerfectMoneyDummy (missing name, nbr, ccy params)" {
run ./bisq-cli --password=xyz createpaymentacct PerfectMoneyDummy run ./bisq-cli --password=xyz createpaymentacct PERFECT_MONEY
[ "$status" -eq 1 ] [ "$status" -eq 1 ]
echo "actual output: $output" >&2 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" { @test "test createpaymentacct PERFECT_MONEY PerfectMoneyDummy 0123456789 USD" {
run ./bisq-cli --password=xyz createpaymentacct PerfectMoneyDummy 0123456789 USD run ./bisq-cli --password=xyz createpaymentacct PERFECT_MONEY PerfectMoneyDummy 0123456789 USD
[ "$status" -eq 0 ] [ "$status" -eq 0 ]
} }

View file

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

View file

@ -24,7 +24,9 @@ import java.io.IOException;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map; import java.util.Map;
import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutionException;
import java.util.stream.Collectors;
import static java.util.Arrays.stream;
import static java.util.concurrent.TimeUnit.MILLISECONDS; 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. // gRPC service stubs are used by method & scenario tests, but not e2e tests.
private static final Map<BisqAppConfig, GrpcStubs> grpcStubsCache = new HashMap<>(); 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 { throws InterruptedException, ExecutionException, IOException {
scaffold = new Scaffold(supportingApps).setUp(); scaffold = new Scaffold(stream(supportingApps).map(Enum::name)
.collect(Collectors.joining(",")))
.setUp();
config = scaffold.config; config = scaffold.config;
bitcoinCli = new BitcoinCliHelper((config)); bitcoinCli = new BitcoinCliHelper((config));
} }
public static void setUpScaffold(String[] params) public static void setUpScaffold(String[] params)
throws InterruptedException, ExecutionException, IOException { 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(); scaffold = new Scaffold(params).setUp();
config = scaffold.config; 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.Test;
import org.junit.jupiter.api.TestMethodOrder; 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.alicedaemon;
import static bisq.apitest.config.BisqAppConfig.seednode;
import static java.util.concurrent.TimeUnit.MILLISECONDS; import static java.util.concurrent.TimeUnit.MILLISECONDS;
import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.fail; import static org.junit.jupiter.api.Assertions.fail;
@ -41,7 +43,7 @@ public class GetBalanceTest extends MethodTest {
@BeforeAll @BeforeAll
public static void setUp() { public static void setUp() {
try { try {
setUpScaffold("bitcoind,seednode,alicedaemon"); setUpScaffold(bitcoind, seednode, alicedaemon);
// Have to generate 1 regtest block for alice's wallet to show 10 BTC balance. // Have to generate 1 regtest block for alice's wallet to show 10 BTC balance.
bitcoinCli.generateBlocks(1); bitcoinCli.generateBlocks(1);

View file

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

View file

@ -25,7 +25,7 @@ public class WalletProtectionTest extends MethodTest {
@BeforeAll @BeforeAll
public static void setUp() { public static void setUp() {
try { try {
setUpScaffold(alicedaemon.name()); setUpScaffold(alicedaemon);
MILLISECONDS.sleep(2000); MILLISECONDS.sleep(2000);
} catch (Exception ex) { } catch (Exception ex) {
fail(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.Test;
import org.junit.jupiter.api.TestMethodOrder; 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.alicedaemon;
import static bisq.apitest.config.BisqAppConfig.seednode;
import static java.util.concurrent.TimeUnit.MILLISECONDS; import static java.util.concurrent.TimeUnit.MILLISECONDS;
import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.fail; import static org.junit.jupiter.api.Assertions.fail;
@ -38,7 +40,7 @@ public class FundWalletScenarioTest extends ScenarioTest {
@BeforeAll @BeforeAll
public static void setUp() { public static void setUp() {
try { try {
setUpScaffold("bitcoind,seednode,alicedaemon"); setUpScaffold(bitcoind, seednode, alicedaemon);
bitcoinCli.generateBlocks(1); bitcoinCli.generateBlocks(1);
MILLISECONDS.sleep(1500); MILLISECONDS.sleep(1500);
} catch (Exception ex) { } catch (Exception ex) {

View file

@ -25,6 +25,7 @@ import bisq.proto.grpc.GetOffersRequest;
import bisq.proto.grpc.GetPaymentAccountsRequest; import bisq.proto.grpc.GetPaymentAccountsRequest;
import bisq.proto.grpc.GetVersionRequest; import bisq.proto.grpc.GetVersionRequest;
import bisq.proto.grpc.LockWalletRequest; import bisq.proto.grpc.LockWalletRequest;
import bisq.proto.grpc.RegisterDisputeAgentRequest;
import bisq.proto.grpc.RemoveWalletPasswordRequest; import bisq.proto.grpc.RemoveWalletPasswordRequest;
import bisq.proto.grpc.SetWalletPasswordRequest; import bisq.proto.grpc.SetWalletPasswordRequest;
import bisq.proto.grpc.UnlockWalletRequest; import bisq.proto.grpc.UnlockWalletRequest;
@ -69,7 +70,8 @@ public class CliMain {
lockwallet, lockwallet,
unlockwallet, unlockwallet,
removewalletpassword, removewalletpassword,
setwalletpassword setwalletpassword,
registerdisputeagent
} }
public static void main(String[] args) { public static void main(String[] args) {
@ -114,9 +116,9 @@ public class CliMain {
} }
var methodName = nonOptionArgs.get(0); var methodName = nonOptionArgs.get(0);
final Method method; Method method;
try { try {
method = Method.valueOf(methodName); method = getMethodFromCmd(methodName);
} catch (IllegalArgumentException ex) { } catch (IllegalArgumentException ex) {
throw new IllegalArgumentException(format("'%s' is not a supported method", methodName)); 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"); throw new IllegalArgumentException("missing required 'password' option");
GrpcStubs grpcStubs = new GrpcStubs(host, port, password); GrpcStubs grpcStubs = new GrpcStubs(host, port, password);
var disputeAgentsService = grpcStubs.disputeAgentsService;
var versionService = grpcStubs.versionService; var versionService = grpcStubs.versionService;
var offersService = grpcStubs.offersService; var offersService = grpcStubs.offersService;
var paymentAccountsService = grpcStubs.paymentAccountsService; var paymentAccountsService = grpcStubs.paymentAccountsService;
@ -166,34 +169,38 @@ public class CliMain {
} }
case getoffers: { case getoffers: {
if (nonOptionArgs.size() < 3) 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 direction = nonOptionArgs.get(1);
var fiatCurrency = nonOptionArgs.get(2); var fiatCurrency = nonOptionArgs.get(2);
var request = GetOffersRequest.newBuilder() var request = GetOffersRequest.newBuilder()
.setDirection(direction) .setDirection(direction)
.setFiatCurrencyCode(fiatCurrency) .setCurrencyCode(fiatCurrency)
.build(); .build();
var reply = offersService.getOffers(request); var reply = offersService.getOffers(request);
out.println(formatOfferTable(reply.getOffersList(), fiatCurrency)); out.println(formatOfferTable(reply.getOffersList(), fiatCurrency));
return; return;
} }
case createpaymentacct: { case createpaymentacct: {
if (nonOptionArgs.size() < 4) if (nonOptionArgs.size() < 5)
throw new IllegalArgumentException( 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 paymentMethodId = nonOptionArgs.get(1);
var accountNumber = nonOptionArgs.get(2); var accountName = nonOptionArgs.get(2);
var fiatCurrencyCode = nonOptionArgs.get(3); var accountNumber = nonOptionArgs.get(3);
var currencyCode = nonOptionArgs.get(4);
var request = CreatePaymentAccountRequest.newBuilder() var request = CreatePaymentAccountRequest.newBuilder()
.setPaymentMethodId(paymentMethodId)
.setAccountName(accountName) .setAccountName(accountName)
.setAccountNumber(accountNumber) .setAccountNumber(accountNumber)
.setFiatCurrencyCode(fiatCurrencyCode).build(); .setCurrencyCode(currencyCode).build();
paymentAccountsService.createPaymentAccount(request); paymentAccountsService.createPaymentAccount(request);
out.println(format("payment account %s saved", accountName)); out.printf("payment account %s saved", accountName);
return; return;
} }
case getpaymentaccts: { case getpaymentaccts: {
@ -232,7 +239,8 @@ public class CliMain {
if (nonOptionArgs.size() < 2) if (nonOptionArgs.size() < 2)
throw new IllegalArgumentException("no password specified"); 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); walletsService.removeWalletPassword(request);
out.println("wallet decrypted"); out.println("wallet decrypted");
return; return;
@ -241,7 +249,8 @@ public class CliMain {
if (nonOptionArgs.size() < 2) if (nonOptionArgs.size() < 2)
throw new IllegalArgumentException("no password specified"); 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; var hasNewPassword = nonOptionArgs.size() == 3;
if (hasNewPassword) if (hasNewPassword)
requestBuilder.setNewPassword(nonOptionArgs.get(2)); requestBuilder.setNewPassword(nonOptionArgs.get(2));
@ -249,6 +258,19 @@ public class CliMain {
out.println("wallet encrypted" + (hasNewPassword ? " with new password" : "")); out.println("wallet encrypted" + (hasNewPassword ? " with new password" : ""));
return; 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: { default: {
throw new RuntimeException(format("unhandled method '%s'", method)); 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) { private static void printHelp(OptionParser parser, PrintStream stream) {
try { try {
stream.println("Bisq RPC Client"); stream.println("Bisq RPC Client");

View file

@ -161,6 +161,19 @@ public class Utilities {
return getOSName().contains("win"); 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() { public static boolean isOSX() {
return getOSName().contains("mac") || getOSName().contains("darwin"); return getOSName().contains("mac") || getOSName().contains("darwin");
} }

View file

@ -139,8 +139,14 @@ public class CoreApi {
// PaymentAccounts // PaymentAccounts
/////////////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////////////
public void createPaymentAccount(String accountName, String accountNumber, String fiatCurrencyCode) { public void createPaymentAccount(String paymentMethodId,
paymentAccountsService.createPaymentAccount(accountName, accountNumber, fiatCurrencyCode); String accountName,
String accountNumber,
String currencyCode) {
paymentAccountsService.createPaymentAccount(paymentMethodId,
accountName,
accountNumber,
currencyCode);
} }
public Set<PaymentAccount> getPaymentAccounts() { public Set<PaymentAccount> getPaymentAccounts() {

View file

@ -68,7 +68,7 @@ class CoreDisputeAgentsService {
this.languageCodes = Arrays.asList("de", "en", "es", "fr"); this.languageCodes = Arrays.asList("de", "en", "es", "fr");
} }
public void registerDisputeAgent(String disputeAgentType, String registrationKey) { void registerDisputeAgent(String disputeAgentType, String registrationKey) {
if (!p2PService.isBootstrapped()) if (!p2PService.isBootstrapped())
throw new IllegalStateException("p2p service is not bootstrapped yet"); throw new IllegalStateException("p2p service is not bootstrapped yet");

View file

@ -58,7 +58,7 @@ class CoreOffersService {
this.user = user; this.user = user;
} }
public List<Offer> getOffers(String direction, String fiatCurrencyCode) { List<Offer> getOffers(String direction, String fiatCurrencyCode) {
List<Offer> offers = offerBookService.getOffers().stream() List<Offer> offers = offerBookService.getOffers().stream()
.filter(o -> { .filter(o -> {
var offerOfWantedDirection = o.getDirection().name().equalsIgnoreCase(direction); var offerOfWantedDirection = o.getDirection().name().equalsIgnoreCase(direction);
@ -77,7 +77,7 @@ class CoreOffersService {
return offers; return offers;
} }
public void createOffer(String currencyCode, void createOffer(String currencyCode,
String directionAsString, String directionAsString,
long priceAsLong, long priceAsLong,
boolean useMarketBasedPrice, boolean useMarketBasedPrice,
@ -111,7 +111,7 @@ class CoreOffersService {
resultHandler); resultHandler);
} }
public void createOffer(String offerId, void createOffer(String offerId,
String currencyCode, String currencyCode,
OfferPayload.Direction direction, OfferPayload.Direction direction,
Price price, Price price,

View file

@ -33,6 +33,9 @@ import java.util.Set;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import static bisq.core.payment.payload.PaymentMethod.*;
import static com.google.common.base.Preconditions.checkNotNull;
@Slf4j @Slf4j
class CorePaymentAccountsService { class CorePaymentAccountsService {
@ -49,17 +52,19 @@ class CorePaymentAccountsService {
this.user = user; this.user = user;
} }
public void createPaymentAccount(String accountName, String accountNumber, String fiatCurrencyCode) { void createPaymentAccount(String paymentMethodId,
// Create and persist a PerfectMoney dummy payment account. There is no guard String accountName,
// against creating accounts with duplicate names & numbers, only the uuid and String accountNumber,
// creation date are unique. String currencyCode) {
PaymentMethod dummyPaymentMethod = PaymentMethod.getDummyPaymentMethod(PaymentMethod.PERFECT_MONEY_ID);
PaymentAccount paymentAccount = PaymentAccountFactory.getPaymentAccount(dummyPaymentMethod); PaymentAccount paymentAccount = getNewPaymentAccount(paymentMethodId,
paymentAccount.init(); accountName,
paymentAccount.setAccountName(accountName); accountNumber,
((PerfectMoneyAccount) paymentAccount).setAccountNr(accountNumber); currencyCode);
paymentAccount.setSingleTradeCurrency(new FiatCurrency(fiatCurrencyCode.toUpperCase()));
user.addPaymentAccount(paymentAccount); // 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. // Don't do this on mainnet until thoroughly tested.
if (config.baseCurrencyNetwork.isRegtest()) if (config.baseCurrencyNetwork.isRegtest())
@ -68,7 +73,64 @@ class CorePaymentAccountsService {
log.info("Payment account {} saved", paymentAccount.getId()); log.info("Payment account {} saved", paymentAccount.getId());
} }
public Set<PaymentAccount> getPaymentAccounts() { Set<PaymentAccount> getPaymentAccounts() {
return user.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; this.btcWalletService = btcWalletService;
} }
public long getAvailableBalance() { long getAvailableBalance() {
verifyWalletsAreAvailable(); verifyWalletsAreAvailable();
verifyEncryptedWalletIsUnlocked(); verifyEncryptedWalletIsUnlocked();
@ -82,27 +82,26 @@ class CoreWalletsService {
return balance.getValue(); return balance.getValue();
} }
public long getAddressBalance(String addressString) { long getAddressBalance(String addressString) {
Address address = getAddressEntry(addressString).getAddress(); Address address = getAddressEntry(addressString).getAddress();
return btcWalletService.getBalanceForAddress(address).value; return btcWalletService.getBalanceForAddress(address).value;
} }
public AddressBalanceInfo getAddressBalanceInfo(String addressString) { AddressBalanceInfo getAddressBalanceInfo(String addressString) {
var satoshiBalance = getAddressBalance(addressString); var satoshiBalance = getAddressBalance(addressString);
var numConfirmations = getNumConfirmationsForMostRecentTransaction(addressString); var numConfirmations = getNumConfirmationsForMostRecentTransaction(addressString);
return new AddressBalanceInfo(addressString, satoshiBalance, numConfirmations); return new AddressBalanceInfo(addressString, satoshiBalance, numConfirmations);
} }
public List<AddressBalanceInfo> getFundingAddresses() { List<AddressBalanceInfo> getFundingAddresses() {
verifyWalletsAreAvailable(); verifyWalletsAreAvailable();
verifyEncryptedWalletIsUnlocked(); verifyEncryptedWalletIsUnlocked();
// Create a new funding address if none exists. // Create a new funding address if none exists.
if (btcWalletService.getAvailableAddressEntries().size() == 0) if (btcWalletService.getAvailableAddressEntries().isEmpty())
btcWalletService.getFreshAddressEntry(); btcWalletService.getFreshAddressEntry();
List<String> addressStrings = List<String> addressStrings = btcWalletService
btcWalletService
.getAvailableAddressEntries() .getAvailableAddressEntries()
.stream() .stream()
.map(AddressEntry::getAddressString) .map(AddressEntry::getAddressString)
@ -113,8 +112,7 @@ class CoreWalletsService {
// this::getAddressBalance cannot return null. // this::getAddressBalance cannot return null.
var balances = memoize(this::getAddressBalance); var balances = memoize(this::getAddressBalance);
boolean noAddressHasZeroBalance = boolean noAddressHasZeroBalance = addressStrings.stream()
addressStrings.stream()
.allMatch(addressString -> balances.getUnchecked(addressString) != 0); .allMatch(addressString -> balances.getUnchecked(addressString) != 0);
if (noAddressHasZeroBalance) { if (noAddressHasZeroBalance) {
@ -129,13 +127,13 @@ class CoreWalletsService {
.collect(Collectors.toList()); .collect(Collectors.toList());
} }
public int getNumConfirmationsForMostRecentTransaction(String addressString) { int getNumConfirmationsForMostRecentTransaction(String addressString) {
Address address = getAddressEntry(addressString).getAddress(); Address address = getAddressEntry(addressString).getAddress();
TransactionConfidence confidence = btcWalletService.getConfidenceForAddress(address); TransactionConfidence confidence = btcWalletService.getConfidenceForAddress(address);
return confidence == null ? 0 : confidence.getDepthInBlocks(); return confidence == null ? 0 : confidence.getDepthInBlocks();
} }
public void setWalletPassword(String password, String newPassword) { void setWalletPassword(String password, String newPassword) {
verifyWalletsAreAvailable(); verifyWalletsAreAvailable();
KeyCrypterScrypt keyCrypterScrypt = getKeyCrypterScrypt(); KeyCrypterScrypt keyCrypterScrypt = getKeyCrypterScrypt();
@ -165,7 +163,7 @@ class CoreWalletsService {
walletsManager.backupWallets(); walletsManager.backupWallets();
} }
public void lockWallet() { void lockWallet() {
if (!walletsManager.areWalletsEncrypted()) if (!walletsManager.areWalletsEncrypted())
throw new IllegalStateException("wallet is not encrypted with a password"); throw new IllegalStateException("wallet is not encrypted with a password");
@ -175,7 +173,7 @@ class CoreWalletsService {
tempAesKey = null; tempAesKey = null;
} }
public void unlockWallet(String password, long timeout) { void unlockWallet(String password, long timeout) {
verifyWalletIsAvailableAndEncrypted(); verifyWalletIsAvailableAndEncrypted();
KeyCrypterScrypt keyCrypterScrypt = getKeyCrypterScrypt(); KeyCrypterScrypt keyCrypterScrypt = getKeyCrypterScrypt();
@ -213,7 +211,7 @@ class CoreWalletsService {
// Provided for automated wallet protection method testing, despite the // Provided for automated wallet protection method testing, despite the
// security risks exposed by providing users the ability to decrypt their wallets. // security risks exposed by providing users the ability to decrypt their wallets.
public void removeWalletPassword(String password) { void removeWalletPassword(String password) {
verifyWalletIsAvailableAndEncrypted(); verifyWalletIsAvailableAndEncrypted();
KeyCrypterScrypt keyCrypterScrypt = getKeyCrypterScrypt(); 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.setShowPopupIfInvalidBtcConfigHandler(() -> log.error("onShowPopupIfInvalidBtcConfigHandler"));
bisqSetup.setRevolutAccountsUpdateHandler(revolutAccountList -> log.info("setRevolutAccountsUpdateHandler: revolutAccountList={}", revolutAccountList)); bisqSetup.setRevolutAccountsUpdateHandler(revolutAccountList -> log.info("setRevolutAccountsUpdateHandler: revolutAccountList={}", revolutAccountList));
bisqSetup.setOsxKeyLoggerWarningHandler(() -> log.info("setOsxKeyLoggerWarningHandler")); bisqSetup.setOsxKeyLoggerWarningHandler(() -> log.info("setOsxKeyLoggerWarningHandler"));
bisqSetup.setQubesOSInfoHandler(() -> log.info("setQubesOSInfoHandler"));
//TODO move to bisqSetup //TODO move to bisqSetup
corruptedDatabaseFilesHandler.getCorruptedDatabaseFiles().ifPresent(files -> log.warn("getCorruptedDatabaseFiles. files={}", files)); corruptedDatabaseFilesHandler.getCorruptedDatabaseFiles().ifPresent(files -> log.warn("getCorruptedDatabaseFiles. files={}", files));

View file

@ -232,6 +232,9 @@ public class BisqSetup {
@Setter @Setter
@Nullable @Nullable
private Runnable osxKeyLoggerWarningHandler; private Runnable osxKeyLoggerWarningHandler;
@Setter
@Nullable
private Runnable qubesOSInfoHandler;
@Getter @Getter
final BooleanProperty newVersionAvailableProperty = new SimpleBooleanProperty(false); final BooleanProperty newVersionAvailableProperty = new SimpleBooleanProperty(false);
@ -357,6 +360,7 @@ public class BisqSetup {
checkCryptoSetup(); checkCryptoSetup();
checkForCorrectOSArchitecture(); checkForCorrectOSArchitecture();
checkOSXVersion(); checkOSXVersion();
checkIfRunningOnQubesOS();
} }
private void step3() { 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() { private void initDomainServices() {
log.info("initDomainServices"); log.info("initDomainServices");

View file

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

View file

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

View file

@ -26,7 +26,6 @@ import org.bitcoinj.wallet.DeterministicKeyChain;
import org.bitcoinj.wallet.DeterministicSeed; import org.bitcoinj.wallet.DeterministicSeed;
import org.bitcoinj.wallet.KeyChainGroupStructure; import org.bitcoinj.wallet.KeyChainGroupStructure;
import org.bitcoinj.wallet.Protos; import org.bitcoinj.wallet.Protos;
import org.bitcoinj.wallet.UnreadableWalletException;
import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableList;
@ -63,12 +62,12 @@ public class BisqKeyChainFactory extends DefaultKeyChainFactory {
} }
@Override @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"); throw new UnsupportedOperationException("Bisq is not supposed to use this");
} }
@Override @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"); 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.LocalBitcoinNode;
import bisq.core.btc.nodes.ProxySocketFactory; import bisq.core.btc.nodes.ProxySocketFactory;
import bisq.core.btc.wallet.BisqRiskAnalysis; import bisq.core.btc.wallet.BisqRiskAnalysis;
import bisq.common.config.Config; import bisq.common.config.Config;
import com.google.common.collect.*; 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 * <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 * 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 * construct and configure a {@link BlockChain}, {@link SPVBlockStore}, {@link Wallet} and {@link PeerGroup}.</p>
* 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>
* *
* <p>To add listeners and modify the objects that are constructed, you can either do that by overriding the * <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, * {@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 vBtcWalletFile;
protected volatile File vBsqWalletFile; protected volatile File vBsqWalletFile;
protected boolean useAutoSave = true;
protected PeerAddress[] peerAddresses; protected PeerAddress[] peerAddresses;
protected DownloadProgressTracker downloadListener; protected DownloadProgressTracker downloadListener;
protected boolean autoStop = true;
protected InputStream checkpoints; protected InputStream checkpoints;
protected boolean blockingStartup = true;
protected String userAgent, version; protected String userAgent, version;
protected WalletProtobufSerializer.WalletFactory walletFactory; protected WalletProtobufSerializer.WalletFactory walletFactory;
@Nullable protected DeterministicSeed restoreFromSeed; @Nullable protected DeterministicSeed restoreFromSeed;
@Nullable protected DeterministicKey restoreFromKey;
@Nullable protected PeerDiscovery discovery; @Nullable protected PeerDiscovery discovery;
protected volatile Context context; 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. * Creates a new WalletConfig, with the given {@link Context}. Files will be stored in the given directory.
*/ */
public WalletConfig(Context context, private WalletConfig(Context context, File directory, String filePrefix) {
File directory, String filePrefix) {
this.context = context; this.context = context;
this.params = checkNotNull(context.getParams()); this.params = checkNotNull(context.getParams());
this.directory = checkDir(directory); this.directory = checkDir(directory);
@ -170,29 +164,15 @@ public class WalletConfig extends AbstractIdleService {
return setPeerNodes(new PeerAddress(params, localHost, params.getPort())); 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 * 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 * {@link DownloadProgressTracker} is a good choice.
* too, due to some missing implementation code.
*/ */
public WalletConfig setDownloadListener(DownloadProgressTracker listener) { public WalletConfig setDownloadListener(DownloadProgressTracker listener) {
this.downloadListener = listener; this.downloadListener = listener;
return this; 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 * 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 * 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; 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. * 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" * @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; 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, * 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 * 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; 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. * 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; 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 * 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. * 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 // 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 @Override
protected void startUp() throws Exception { protected void startUp() throws Exception {
// Runs in a separate thread. // Runs in a separate thread.
Context.propagate(context); 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); log.info("Starting up with directory = {}", directory);
try { try {
File chainFile = new File(directory, filePrefix + ".spvchain"); File chainFile = new File(directory, filePrefix + ".spvchain");
boolean chainFileExists = chainFile.exists(); boolean chainFileExists = chainFile.exists();
String btcPrefix = "_BTC"; String btcPrefix = "_BTC";
vBtcWalletFile = new File(directory, filePrefix + btcPrefix + ".wallet"); 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 = createOrLoadWallet(shouldReplayWallet, vBtcWalletFile, false);
vBtcWallet.allowSpendingUnconfirmedTransactions(); vBtcWallet.allowSpendingUnconfirmedTransactions();
vBtcWallet.setRiskAnalyzer(new BisqRiskAnalysis.Analyzer()); vBtcWallet.setRiskAnalyzer(new BisqRiskAnalysis.Analyzer());
@ -341,8 +247,8 @@ public class WalletConfig extends AbstractIdleService {
// Initiate Bitcoin network objects (block store, blockchain and peer group) // Initiate Bitcoin network objects (block store, blockchain and peer group)
vStore = new SPVBlockStore(params, chainFile); vStore = new SPVBlockStore(params, chainFile);
if (!chainFileExists || restoreFromSeed != null || restoreFromKey != null) { if (!chainFileExists || restoreFromSeed != null) {
if (checkpoints == null && !Utils.isAndroidRuntime()) { if (checkpoints == null) {
checkpoints = CheckpointManager.openStream(params); checkpoints = CheckpointManager.openStream(params);
} }
@ -355,12 +261,6 @@ public class WalletConfig extends AbstractIdleService {
log.info("Clearing the chain file in preparation for restore."); log.info("Clearing the chain file in preparation for restore.");
vStore.clear(); vStore.clear();
} }
} else if (restoreFromKey != null) {
time = restoreFromKey.getCreationTimeSeconds();
if (chainFileExists) {
log.info("Clearing the chain file in preparation for restore.");
vStore.clear();
}
} else { } else {
time = vBtcWallet.getEarliestKeyCreationTime(); time = vBtcWallet.getEarliestKeyCreationTime();
} }
@ -398,23 +298,12 @@ public class WalletConfig extends AbstractIdleService {
vPeerGroup.addWallet(vBsqWallet); vPeerGroup.addWallet(vBsqWallet);
onSetupCompleted(); 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>() { Futures.addCallback((ListenableFuture<?>) vPeerGroup.startAsync(), new FutureCallback<Object>() {
@Override @Override
public void onSuccess(@Nullable Object result) { public void onSuccess(@Nullable Object result) {
//completeExtensionInitiations(vPeerGroup); //completeExtensionInitiations(vPeerGroup);
final DownloadProgressTracker l = downloadListener == null ? new DownloadProgressTracker() : downloadListener; DownloadProgressTracker tracker = downloadListener == null ? new DownloadProgressTracker() : downloadListener;
vPeerGroup.startBlockChainDownload(l); vPeerGroup.startBlockChainDownload(tracker);
} }
@Override @Override
@ -423,7 +312,6 @@ public class WalletConfig extends AbstractIdleService {
} }
}, MoreExecutors.directExecutor()); }, MoreExecutors.directExecutor());
}
} catch (BlockStoreException e) { } catch (BlockStoreException e) {
throw new IOException(e); throw new IOException(e);
} }
@ -439,9 +327,6 @@ public class WalletConfig extends AbstractIdleService {
} else { } else {
wallet = createWallet(isBsqWallet); wallet = createWallet(isBsqWallet);
wallet.freshReceiveKey(); 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 // 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[])) // deserializing the extension (see WalletExtension#deserializeWalletExtension(Wallet, byte[]))
@ -450,9 +335,7 @@ public class WalletConfig extends AbstractIdleService {
wallet = loadWallet(false, walletFile, isBsqWallet); wallet = loadWallet(false, walletFile, isBsqWallet);
} }
if (useAutoSave) {
this.setupAutoSave(wallet, walletFile); this.setupAutoSave(wallet, walletFile);
}
return wallet; return wallet;
} }
@ -465,8 +348,7 @@ public class WalletConfig extends AbstractIdleService {
Wallet wallet; Wallet wallet;
FileInputStream walletStream = new FileInputStream(walletFile); FileInputStream walletStream = new FileInputStream(walletFile);
try { try {
List<WalletExtension> extensions = provideWalletExtensions(); WalletExtension[] extArray = new WalletExtension[]{};
WalletExtension[] extArray = extensions.toArray(new WalletExtension[extensions.size()]);
Protos.Wallet proto = WalletProtobufSerializer.parseToProto(walletStream); Protos.Wallet proto = WalletProtobufSerializer.parseToProto(walletStream);
final WalletProtobufSerializer serializer; final WalletProtobufSerializer serializer;
if (walletFactory != null) if (walletFactory != null)
@ -492,8 +374,6 @@ public class WalletConfig extends AbstractIdleService {
KeyChainGroup.Builder kcg = KeyChainGroup.builder(params, structure); KeyChainGroup.Builder kcg = KeyChainGroup.builder(params, structure);
if (restoreFromSeed != null) { if (restoreFromSeed != null) {
kcg.fromSeed(restoreFromSeed, preferredOutputScriptType).build(); kcg.fromSeed(restoreFromSeed, preferredOutputScriptType).build();
} else if (restoreFromKey != null) {
kcg.addChain(DeterministicKeyChain.builder().spend(restoreFromKey).outputScriptType(preferredOutputScriptType).build());
} else { } else {
// new wallet // new wallet
if (!isBsqWallet) { if (!isBsqWallet) {
@ -512,7 +392,7 @@ public class WalletConfig extends AbstractIdleService {
} }
private void maybeMoveOldWalletOutOfTheWay(File walletFile) { private void maybeMoveOldWalletOutOfTheWay(File walletFile) {
if (restoreFromSeed == null && restoreFromKey == null) return; if (restoreFromSeed == null) return;
if (!walletFile.exists()) return; if (!walletFile.exists()) return;
int counter = 1; int counter = 1;
File newName; 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() { private PeerGroup createPeerGroup() {
PeerGroup peerGroup; PeerGroup peerGroup;
// no proxy case. // no proxy case.
@ -573,19 +436,6 @@ public class WalletConfig extends AbstractIdleService {
return peerGroup; 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 @Override
protected void shutDown() throws Exception { protected void shutDown() throws Exception {
// Runs in a separate thread. // Runs in a separate thread.

View file

@ -249,7 +249,7 @@ public class WalletsSetup {
UserThread.execute(() -> { UserThread.execute(() -> {
addressEntryList.onWalletReady(walletConfig.btcWallet()); addressEntryList.onWalletReady(walletConfig.btcWallet());
timeoutTimer.stop(); 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 // 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) walletConfig.setDownloadListener(downloadListener);
.setBlockingStartup(false);
// If seed is non-null it means we are restoring from backup. // If seed is non-null it means we are restoring from backup.
if (seed != null) {
walletConfig.restoreWalletFromSeed(seed); walletConfig.restoreWalletFromSeed(seed);
}
walletConfig.addListener(new Service.Listener() { walletConfig.addListener(new Service.Listener() {
@Override @Override

View file

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

View file

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

View file

@ -74,7 +74,7 @@ public abstract class BaseProposalFactory<R extends Proposal> {
R proposal = createProposalWithoutTxId(); R proposal = createProposalWithoutTxId();
proposalValidator.validateDataFields(proposal); proposalValidator.validateDataFields(proposal);
Transaction transaction = createTransaction(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); 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 // 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 // mediation/arbitration and log only the issue. The dispute agent will run validation as well and will get
// a popup displayed to react. // a popup displayed to react.
log.error("Donation address invalid. {}", e.toString()); log.warn("Donation address is invalid. {}", e.toString());
} }
if (!isAgent(dispute)) { 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: {}", 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)); new Date(startTime), new Date(tradeTime), new Date(blockTime));
} else { } 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; startTime = now;
} }
} else { } else {

View file

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

View file

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

View file

@ -189,6 +189,12 @@ public class User implements PersistedDataHost {
// Collection operations // Collection operations
/////////////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////////////
public void addPaymentAccountIfNotExists(PaymentAccount paymentAccount) {
if (!paymentAccountExists(paymentAccount)) {
addPaymentAccount(paymentAccount);
}
}
public void addPaymentAccount(PaymentAccount paymentAccount) { public void addPaymentAccount(PaymentAccount paymentAccount) {
paymentAccount.onAddToUser(); paymentAccount.onAddToUser();
@ -493,4 +499,8 @@ public class User implements PersistedDataHost {
public boolean isPaymentAccountImport() { public boolean isPaymentAccountImport() {
return 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\ 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 \ 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). (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! popup.privateNotification.headline=Important private notification!

View file

@ -23,6 +23,7 @@ import bisq.core.app.CoreModule;
import bisq.common.UserThread; import bisq.common.UserThread;
import bisq.common.app.AppModule; import bisq.common.app.AppModule;
import bisq.common.handlers.ResultHandler;
import bisq.common.setup.CommonSetup; import bisq.common.setup.CommonSetup;
import com.google.common.util.concurrent.ThreadFactoryBuilder; import com.google.common.util.concurrent.ThreadFactoryBuilder;
@ -39,13 +40,15 @@ import bisq.daemon.grpc.GrpcServer;
@Slf4j @Slf4j
public class BisqDaemonMain extends BisqHeadlessAppMain implements BisqSetup.BisqSetupListener { public class BisqDaemonMain extends BisqHeadlessAppMain implements BisqSetup.BisqSetupListener {
private GrpcServer grpcServer;
public static void main(String[] args) { public static void main(String[] args) {
new BisqDaemonMain().execute(args); new BisqDaemonMain().execute(args);
} }
/////////////////////////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////////////////
// First synchronous execution tasks // First synchronous execution tasks
/////////////////////////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////////////////
@Override @Override
protected void configUserThread() { protected void configUserThread() {
@ -70,9 +73,9 @@ public class BisqDaemonMain extends BisqHeadlessAppMain implements BisqSetup.Bis
headlessApp.setGracefulShutDownHandler(this); headlessApp.setGracefulShutDownHandler(this);
} }
/////////////////////////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////////////////
// We continue with a series of synchronous execution tasks // We continue with a series of synchronous execution tasks
/////////////////////////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////////////////
@Override @Override
protected AppModule getModule() { 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... // We need to be in user thread! We mapped at launchApplication already...
headlessApp.startApplication(); 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(); onApplicationStarted();
} }
@ -99,7 +103,14 @@ public class BisqDaemonMain extends BisqHeadlessAppMain implements BisqSetup.Bis
protected void onApplicationStarted() { protected void onApplicationStarted() {
super.onApplicationStarted(); super.onApplicationStarted();
GrpcServer grpcServer = injector.getInstance(GrpcServer.class); grpcServer = injector.getInstance(GrpcServer.class);
grpcServer.start(); 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. // The client cannot see bisq.core.Offer or its fromProto method.
// We use the lighter weight OfferInfo proto wrapper instead, containing just // We use the lighter weight OfferInfo proto wrapper instead, containing just
// enough fields to view and create offers. // 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() .stream().map(offer -> new OfferInfo.OfferInfoBuilder()
.withId(offer.getId()) .withId(offer.getId())
.withDirection(offer.getDirection().name()) .withDirection(offer.getDirection().name())

View file

@ -45,7 +45,10 @@ class GrpcPaymentAccountsService extends PaymentAccountsGrpc.PaymentAccountsImpl
@Override @Override
public void createPaymentAccount(CreatePaymentAccountRequest req, public void createPaymentAccount(CreatePaymentAccountRequest req,
StreamObserver<CreatePaymentAccountReply> responseObserver) { 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(); var reply = CreatePaymentAccountReply.newBuilder().build();
responseObserver.onNext(reply); responseObserver.onNext(reply);
responseObserver.onCompleted(); responseObserver.onCompleted();
@ -54,10 +57,11 @@ class GrpcPaymentAccountsService extends PaymentAccountsGrpc.PaymentAccountsImpl
@Override @Override
public void getPaymentAccounts(GetPaymentAccountsRequest req, public void getPaymentAccounts(GetPaymentAccountsRequest req,
StreamObserver<GetPaymentAccountsReply> responseObserver) { StreamObserver<GetPaymentAccountsReply> responseObserver) {
var tradeStatistics = coreApi.getPaymentAccounts().stream() var paymentAccounts = coreApi.getPaymentAccounts().stream()
.map(PaymentAccount::toProtoMessage) .map(PaymentAccount::toProtoMessage)
.collect(Collectors.toList()); .collect(Collectors.toList());
var reply = GetPaymentAccountsReply.newBuilder().addAllPaymentAccounts(tradeStatistics).build(); var reply = GetPaymentAccountsReply.newBuilder()
.addAllPaymentAccounts(paymentAccounts).build();
responseObserver.onNext(reply); responseObserver.onNext(reply);
responseObserver.onCompleted(); responseObserver.onCompleted();
} }

View file

@ -18,30 +18,21 @@
package bisq.daemon.grpc; package bisq.daemon.grpc;
import bisq.core.api.CoreApi; import bisq.core.api.CoreApi;
import bisq.core.trade.statistics.TradeStatistics2;
import bisq.common.config.Config; 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.Server;
import io.grpc.ServerBuilder; import io.grpc.ServerBuilder;
import io.grpc.stub.StreamObserver;
import javax.inject.Inject; import javax.inject.Inject;
import javax.inject.Singleton;
import java.io.IOException; import java.io.IOException;
import java.io.UncheckedIOException; import java.io.UncheckedIOException;
import java.util.stream.Collectors;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
@Singleton
@Slf4j @Slf4j
public class GrpcServer { public class GrpcServer {
@ -51,19 +42,22 @@ public class GrpcServer {
@Inject @Inject
public GrpcServer(Config config, public GrpcServer(Config config,
CoreApi coreApi, CoreApi coreApi,
PasswordAuthInterceptor passwordAuthInterceptor,
GrpcDisputeAgentsService disputeAgentsService, GrpcDisputeAgentsService disputeAgentsService,
GrpcOffersService offersService, GrpcOffersService offersService,
GrpcPaymentAccountsService paymentAccountsService, GrpcPaymentAccountsService paymentAccountsService,
GrpcVersionService versionService,
GrpcGetTradeStatisticsService tradeStatisticsService,
GrpcWalletsService walletsService) { GrpcWalletsService walletsService) {
this.coreApi = coreApi; this.coreApi = coreApi;
this.server = ServerBuilder.forPort(config.apiPort) this.server = ServerBuilder.forPort(config.apiPort)
.addService(disputeAgentsService) .addService(disputeAgentsService)
.addService(new GetVersionService())
.addService(new GetTradeStatisticsService())
.addService(offersService) .addService(offersService)
.addService(paymentAccountsService) .addService(paymentAccountsService)
.addService(tradeStatisticsService)
.addService(versionService)
.addService(walletsService) .addService(walletsService)
.intercept(new PasswordAuthInterceptor(config.apiPassword)) .intercept(passwordAuthInterceptor)
.build(); .build();
} }
@ -71,36 +65,14 @@ public class GrpcServer {
try { try {
server.start(); server.start();
log.info("listening on port {}", server.getPort()); log.info("listening on port {}", server.getPort());
Runtime.getRuntime().addShutdownHook(new Thread(() -> {
server.shutdown();
log.info("shutdown complete");
}));
} catch (IOException ex) { } catch (IOException ex) {
throw new UncheckedIOException(ex); throw new UncheckedIOException(ex);
} }
} }
class GetVersionService extends GetVersionGrpc.GetVersionImplBase { public void shutdown() {
@Override log.info("Server shutdown started");
public void getVersion(GetVersionRequest req, StreamObserver<GetVersionReply> responseObserver) { server.shutdown();
var reply = GetVersionReply.newBuilder().setVersion(coreApi.getVersion()).build(); log.info("Server shutdown complete");
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();
}
} }
} }

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; 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 @Override
public void getBalance(GetBalanceRequest req, StreamObserver<GetBalanceReply> responseObserver) { public void getBalance(GetBalanceRequest req, StreamObserver<GetBalanceReply> responseObserver) {
try { try {
long result = coreApi.getAvailableBalance(); long availableBalance = coreApi.getAvailableBalance();
var reply = GetBalanceReply.newBuilder().setBalance(result).build(); var reply = GetBalanceReply.newBuilder().setBalance(availableBalance).build();
responseObserver.onNext(reply); responseObserver.onNext(reply);
responseObserver.onCompleted(); responseObserver.onCompleted();
} catch (IllegalStateException cause) { } catch (IllegalStateException cause) {
@ -72,8 +78,9 @@ class GrpcWalletsService extends WalletsGrpc.WalletsImplBase {
public void getAddressBalance(GetAddressBalanceRequest req, public void getAddressBalance(GetAddressBalanceRequest req,
StreamObserver<GetAddressBalanceReply> responseObserver) { StreamObserver<GetAddressBalanceReply> responseObserver) {
try { try {
AddressBalanceInfo result = coreApi.getAddressBalanceInfo(req.getAddress()); AddressBalanceInfo balanceInfo = coreApi.getAddressBalanceInfo(req.getAddress());
var reply = GetAddressBalanceReply.newBuilder().setAddressBalanceInfo(result.toProtoMessage()).build(); var reply = GetAddressBalanceReply.newBuilder()
.setAddressBalanceInfo(balanceInfo.toProtoMessage()).build();
responseObserver.onNext(reply); responseObserver.onNext(reply);
responseObserver.onCompleted(); responseObserver.onCompleted();
} catch (IllegalStateException cause) { } catch (IllegalStateException cause) {
@ -87,10 +94,10 @@ class GrpcWalletsService extends WalletsGrpc.WalletsImplBase {
public void getFundingAddresses(GetFundingAddressesRequest req, public void getFundingAddresses(GetFundingAddressesRequest req,
StreamObserver<GetFundingAddressesReply> responseObserver) { StreamObserver<GetFundingAddressesReply> responseObserver) {
try { try {
List<AddressBalanceInfo> result = coreApi.getFundingAddresses(); List<AddressBalanceInfo> balanceInfo = coreApi.getFundingAddresses();
var reply = GetFundingAddressesReply.newBuilder() var reply = GetFundingAddressesReply.newBuilder()
.addAllAddressBalanceInfo( .addAllAddressBalanceInfo(
result.stream() balanceInfo.stream()
.map(AddressBalanceInfo::toProtoMessage) .map(AddressBalanceInfo::toProtoMessage)
.collect(Collectors.toList())) .collect(Collectors.toList()))
.build(); .build();

View file

@ -17,12 +17,16 @@
package bisq.daemon.grpc; package bisq.daemon.grpc;
import bisq.common.config.Config;
import io.grpc.Metadata; import io.grpc.Metadata;
import io.grpc.ServerCall; import io.grpc.ServerCall;
import io.grpc.ServerCallHandler; import io.grpc.ServerCallHandler;
import io.grpc.ServerInterceptor; import io.grpc.ServerInterceptor;
import io.grpc.StatusRuntimeException; import io.grpc.StatusRuntimeException;
import javax.inject.Inject;
import static io.grpc.Metadata.ASCII_STRING_MARSHALLER; import static io.grpc.Metadata.ASCII_STRING_MARSHALLER;
import static io.grpc.Metadata.Key; import static io.grpc.Metadata.Key;
import static io.grpc.Status.UNAUTHENTICATED; import static io.grpc.Status.UNAUTHENTICATED;
@ -36,12 +40,13 @@ import static java.lang.String.format;
*/ */
class PasswordAuthInterceptor implements ServerInterceptor { class PasswordAuthInterceptor implements ServerInterceptor {
public static final String PASSWORD_KEY = "password"; private static final String PASSWORD_KEY = "password";
private final String expectedPasswordValue; private final String expectedPasswordValue;
public PasswordAuthInterceptor(String expectedPasswordValue) { @Inject
this.expectedPasswordValue = expectedPasswordValue; public PasswordAuthInterceptor(Config config) {
this.expectedPasswordValue = config.apiPassword;
} }
@Override @Override

View file

@ -395,6 +395,15 @@ public class MainViewModel implements ViewModel, BisqSetup.BisqSetupListener {
.show(); .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() corruptedDatabaseFilesHandler.getCorruptedDatabaseFiles().ifPresent(files -> new Popup()
.warning(Res.get("popup.warning.incompatibleDB", files.toString(), config.appDataDir)) .warning(Res.get("popup.warning.incompatibleDB", files.toString(), config.appDataDir))

View file

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

View file

@ -103,12 +103,6 @@ public class TransactionsView extends ActivatableView<VBox, Void> {
private final TradeDetailsWindow tradeDetailsWindow; private final TradeDetailsWindow tradeDetailsWindow;
private final OfferDetailsWindow offerDetailsWindow; 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 WalletChangeEventListener walletChangeEventListener;
private EventHandler<KeyEvent> keyEventEventHandler; private EventHandler<KeyEvent> keyEventEventHandler;
@ -173,23 +167,6 @@ public class TransactionsView extends ActivatableView<VBox, Void> {
dateColumn.setSortType(TableColumn.SortType.DESCENDING); dateColumn.setSortType(TableColumn.SortType.DESCENDING);
tableView.getSortOrder().add(dateColumn); 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 -> { walletChangeEventListener = wallet -> {
displayedTransactions.update(); displayedTransactions.update();
}; };
@ -215,12 +192,6 @@ public class TransactionsView extends ActivatableView<VBox, Void> {
tableView.setItems(sortedDisplayedTransactions); tableView.setItems(sortedDisplayedTransactions);
displayedTransactions.update(); displayedTransactions.update();
btcWalletService.addCoinsReceivedEventListener(walletCoinsReceivedEventListener);
btcWalletService.addCoinsSentEventListener(walletCoinsSentEventListener);
btcWalletService.addReorganizeEventListener(walletReorganizeEventListener);
btcWalletService.addTransactionConfidenceEventListener(transactionConfidenceEventListener);
btcWalletService.addKeyChainEventListener(keyChainEventListener);
btcWalletService.addScriptChangeEventListener(scriptsChangeEventListener);
btcWalletService.addChangeEventListener(walletChangeEventListener); btcWalletService.addChangeEventListener(walletChangeEventListener);
scene = root.getScene(); scene = root.getScene();
@ -257,12 +228,6 @@ public class TransactionsView extends ActivatableView<VBox, Void> {
protected void deactivate() { protected void deactivate() {
sortedDisplayedTransactions.comparatorProperty().unbind(); sortedDisplayedTransactions.comparatorProperty().unbind();
displayedTransactions.forEach(TransactionsListItem::cleanup); 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); btcWalletService.removeChangeEventListener(walletChangeEventListener);
if (scene != null) if (scene != null)

View file

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

View file

@ -108,6 +108,8 @@ Full node mode:
Lite 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` `--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._ _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 { message GetOffersRequest {
string direction = 1; string direction = 1;
string fiatCurrencyCode = 2; string currencyCode = 2;
} }
message GetOffersReply { message GetOffersReply {
@ -61,7 +61,7 @@ message GetOffersReply {
} }
message CreateOfferRequest { message CreateOfferRequest {
string currencyCode = 1; // TODO switch order w/ direction field in next PR string currencyCode = 1;
string direction = 2; string direction = 2;
uint64 price = 3; uint64 price = 3;
bool useMarketBasedPrice = 4; bool useMarketBasedPrice = 4;
@ -107,9 +107,11 @@ service PaymentAccounts {
} }
message CreatePaymentAccountRequest { message CreatePaymentAccountRequest {
string accountName = 1; string paymentMethodId = 1;
string accountNumber = 2; string accountName = 2;
string fiatCurrencyCode = 3; 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 { message CreatePaymentAccountReply {
@ -123,7 +125,7 @@ message GetPaymentAccountsReply {
} }
/////////////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////////////
// TradeStatistics // GetTradeStatistics
/////////////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////////////
service GetTradeStatistics { service GetTradeStatistics {

View file

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