Merge remote-tracking branch 'bisq-network/release/v1.3.9' into upgrade-javafax-14

This commit is contained in:
cd2357 2020-09-18 14:13:56 +02:00
commit fbe43c53ed
No known key found for this signature in database
GPG key ID: F26C56748514D0D3
32 changed files with 509 additions and 136 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

@ -376,7 +376,7 @@ configure(project(':desktop')) {
apply plugin: 'witness'
apply from: '../gradle/witness/gradle-witness.gradle'
version = '1.3.8-SNAPSHOT'
version = '1.3.9-SNAPSHOT'
mainClassName = 'bisq.desktop.app.BisqAppMain'

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

@ -27,7 +27,7 @@ public class Version {
// VERSION = 0.5.0 introduces proto buffer for the P2P network and local DB and is a not backward compatible update
// Therefore all sub versions start again with 1
// We use semantic versioning with major, minor and patch
public static final String VERSION = "1.3.8";
public static final String VERSION = "1.3.9";
public static int getMajorVersion(String version) {
return getSubVersion(version, 0);

View file

@ -29,6 +29,8 @@ import java.util.Optional;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import static com.google.common.base.Preconditions.checkArgument;
public class MathUtils {
private static final Logger log = LoggerFactory.getLogger(MathUtils.class);
@ -127,24 +129,30 @@ public class MathUtils {
}
public Optional<Double> next(long val) {
var fullAtStart = isFull();
if (fullAtStart) {
if (outlier > 0) {
// Return early if it's an outlier
var avg = (double) sum / size;
if (Math.abs(avg - val) / avg > outlier) {
return Optional.empty();
try {
var fullAtStart = isFull();
if (fullAtStart) {
if (outlier > 0) {
// Return early if it's an outlier
checkArgument(size != 0);
var avg = (double) sum / size;
if (Math.abs(avg - val) / avg > outlier) {
return Optional.empty();
}
}
sum -= window.remove();
}
sum -= window.remove();
window.add(val);
sum += val;
if (!fullAtStart && isFull() && outlier != 0) {
removeInitialOutlier();
}
// When discarding outliers, the first n non discarded elements return Optional.empty()
return outlier > 0 && !isFull() ? Optional.empty() : current();
} catch (Throwable t) {
log.error(t.toString());
return Optional.empty();
}
window.add(val);
sum += val;
if (!fullAtStart && isFull() && outlier != 0) {
removeInitialOutlier();
}
// When discarding outliers, the first n non discarded elements return Optional.empty()
return outlier > 0 && !isFull() ? Optional.empty() : current();
}
boolean isFull() {
@ -155,7 +163,9 @@ public class MathUtils {
var element = window.iterator();
while (element.hasNext()) {
var val = element.next();
var avgExVal = (double) (sum - val) / (size - 1);
int div = size - 1;
checkArgument(div != 0);
var avgExVal = (double) (sum - val) / div;
if (Math.abs(avgExVal - val) / avgExVal > outlier) {
element.remove();
break;

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

@ -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
@ -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.

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

@ -8,7 +8,7 @@
# pull base image
FROM openjdk:8-jdk
ENV version 1.3.8-SNAPSHOT
ENV version 1.3.9-SNAPSHOT
RUN apt-get update && apt-get install -y --no-install-recommends openjfx && rm -rf /var/lib/apt/lists/* &&
apt-get install -y vim fakeroot

View file

@ -6,7 +6,7 @@
# - Update version below
# - Ensure JAVA_HOME below is pointing to OracleJDK 10 directory
version=1.3.8-SNAPSHOT
version=1.3.9-SNAPSHOT
version_base=$(echo $version | awk -F'[_-]' '{print $1}')
if [ ! -f "$JAVA_HOME/bin/javapackager" ]; then
if [ -d "/usr/lib/jvm/jdk-10.0.2" ]; then

View file

@ -4,7 +4,7 @@
# Prior to running this script:
# - Update version below
version=1.3.8-SNAPSHOT
version=1.3.9-SNAPSHOT
base_dir=$( cd "$(dirname "$0")" ; pwd -P )/../../..
package_dir=$base_dir/desktop/package
release_dir=$base_dir/desktop/release/$version

View file

@ -5,10 +5,10 @@
<!-- See: https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html -->
<key>CFBundleVersion</key>
<string>1.3.8</string>
<string>1.3.9</string>
<key>CFBundleShortVersionString</key>
<string>1.3.8</string>
<string>1.3.9</string>
<key>CFBundleExecutable</key>
<string>Bisq</string>

View file

@ -6,7 +6,7 @@ mkdir -p deploy
set -e
version="1.3.8-SNAPSHOT"
version="1.3.9-SNAPSHOT"
cd ..
./gradlew :desktop:build -x test shadowJar

View file

@ -2,7 +2,7 @@
cd ../../
version="1.3.8-SNAPSHOT"
version="1.3.9-SNAPSHOT"
target_dir="releases/$version"

View file

@ -2,7 +2,7 @@
cd $(dirname $0)/../../../
version=1.3.8
version=1.3.9
find . -type f \( -name "finalize.sh" \
-o -name "create_app.sh" \

View file

@ -2,8 +2,8 @@
cd $(dirname $0)/../../../
oldVersion=1.3.7
newVersion=1.3.8
oldVersion=1.3.8
newVersion=1.3.9
find . -type f \( -name "finalize.sh" \
-o -name "create_app.sh" \

View file

@ -11,7 +11,7 @@
@echo off
set version=1.3.8-SNAPSHOT
set version=1.3.9-SNAPSHOT
if not exist "%JAVA_HOME%\bin\javapackager.exe" (
if not exist "%ProgramFiles%\Java\jdk-10.0.2" (
echo Javapackager not found. Update JAVA_HOME variable to point to OracleJDK.

View file

@ -6,7 +6,7 @@
@echo off
set version=1.3.8-SNAPSHOT
set version=1.3.9-SNAPSHOT
set release_dir=%~dp0..\..\..\releases\%version%
set package_dir=%~dp0..

View file

@ -335,37 +335,42 @@ public abstract class MutableOfferDataModel extends OfferDataModel implements Bs
private void setSuggestedSecurityDeposit(PaymentAccount paymentAccount) {
var minSecurityDeposit = preferences.getBuyerSecurityDepositAsPercent(getPaymentAccount());
if (getTradeCurrency() == null) {
setBuyerSecurityDeposit(minSecurityDeposit, false);
return;
}
// Get average historic prices over for the prior trade period equaling the lock time
var blocksRange = Restrictions.getLockTime(paymentAccount.getPaymentMethod().isAsset());
var startDate = new Date(System.currentTimeMillis() - blocksRange * 10 * 60000);
var sortedRangeData = tradeStatisticsManager.getObservableTradeStatisticsSet().stream()
.filter(e -> e.getCurrencyCode().equals(getTradeCurrency().getCode()))
.filter(e -> e.getTradeDate().compareTo(startDate) >= 0)
.sorted(Comparator.comparing(TradeStatistics2::getTradeDate))
.collect(Collectors.toList());
var movingAverage = new MathUtils.MovingAverage(10, 0.2);
double[] extremes = {Double.MAX_VALUE, Double.MIN_VALUE};
sortedRangeData.forEach(e -> {
var price = e.getTradePrice().getValue();
movingAverage.next(price).ifPresent(val -> {
if (val < extremes[0]) extremes[0] = val;
if (val > extremes[1]) extremes[1] = val;
try {
if (getTradeCurrency() == null) {
setBuyerSecurityDeposit(minSecurityDeposit, false);
return;
}
// Get average historic prices over for the prior trade period equaling the lock time
var blocksRange = Restrictions.getLockTime(paymentAccount.getPaymentMethod().isAsset());
var startDate = new Date(System.currentTimeMillis() - blocksRange * 10 * 60000);
var sortedRangeData = tradeStatisticsManager.getObservableTradeStatisticsSet().stream()
.filter(e -> e.getCurrencyCode().equals(getTradeCurrency().getCode()))
.filter(e -> e.getTradeDate().compareTo(startDate) >= 0)
.sorted(Comparator.comparing(TradeStatistics2::getTradeDate))
.collect(Collectors.toList());
var movingAverage = new MathUtils.MovingAverage(10, 0.2);
double[] extremes = {Double.MAX_VALUE, Double.MIN_VALUE};
sortedRangeData.forEach(e -> {
var price = e.getTradePrice().getValue();
movingAverage.next(price).ifPresent(val -> {
if (val < extremes[0]) extremes[0] = val;
if (val > extremes[1]) extremes[1] = val;
});
});
});
var min = extremes[0];
var max = extremes[1];
if (min == 0d || max == 0d) {
setBuyerSecurityDeposit(minSecurityDeposit, false);
return;
var min = extremes[0];
var max = extremes[1];
if (min == 0d || max == 0d) {
setBuyerSecurityDeposit(minSecurityDeposit, false);
return;
}
// Suggested deposit is double the trade range over the previous lock time period, bounded by min/max deposit
var suggestedSecurityDeposit =
Math.min(2 * (max - min) / max, Restrictions.getMaxBuyerSecurityDepositAsPercent());
buyerSecurityDeposit.set(Math.max(suggestedSecurityDeposit, minSecurityDeposit));
} catch (Throwable t) {
log.error(t.toString());
buyerSecurityDeposit.set(minSecurityDeposit);
}
// Suggested deposit is double the trade range over the previous lock time period, bounded by min/max deposit
var suggestedSecurityDeposit =
Math.min(2 * (max - min) / max, Restrictions.getMaxBuyerSecurityDepositAsPercent());
buyerSecurityDeposit.set(Math.max(suggestedSecurityDeposit, minSecurityDeposit));
}

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;
}

View file

@ -1 +1 @@
1.3.8-SNAPSHOT
1.3.9-SNAPSHOT

View file

@ -33,7 +33,7 @@ import lombok.extern.slf4j.Slf4j;
@Slf4j
public class SeedNodeMain extends ExecutableForAppWithP2p {
private static final String VERSION = "1.3.8";
private static final String VERSION = "1.3.9";
private SeedNode seedNode;
public SeedNodeMain() {