Merge branch 'master' of github.com:bisq-network/bisq into release/v1.3.9

# Conflicts:
#	build.gradle
#	desktop/package/linux/Dockerfile
#	desktop/package/linux/package.sh
#	desktop/package/linux/release.sh
#	desktop/package/macosx/create_app.sh
#	desktop/package/macosx/finalize.sh
#	desktop/package/macosx/insert_snapshot_version.sh
#	desktop/package/windows/package.bat
#	desktop/package/windows/release.bat
#	relay/src/main/resources/version.txt
This commit is contained in:
Christoph Atteneder 2020-09-16 16:52:54 +02:00
commit 81df879881
No known key found for this signature in database
GPG Key ID: CD5DC1C529CDFD3B
38 changed files with 793 additions and 289 deletions

View File

@ -48,14 +48,14 @@
run ./bisq-cli --password="xyz" getversion
[ "$status" -eq 0 ]
echo "actual output: $output" >&2
[ "$output" = "1.3.7" ]
[ "$output" = "1.3.8" ]
}
@test "test getversion" {
run ./bisq-cli --password=xyz getversion
[ "$status" -eq 0 ]
echo "actual output: $output" >&2
[ "$output" = "1.3.7" ]
[ "$output" = "1.3.8" ]
}
@test "test setwalletpassword \"a b c\"" {

View File

@ -27,6 +27,8 @@ import joptsimple.OptionParser;
import joptsimple.OptionSet;
import joptsimple.OptionSpec;
import java.net.InetAddress;
import java.nio.file.Paths;
import java.io.File;
@ -169,7 +171,7 @@ public class ApiTestConfig {
ArgumentAcceptingOptionSpec<String> bitcoinRegtestHostOpt =
parser.accepts(BITCOIN_REGTEST_HOST, "Bitcoin Core regtest host")
.withRequiredArg()
.ofType(String.class).defaultsTo("localhost");
.ofType(String.class).defaultsTo(InetAddress.getLoopbackAddress().getHostAddress());
ArgumentAcceptingOptionSpec<Integer> bitcoinRpcPortOpt =
parser.accepts(BITCOIN_RPC_PORT, "Bitcoin Core rpc port (non-default)")

View File

@ -17,16 +17,20 @@
package bisq.apitest;
import java.net.InetAddress;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.ExecutionException;
import static bisq.apitest.config.BisqAppConfig.alicedaemon;
import static java.util.concurrent.TimeUnit.MILLISECONDS;
import bisq.apitest.config.ApiTestConfig;
import bisq.apitest.config.BisqAppConfig;
import bisq.apitest.method.BitcoinCliHelper;
import bisq.cli.GrpcStubs;
@ -57,34 +61,41 @@ import bisq.cli.GrpcStubs;
*/
public class ApiTestCase {
// The gRPC service stubs are used by method & scenario tests, but not e2e tests.
protected static GrpcStubs grpcStubs;
protected static Scaffold scaffold;
protected static ApiTestConfig config;
protected static BitcoinCliHelper bitcoinCli;
// gRPC service stubs are used by method & scenario tests, but not e2e tests.
private static final Map<BisqAppConfig, GrpcStubs> grpcStubsCache = new HashMap<>();
public static void setUpScaffold(String supportingApps)
throws InterruptedException, ExecutionException, IOException {
scaffold = new Scaffold(supportingApps).setUp();
config = scaffold.config;
bitcoinCli = new BitcoinCliHelper((config));
// For now, all grpc requests are sent to the alicedaemon, but this will need to
// be made configurable for new test cases that call arb or bob node daemons.
grpcStubs = new GrpcStubs("localhost", alicedaemon.apiPort, config.apiPassword);
}
public static void setUpScaffold(String[] params)
throws InterruptedException, ExecutionException, IOException {
scaffold = new Scaffold(params).setUp();
config = scaffold.config;
grpcStubs = new GrpcStubs("localhost", alicedaemon.apiPort, config.apiPassword);
}
public static void tearDownScaffold() {
scaffold.tearDown();
}
protected static GrpcStubs grpcStubs(BisqAppConfig bisqAppConfig) {
if (grpcStubsCache.containsKey(bisqAppConfig)) {
return grpcStubsCache.get(bisqAppConfig);
} else {
GrpcStubs stubs = new GrpcStubs(InetAddress.getLoopbackAddress().getHostAddress(),
bisqAppConfig.apiPort, config.apiPassword);
grpcStubsCache.put(bisqAppConfig, stubs);
return stubs;
}
}
protected void sleep(long ms) {
try {
MILLISECONDS.sleep(ms);

View File

@ -27,6 +27,7 @@ import org.junit.jupiter.api.Order;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.TestMethodOrder;
import static bisq.apitest.config.BisqAppConfig.alicedaemon;
import static java.util.concurrent.TimeUnit.MILLISECONDS;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.fail;
@ -57,7 +58,8 @@ public class GetBalanceTest extends MethodTest {
public void testGetBalance() {
// All tests depend on the DAO / regtest environment, and Alice's wallet is
// initialized with 10 BTC during the scaffolding setup.
var balance = grpcStubs.walletsService.getBalance(GetBalanceRequest.newBuilder().build()).getBalance();
var balance = grpcStubs(alicedaemon).walletsService
.getBalance(GetBalanceRequest.newBuilder().build()).getBalance();
assertEquals(1000000000, balance);
}

View File

@ -50,7 +50,8 @@ public class GetVersionTest extends MethodTest {
@Test
@Order(1)
public void testGetVersion() {
var version = grpcStubs.versionService.getVersion(GetVersionRequest.newBuilder().build()).getVersion();
var version = grpcStubs(alicedaemon).versionService
.getVersion(GetVersionRequest.newBuilder().build()).getVersion();
assertEquals(VERSION, version);
}

View File

@ -20,13 +20,17 @@ package bisq.apitest.method;
import bisq.proto.grpc.GetBalanceRequest;
import bisq.proto.grpc.GetFundingAddressesRequest;
import bisq.proto.grpc.LockWalletRequest;
import bisq.proto.grpc.RegisterDisputeAgentRequest;
import bisq.proto.grpc.RemoveWalletPasswordRequest;
import bisq.proto.grpc.SetWalletPasswordRequest;
import bisq.proto.grpc.UnlockWalletRequest;
import static bisq.common.app.DevEnv.DEV_PRIVILEGE_PRIV_KEY;
import bisq.apitest.ApiTestCase;
import bisq.apitest.config.BisqAppConfig;
public class MethodTest extends ApiTestCase {
@ -60,24 +64,31 @@ public class MethodTest extends ApiTestCase {
return GetFundingAddressesRequest.newBuilder().build();
}
protected final RegisterDisputeAgentRequest createRegisterDisputeAgentRequest(String disputeAgentType) {
return RegisterDisputeAgentRequest.newBuilder()
.setDisputeAgentType(disputeAgentType)
.setRegistrationKey(DEV_PRIVILEGE_PRIV_KEY).build();
}
// Convenience methods for calling frequently used & thoroughly tested gRPC services.
protected final long getBalance() {
return grpcStubs.walletsService.getBalance(createBalanceRequest()).getBalance();
protected final long getBalance(BisqAppConfig bisqAppConfig) {
return grpcStubs(bisqAppConfig).walletsService.getBalance(createBalanceRequest()).getBalance();
}
protected final void unlockWallet(String password, long timeout) {
protected final void unlockWallet(BisqAppConfig bisqAppConfig, String password, long timeout) {
//noinspection ResultOfMethodCallIgnored
grpcStubs.walletsService.unlockWallet(createUnlockWalletRequest(password, timeout));
grpcStubs(bisqAppConfig).walletsService.unlockWallet(createUnlockWalletRequest(password, timeout));
}
protected final void lockWallet() {
protected final void lockWallet(BisqAppConfig bisqAppConfig) {
//noinspection ResultOfMethodCallIgnored
grpcStubs.walletsService.lockWallet(createLockWalletRequest());
grpcStubs(bisqAppConfig).walletsService.lockWallet(createLockWalletRequest());
}
protected final String getUnusedBtcAddress() {
return grpcStubs.walletsService.getFundingAddresses(createGetFundingAddressesRequest())
protected final String getUnusedBtcAddress(BisqAppConfig bisqAppConfig) {
//noinspection OptionalGetWithoutIsPresent
return grpcStubs(bisqAppConfig).walletsService.getFundingAddresses(createGetFundingAddressesRequest())
.getAddressBalanceInfoList()
.stream()
.filter(a -> a.getBalance() == 0 && a.getNumConfirmations() == 0)

View File

@ -0,0 +1,108 @@
/*
* This file is part of Bisq.
*
* Bisq is free software: you can redistribute it and/or modify it
* under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or (at
* your option) any later version.
*
* Bisq is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
* License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Bisq. If not, see <http://www.gnu.org/licenses/>.
*/
package bisq.apitest.method;
import bisq.proto.grpc.RegisterDisputeAgentRequest;
import io.grpc.StatusRuntimeException;
import lombok.extern.slf4j.Slf4j;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Order;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.TestMethodOrder;
import static bisq.apitest.config.BisqAppConfig.arbdaemon;
import static bisq.common.app.DevEnv.DEV_PRIVILEGE_PRIV_KEY;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.fail;
import static org.junit.jupiter.api.MethodOrderer.OrderAnnotation;
@SuppressWarnings("ResultOfMethodCallIgnored")
@Slf4j
@TestMethodOrder(OrderAnnotation.class)
public class RegisterDisputeAgentsTest extends MethodTest {
@BeforeAll
public static void setUp() {
try {
setUpScaffold("bitcoind,seednode,arbdaemon");
} catch (Exception ex) {
fail(ex);
}
}
@Test
@Order(1)
public void testRegisterArbitratorShouldThrowException() {
var req =
createRegisterDisputeAgentRequest("arbitrator");
Throwable exception = assertThrows(StatusRuntimeException.class, () ->
grpcStubs(arbdaemon).disputeAgentsService.registerDisputeAgent(req));
assertEquals("INVALID_ARGUMENT: arbitrators must be registered in a Bisq UI",
exception.getMessage());
}
@Test
@Order(2)
public void testInvalidDisputeAgentTypeArgShouldThrowException() {
var req =
createRegisterDisputeAgentRequest("badagent");
Throwable exception = assertThrows(StatusRuntimeException.class, () ->
grpcStubs(arbdaemon).disputeAgentsService.registerDisputeAgent(req));
assertEquals("INVALID_ARGUMENT: unknown dispute agent type badagent",
exception.getMessage());
}
@Test
@Order(3)
public void testInvalidRegistrationKeyArgShouldThrowException() {
var req = RegisterDisputeAgentRequest.newBuilder()
.setDisputeAgentType("refundagent")
.setRegistrationKey("invalid" + DEV_PRIVILEGE_PRIV_KEY).build();
Throwable exception = assertThrows(StatusRuntimeException.class, () ->
grpcStubs(arbdaemon).disputeAgentsService.registerDisputeAgent(req));
assertEquals("INVALID_ARGUMENT: invalid registration key",
exception.getMessage());
}
@Test
@Order(4)
public void testRegisterMediator() {
var req =
createRegisterDisputeAgentRequest("mediator");
grpcStubs(arbdaemon).disputeAgentsService.registerDisputeAgent(req);
}
@Test
@Order(5)
public void testRegisterRefundAgent() {
var req =
createRegisterDisputeAgentRequest("refundagent");
grpcStubs(arbdaemon).disputeAgentsService.registerDisputeAgent(req);
}
@AfterAll
public static void tearDown() {
tearDownScaffold();
}
}

View File

@ -36,13 +36,13 @@ public class WalletProtectionTest extends MethodTest {
@Order(1)
public void testSetWalletPassword() {
var request = createSetWalletPasswordRequest("first-password");
grpcStubs.walletsService.setWalletPassword(request);
grpcStubs(alicedaemon).walletsService.setWalletPassword(request);
}
@Test
@Order(2)
public void testGetBalanceOnEncryptedWalletShouldThrowException() {
Throwable exception = assertThrows(StatusRuntimeException.class, this::getBalance);
Throwable exception = assertThrows(StatusRuntimeException.class, () -> getBalance(alicedaemon));
assertEquals("UNKNOWN: wallet is locked", exception.getMessage());
}
@ -50,11 +50,10 @@ public class WalletProtectionTest extends MethodTest {
@Order(3)
public void testUnlockWalletFor4Seconds() {
var request = createUnlockWalletRequest("first-password", 4);
grpcStubs.walletsService.unlockWallet(request);
getBalance(); // should not throw 'wallet locked' exception
grpcStubs(alicedaemon).walletsService.unlockWallet(request);
getBalance(alicedaemon); // should not throw 'wallet locked' exception
sleep(4500); // let unlock timeout expire
Throwable exception = assertThrows(StatusRuntimeException.class, this::getBalance);
Throwable exception = assertThrows(StatusRuntimeException.class, () -> getBalance(alicedaemon));
assertEquals("UNKNOWN: wallet is locked", exception.getMessage());
}
@ -62,20 +61,19 @@ public class WalletProtectionTest extends MethodTest {
@Order(4)
public void testGetBalanceAfterUnlockTimeExpiryShouldThrowException() {
var request = createUnlockWalletRequest("first-password", 3);
grpcStubs.walletsService.unlockWallet(request);
grpcStubs(alicedaemon).walletsService.unlockWallet(request);
sleep(4000); // let unlock timeout expire
Throwable exception = assertThrows(StatusRuntimeException.class, this::getBalance);
Throwable exception = assertThrows(StatusRuntimeException.class, () -> getBalance(alicedaemon));
assertEquals("UNKNOWN: wallet is locked", exception.getMessage());
}
@Test
@Order(5)
public void testLockWalletBeforeUnlockTimeoutExpiry() {
unlockWallet("first-password", 60);
unlockWallet(alicedaemon, "first-password", 60);
var request = createLockWalletRequest();
grpcStubs.walletsService.lockWallet(request);
Throwable exception = assertThrows(StatusRuntimeException.class, this::getBalance);
grpcStubs(alicedaemon).walletsService.lockWallet(request);
Throwable exception = assertThrows(StatusRuntimeException.class, () -> getBalance(alicedaemon));
assertEquals("UNKNOWN: wallet is locked", exception.getMessage());
}
@ -83,40 +81,39 @@ public class WalletProtectionTest extends MethodTest {
@Order(6)
public void testLockWalletWhenWalletAlreadyLockedShouldThrowException() {
var request = createLockWalletRequest();
Throwable exception = assertThrows(StatusRuntimeException.class, () ->
grpcStubs.walletsService.lockWallet(request));
grpcStubs(alicedaemon).walletsService.lockWallet(request));
assertEquals("UNKNOWN: wallet is already locked", exception.getMessage());
}
@Test
@Order(7)
public void testUnlockWalletTimeoutOverride() {
unlockWallet("first-password", 2);
unlockWallet(alicedaemon, "first-password", 2);
sleep(500); // override unlock timeout after 0.5s
unlockWallet("first-password", 6);
unlockWallet(alicedaemon, "first-password", 6);
sleep(5000);
getBalance(); // getbalance 5s after resetting unlock timeout to 6s
getBalance(alicedaemon); // getbalance 5s after resetting unlock timeout to 6s
}
@Test
@Order(8)
public void testSetNewWalletPassword() {
var request = createSetWalletPasswordRequest("first-password", "second-password");
grpcStubs.walletsService.setWalletPassword(request);
unlockWallet("second-password", 2);
getBalance();
var request = createSetWalletPasswordRequest(
"first-password", "second-password");
grpcStubs(alicedaemon).walletsService.setWalletPassword(request);
unlockWallet(alicedaemon, "second-password", 2);
getBalance(alicedaemon);
sleep(2500); // allow time for wallet save
}
@Test
@Order(9)
public void testSetNewWalletPasswordWithIncorrectNewPasswordShouldThrowException() {
var request = createSetWalletPasswordRequest("bad old password", "irrelevant");
var request = createSetWalletPasswordRequest(
"bad old password", "irrelevant");
Throwable exception = assertThrows(StatusRuntimeException.class, () ->
grpcStubs.walletsService.setWalletPassword(request));
grpcStubs(alicedaemon).walletsService.setWalletPassword(request));
assertEquals("UNKNOWN: incorrect old password", exception.getMessage());
}
@ -124,8 +121,8 @@ public class WalletProtectionTest extends MethodTest {
@Order(10)
public void testRemoveNewWalletPassword() {
var request = createRemoveWalletPasswordRequest("second-password");
grpcStubs.walletsService.removeWalletPassword(request);
getBalance(); // should not throw 'wallet locked' exception
grpcStubs(alicedaemon).walletsService.removeWalletPassword(request);
getBalance(alicedaemon); // should not throw 'wallet locked' exception
}
@AfterAll

View File

@ -26,6 +26,7 @@ import org.junit.jupiter.api.Order;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.TestMethodOrder;
import static bisq.apitest.config.BisqAppConfig.alicedaemon;
import static java.util.concurrent.TimeUnit.MILLISECONDS;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.fail;
@ -48,16 +49,17 @@ public class FundWalletScenarioTest extends ScenarioTest {
@Test
@Order(1)
public void testFundWallet() {
long balance = getBalance(); // bisq wallet was initialized with 10 btc
// bisq wallet was initialized with 10 btc
long balance = getBalance(alicedaemon);
assertEquals(1000000000, balance);
String unusedAddress = getUnusedBtcAddress();
String unusedAddress = getUnusedBtcAddress(alicedaemon);
bitcoinCli.sendToAddress(unusedAddress, "2.5");
bitcoinCli.generateBlocks(1);
sleep(1500);
balance = getBalance();
balance = getBalance(alicedaemon);
assertEquals(1250000000L, balance); // new balance is 12.5 btc
}

View File

@ -63,7 +63,7 @@ configure(subprojects) {
loggingVersion = '1.2'
lombokVersion = '1.18.2'
mockitoVersion = '3.0.0'
netlayerVersion = '0.6.7'
netlayerVersion = '0.6.8'
protobufVersion = '3.10.0'
protocVersion = protobufVersion
pushyVersion = '0.13.2'
@ -497,7 +497,7 @@ configure(project(':pricenode')) {
test {
useJUnitPlatform()
// Disabled by default, since spot provider tests include connections to external API endpoints
// Can be enabled by adding -Dtest.pricenode.includeSpotProviderTests=true to the gradle command:
// ./gradlew test -Dtest.pricenode.includeSpotProviderTests=true

View File

@ -17,6 +17,7 @@
package bisq.cli;
import bisq.proto.grpc.DisputeAgentsGrpc;
import bisq.proto.grpc.GetVersionGrpc;
import bisq.proto.grpc.OffersGrpc;
import bisq.proto.grpc.PaymentAccountsGrpc;
@ -29,6 +30,7 @@ import static java.util.concurrent.TimeUnit.SECONDS;
public class GrpcStubs {
public final DisputeAgentsGrpc.DisputeAgentsBlockingStub disputeAgentsService;
public final GetVersionGrpc.GetVersionBlockingStub versionService;
public final OffersGrpc.OffersBlockingStub offersService;
public final PaymentAccountsGrpc.PaymentAccountsBlockingStub paymentAccountsService;
@ -46,6 +48,7 @@ public class GrpcStubs {
}
}));
this.disputeAgentsService = DisputeAgentsGrpc.newBlockingStub(channel).withCallCredentials(credentials);
this.versionService = GetVersionGrpc.newBlockingStub(channel).withCallCredentials(credentials);
this.offersService = OffersGrpc.newBlockingStub(channel).withCallCredentials(credentials);
this.paymentAccountsService = PaymentAccountsGrpc.newBlockingStub(channel).withCallCredentials(credentials);

View File

@ -102,7 +102,9 @@ public class FileUtil {
deleteDirectory(file, null, true);
}
public static void deleteDirectory(File file, @Nullable File exclude, boolean ignoreLockedFiles) throws IOException {
public static void deleteDirectory(File file,
@Nullable File exclude,
boolean ignoreLockedFiles) throws IOException {
boolean excludeFileFound = false;
if (file.isDirectory()) {
File[] files = file.listFiles();
@ -156,7 +158,8 @@ public class FileUtil {
return !file.canWrite();
}
public static void resourceToFile(String resourcePath, File destinationFile) throws ResourceNotFoundException, IOException {
public static void resourceToFile(String resourcePath,
File destinationFile) throws ResourceNotFoundException, IOException {
try (InputStream inputStream = ClassLoader.getSystemClassLoader().getResourceAsStream(resourcePath)) {
if (inputStream == null) {
throw new ResourceNotFoundException(resourcePath);
@ -182,6 +185,20 @@ public class FileUtil {
}
}
public static void copyFile(File origin, File target) throws IOException {
if (!origin.exists()) {
return;
}
try {
Files.copy(origin, target);
} catch (IOException e) {
log.error("Copy file failed", e);
throw new IOException("Failed to copy " + origin + " to " + target);
}
}
public static void copyDirectory(File source, File destination) throws IOException {
FileUtils.copyDirectory(source, destination);
}

View File

@ -20,7 +20,6 @@ package bisq.core.account.witness;
import bisq.core.account.sign.SignedWitness;
import bisq.core.account.sign.SignedWitnessService;
import bisq.core.filter.FilterManager;
import bisq.core.filter.PaymentAccountFilter;
import bisq.core.locale.CurrencyUtil;
import bisq.core.locale.Res;
import bisq.core.offer.Offer;
@ -718,10 +717,8 @@ public class AccountAgeWitnessService {
filterManager.isCurrencyBanned(dispute.getContract().getOfferPayload().getCurrencyCode()) ||
filterManager.isPaymentMethodBanned(
PaymentMethod.getPaymentMethodById(dispute.getContract().getPaymentMethodId())) ||
filterManager.arePeersPaymentAccountDataBanned(dispute.getContract().getBuyerPaymentAccountPayload(),
new PaymentAccountFilter[1]) ||
filterManager.arePeersPaymentAccountDataBanned(dispute.getContract().getSellerPaymentAccountPayload(),
new PaymentAccountFilter[1]) ||
filterManager.arePeersPaymentAccountDataBanned(dispute.getContract().getBuyerPaymentAccountPayload()) ||
filterManager.arePeersPaymentAccountDataBanned(dispute.getContract().getSellerPaymentAccountPayload()) ||
filterManager.isWitnessSignerPubKeyBanned(
Utils.HEX.encode(dispute.getContract().getBuyerPubKeyRing().getSignaturePubKeyBytes())) ||
filterManager.isWitnessSignerPubKeyBanned(

View File

@ -47,26 +47,38 @@ import lombok.extern.slf4j.Slf4j;
@Slf4j
public class CoreApi {
private final CoreDisputeAgentsService coreDisputeAgentsService;
private final CoreOffersService coreOffersService;
private final CorePaymentAccountsService paymentAccountsService;
private final CoreWalletsService walletsService;
private final TradeStatisticsManager tradeStatisticsManager;
@Inject
public CoreApi(CoreOffersService coreOffersService,
public CoreApi(CoreDisputeAgentsService coreDisputeAgentsService,
CoreOffersService coreOffersService,
CorePaymentAccountsService paymentAccountsService,
CoreWalletsService walletsService,
TradeStatisticsManager tradeStatisticsManager) {
this.coreDisputeAgentsService = coreDisputeAgentsService;
this.coreOffersService = coreOffersService;
this.paymentAccountsService = paymentAccountsService;
this.walletsService = walletsService;
this.tradeStatisticsManager = tradeStatisticsManager;
}
@SuppressWarnings("SameReturnValue")
public String getVersion() {
return Version.VERSION;
}
///////////////////////////////////////////////////////////////////////////////////////////
// Dispute Agents
///////////////////////////////////////////////////////////////////////////////////////////
public void registerDisputeAgent(String disputeAgentType, String registrationKey) {
coreDisputeAgentsService.registerDisputeAgent(disputeAgentType, registrationKey);
}
///////////////////////////////////////////////////////////////////////////////////////////
// Offers
///////////////////////////////////////////////////////////////////////////////////////////

View File

@ -0,0 +1,144 @@
/*
* This file is part of Bisq.
*
* Bisq is free software: you can redistribute it and/or modify it
* under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or (at
* your option) any later version.
*
* Bisq is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
* License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Bisq. If not, see <http://www.gnu.org/licenses/>.
*/
package bisq.core.api;
import bisq.core.support.dispute.mediation.mediator.Mediator;
import bisq.core.support.dispute.mediation.mediator.MediatorManager;
import bisq.core.support.dispute.refund.refundagent.RefundAgent;
import bisq.core.support.dispute.refund.refundagent.RefundAgentManager;
import bisq.network.p2p.NodeAddress;
import bisq.network.p2p.P2PService;
import bisq.common.config.Config;
import bisq.common.crypto.KeyRing;
import org.bitcoinj.core.ECKey;
import javax.inject.Inject;
import java.util.Arrays;
import java.util.Date;
import java.util.List;
import java.util.Objects;
import lombok.extern.slf4j.Slf4j;
import static bisq.common.app.DevEnv.DEV_PRIVILEGE_PRIV_KEY;
import static java.net.InetAddress.getLoopbackAddress;
@Slf4j
class CoreDisputeAgentsService {
private final Config config;
private final KeyRing keyRing;
private final MediatorManager mediatorManager;
private final RefundAgentManager refundAgentManager;
private final P2PService p2PService;
private final NodeAddress nodeAddress;
private final List<String> languageCodes;
@Inject
public CoreDisputeAgentsService(Config config,
KeyRing keyRing,
MediatorManager mediatorManager,
RefundAgentManager refundAgentManager,
P2PService p2PService) {
this.config = config;
this.keyRing = keyRing;
this.mediatorManager = mediatorManager;
this.refundAgentManager = refundAgentManager;
this.p2PService = p2PService;
this.nodeAddress = new NodeAddress(getLoopbackAddress().getHostAddress(), config.nodePort);
this.languageCodes = Arrays.asList("de", "en", "es", "fr");
}
public void registerDisputeAgent(String disputeAgentType, String registrationKey) {
if (!p2PService.isBootstrapped())
throw new IllegalStateException("p2p service is not bootstrapped yet");
if (config.baseCurrencyNetwork.isMainnet()
|| config.baseCurrencyNetwork.isDaoBetaNet()
|| !config.useLocalhostForP2P)
throw new IllegalStateException("dispute agents must be registered in a Bisq UI");
if (!registrationKey.equals(DEV_PRIVILEGE_PRIV_KEY))
throw new IllegalArgumentException("invalid registration key");
ECKey ecKey;
String signature;
switch (disputeAgentType) {
case "arbitrator":
throw new IllegalArgumentException("arbitrators must be registered in a Bisq UI");
case "mediator":
ecKey = mediatorManager.getRegistrationKey(registrationKey);
signature = mediatorManager.signStorageSignaturePubKey(Objects.requireNonNull(ecKey));
registerMediator(nodeAddress, languageCodes, ecKey, signature);
return;
case "refundagent":
ecKey = refundAgentManager.getRegistrationKey(registrationKey);
signature = refundAgentManager.signStorageSignaturePubKey(Objects.requireNonNull(ecKey));
registerRefundAgent(nodeAddress, languageCodes, ecKey, signature);
return;
default:
throw new IllegalArgumentException("unknown dispute agent type " + disputeAgentType);
}
}
private void registerMediator(NodeAddress nodeAddress,
List<String> languageCodes,
ECKey ecKey,
String signature) {
Mediator mediator = new Mediator(nodeAddress,
keyRing.getPubKeyRing(),
languageCodes,
new Date().getTime(),
ecKey.getPubKey(),
signature,
null,
null,
null
);
mediatorManager.addDisputeAgent(mediator, () -> {
}, errorMessage -> {
});
mediatorManager.getDisputeAgentByNodeAddress(nodeAddress).orElseThrow(() ->
new IllegalStateException("could not register mediator"));
}
private void registerRefundAgent(NodeAddress nodeAddress,
List<String> languageCodes,
ECKey ecKey,
String signature) {
RefundAgent refundAgent = new RefundAgent(nodeAddress,
keyRing.getPubKeyRing(),
languageCodes,
new Date().getTime(),
ecKey.getPubKey(),
signature,
null,
null,
null
);
refundAgentManager.addDisputeAgent(refundAgent, () -> {
}, errorMessage -> {
});
refundAgentManager.getDisputeAgentByNodeAddress(nodeAddress).orElseThrow(() ->
new IllegalStateException("could not register refund agent"));
}
}

View File

@ -186,10 +186,6 @@ public final class AddressEntry implements PersistablePayload {
return context == Context.MULTI_SIG || context == Context.TRADE_PAYOUT;
}
public boolean isTradable() {
return isOpenOffer() || isTrade();
}
public Coin getCoinLockedInMultiSig() {
return Coin.valueOf(coinLockedInMultiSig);
}
@ -197,9 +193,10 @@ public final class AddressEntry implements PersistablePayload {
@Override
public String toString() {
return "AddressEntry{" +
"offerId='" + getOfferId() + '\'' +
"address=" + address +
", context=" + context +
", address=" + getAddressString() +
'}';
", offerId='" + offerId + '\'' +
", coinLockedInMultiSig=" + coinLockedInMultiSig +
"}";
}
}

View File

@ -17,38 +17,41 @@
package bisq.core.btc.model;
import bisq.common.config.Config;
import bisq.common.proto.persistable.PersistedDataHost;
import bisq.common.proto.persistable.UserThreadMappedPersistableEnvelope;
import bisq.common.storage.Storage;
import com.google.protobuf.Message;
import org.bitcoinj.core.Address;
import org.bitcoinj.core.Transaction;
import org.bitcoinj.crypto.DeterministicKey;
import org.bitcoinj.wallet.Wallet;
import com.google.inject.Inject;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import com.google.common.collect.ImmutableList;
import java.util.HashSet;
import java.util.Objects;
import java.util.Set;
import java.util.concurrent.CopyOnWriteArraySet;
import java.util.stream.Collectors;
import lombok.Getter;
import lombok.ToString;
import lombok.extern.slf4j.Slf4j;
/**
* The List supporting our persistence solution.
* The AddressEntries was previously stored as list, now as hashSet. We still keep the old name to reflect the
* associated protobuf message.
*/
@ToString
@Slf4j
public final class AddressEntryList implements UserThreadMappedPersistableEnvelope, PersistedDataHost {
transient private Storage<AddressEntryList> storage;
transient private Wallet wallet;
@Getter
private List<AddressEntry> list;
private final Set<AddressEntry> entrySet = new CopyOnWriteArraySet<>();
@Inject
public AddressEntryList(Storage<AddressEntryList> storage) {
@ -58,8 +61,10 @@ public final class AddressEntryList implements UserThreadMappedPersistableEnvelo
@Override
public void readPersisted() {
AddressEntryList persisted = storage.initAndGetPersisted(this, 50);
if (persisted != null)
list = new ArrayList<>(persisted.getList());
if (persisted != null) {
entrySet.clear();
entrySet.addAll(persisted.entrySet);
}
}
@ -67,22 +72,22 @@ public final class AddressEntryList implements UserThreadMappedPersistableEnvelo
// PROTO BUFFER
///////////////////////////////////////////////////////////////////////////////////////////
private AddressEntryList(List<AddressEntry> list) {
this.list = list;
private AddressEntryList(Set<AddressEntry> entrySet) {
this.entrySet.addAll(entrySet);
}
public static AddressEntryList fromProto(protobuf.AddressEntryList proto) {
return new AddressEntryList(new ArrayList<>(proto.getAddressEntryList().stream().map(AddressEntry::fromProto).collect(Collectors.toList())));
Set<AddressEntry> entrySet = proto.getAddressEntryList().stream()
.map(AddressEntry::fromProto)
.collect(Collectors.toSet());
return new AddressEntryList(entrySet);
}
@Override
public Message toProtoMessage() {
// We clone list as we got ConcurrentModificationExceptions
List<AddressEntry> clone = new ArrayList<>(list);
List<protobuf.AddressEntry> addressEntries = clone.stream()
Set<protobuf.AddressEntry> addressEntries = entrySet.stream()
.map(AddressEntry::toProtoMessage)
.collect(Collectors.toList());
.collect(Collectors.toSet());
return protobuf.PersistableEnvelope.newBuilder()
.setAddressEntryList(protobuf.AddressEntryList.newBuilder()
.addAllAddressEntry(addressEntries))
@ -97,29 +102,50 @@ public final class AddressEntryList implements UserThreadMappedPersistableEnvelo
public void onWalletReady(Wallet wallet) {
this.wallet = wallet;
if (list != null) {
list.forEach(addressEntry -> {
if (!entrySet.isEmpty()) {
Set<AddressEntry> toBeRemoved = new HashSet<>();
entrySet.forEach(addressEntry -> {
DeterministicKey keyFromPubHash = (DeterministicKey) wallet.findKeyFromPubHash(addressEntry.getPubKeyHash());
if (keyFromPubHash != null) {
addressEntry.setDeterministicKey(keyFromPubHash);
Address addressFromKey = keyFromPubHash.toAddress(Config.baseCurrencyNetworkParameters());
// We want to ensure key and address matches in case we have address in entry available already
if (addressEntry.getAddress() == null || addressFromKey.equals(addressEntry.getAddress())) {
addressEntry.setDeterministicKey(keyFromPubHash);
} else {
log.error("We found an address entry without key but cannot apply the key as the address " +
"is not matching. " +
"We remove that entry as it seems it is not compatible with our wallet. " +
"addressFromKey={}, addressEntry.getAddress()={}",
addressFromKey, addressEntry.getAddress());
toBeRemoved.add(addressEntry);
}
} else {
log.error("Key from addressEntry not found in that wallet " + addressEntry.toString());
log.error("Key from addressEntry {} not found in that wallet. We remove that entry. " +
"This is expected at restore from seeds.", addressEntry.toString());
toBeRemoved.add(addressEntry);
}
});
} else {
list = new ArrayList<>();
add(new AddressEntry(wallet.freshReceiveKey(), AddressEntry.Context.ARBITRATOR));
// In case we restore from seed words and have balance we need to add the relevant addresses to our list.
// IssuedReceiveAddresses does not contain all addresses where we expect balance so we need to listen to
// incoming txs at blockchain sync to add the rest.
if (wallet.getBalance().isPositive()) {
wallet.getIssuedReceiveAddresses().forEach(address -> {
log.info("Create AddressEntry for IssuedReceiveAddress. address={}", address.toString());
add(new AddressEntry((DeterministicKey) wallet.findKeyFromPubHash(address.getHash160()), AddressEntry.Context.AVAILABLE));
});
}
persist();
toBeRemoved.forEach(entrySet::remove);
} else {
// As long the old arbitration domain is not removed from the code base we still support it here.
entrySet.add(new AddressEntry(wallet.freshReceiveKey(), AddressEntry.Context.ARBITRATOR));
}
// In case we restore from seed words and have balance we need to add the relevant addresses to our list.
// IssuedReceiveAddresses does not contain all addresses where we expect balance so we need to listen to
// incoming txs at blockchain sync to add the rest.
if (wallet.getBalance().isPositive()) {
wallet.getIssuedReceiveAddresses().stream()
.filter(this::isAddressNotInEntries)
.forEach(address -> {
log.info("Create AddressEntry for IssuedReceiveAddress. address={}", address.toString());
DeterministicKey key = (DeterministicKey) wallet.findKeyFromPubHash(address.getHash160());
if (key != null) {
// Address will be derived from key in getAddress method
entrySet.add(new AddressEntry(key, AddressEntry.Context.AVAILABLE));
}
});
}
// We add those listeners to get notified about potential new transactions and
@ -127,62 +153,41 @@ public final class AddressEntryList implements UserThreadMappedPersistableEnvelo
// but can help as well in case the addressEntry list would miss an address where the wallet was received
// funds (e.g. if the user sends funds to an address which has not been provided in the main UI - like from the
// wallet details window).
wallet.addCoinsReceivedEventListener((w, tx, prevBalance, newBalance) -> {
updateList(tx);
wallet.addCoinsReceivedEventListener((wallet1, tx, prevBalance, newBalance) -> {
maybeAddNewAddressEntry(tx);
});
wallet.addCoinsSentEventListener((w, tx, prevBalance, newBalance) -> {
updateList(tx);
wallet.addCoinsSentEventListener((wallet1, tx, prevBalance, newBalance) -> {
maybeAddNewAddressEntry(tx);
});
persist();
}
private void updateList(Transaction tx) {
tx.getOutputs().stream()
.filter(output -> output.isMine(wallet))
.map(output -> output.getAddressFromP2PKHScript(wallet.getNetworkParameters()))
.filter(Objects::nonNull)
.filter(address -> !listContainsEntryWithAddress(address.toBase58()))
.map(address -> (DeterministicKey) wallet.findKeyFromPubHash(address.getHash160()))
.filter(Objects::nonNull)
.map(deterministicKey -> new AddressEntry(deterministicKey, AddressEntry.Context.AVAILABLE))
.forEach(addressEntry -> list.add(addressEntry));
public ImmutableList<AddressEntry> getAddressEntriesAsListImmutable() {
return ImmutableList.copyOf(entrySet);
}
private boolean listContainsEntryWithAddress(String addressString) {
return list.stream().anyMatch(addressEntry -> Objects.equals(addressEntry.getAddressString(), addressString));
}
private boolean add(AddressEntry addressEntry) {
return list.add(addressEntry);
}
private boolean remove(AddressEntry addressEntry) {
return list.remove(addressEntry);
}
public AddressEntry addAddressEntry(AddressEntry addressEntry) {
boolean changed = add(addressEntry);
if (changed)
public void addAddressEntry(AddressEntry addressEntry) {
boolean setChangedByAdd = entrySet.add(addressEntry);
if (setChangedByAdd)
persist();
return addressEntry;
}
public void swapTradeToSavings(String offerId) {
list.stream().filter(addressEntry -> offerId.equals(addressEntry.getOfferId()))
.findAny().ifPresent(this::swapToAvailable);
}
public void swapToAvailable(AddressEntry addressEntry) {
boolean changed1 = remove(addressEntry);
boolean changed2 = add(new AddressEntry(addressEntry.getKeyPair(), AddressEntry.Context.AVAILABLE));
if (changed1 || changed2)
boolean setChangedByRemove = entrySet.remove(addressEntry);
boolean setChangedByAdd = entrySet.add(new AddressEntry(addressEntry.getKeyPair(), AddressEntry.Context.AVAILABLE));
if (setChangedByRemove || setChangedByAdd) {
persist();
}
}
public AddressEntry swapAvailableToAddressEntryWithOfferId(AddressEntry addressEntry, AddressEntry.Context context, String offerId) {
boolean changed1 = remove(addressEntry);
public AddressEntry swapAvailableToAddressEntryWithOfferId(AddressEntry addressEntry,
AddressEntry.Context context,
String offerId) {
boolean setChangedByRemove = entrySet.remove(addressEntry);
final AddressEntry newAddressEntry = new AddressEntry(addressEntry.getKeyPair(), context, offerId);
boolean changed2 = add(newAddressEntry);
if (changed1 || changed2)
boolean setChangedByAdd = entrySet.add(newAddressEntry);
if (setChangedByRemove || setChangedByAdd)
persist();
return newAddressEntry;
@ -192,7 +197,24 @@ public final class AddressEntryList implements UserThreadMappedPersistableEnvelo
storage.queueUpForSave(50);
}
public Stream<AddressEntry> stream() {
return list.stream();
///////////////////////////////////////////////////////////////////////////////////////////
// Private
///////////////////////////////////////////////////////////////////////////////////////////
private void maybeAddNewAddressEntry(Transaction tx) {
tx.getOutputs().stream()
.filter(output -> output.isMine(wallet))
.map(output -> output.getAddressFromP2PKHScript(wallet.getNetworkParameters()))
.filter(Objects::nonNull)
.filter(this::isAddressNotInEntries)
.map(address -> (DeterministicKey) wallet.findKeyFromPubHash(address.getHash160()))
.filter(Objects::nonNull)
.map(deterministicKey -> new AddressEntry(deterministicKey, AddressEntry.Context.AVAILABLE))
.forEach(this::addAddressEntry);
}
private boolean isAddressNotInEntries(Address address) {
return entrySet.stream().noneMatch(e -> address.equals(e.getAddress()));
}
}

View File

@ -59,7 +59,6 @@ import javax.inject.Inject;
import javax.inject.Named;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.collect.ImmutableList;
import com.google.common.util.concurrent.Service;
import org.apache.commons.lang3.StringUtils;
@ -498,7 +497,7 @@ public class WalletsSetup {
}
public Set<Address> getAddressesByContext(@SuppressWarnings("SameParameterValue") AddressEntry.Context context) {
return ImmutableList.copyOf(addressEntryList.getList()).stream()
return addressEntryList.getAddressEntriesAsListImmutable().stream()
.filter(addressEntry -> addressEntry.getContext() == context)
.map(AddressEntry::getAddress)
.collect(Collectors.toSet());

View File

@ -47,7 +47,6 @@ import org.bitcoinj.wallet.Wallet;
import javax.inject.Inject;
import com.google.common.base.Preconditions;
import com.google.common.collect.ImmutableList;
import com.google.common.util.concurrent.FutureCallback;
import com.google.common.util.concurrent.Futures;
@ -108,8 +107,8 @@ public class BtcWalletService extends WalletService {
void decryptWallet(@NotNull KeyParameter key) {
super.decryptWallet(key);
addressEntryList.stream().forEach(e -> {
final DeterministicKey keyPair = e.getKeyPair();
addressEntryList.getAddressEntriesAsListImmutable().stream().forEach(e -> {
DeterministicKey keyPair = e.getKeyPair();
if (keyPair.isEncrypted())
e.setDeterministicKey(keyPair.decrypt(key));
});
@ -119,8 +118,8 @@ public class BtcWalletService extends WalletService {
@Override
void encryptWallet(KeyCrypterScrypt keyCrypterScrypt, KeyParameter key) {
super.encryptWallet(keyCrypterScrypt, key);
addressEntryList.stream().forEach(e -> {
final DeterministicKey keyPair = e.getKeyPair();
addressEntryList.getAddressEntriesAsListImmutable().stream().forEach(e -> {
DeterministicKey keyPair = e.getKeyPair();
if (keyPair.isEncrypted())
e.setDeterministicKey(keyPair.encrypt(keyCrypterScrypt, key));
});
@ -157,12 +156,18 @@ public class BtcWalletService extends WalletService {
// Proposal txs
///////////////////////////////////////////////////////////////////////////////////////////
public Transaction completePreparedReimbursementRequestTx(Coin issuanceAmount, Address issuanceAddress, Transaction feeTx, byte[] opReturnData)
public Transaction completePreparedReimbursementRequestTx(Coin issuanceAmount,
Address issuanceAddress,
Transaction feeTx,
byte[] opReturnData)
throws TransactionVerificationException, WalletException, InsufficientMoneyException {
return completePreparedProposalTx(feeTx, opReturnData, issuanceAmount, issuanceAddress);
}
public Transaction completePreparedCompensationRequestTx(Coin issuanceAmount, Address issuanceAddress, Transaction feeTx, byte[] opReturnData)
public Transaction completePreparedCompensationRequestTx(Coin issuanceAmount,
Address issuanceAddress,
Transaction feeTx,
byte[] opReturnData)
throws TransactionVerificationException, WalletException, InsufficientMoneyException {
return completePreparedProposalTx(feeTx, opReturnData, issuanceAmount, issuanceAddress);
}
@ -292,7 +297,8 @@ public class BtcWalletService extends WalletService {
return completePreparedBsqTxWithBtcFee(preparedTx, opReturnData);
}
private Transaction completePreparedBsqTxWithBtcFee(Transaction preparedTx, byte[] opReturnData) throws InsufficientMoneyException, TransactionVerificationException, WalletException {
private Transaction completePreparedBsqTxWithBtcFee(Transaction preparedTx,
byte[] opReturnData) throws InsufficientMoneyException, TransactionVerificationException, WalletException {
// Remember index for first BTC input
int indexOfBtcFirstInput = preparedTx.getInputs().size();
@ -306,7 +312,8 @@ public class BtcWalletService extends WalletService {
return tx;
}
private Transaction addInputsForMinerFee(Transaction preparedTx, byte[] opReturnData) throws InsufficientMoneyException {
private Transaction addInputsForMinerFee(Transaction preparedTx,
byte[] opReturnData) throws InsufficientMoneyException {
// safety check counter to avoid endless loops
int counter = 0;
// estimated size of input sig
@ -421,7 +428,9 @@ public class BtcWalletService extends WalletService {
return completePreparedBsqTx(preparedBsqTx, isSendTx, null);
}
public Transaction completePreparedBsqTx(Transaction preparedBsqTx, boolean useCustomTxFee, @Nullable byte[] opReturnData) throws
public Transaction completePreparedBsqTx(Transaction preparedBsqTx,
boolean useCustomTxFee,
@Nullable byte[] opReturnData) throws
TransactionVerificationException, WalletException, InsufficientMoneyException {
// preparedBsqTx has following structure:
@ -545,7 +554,8 @@ public class BtcWalletService extends WalletService {
// AddressEntry
///////////////////////////////////////////////////////////////////////////////////////////
public Optional<AddressEntry> getAddressEntry(String offerId, @SuppressWarnings("SameParameterValue") AddressEntry.Context context) {
public Optional<AddressEntry> getAddressEntry(String offerId,
@SuppressWarnings("SameParameterValue") AddressEntry.Context context) {
return getAddressEntryListAsImmutableList().stream()
.filter(e -> offerId.equals(e.getOfferId()))
.filter(e -> context == e.getContext())
@ -592,17 +602,14 @@ public class BtcWalletService extends WalletService {
return getOrCreateAddressEntry(context, addressEntry);
}
public AddressEntry getNewAddressEntry(String offerId, AddressEntry.Context context) {
public void getNewAddressEntry(String offerId, AddressEntry.Context context) {
AddressEntry entry = new AddressEntry(wallet.freshReceiveKey(), context, offerId);
addressEntryList.addAddressEntry(entry);
return entry;
}
public AddressEntry recoverAddressEntry(String offerId, String address, AddressEntry.Context context) {
var available = findAddressEntry(address, AddressEntry.Context.AVAILABLE);
if (!available.isPresent())
return null;
return addressEntryList.swapAvailableToAddressEntryWithOfferId(available.get(), context, offerId);
public void recoverAddressEntry(String offerId, String address, AddressEntry.Context context) {
findAddressEntry(address, AddressEntry.Context.AVAILABLE).ifPresent(addressEntry ->
addressEntryList.swapAvailableToAddressEntryWithOfferId(addressEntry, context, offerId));
}
private AddressEntry getOrCreateAddressEntry(AddressEntry.Context context, Optional<AddressEntry> addressEntry) {
@ -655,20 +662,18 @@ public class BtcWalletService extends WalletService {
}
public List<AddressEntry> getAddressEntryListAsImmutableList() {
return ImmutableList.copyOf(addressEntryList.getList());
return addressEntryList.getAddressEntriesAsListImmutable();
}
public void swapTradeEntryToAvailableEntry(String offerId, AddressEntry.Context context) {
Optional<AddressEntry> addressEntryOptional = getAddressEntryListAsImmutableList().stream()
getAddressEntryListAsImmutableList().stream()
.filter(e -> offerId.equals(e.getOfferId()))
.filter(e -> context == e.getContext())
.findAny();
addressEntryOptional.ifPresent(e -> {
log.info("swap addressEntry with address {} and offerId {} from context {} to available",
e.getAddressString(), e.getOfferId(), context);
addressEntryList.swapToAvailable(e);
saveAddressEntryList();
});
.forEach(e -> {
log.info("swap addressEntry with address {} and offerId {} from context {} to available",
e.getAddressString(), e.getOfferId(), context);
addressEntryList.swapToAvailable(e);
});
}
public void resetAddressEntriesForOpenOffer(String offerId) {

View File

@ -371,24 +371,19 @@ public class FilterManager {
return requireUpdateToNewVersion;
}
public boolean arePeersPaymentAccountDataBanned(PaymentAccountPayload paymentAccountPayload,
PaymentAccountFilter[] appliedPaymentAccountFilter) {
public boolean arePeersPaymentAccountDataBanned(PaymentAccountPayload paymentAccountPayload) {
return getFilter() != null &&
getFilter().getBannedPaymentAccounts().stream()
.filter(paymentAccountFilter -> paymentAccountFilter.getPaymentMethodId().equals(
paymentAccountPayload.getPaymentMethodId()))
.anyMatch(paymentAccountFilter -> {
final boolean samePaymentMethodId = paymentAccountFilter.getPaymentMethodId().equals(
paymentAccountPayload.getPaymentMethodId());
if (samePaymentMethodId) {
try {
Method method = paymentAccountPayload.getClass().getMethod(paymentAccountFilter.getGetMethodName());
String result = (String) method.invoke(paymentAccountPayload);
appliedPaymentAccountFilter[0] = paymentAccountFilter;
return result.toLowerCase().equals(paymentAccountFilter.getValue().toLowerCase());
} catch (Throwable e) {
log.error(e.getMessage());
return false;
}
} else {
try {
Method method = paymentAccountPayload.getClass().getMethod(paymentAccountFilter.getGetMethodName());
// We invoke getter methods (no args), e.g. getHolderName
String valueFromInvoke = (String) method.invoke(paymentAccountPayload);
return valueFromInvoke.equalsIgnoreCase(paymentAccountFilter.getValue());
} catch (Throwable e) {
log.error(e.getMessage());
return false;
}
});

View File

@ -200,18 +200,19 @@ public class MultipleHolderNameDetection {
private Map<String, List<Dispute>> getAllDisputesByTraderMap() {
Map<String, List<Dispute>> allDisputesByTraderMap = new HashMap<>();
disputeManager.getDisputesAsObservableList()
.forEach(dispute -> {
disputeManager.getDisputesAsObservableList().stream()
.filter(dispute -> {
Contract contract = dispute.getContract();
PaymentAccountPayload paymentAccountPayload = isBuyer(dispute) ?
contract.getBuyerPaymentAccountPayload() :
contract.getSellerPaymentAccountPayload();
if (paymentAccountPayload instanceof PayloadWithHolderName) {
String traderPubKeyHash = getSigPubKeyHashAsHex(dispute);
allDisputesByTraderMap.putIfAbsent(traderPubKeyHash, new ArrayList<>());
List<Dispute> disputes = allDisputesByTraderMap.get(traderPubKeyHash);
disputes.add(dispute);
}
return paymentAccountPayload instanceof PayloadWithHolderName;
})
.forEach(dispute -> {
String traderPubKeyHash = getSigPubKeyHashAsHex(dispute);
allDisputesByTraderMap.putIfAbsent(traderPubKeyHash, new ArrayList<>());
List<Dispute> disputes = allDisputesByTraderMap.get(traderPubKeyHash);
disputes.add(dispute);
});
return allDisputesByTraderMap;
}

View File

@ -18,7 +18,6 @@
package bisq.core.trade.protocol.tasks;
import bisq.core.filter.FilterManager;
import bisq.core.filter.PaymentAccountFilter;
import bisq.core.payment.payload.PaymentAccountPayload;
import bisq.core.trade.Trade;
@ -42,9 +41,8 @@ public class ApplyFilter extends TradeTask {
try {
runInterceptHook();
final NodeAddress nodeAddress = processModel.getTempTradingPeerNodeAddress();
NodeAddress nodeAddress = processModel.getTempTradingPeerNodeAddress();
PaymentAccountPayload paymentAccountPayload = checkNotNull(processModel.getTradingPeer().getPaymentAccountPayload());
final PaymentAccountFilter[] appliedPaymentAccountFilter = new PaymentAccountFilter[1];
FilterManager filterManager = processModel.getFilterManager();
if (nodeAddress != null && filterManager.isNodeAddressBanned(nodeAddress)) {
@ -56,13 +54,12 @@ public class ApplyFilter extends TradeTask {
} else if (trade.getOffer() != null && filterManager.isCurrencyBanned(trade.getOffer().getCurrencyCode())) {
failed("Currency is banned.\n" +
"Currency code=" + trade.getOffer().getCurrencyCode());
} else if (filterManager.isPaymentMethodBanned(trade.getOffer().getPaymentMethod())) {
} else if (filterManager.isPaymentMethodBanned(checkNotNull(trade.getOffer()).getPaymentMethod())) {
failed("Payment method is banned.\n" +
"Payment method=" + trade.getOffer().getPaymentMethod().getId());
} else if (filterManager.arePeersPaymentAccountDataBanned(paymentAccountPayload, appliedPaymentAccountFilter)) {
} else if (filterManager.arePeersPaymentAccountDataBanned(paymentAccountPayload)) {
failed("Other trader is banned by their trading account data.\n" +
"paymentAccountPayload=" + paymentAccountPayload.getPaymentDetails() + "\n" +
"banFilter=" + appliedPaymentAccountFilter[0].toString());
"paymentAccountPayload=" + paymentAccountPayload.getPaymentDetails());
} else if (filterManager.requireUpdateToNewVersionForTrading()) {
failed("Your version of Bisq is not compatible for trading anymore. " +
"Please update to the latest Bisq version at https://bisq.network/downloads.");

View File

@ -166,6 +166,7 @@ public class XmrTxProofService implements AssetTxProofService {
if (!preferences.findAutoConfirmSettings("XMR").isPresent()) {
log.error("AutoConfirmSettings is not present");
return;
}
autoConfirmSettings = preferences.findAutoConfirmSettings("XMR").get();

View File

@ -35,7 +35,7 @@ shared.no=No
shared.iUnderstand=I understand
shared.na=N/A
shared.shutDown=Shut down
shared.reportBug=Report bug at GitHub issues
shared.reportBug=Report bug on GitHub
shared.buyBitcoin=Buy bitcoin
shared.sellBitcoin=Sell bitcoin
shared.buyCurrency=Buy {0}
@ -94,21 +94,21 @@ shared.BTCMinMax=BTC (min - max)
shared.removeOffer=Remove offer
shared.dontRemoveOffer=Don't remove offer
shared.editOffer=Edit offer
shared.openLargeQRWindow=Open large QR-Code window
shared.openLargeQRWindow=Open large QR code window
shared.tradingAccount=Trading account
shared.faq=Visit FAQ web page
shared.faq=Visit FAQ page
shared.yesCancel=Yes, cancel
shared.nextStep=Next step
shared.selectTradingAccount=Select trading account
shared.fundFromSavingsWalletButton=Transfer funds from Bisq wallet
shared.fundFromExternalWalletButton=Open your external wallet for funding
shared.openDefaultWalletFailed=Opening a default Bitcoin wallet application has failed. Perhaps you don't have one installed?
shared.openDefaultWalletFailed=Failed to open a Bitcoin wallet application. Are you sure you have one installed?
shared.distanceInPercent=Distance in % from market price
shared.belowInPercent=Below % from market price
shared.aboveInPercent=Above % from market price
shared.enterPercentageValue=Enter % value
shared.OR=OR
shared.notEnoughFunds=You don''t have enough funds in your Bisq wallet.\nYou need {0} but you have only {1} in your Bisq wallet.\n\nPlease fund the trade from an external Bitcoin wallet or fund your Bisq wallet at \"Funds/Receive funds\".
shared.notEnoughFunds=You don''t have enough funds in your Bisq wallet for this transaction—{0} is needed but only {1} is available.\n\nPlease add funds from an external wallet, or fund your Bisq wallet at Funds > Receive Funds.
shared.waitingForFunds=Waiting for funds...
shared.depositTransactionId=Deposit transaction ID
shared.TheBTCBuyer=The BTC buyer
@ -116,22 +116,22 @@ shared.You=You
shared.reasonForPayment=Reason for payment
shared.sendingConfirmation=Sending confirmation...
shared.sendingConfirmationAgain=Please send confirmation again
shared.exportCSV=Export to csv
shared.exportCSV=Export to CSV
shared.exportJSON=Export to JSON
shared.noDateAvailable=No date available
shared.noDetailsAvailable=No details available
shared.notUsedYet=Not used yet
shared.date=Date
shared.sendFundsDetailsWithFee=Sending: {0}\nFrom address: {1}\nTo receiving address: {2}.\nRequired mining fee is: {3} ({4} satoshis/byte)\nTransaction size: {5} Kb\n\nThe recipient will receive: {6}\n\nAre you sure you want to withdraw this amount?
shared.sendFundsDetailsDust=Bisq detected that this transaction would create a change output which is below the minimum dust threshold (and not allowed by Bitcoin consensus rules). Instead, this dust ({0} satoshi{1}) will be added to the mining fee.\n\n\n
shared.sendFundsDetailsDust=Bisq detected that this transaction would create a change output which is below the minimum dust threshold (and therefore not allowed by Bitcoin consensus rules). Instead, this dust ({0} satoshi{1}) will be added to the mining fee.\n\n\n
shared.copyToClipboard=Copy to clipboard
shared.language=Language
shared.country=Country
shared.applyAndShutDown=Apply and shut down
shared.selectPaymentMethod=Select payment method
shared.accountNameAlreadyUsed=That account name is already used in a saved account.\nPlease use another name.
shared.accountNameAlreadyUsed=That account name is already used for another saved account.\nPlease choose another name.
shared.askConfirmDeleteAccount=Do you really want to delete the selected account?
shared.cannotDeleteAccount=You cannot delete that account because it is used in an open offer or in a trade.
shared.cannotDeleteAccount=You cannot delete that account because it is being used in an open offer (or in an open trade).
shared.noAccountsSetupYet=There are no accounts set up yet
shared.manageAccounts=Manage accounts
shared.addNewAccount=Add new account
@ -375,10 +375,10 @@ offerbook.deactivateOffer.failed=Deactivating of offer failed:\n{0}
offerbook.activateOffer.failed=Publishing of offer failed:\n{0}
offerbook.withdrawFundsHint=You can withdraw the funds you paid in from the {0} screen.
offerbook.warning.noTradingAccountForCurrency.headline=No trading account for selected currency
offerbook.warning.noTradingAccountForCurrency.msg=You don't have a trading account for the selected currency.\nDo you want to create an offer with one of your existing trading accounts?
offerbook.warning.noMatchingAccount.headline=No matching trading account.
offerbook.warning.noMatchingAccount.msg=To take this offer, you will need to set up a payment account using this payment method.\n\nWould you like to do this now?
offerbook.warning.noTradingAccountForCurrency.headline=No payment account for selected currency
offerbook.warning.noTradingAccountForCurrency.msg=You don't have a payment account set up for the selected currency.\n\nWould you like to create an offer for another currency instead?
offerbook.warning.noMatchingAccount.headline=No matching payment account.
offerbook.warning.noMatchingAccount.msg=This offer uses a payment method you haven't set up yet. \n\nWould you like to set up a new payment account now?
offerbook.warning.counterpartyTradeRestrictions=This offer cannot be taken due to counterparty trade restrictions
@ -1931,9 +1931,9 @@ dao.bond.bondedRoleType.DATA_RELAY_NODE_OPERATOR=Price node operator
# suppress inspection "UnusedProperty"
dao.bond.bondedRoleType.BTC_NODE_OPERATOR=Bitcoin node operator
# suppress inspection "UnusedProperty"
dao.bond.bondedRoleType.MARKETS_OPERATOR=Markets API operator
dao.bond.bondedRoleType.MARKETS_OPERATOR=Markets operator
# suppress inspection "UnusedProperty"
dao.bond.bondedRoleType.BSQ_EXPLORER_OPERATOR=BSQ explorer operator
dao.bond.bondedRoleType.BSQ_EXPLORER_OPERATOR=Explorer operator
# suppress inspection "UnusedProperty"
dao.bond.bondedRoleType.MOBILE_NOTIFICATIONS_RELAY_OPERATOR=Mobile notifications relay operator
# suppress inspection "UnusedProperty"
@ -2512,8 +2512,8 @@ offerDetailsWindow.confirm.taker=Confirm: Take offer to {0} bitcoin
offerDetailsWindow.creationDate=Creation date
offerDetailsWindow.makersOnion=Maker's onion address
qRCodeWindow.headline=QR-Code
qRCodeWindow.msg=Please use that QR-Code for funding your Bisq wallet from your external wallet.
qRCodeWindow.headline=QR Code
qRCodeWindow.msg=Please use this QR code for funding your Bisq wallet from your external wallet.
qRCodeWindow.request=Payment request:\n{0}
selectDepositTxWindow.headline=Select deposit transaction for dispute
@ -2859,8 +2859,8 @@ systemTray.tooltip=Bisq: A decentralized bitcoin exchange network
# GUI Util
####################################################################
guiUtil.miningFeeInfo=Please be sure that the mining fee used at your external wallet is \
at least {0} satoshis/byte. Otherwise the trade transactions cannot be confirmed and a trade would end up in a dispute.
guiUtil.miningFeeInfo=Please be sure that the mining fee used by your external wallet is \
at least {0} satoshis/byte. Otherwise the trade transactions may not be confirmed in time and the trade will end up in a dispute.
guiUtil.accountExport.savedToPath=Trading accounts saved to path:\n{0}
guiUtil.accountExport.noAccountSetup=You don't have trading accounts set up for exporting.
@ -3036,6 +3036,8 @@ Ideally you should specify the date your wallet seed was created.\n\n\n\
Are you sure you want to go ahead without specifying a wallet date?
seed.restore.success=Wallets restored successfully with the new seed words.\n\nYou need to shut down and restart the application.
seed.restore.error=An error occurred when restoring the wallets with seed words.{0}
seed.restore.openOffers.warn=You have open offers which will be removed if you restore from seed words.\n\
Are you sure that you want to continue?
####################################################################

View File

@ -218,7 +218,7 @@ public class AccountAgeWitnessServiceTest {
when(filterManager.isNodeAddressBanned(any())).thenReturn(false);
when(filterManager.isCurrencyBanned(any())).thenReturn(false);
when(filterManager.isPaymentMethodBanned(any())).thenReturn(false);
when(filterManager.arePeersPaymentAccountDataBanned(any(), any())).thenReturn(false);
when(filterManager.arePeersPaymentAccountDataBanned(any())).thenReturn(false);
when(filterManager.isWitnessSignerPubKeyBanned(any())).thenReturn(false);
when(chargeBackRisk.hasChargebackRisk(any(), any())).thenReturn(true);

View File

@ -0,0 +1,45 @@
package bisq.daemon.grpc;
import bisq.core.api.CoreApi;
import bisq.proto.grpc.DisputeAgentsGrpc;
import bisq.proto.grpc.RegisterDisputeAgentReply;
import bisq.proto.grpc.RegisterDisputeAgentRequest;
import io.grpc.Status;
import io.grpc.StatusRuntimeException;
import io.grpc.stub.StreamObserver;
import javax.inject.Inject;
import lombok.extern.slf4j.Slf4j;
@Slf4j
class GrpcDisputeAgentsService extends DisputeAgentsGrpc.DisputeAgentsImplBase {
private final CoreApi coreApi;
@Inject
public GrpcDisputeAgentsService(CoreApi coreApi) {
this.coreApi = coreApi;
}
@Override
public void registerDisputeAgent(RegisterDisputeAgentRequest req,
StreamObserver<RegisterDisputeAgentReply> responseObserver) {
try {
coreApi.registerDisputeAgent(req.getDisputeAgentType(), req.getRegistrationKey());
var reply = RegisterDisputeAgentReply.newBuilder().build();
responseObserver.onNext(reply);
responseObserver.onCompleted();
} catch (IllegalArgumentException cause) {
var ex = new StatusRuntimeException(Status.INVALID_ARGUMENT.withDescription(cause.getMessage()));
responseObserver.onError(ex);
throw ex;
} catch (IllegalStateException cause) {
var ex = new StatusRuntimeException(Status.UNKNOWN.withDescription(cause.getMessage()));
responseObserver.onError(ex);
throw ex;
}
}
}

View File

@ -51,11 +51,13 @@ public class GrpcServer {
@Inject
public GrpcServer(Config config,
CoreApi coreApi,
GrpcDisputeAgentsService disputeAgentsService,
GrpcOffersService offersService,
GrpcPaymentAccountsService paymentAccountsService,
GrpcWalletsService walletsService) {
this.coreApi = coreApi;
this.server = ServerBuilder.forPort(config.apiPort)
.addService(disputeAgentsService)
.addService(new GetVersionService())
.addService(new GetTradeStatisticsService())
.addService(offersService)

View File

@ -54,6 +54,8 @@ import com.google.inject.Injector;
import com.google.inject.Key;
import com.google.inject.name.Names;
import com.google.common.base.Joiner;
import javafx.application.Application;
import javafx.stage.Modality;
@ -69,6 +71,8 @@ import javafx.scene.layout.StackPane;
import java.awt.GraphicsEnvironment;
import java.awt.Rectangle;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.concurrent.TimeUnit;
import java.util.function.Consumer;
@ -241,8 +245,19 @@ public class BisqApp extends Application implements UncaughtExceptionHandler {
// configure the primary stage
String appName = injector.getInstance(Key.get(String.class, Names.named(Config.APP_NAME)));
if (!Config.baseCurrencyNetwork().isMainnet())
appName += " [" + Res.get(Config.baseCurrencyNetwork().name()) + "]";
List<String> postFixes = new ArrayList<>();
if (!Config.baseCurrencyNetwork().isMainnet()) {
postFixes.add(Config.baseCurrencyNetwork().name());
}
if (injector.getInstance(Config.class).useLocalhostForP2P) {
postFixes.add("LOCALHOST");
}
if (injector.getInstance(Config.class).useDevMode) {
postFixes.add("DEV MODE");
}
if (!postFixes.isEmpty()) {
appName += " [" + Joiner.on(", ").join(postFixes) + " ]";
}
stage.setTitle(appName);
stage.setScene(scene);

View File

@ -38,6 +38,7 @@ import bisq.core.payment.validation.AltCoinAddressValidator;
import bisq.core.util.coin.CoinFormatter;
import bisq.core.util.validation.InputValidator;
import bisq.common.UserThread;
import bisq.common.util.Tuple3;
import org.apache.commons.lang3.StringUtils;
@ -123,6 +124,13 @@ public class AssetsForm extends PaymentMethodForm {
addressInputTextField.setValidator(altCoinAddressValidator);
addressInputTextField.textProperty().addListener((ov, oldValue, newValue) -> {
if (newValue.startsWith("monero:")) {
UserThread.execute(() -> {
String addressWithoutPrefix = newValue.replace("monero:", "");
addressInputTextField.setText(addressWithoutPrefix);
});
return;
}
assetAccount.setAddress(newValue);
updateFromInputs();
});

View File

@ -685,10 +685,20 @@ public class MainViewModel implements ViewModel, BisqSetup.BisqSetupListener {
return marketPricePresentation.getPriceFeedComboBoxItems();
}
// We keep daoPresentation and accountPresentation support even it is not used atm. But if we add a new feature and
// add a badge again it will be needed.
@SuppressWarnings({"unused"})
public BooleanProperty getShowDaoUpdatesNotification() {
return daoPresentation.getShowDaoUpdatesNotification();
}
// We keep daoPresentation and accountPresentation support even it is not used atm. But if we add a new feature and
// add a badge again it will be needed.
@SuppressWarnings("unused")
public BooleanProperty getShowAccountUpdatesNotification() {
return accountPresentation.getShowAccountUpdatesNotification();
}
public BooleanProperty getShowSettingsUpdatesNotification() {
return settingsPresentation.getShowSettingsUpdatesNotification();
}

View File

@ -0,0 +1,87 @@
/*
* This file is part of Bisq.
*
* Bisq is free software: you can redistribute it and/or modify it
* under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or (at
* your option) any later version.
*
* Bisq is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
* License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Bisq. If not, see <http://www.gnu.org/licenses/>.
*/
package bisq.desktop.main;
import bisq.desktop.app.BisqApp;
import bisq.desktop.main.overlays.popups.Popup;
import bisq.core.btc.wallet.WalletsManager;
import bisq.core.locale.Res;
import bisq.core.offer.OpenOfferManager;
import bisq.common.UserThread;
import bisq.common.storage.FileUtil;
import org.bitcoinj.wallet.DeterministicSeed;
import java.io.File;
import java.util.concurrent.TimeUnit;
import lombok.extern.slf4j.Slf4j;
/**
* This serves as shared space for static methods used from different views where no common parent view would fit as
* owner of that code. We keep it strictly static. It should replace GUIUtil for those methods which are not utility
* methods.
*/
@Slf4j
public class SharedPresentation {
public static void restoreSeedWords(WalletsManager walletsManager,
OpenOfferManager openOfferManager,
DeterministicSeed seed,
File storageDir) {
if (!openOfferManager.getObservableList().isEmpty()) {
UserThread.runAfter(() ->
new Popup().warning(Res.get("seed.restore.openOffers.warn"))
.actionButtonText(Res.get("shared.yes"))
.onAction(() -> {
openOfferManager.removeAllOpenOffers(() -> {
doRestoreSeedWords(walletsManager, seed, storageDir);
});
})
.show(), 100, TimeUnit.MILLISECONDS);
} else {
doRestoreSeedWords(walletsManager, seed, storageDir);
}
}
private static void doRestoreSeedWords(WalletsManager walletsManager,
DeterministicSeed seed,
File storageDir) {
try {
File backup = new File(storageDir, "AddressEntryList_backup_pre_wallet_restore_" + System.currentTimeMillis());
FileUtil.copyFile(new File(storageDir, "AddressEntryList"), backup);
} catch (Throwable t) {
new Popup().error(Res.get("error.deleteAddressEntryListFailed", t)).show();
}
walletsManager.restoreSeedWords(
seed,
() -> UserThread.execute(() -> {
log.info("Wallets restored with seed words");
new Popup().feedback(Res.get("seed.restore.success")).hideCloseButton().show();
BisqApp.getShutDownHandler().run();
}),
throwable -> UserThread.execute(() -> {
log.error(throwable.toString());
new Popup().error(Res.get("seed.restore.error", Res.get("shared.errorMessageInline", throwable)))
.show();
}));
}
}

View File

@ -19,14 +19,15 @@ package bisq.desktop.main.account.content.seedwords;
import bisq.desktop.common.view.ActivatableView;
import bisq.desktop.common.view.FxmlView;
import bisq.desktop.main.SharedPresentation;
import bisq.desktop.main.overlays.popups.Popup;
import bisq.desktop.main.overlays.windows.WalletPasswordWindow;
import bisq.desktop.util.GUIUtil;
import bisq.desktop.util.Layout;
import bisq.core.btc.wallet.BtcWalletService;
import bisq.core.btc.wallet.WalletsManager;
import bisq.core.locale.Res;
import bisq.core.offer.OpenOfferManager;
import bisq.core.user.DontShowAgainLookup;
import bisq.common.config.Config;
@ -68,6 +69,7 @@ import static javafx.beans.binding.Bindings.createBooleanBinding;
@FxmlView
public class SeedWordsView extends ActivatableView<GridPane, Void> {
private final WalletsManager walletsManager;
private final OpenOfferManager openOfferManager;
private final BtcWalletService btcWalletService;
private final WalletPasswordWindow walletPasswordWindow;
private final File storageDir;
@ -91,10 +93,12 @@ public class SeedWordsView extends ActivatableView<GridPane, Void> {
@Inject
private SeedWordsView(WalletsManager walletsManager,
OpenOfferManager openOfferManager,
BtcWalletService btcWalletService,
WalletPasswordWindow walletPasswordWindow,
@Named(Config.STORAGE_DIR) File storageDir) {
this.walletsManager = walletsManager;
this.openOfferManager = openOfferManager;
this.btcWalletService = btcWalletService;
this.walletPasswordWindow = walletPasswordWindow;
this.storageDir = storageDir;
@ -166,20 +170,18 @@ public class SeedWordsView extends ActivatableView<GridPane, Void> {
String key = "showBackupWarningAtSeedPhrase";
if (DontShowAgainLookup.showAgain(key)) {
new Popup().warning(Res.get("account.seed.backup.warning"))
.onAction(() -> {
showSeedPhrase();
})
.actionButtonText(Res.get("shared.iUnderstand"))
.useIUnderstandButton()
.dontShowAgainId(key)
.hideCloseButton()
.show();
.onAction(this::showSeedPhrase)
.actionButtonText(Res.get("shared.iUnderstand"))
.useIUnderstandButton()
.dontShowAgainId(key)
.hideCloseButton()
.show();
} else {
showSeedPhrase();
}
}
public void showSeedPhrase() {
private void showSeedPhrase() {
DeterministicSeed keyChainSeed = btcWalletService.getKeyChainSeed();
// wallet creation date is not encrypted
walletCreationDate = Instant.ofEpochSecond(walletsManager.getChainSeedCreationTimeSeconds()).atZone(ZoneId.systemDefault()).toLocalDate();
@ -305,6 +307,6 @@ public class SeedWordsView extends ActivatableView<GridPane, Void> {
long date = localDateTime.toEpochSecond(ZoneOffset.UTC);
DeterministicSeed seed = new DeterministicSeed(Splitter.on(" ").splitToList(seedWordsTextArea.getText()), null, "", date);
GUIUtil.restoreSeedWords(seed, walletsManager, storageDir);
SharedPresentation.restoreSeedWords(walletsManager, openOfferManager, seed, storageDir);
}
}

View File

@ -21,15 +21,16 @@ import bisq.desktop.components.AutoTooltipButton;
import bisq.desktop.components.AutoTooltipLabel;
import bisq.desktop.components.BusyAnimation;
import bisq.desktop.components.PasswordTextField;
import bisq.desktop.main.SharedPresentation;
import bisq.desktop.main.overlays.Overlay;
import bisq.desktop.main.overlays.popups.Popup;
import bisq.desktop.util.GUIUtil;
import bisq.desktop.util.Layout;
import bisq.desktop.util.Transitions;
import bisq.core.btc.wallet.WalletsManager;
import bisq.core.crypto.ScryptUtil;
import bisq.core.locale.Res;
import bisq.core.offer.OpenOfferManager;
import bisq.common.UserThread;
import bisq.common.config.Config;
@ -88,6 +89,7 @@ import static javafx.beans.binding.Bindings.createBooleanBinding;
@Slf4j
public class WalletPasswordWindow extends Overlay<WalletPasswordWindow> {
private final WalletsManager walletsManager;
private final OpenOfferManager openOfferManager;
private File storageDir;
private Button unlockButton;
@ -115,8 +117,10 @@ public class WalletPasswordWindow extends Overlay<WalletPasswordWindow> {
@Inject
private WalletPasswordWindow(WalletsManager walletsManager,
OpenOfferManager openOfferManager,
@Named(Config.STORAGE_DIR) File storageDir) {
this.walletsManager = walletsManager;
this.openOfferManager = openOfferManager;
this.storageDir = storageDir;
type = Type.Attention;
width = 900;
@ -277,7 +281,6 @@ public class WalletPasswordWindow extends Overlay<WalletPasswordWindow> {
gridPane.getChildren().add(headLine2Label);
seedWordsTextArea = addTextArea(gridPane, ++rowIndex, Res.get("seed.enterSeedWords"), 5);
;
seedWordsTextArea.setPrefHeight(60);
Tuple2<Label, DatePicker> labelDatePickerTuple2 = addTopLabelDatePicker(gridPane, ++rowIndex,
@ -356,6 +359,6 @@ public class WalletPasswordWindow extends Overlay<WalletPasswordWindow> {
//TODO Is ZoneOffset correct?
long date = value != null ? value.atStartOfDay().toEpochSecond(ZoneOffset.UTC) : 0;
DeterministicSeed seed = new DeterministicSeed(Splitter.on(" ").splitToList(seedWordsTextArea.getText()), null, "", date);
GUIUtil.restoreSeedWords(seed, walletsManager, storageDir);
SharedPresentation.restoreSeedWords(walletsManager, openOfferManager, seed, storageDir);
}
}

View File

@ -32,7 +32,6 @@ import bisq.desktop.util.validation.RegexValidator;
import bisq.core.account.witness.AccountAgeWitness;
import bisq.core.account.witness.AccountAgeWitnessService;
import bisq.core.btc.setup.WalletsSetup;
import bisq.core.btc.wallet.WalletsManager;
import bisq.core.locale.Country;
import bisq.core.locale.CountryUtil;
import bisq.core.locale.CurrencyUtil;
@ -63,7 +62,6 @@ import bisq.common.config.Config;
import bisq.common.proto.persistable.PersistableList;
import bisq.common.proto.persistable.PersistenceProtoResolver;
import bisq.common.storage.CorruptedDatabaseFilesHandler;
import bisq.common.storage.FileUtil;
import bisq.common.storage.Storage;
import bisq.common.util.MathUtils;
import bisq.common.util.Tuple2;
@ -75,7 +73,6 @@ import org.bitcoinj.core.Coin;
import org.bitcoinj.core.TransactionConfidence;
import org.bitcoinj.uri.BitcoinURI;
import org.bitcoinj.utils.Fiat;
import org.bitcoinj.wallet.DeterministicSeed;
import com.googlecode.jcsv.CSVStrategy;
import com.googlecode.jcsv.writer.CSVEntryConverter;
@ -803,26 +800,6 @@ public class GUIUtil {
}
}
public static void restoreSeedWords(DeterministicSeed seed, WalletsManager walletsManager, File storageDir) {
try {
FileUtil.renameFile(new File(storageDir, "AddressEntryList"), new File(storageDir, "AddressEntryList_wallet_restore_" + System.currentTimeMillis()));
} catch (Throwable t) {
new Popup().error(Res.get("error.deleteAddressEntryListFailed", t)).show();
}
walletsManager.restoreSeedWords(
seed,
() -> UserThread.execute(() -> {
log.info("Wallets restored with seed words");
new Popup().feedback(Res.get("seed.restore.success")).hideCloseButton().show();
BisqApp.getShutDownHandler().run();
}),
throwable -> UserThread.execute(() -> {
log.error(throwable.toString());
new Popup().error(Res.get("seed.restore.error", Res.get("shared.errorMessageInline", throwable)))
.show();
}));
}
public static void showSelectableTextModal(String title, String text) {
TextArea textArea = new BisqTextArea();
textArea.setText(text);

View File

@ -7,7 +7,7 @@ Bisq uses Git LFS to track certain large binary files. Follow the instructions a
$ git lfs version
git-lfs/2.10.0 (GitHub; darwin amd64; go 1.13.6)
## Clone
@ -17,8 +17,9 @@ Bisq uses Git LFS to track certain large binary files. Follow the instructions a
## Build
You do _not_ need to install Gradle to complete the following command. The `gradlew` shell script will install it for you if necessary.
You do _not_ need to install Gradle to complete the following command. The `gradlew` shell script will install it for you if necessary. Pull the lfs data first.
git lfs pull
./gradlew build
If on Windows run `gradlew.bat build` instead.

View File

@ -19,14 +19,14 @@ dependencyVerification {
'com.fasterxml.jackson.core:jackson-annotations:2566b3a6662afa3c6af4f5b25006cb46be2efc68f1b5116291d6998a8cdf7ed3',
'com.fasterxml.jackson.core:jackson-core:39a74610521d7fb9eb3f437bb8739bbf47f6435be12d17bf954c731a0c6352bb',
'com.fasterxml.jackson.core:jackson-databind:fcf3c2b0c332f5f54604f7e27fa7ee502378a2cc5df6a944bbfae391872c32ff',
'com.github.JesusMcCloud.netlayer:tor.external:fb080d812aa88f5fb1ec74273ae24ae13980b7902c75e0ffc3ac462b4cf6b242',
'com.github.JesusMcCloud.netlayer:tor.native:1227275377ac73d9d25dec1d05bc2f7a2ae3b1f1afde77f39049637dcb8fc407',
'com.github.JesusMcCloud.netlayer:tor:6ff47a97dc0dc97079d83c1f2c66ebcc956f25dd3bacfa6e24206cb2c742f52b',
'com.github.JesusMcCloud.tor-binary:tor-binary-geoip:13a3c6e02f37f9def172ce1d231ea1a45aa1ddbc36b1d9e81b7a91632bc474ed',
'com.github.JesusMcCloud.tor-binary:tor-binary-linux32:2022da3a398527d366bd954bea92069cf209218c649aeb3342f85f7434aa0786',
'com.github.JesusMcCloud.tor-binary:tor-binary-linux64:972d5a946964176fe849eb872baa64a591d9e6fe0467c62ae71986b946101836',
'com.github.JesusMcCloud.tor-binary:tor-binary-macos:95a4d093e2d48099623015e70add1bd7dcc3e392f9e87ed4d691286c868516c9',
'com.github.JesusMcCloud.tor-binary:tor-binary-windows:43a07290443a1c55211eaa6001681839f9cb7453574089a939d0da3dd20e2cf2',
'com.github.JesusMcCloud.netlayer:tor.external:1facb63d5f4107a1d4a176a03c149b60eed19a76371b58d1b6aa62ad5cacde3f',
'com.github.JesusMcCloud.netlayer:tor.native:88267f1a3b2d1433a77a2fd9dc3a8f1d5cd28fbbefa2e078d83b176cbe91d477',
'com.github.JesusMcCloud.netlayer:tor:0eaae7bcea11ef25e4f041e77c237ebc96f120462e64a7818073a88dadf9b805',
'com.github.JesusMcCloud.tor-binary:tor-binary-geoip:08a461608990aed2c272121dc835a38687017d3bb172bc181ea0793df7ebb5aa',
'com.github.JesusMcCloud.tor-binary:tor-binary-linux32:81fb1e80d191ed9d0a509e514311d21d59c87c6edee6a490975061a18390b10c',
'com.github.JesusMcCloud.tor-binary:tor-binary-linux64:7b90b5e8f1cc32d86ed8e2e5b1f57d774bc27bebc679ececdbf1ff0699bd1f4c',
'com.github.JesusMcCloud.tor-binary:tor-binary-macos:db5aef1082fdd8afb4f7f21cf0956f46a76e93aad75bed6ed149dd3e3767c0a7',
'com.github.JesusMcCloud.tor-binary:tor-binary-windows:1bb83786a71449d8edb152e40ce5efc129eaca6a0900f4912d7555b6df2f52a2',
'com.github.JesusMcCloud:jtorctl:389d61b1b5a85eb2f23c582c3913ede49f80c9f2b553e4762382c836270e57e5',
'com.github.bisq-network.bitcoinj:bitcoinj-core:f979c2187e61ee3b08dd4cbfc49a149734cff64c045d29ed112f2e12f34068a3',
'com.github.ravn:jsocks:3c71600af027b2b6d4244e4ad14d98ff2352a379410daebefff5d8cd48d742a4',

View File

@ -48,6 +48,8 @@ import bisq.common.proto.network.NetworkEnvelope;
import bisq.common.proto.network.NetworkProtoResolver;
import bisq.common.util.Utilities;
import com.google.protobuf.InvalidProtocolBufferException;
import javax.inject.Inject;
import com.google.common.util.concurrent.MoreExecutors;
@ -283,12 +285,20 @@ public class Connection implements HasCapabilities, Runnable, MessageListener {
bundleSender.schedule(() -> {
if (!stopped) {
synchronized (lock) {
BundleOfEnvelopes current = queueOfBundles.poll();
if (current != null && !stopped) {
if (current.getEnvelopes().size() == 1) {
protoOutputStream.writeEnvelope(current.getEnvelopes().get(0));
} else {
protoOutputStream.writeEnvelope(current);
BundleOfEnvelopes bundle = queueOfBundles.poll();
if (bundle != null && !stopped) {
NetworkEnvelope envelope = bundle.getEnvelopes().size() == 1 ?
bundle.getEnvelopes().get(0) :
bundle;
try {
protoOutputStream.writeEnvelope(envelope);
} catch (Throwable t) {
log.error("Sending envelope of class {} to address {} " +
"failed due {}",
envelope.getClass().getSimpleName(),
this.getPeersNodeAddressOptional(),
t.toString());
log.error("envelope: {}", envelope);
}
}
}
@ -876,7 +886,7 @@ public class Connection implements HasCapabilities, Runnable, MessageListener {
log.error(e.getMessage());
e.printStackTrace();
reportInvalidRequest(RuleViolation.INVALID_CLASS);
} catch (ProtobufferException | NoClassDefFoundError e) {
} catch (ProtobufferException | NoClassDefFoundError | InvalidProtocolBufferException e) {
log.error(e.getMessage());
e.printStackTrace();
reportInvalidRequest(RuleViolation.INVALID_DATA_TYPE);

View File

@ -24,19 +24,20 @@ option java_package = "bisq.proto.grpc";
option java_multiple_files = true;
///////////////////////////////////////////////////////////////////////////////////////////
// Version
// DisputeAgents
///////////////////////////////////////////////////////////////////////////////////////////
service GetVersion {
rpc GetVersion (GetVersionRequest) returns (GetVersionReply) {
service DisputeAgents {
rpc RegisterDisputeAgent (RegisterDisputeAgentRequest) returns (RegisterDisputeAgentReply) {
}
}
message GetVersionRequest {
message RegisterDisputeAgentRequest {
string disputeAgentType = 1;
string registrationKey = 2;
}
message GetVersionReply {
string version = 1;
message RegisterDisputeAgentReply {
}
///////////////////////////////////////////////////////////////////////////////////////////
@ -214,3 +215,20 @@ message AddressBalanceInfo {
int64 balance = 2;
int64 numConfirmations = 3;
}
///////////////////////////////////////////////////////////////////////////////////////////
// Version
///////////////////////////////////////////////////////////////////////////////////////////
service GetVersion {
rpc GetVersion (GetVersionRequest) returns (GetVersionReply) {
}
}
message GetVersionRequest {
}
message GetVersionReply {
string version = 1;
}