Merge pull request #5775 from chimp1984/bsq-swap-impl

Bsq swap: Implementation [3]
This commit is contained in:
Christoph Atteneder 2021-11-04 10:05:32 +01:00 committed by GitHub
commit da66ffe8f5
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
522 changed files with 20014 additions and 3714 deletions

View file

@ -458,7 +458,7 @@ delayconfirmpaymentreceived() {
}
# This is a large function that should be broken up if it ever makes sense to not treat a trade
# execution simulation as an atomic operation. But we are not testing api methods here, just
# execution simulation as an bsq swap operation. But we are not testing api methods here, just
# demonstrating how to use them to get through the trade protocol. It should work for any trade
# between Bob & Alice, as long as Alice is maker, Bob is taker, and the offer to be taken is the
# first displayed in Bob's getoffers command output.

View file

@ -24,7 +24,7 @@ import java.util.List;
import lombok.extern.slf4j.Slf4j;
import org.jetbrains.annotations.NotNull;
import javax.annotation.Nullable;
import static bisq.apitest.config.ApiTestConfig.BASH_PATH_VALUE;
import static java.lang.management.ManagementFactory.getRuntimeMXBean;
@ -33,7 +33,9 @@ import static java.lang.management.ManagementFactory.getRuntimeMXBean;
public class BashCommand {
private int exitStatus = -1;
@Nullable
private String output;
@Nullable
private String error;
private final String command;
@ -92,6 +94,7 @@ public class BashCommand {
}
// TODO return Optional<String>
@Nullable
public String getOutput() {
return this.output;
}
@ -101,7 +104,6 @@ public class BashCommand {
return this.error;
}
@NotNull
private List<String> tokenizeSystemCommand() {
return new ArrayList<>() {{
add(BASH_PATH_VALUE);

View file

@ -57,15 +57,13 @@ class SystemCommandExecutor {
private ThreadedStreamHandler errorStreamHandler;
public SystemCommandExecutor(final List<String> cmdOptions) {
if (log.isDebugEnabled())
log.debug("cmd options {}", cmdOptions.toString());
if (cmdOptions.isEmpty())
throw new IllegalStateException("No command params specified.");
if (cmdOptions.contains("sudo"))
throw new IllegalStateException("'sudo' commands are prohibited.");
log.trace("System cmd options {}", cmdOptions);
this.cmdOptions = cmdOptions;
}

View file

@ -17,6 +17,8 @@
package bisq.apitest;
import java.time.Duration;
import java.io.IOException;
import java.util.concurrent.ExecutionException;
@ -32,9 +34,9 @@ import static bisq.apitest.config.ApiTestRateMeterInterceptorConfig.getTestRateM
import static bisq.apitest.config.BisqAppConfig.alicedaemon;
import static bisq.apitest.config.BisqAppConfig.arbdaemon;
import static bisq.apitest.config.BisqAppConfig.bobdaemon;
import static com.google.common.util.concurrent.Uninterruptibles.sleepUninterruptibly;
import static java.net.InetAddress.getLoopbackAddress;
import static java.util.Arrays.stream;
import static java.util.concurrent.TimeUnit.MILLISECONDS;
@ -131,11 +133,7 @@ public class ApiTestCase {
}
protected static void sleep(long ms) {
try {
MILLISECONDS.sleep(ms);
} catch (InterruptedException ignored) {
// empty
}
sleepUninterruptibly(Duration.ofMillis(ms));
}
protected final String testName(TestInfo testInfo) {

View file

@ -31,7 +31,12 @@ import java.io.PrintWriter;
import java.util.function.Function;
import java.util.stream.Collectors;
import org.slf4j.Logger;
import javax.annotation.Nullable;
import static bisq.apitest.config.ApiTestRateMeterInterceptorConfig.getTestRateMeterInterceptorConfig;
import static java.lang.String.format;
import static java.nio.charset.StandardCharsets.UTF_8;
import static java.util.Arrays.stream;
import static org.junit.jupiter.api.Assertions.fail;
@ -39,6 +44,7 @@ import static org.junit.jupiter.api.Assertions.fail;
import bisq.apitest.ApiTestCase;
import bisq.apitest.linux.BashCommand;
import bisq.cli.GrpcClient;
public class MethodTest extends ApiTestCase {
@ -144,14 +150,42 @@ public class MethodTest extends ApiTestCase {
protected final bisq.core.payment.PaymentAccount createPaymentAccount(GrpcClient grpcClient, String jsonString) {
// Normally, we do asserts on the protos from the gRPC service, but in this
// case we need a bisq.core.payment.PaymentAccount so it can be cast to its
// sub type.
// sub-type.
var paymentAccount = grpcClient.createPaymentAccount(jsonString);
return bisq.core.payment.PaymentAccount.fromProto(paymentAccount, CORE_PROTO_RESOLVER);
}
// Static conveniences for test methods and test case fixture setups.
protected static String encodeToHex(String s) {
return Utilities.bytesAsHexString(s.getBytes(UTF_8));
}
protected void verifyNoLoggedNodeExceptions() {
var loggedExceptions = getNodeExceptionMessages();
if (loggedExceptions != null) {
String err = format("Exception(s) found in daemon log(s):%n%s", loggedExceptions);
fail(err);
}
}
protected void printNodeExceptionMessages(Logger log) {
var loggedExceptions = getNodeExceptionMessages();
if (loggedExceptions != null)
log.error("Exception(s) found in daemon log(s):\n{}", loggedExceptions);
}
@Nullable
protected static String getNodeExceptionMessages() {
var nodeLogsSpec = config.rootAppDataDir.getAbsolutePath() + "/bisq-BTC_REGTEST_*_dao/bisq.log";
var grep = "grep Exception " + nodeLogsSpec;
var bashCommand = new BashCommand(grep);
try {
bashCommand.run();
} catch (IOException | InterruptedException ex) {
fail("Bash command execution error: " + ex);
}
if (bashCommand.getError() == null)
return bashCommand.getOutput();
else
throw new IllegalStateException("Bash command execution error: " + bashCommand.getError());
}
}

View file

@ -66,6 +66,16 @@ public abstract class AbstractOfferTest extends MethodTest {
bobdaemon);
}
public static void createBsqSwapBsqPaymentAccounts() {
alicesBsqAcct = aliceClient.createCryptoCurrencyPaymentAccount("Alice's BsqSwap Account",
BSQ,
aliceClient.getUnusedBsqAddress(), // TODO refactor, bsq address not needed for atom acct
false);
bobsBsqAcct = bobClient.createCryptoCurrencyPaymentAccount("Bob's BsqSwap Account",
BSQ,
bobClient.getUnusedBsqAddress(), // TODO refactor, bsq address not needed for atom acct
false);
}
// Mkt Price Margin value of offer returned from server is scaled down by 10^-2.
protected final Function<Double, Double> scaledDownMktPriceMargin = (mktPriceMargin) ->

View file

@ -0,0 +1,173 @@
/*
* 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.offer;
import bisq.proto.grpc.BsqSwapOfferInfo;
import lombok.extern.slf4j.Slf4j;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.MethodOrderer;
import org.junit.jupiter.api.Order;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.TestMethodOrder;
import static bisq.apitest.config.ApiTestConfig.BSQ;
import static bisq.apitest.config.ApiTestConfig.BTC;
import static bisq.cli.TableFormat.formatBalancesTbls;
import static java.lang.String.format;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotEquals;
import static org.junit.jupiter.api.Assertions.fail;
import static protobuf.OfferDirection.BUY;
@Disabled
@Slf4j
@TestMethodOrder(MethodOrderer.OrderAnnotation.class)
public class BsqSwapOfferTest extends AbstractOfferTest {
@BeforeAll
public static void setUp() {
AbstractOfferTest.setUp();
createBsqSwapBsqPaymentAccounts();
}
@BeforeEach
public void generateBtcBlock() {
genBtcBlocksThenWait(1, 2000);
}
@Test
@Order(1)
public void testGetBalancesBeforeCreateOffers() {
var alicesBalances = aliceClient.getBalances();
log.debug("Alice's Before Trade Balance:\n{}", formatBalancesTbls(alicesBalances));
var bobsBalances = bobClient.getBalances();
log.debug("Bob's Before Trade Balance:\n{}", formatBalancesTbls(bobsBalances));
}
@Test
@Order(2)
public void testAliceCreateBsqSwapBuyOffer1() {
createBsqSwapOffer();
}
@Test
@Order(3)
public void testAliceCreateBsqSwapBuyOffer2() {
createBsqSwapOffer();
}
@Test
@Order(4)
public void testAliceCreateBsqSwapBuyOffer3() {
createBsqSwapOffer();
}
@Test
@Order(5)
public void testAliceCreateBsqSwapBuyOffer4() {
createBsqSwapOffer();
}
@Test
@Order(6)
public void testGetMyBsqSwapOffers() {
var offers = aliceClient.getMyBsqSwapBsqOffersSortedByDate();
assertEquals(4, offers.size());
}
@Test
@Order(7)
public void testGetAvailableBsqSwapOffers() {
var offers = bobClient.getBsqSwapOffersSortedByDate();
assertEquals(4, offers.size());
}
@Test
@Order(8)
public void testGetBalancesAfterCreateOffers() {
var alicesBalances = aliceClient.getBalances();
log.debug("Alice's After Trade Balance:\n{}", formatBalancesTbls(alicesBalances));
var bobsBalances = bobClient.getBalances();
log.debug("Bob's After Trade Balance:\n{}", formatBalancesTbls(bobsBalances));
}
private void createBsqSwapOffer() {
var bsqSwapOffer = aliceClient.createBsqSwapOffer(BUY.name(),
1_000_000L,
1_000_000L,
"0.00005",
alicesBsqAcct.getId());
log.debug("BsqSwap Sell BSQ (Buy BTC) OFFER:\n{}", bsqSwapOffer);
var newOfferId = bsqSwapOffer.getId();
assertNotEquals("", newOfferId);
assertEquals(BUY.name(), bsqSwapOffer.getDirection());
assertEquals(5_000, bsqSwapOffer.getPrice());
assertEquals(1_000_000L, bsqSwapOffer.getAmount());
assertEquals(1_000_000L, bsqSwapOffer.getMinAmount());
// assertEquals(alicesBsqAcct.getId(), atomicOffer.getMakerPaymentAccountId());
assertEquals(BSQ, bsqSwapOffer.getBaseCurrencyCode());
assertEquals(BTC, bsqSwapOffer.getCounterCurrencyCode());
testGetMyBsqSwapOffer(bsqSwapOffer);
testGetBsqSwapOffer(bsqSwapOffer);
}
private void testGetMyBsqSwapOffer(BsqSwapOfferInfo bsqSwapOfferInfo) {
int numFetchAttempts = 0;
while (true) {
try {
numFetchAttempts++;
var fetchedBsqSwapOffer = aliceClient.getMyBsqSwapOffer(bsqSwapOfferInfo.getId());
assertEquals(bsqSwapOfferInfo.getId(), fetchedBsqSwapOffer.getId());
log.debug("Alice found her (my) new bsq swap offer on attempt # {}.", numFetchAttempts);
break;
} catch (Exception ex) {
log.warn(ex.getMessage());
if (numFetchAttempts >= 9)
fail(format("Alice giving up on fetching her (my) bsq swap offer after %d attempts.", numFetchAttempts), ex);
sleep(1000);
}
}
}
private void testGetBsqSwapOffer(BsqSwapOfferInfo bsqSwapOfferInfo) {
int numFetchAttempts = 0;
while (true) {
try {
numFetchAttempts++;
var fetchedBsqSwapOffer = bobClient.getBsqSwapOffer(bsqSwapOfferInfo.getId());
assertEquals(bsqSwapOfferInfo.getId(), fetchedBsqSwapOffer.getId());
log.debug("Bob found new available bsq swap offer on attempt # {}.", numFetchAttempts);
break;
} catch (Exception ex) {
log.warn(ex.getMessage());
if (numFetchAttempts > 9)
fail(format("Bob gave up on fetching available bsq swap offer after %d attempts.", numFetchAttempts), ex);
sleep(1000);
}
}
}
}

View file

@ -35,7 +35,7 @@ import org.junit.jupiter.api.TestMethodOrder;
import static bisq.apitest.config.ApiTestConfig.BSQ;
import static bisq.core.btc.wallet.Restrictions.getDefaultBuyerSecurityDepositAsPercent;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static protobuf.OfferPayload.Direction.BUY;
import static protobuf.OfferDirection.BUY;
@Disabled
@Slf4j

View file

@ -40,8 +40,8 @@ import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNotEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static protobuf.OfferPayload.Direction.BUY;
import static protobuf.OfferPayload.Direction.SELL;
import static protobuf.OfferDirection.BUY;
import static protobuf.OfferDirection.SELL;
@Disabled
@Slf4j

View file

@ -36,8 +36,8 @@ import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNotEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static protobuf.OfferPayload.Direction.BUY;
import static protobuf.OfferPayload.Direction.SELL;
import static protobuf.OfferDirection.BUY;
import static protobuf.OfferDirection.SELL;
@Disabled
@Slf4j

View file

@ -52,8 +52,8 @@ import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNotEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static protobuf.OfferPayload.Direction.BUY;
import static protobuf.OfferPayload.Direction.SELL;
import static protobuf.OfferDirection.BUY;
import static protobuf.OfferDirection.SELL;
@SuppressWarnings("ConstantConditions")
@Disabled

View file

@ -47,8 +47,8 @@ import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static protobuf.OfferPayload.Direction.BUY;
import static protobuf.OfferPayload.Direction.SELL;
import static protobuf.OfferDirection.BUY;
import static protobuf.OfferDirection.SELL;
@SuppressWarnings("ALL")
@Disabled

View file

@ -35,7 +35,7 @@ import static bisq.core.btc.wallet.Restrictions.getDefaultBuyerSecurityDepositAs
import static java.lang.String.format;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static protobuf.OfferPayload.Direction.BUY;
import static protobuf.OfferDirection.BUY;
@Disabled
@Slf4j

View file

@ -0,0 +1,173 @@
/*
* 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.trade;
import bisq.proto.grpc.BsqSwapOfferInfo;
import bisq.proto.grpc.BsqSwapTradeInfo;
import protobuf.BsqSwapTrade;
import java.util.ArrayList;
import java.util.List;
import lombok.Setter;
import lombok.extern.slf4j.Slf4j;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.MethodOrderer;
import org.junit.jupiter.api.Order;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.TestMethodOrder;
import static bisq.apitest.config.ApiTestConfig.BSQ;
import static bisq.apitest.config.ApiTestConfig.BTC;
import static bisq.cli.TableFormat.formatBalancesTbls;
import static java.lang.String.format;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotEquals;
import static org.junit.jupiter.api.Assertions.fail;
import static protobuf.OfferDirection.BUY;
import bisq.apitest.method.offer.AbstractOfferTest;
@Disabled
@Slf4j
@TestMethodOrder(MethodOrderer.OrderAnnotation.class)
public class BsqSwapTradeTest extends AbstractOfferTest {
private static final String BISQ_FEE_CURRENCY_CODE = BSQ;
// Long-running swap trade tests might want to check node logs for exceptions.
@Setter
private boolean checkForLoggedExceptions;
@BeforeAll
public static void setUp() {
AbstractOfferTest.setUp();
createBsqSwapBsqPaymentAccounts();
}
@BeforeEach
public void generateBtcBlock() {
genBtcBlocksThenWait(1, 2000);
}
@Test
@Order(1)
public void testGetBalancesBeforeTrade() {
var alicesBalances = aliceClient.getBalances();
log.info("Alice's Before Trade Balance:\n{}", formatBalancesTbls(alicesBalances));
var bobsBalances = bobClient.getBalances();
log.info("Bob's Before Trade Balance:\n{}", formatBalancesTbls(bobsBalances));
}
@Test
@Order(2)
public void testAliceCreateBsqSwapBuyOffer() {
var bsqSwapOffer = aliceClient.createBsqSwapOffer(BUY.name(),
1_000_000L,
1_000_000L,
"0.00005",
alicesBsqAcct.getId());
log.debug("BsqSwap Sell BSQ (Buy BTC) OFFER:\n{}", bsqSwapOffer);
var newOfferId = bsqSwapOffer.getId();
assertNotEquals("", newOfferId);
assertEquals(BUY.name(), bsqSwapOffer.getDirection());
assertEquals(5_000, bsqSwapOffer.getPrice());
assertEquals(1_000_000L, bsqSwapOffer.getAmount());
assertEquals(1_000_000L, bsqSwapOffer.getMinAmount());
// assertEquals(alicesBsqAcct.getId(), atomicOffer.getMakerPaymentAccountId());
assertEquals(BSQ, bsqSwapOffer.getBaseCurrencyCode());
assertEquals(BTC, bsqSwapOffer.getCounterCurrencyCode());
}
@Test
@Order(3)
public void testBobTakesBsqSwapOffer() {
var bsqSwapOffer = getAvailableBsqSwapOffer();
var bsqSwapTradeInfo = bobClient.takeBsqSwapOffer(bsqSwapOffer.getId(),
bobsBsqAcct.getId(),
BISQ_FEE_CURRENCY_CODE);
log.debug("Trade at t1: {}", bsqSwapTradeInfo);
assertEquals(BsqSwapTrade.State.PREPARATION.name(), bsqSwapTradeInfo.getState());
genBtcBlocksThenWait(1, 3_000);
bsqSwapTradeInfo = getBsqSwapTrade(bsqSwapTradeInfo.getTradeId());
log.debug("Trade at t2: {}", bsqSwapTradeInfo);
assertEquals(BsqSwapTrade.State.COMPLETED.name(), bsqSwapTradeInfo.getState());
}
@Test
@Order(4)
public void testGetBalancesAfterTrade() {
var alicesBalances = aliceClient.getBalances();
log.info("Alice's After Trade Balance:\n{}", formatBalancesTbls(alicesBalances));
var bobsBalances = bobClient.getBalances();
log.info("Bob's After Trade Balance:\n{}", formatBalancesTbls(bobsBalances));
}
private BsqSwapOfferInfo getAvailableBsqSwapOffer() {
List<BsqSwapOfferInfo> bsqSwapOffers = new ArrayList<>();
int numFetchAttempts = 0;
while (bsqSwapOffers.size() == 0) {
bsqSwapOffers.addAll(bobClient.getBsqSwapOffers(BUY.name(), BSQ));
numFetchAttempts++;
if (bsqSwapOffers.size() == 0) {
log.warn("No available bsq swap offers found after {} fetch attempts.", numFetchAttempts);
if (numFetchAttempts > 9) {
if (checkForLoggedExceptions) {
printNodeExceptionMessages(log);
}
fail(format("Bob gave up on fetching available bsq swap offers after %d attempts.", numFetchAttempts));
}
sleep(1000);
} else {
assertEquals(1, bsqSwapOffers.size());
log.debug("Bob found new available bsq swap offer on attempt # {}.", numFetchAttempts);
break;
}
}
var bsqSwapOffer = bobClient.getBsqSwapOffer(bsqSwapOffers.get(0).getId());
assertEquals(bsqSwapOffers.get(0).getId(), bsqSwapOffer.getId());
return bsqSwapOffer;
}
private BsqSwapTradeInfo getBsqSwapTrade(String tradeId) {
int numFetchAttempts = 0;
while (true) {
try {
numFetchAttempts++;
return bobClient.getBsqSwapTrade(tradeId);
} catch (Exception ex) {
log.warn(ex.getMessage());
if (numFetchAttempts > 9) {
if (checkForLoggedExceptions) {
printNodeExceptionMessages(log);
}
fail(format("Could not find new bsq swap trade after %d attempts.", numFetchAttempts));
} else {
sleep(1000);
}
}
}
}
}

View file

@ -1,6 +1,6 @@
package bisq.apitest.method.trade;
import bisq.core.trade.Trade;
import bisq.core.trade.model.bisq_v1.Trade;
/**
* A test fixture encapsulating expected trade protocol status.

View file

@ -37,13 +37,13 @@ import static bisq.apitest.config.ApiTestConfig.BSQ;
import static bisq.cli.TableFormat.formatBalancesTbls;
import static bisq.cli.TableFormat.formatOfferTable;
import static bisq.core.btc.wallet.Restrictions.getDefaultBuyerSecurityDepositAsPercent;
import static bisq.core.trade.Trade.Phase.DEPOSIT_CONFIRMED;
import static bisq.core.trade.Trade.Phase.FIAT_SENT;
import static bisq.core.trade.Trade.Phase.PAYOUT_PUBLISHED;
import static bisq.core.trade.Trade.State.BUYER_RECEIVED_PAYOUT_TX_PUBLISHED_MSG;
import static bisq.core.trade.Trade.State.DEPOSIT_CONFIRMED_IN_BLOCK_CHAIN;
import static bisq.core.trade.Trade.State.SELLER_RECEIVED_FIAT_PAYMENT_INITIATED_MSG;
import static bisq.core.trade.Trade.State.SELLER_SAW_ARRIVED_PAYOUT_TX_PUBLISHED_MSG;
import static bisq.core.trade.model.bisq_v1.Trade.Phase.DEPOSIT_CONFIRMED;
import static bisq.core.trade.model.bisq_v1.Trade.Phase.FIAT_SENT;
import static bisq.core.trade.model.bisq_v1.Trade.Phase.PAYOUT_PUBLISHED;
import static bisq.core.trade.model.bisq_v1.Trade.State.BUYER_RECEIVED_PAYOUT_TX_PUBLISHED_MSG;
import static bisq.core.trade.model.bisq_v1.Trade.State.DEPOSIT_CONFIRMED_IN_BLOCK_CHAIN;
import static bisq.core.trade.model.bisq_v1.Trade.State.SELLER_RECEIVED_FIAT_PAYMENT_INITIATED_MSG;
import static bisq.core.trade.model.bisq_v1.Trade.State.SELLER_SAW_ARRIVED_PAYOUT_TX_PUBLISHED_MSG;
import static java.lang.String.format;
import static java.util.Collections.singletonList;
import static org.junit.jupiter.api.Assertions.assertEquals;
@ -51,7 +51,7 @@ import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.fail;
import static protobuf.Offer.State.OFFER_FEE_PAID;
import static protobuf.OfferPayload.Direction.SELL;
import static protobuf.OfferDirection.SELL;

View file

@ -37,17 +37,17 @@ import org.junit.jupiter.api.TestMethodOrder;
import static bisq.apitest.config.ApiTestConfig.BSQ;
import static bisq.cli.TableFormat.formatBalancesTbls;
import static bisq.core.btc.wallet.Restrictions.getDefaultBuyerSecurityDepositAsPercent;
import static bisq.core.trade.Trade.Phase.DEPOSIT_CONFIRMED;
import static bisq.core.trade.Trade.Phase.FIAT_SENT;
import static bisq.core.trade.Trade.Phase.PAYOUT_PUBLISHED;
import static bisq.core.trade.Trade.State.*;
import static bisq.core.trade.model.bisq_v1.Trade.Phase.DEPOSIT_CONFIRMED;
import static bisq.core.trade.model.bisq_v1.Trade.Phase.FIAT_SENT;
import static bisq.core.trade.model.bisq_v1.Trade.Phase.PAYOUT_PUBLISHED;
import static bisq.core.trade.model.bisq_v1.Trade.State.*;
import static java.lang.String.format;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.fail;
import static protobuf.Offer.State.OFFER_FEE_PAID;
import static protobuf.OfferPayload.Direction.BUY;
import static protobuf.OfferDirection.BUY;
import static protobuf.OpenOffer.State.AVAILABLE;
@Disabled

View file

@ -55,14 +55,14 @@ import org.junit.jupiter.api.TestMethodOrder;
import static bisq.apitest.config.ApiTestConfig.BSQ;
import static bisq.cli.TableFormat.formatBalancesTbls;
import static bisq.core.btc.wallet.Restrictions.getDefaultBuyerSecurityDepositAsPercent;
import static bisq.core.trade.Trade.Phase.DEPOSIT_CONFIRMED;
import static bisq.core.trade.Trade.Phase.FIAT_SENT;
import static bisq.core.trade.Trade.Phase.PAYOUT_PUBLISHED;
import static bisq.core.trade.Trade.State.*;
import static bisq.core.trade.model.bisq_v1.Trade.Phase.DEPOSIT_CONFIRMED;
import static bisq.core.trade.model.bisq_v1.Trade.Phase.FIAT_SENT;
import static bisq.core.trade.model.bisq_v1.Trade.Phase.PAYOUT_PUBLISHED;
import static bisq.core.trade.model.bisq_v1.Trade.State.*;
import static java.lang.String.format;
import static org.junit.jupiter.api.Assertions.*;
import static protobuf.Offer.State.OFFER_FEE_PAID;
import static protobuf.OfferPayload.Direction.BUY;
import static protobuf.OfferDirection.BUY;
import static protobuf.OpenOffer.State.AVAILABLE;
/**

View file

@ -38,21 +38,21 @@ import static bisq.apitest.config.ApiTestConfig.BTC;
import static bisq.cli.TableFormat.formatBalancesTbls;
import static bisq.cli.TableFormat.formatOfferTable;
import static bisq.core.btc.wallet.Restrictions.getDefaultBuyerSecurityDepositAsPercent;
import static bisq.core.trade.Trade.Phase.DEPOSIT_CONFIRMED;
import static bisq.core.trade.Trade.Phase.FIAT_SENT;
import static bisq.core.trade.Trade.Phase.PAYOUT_PUBLISHED;
import static bisq.core.trade.Trade.Phase.WITHDRAWN;
import static bisq.core.trade.Trade.State.DEPOSIT_CONFIRMED_IN_BLOCK_CHAIN;
import static bisq.core.trade.Trade.State.SELLER_RECEIVED_FIAT_PAYMENT_INITIATED_MSG;
import static bisq.core.trade.Trade.State.SELLER_SAW_ARRIVED_PAYOUT_TX_PUBLISHED_MSG;
import static bisq.core.trade.Trade.State.WITHDRAW_COMPLETED;
import static bisq.core.trade.model.bisq_v1.Trade.Phase.DEPOSIT_CONFIRMED;
import static bisq.core.trade.model.bisq_v1.Trade.Phase.FIAT_SENT;
import static bisq.core.trade.model.bisq_v1.Trade.Phase.PAYOUT_PUBLISHED;
import static bisq.core.trade.model.bisq_v1.Trade.Phase.WITHDRAWN;
import static bisq.core.trade.model.bisq_v1.Trade.State.DEPOSIT_CONFIRMED_IN_BLOCK_CHAIN;
import static bisq.core.trade.model.bisq_v1.Trade.State.SELLER_RECEIVED_FIAT_PAYMENT_INITIATED_MSG;
import static bisq.core.trade.model.bisq_v1.Trade.State.SELLER_SAW_ARRIVED_PAYOUT_TX_PUBLISHED_MSG;
import static bisq.core.trade.model.bisq_v1.Trade.State.WITHDRAW_COMPLETED;
import static java.lang.String.format;
import static java.util.Collections.singletonList;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.junit.jupiter.api.Assertions.fail;
import static protobuf.OfferPayload.Direction.BUY;
import static protobuf.OfferDirection.BUY;

View file

@ -37,18 +37,18 @@ import org.junit.jupiter.api.TestMethodOrder;
import static bisq.apitest.config.ApiTestConfig.BTC;
import static bisq.cli.TableFormat.formatBalancesTbls;
import static bisq.core.btc.wallet.Restrictions.getDefaultBuyerSecurityDepositAsPercent;
import static bisq.core.trade.Trade.Phase.DEPOSIT_CONFIRMED;
import static bisq.core.trade.Trade.Phase.FIAT_SENT;
import static bisq.core.trade.Trade.Phase.PAYOUT_PUBLISHED;
import static bisq.core.trade.Trade.Phase.WITHDRAWN;
import static bisq.core.trade.Trade.State.*;
import static bisq.core.trade.model.bisq_v1.Trade.Phase.DEPOSIT_CONFIRMED;
import static bisq.core.trade.model.bisq_v1.Trade.Phase.FIAT_SENT;
import static bisq.core.trade.model.bisq_v1.Trade.Phase.PAYOUT_PUBLISHED;
import static bisq.core.trade.model.bisq_v1.Trade.Phase.WITHDRAWN;
import static bisq.core.trade.model.bisq_v1.Trade.State.*;
import static java.lang.String.format;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.junit.jupiter.api.Assertions.fail;
import static protobuf.Offer.State.OFFER_FEE_PAID;
import static protobuf.OfferPayload.Direction.SELL;
import static protobuf.OfferDirection.SELL;
import static protobuf.OpenOffer.State.AVAILABLE;
@Disabled

View file

@ -124,7 +124,7 @@ public class BsqWalletTest extends MethodTest {
genBtcBlocksThenWait(1, 4000);
BsqBalanceInfo alicesBsqBalances = aliceClient.getBalances().getBsq();
BsqBalanceInfo bobsBsqBalances = waitForBsqNewAvailableConfirmedBalance(bobClient, 150000000);
BsqBalanceInfo bobsBsqBalances = waitForBsqNewAvailableBalance(bobClient, 150000000);
log.debug("See Available Confirmed BSQ Balances...");
printBobAndAliceBsqBalances(testInfo,
@ -166,8 +166,8 @@ public class BsqWalletTest extends MethodTest {
return bsqBalance;
}
private BsqBalanceInfo waitForBsqNewAvailableConfirmedBalance(GrpcClient grpcClient,
long staleBalance) {
private BsqBalanceInfo waitForBsqNewAvailableBalance(GrpcClient grpcClient,
long staleBalance) {
BsqBalanceInfo bsqBalance = grpcClient.getBsqBalances();
for (int numRequests = 1;
numRequests <= 15 && bsqBalance.getAvailableConfirmedBalance() == staleBalance;

View file

@ -38,13 +38,13 @@ public class WalletTestUtil {
0);
@SuppressWarnings("SameParameterValue")
public static bisq.core.api.model.BsqBalanceInfo bsqBalanceModel(long availableConfirmedBalance,
public static bisq.core.api.model.BsqBalanceInfo bsqBalanceModel(long availableBalance,
long unverifiedBalance,
long unconfirmedChangeBalance,
long lockedForVotingBalance,
long lockupBondsBalance,
long unlockingBondsBalance) {
return bisq.core.api.model.BsqBalanceInfo.valueOf(availableConfirmedBalance,
return bisq.core.api.model.BsqBalanceInfo.valueOf(availableBalance,
unverifiedBalance,
unconfirmedChangeBalance,
lockedForVotingBalance,
@ -54,7 +54,7 @@ public class WalletTestUtil {
public static void verifyBsqBalances(bisq.core.api.model.BsqBalanceInfo expected,
BsqBalanceInfo actual) {
assertEquals(expected.getAvailableConfirmedBalance(), actual.getAvailableConfirmedBalance());
assertEquals(expected.getAvailableBalance(), actual.getAvailableConfirmedBalance());
assertEquals(expected.getUnverifiedBalance(), actual.getUnverifiedBalance());
assertEquals(expected.getUnconfirmedChangeBalance(), actual.getUnconfirmedChangeBalance());
assertEquals(expected.getLockedForVotingBalance(), actual.getLockedForVotingBalance());

View file

@ -0,0 +1,86 @@
/*
* 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.scenario;
import lombok.extern.slf4j.Slf4j;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.MethodOrderer;
import org.junit.jupiter.api.Order;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.TestMethodOrder;
import org.junit.jupiter.api.condition.EnabledIf;
import static java.lang.System.getenv;
import bisq.apitest.method.offer.AbstractOfferTest;
import bisq.apitest.method.trade.BsqSwapTradeTest;
@EnabledIf("envLongRunningTestEnabled")
@Slf4j
@TestMethodOrder(MethodOrderer.OrderAnnotation.class)
public class LongRunningBsqSwapTest extends AbstractOfferTest {
private static final int MAX_SWAPS = 250;
@BeforeAll
public static void setUp() {
AbstractOfferTest.setUp();
createBsqSwapBsqPaymentAccounts();
}
@Test
@Order(1)
public void testBsqSwaps() {
// TODO Fix wallet inconsistency bugs after N(?) trades.
BsqSwapTradeTest test = new BsqSwapTradeTest();
test.setCheckForLoggedExceptions(true);
for (int swapCount = 1; swapCount <= MAX_SWAPS; swapCount++) {
log.info("Beginning BSQ Swap # {}", swapCount);
test.testGetBalancesBeforeTrade();
test.testAliceCreateBsqSwapBuyOffer();
genBtcBlocksThenWait(1, 8_000);
test.testBobTakesBsqSwapOffer();
genBtcBlocksThenWait(1, 8_000);
test.testGetBalancesAfterTrade();
log.info("Finished BSQ Swap # {}", swapCount);
}
}
protected static boolean envLongRunningTestEnabled() {
String envName = "LONG_RUNNING_BSQ_SWAP_TEST_ENABLED";
String envX = getenv(envName);
if (envX != null) {
log.info("Enabled, found {}.", envName);
return true;
} else {
log.info("Skipped, no environment variable {} defined.", envName);
log.info("To enable on Mac OS or Linux:"
+ "\tIf running in terminal, export LONG_RUNNING_BSQ_SWAP_TEST_ENABLED=true in bash shell."
+ "\tIf running in Intellij, set LONG_RUNNING_BSQ_SWAP_TEST_ENABLED=true in launcher's Environment variables field.");
return false;
}
}
}

View file

@ -35,8 +35,8 @@ import static bisq.cli.CurrencyFormat.formatPrice;
import static bisq.core.btc.wallet.Restrictions.getDefaultBuyerSecurityDepositAsPercent;
import static java.lang.System.getenv;
import static org.junit.jupiter.api.Assertions.fail;
import static protobuf.OfferPayload.Direction.BUY;
import static protobuf.OfferPayload.Direction.SELL;
import static protobuf.OfferDirection.BUY;
import static protobuf.OfferDirection.SELL;

View file

@ -28,6 +28,7 @@ import org.junit.jupiter.api.TestMethodOrder;
import bisq.apitest.method.offer.AbstractOfferTest;
import bisq.apitest.method.offer.BsqSwapOfferTest;
import bisq.apitest.method.offer.CancelOfferTest;
import bisq.apitest.method.offer.CreateBSQOffersTest;
import bisq.apitest.method.offer.CreateOfferUsingFixedPriceTest;
@ -90,6 +91,19 @@ public class OfferTest extends AbstractOfferTest {
@Test
@Order(6)
public void testCreateBSQSwapOffers() {
BsqSwapOfferTest test = new BsqSwapOfferTest();
BsqSwapOfferTest.createBsqSwapBsqPaymentAccounts();
test.testAliceCreateBsqSwapBuyOffer1();
test.testAliceCreateBsqSwapBuyOffer2();
test.testAliceCreateBsqSwapBuyOffer3();
test.testAliceCreateBsqSwapBuyOffer4();
test.testGetMyBsqSwapOffers();
test.testGetAvailableBsqSwapOffers();
}
@Test
@Order(7)
public void testEditOffer() {
EditOfferTest test = new EditOfferTest();
// Edit fiat offer tests

View file

@ -29,6 +29,7 @@ import org.junit.jupiter.api.TestMethodOrder;
import bisq.apitest.method.trade.AbstractTradeTest;
import bisq.apitest.method.trade.BsqSwapTradeTest;
import bisq.apitest.method.trade.TakeBuyBSQOfferTest;
import bisq.apitest.method.trade.TakeBuyBTCOfferTest;
import bisq.apitest.method.trade.TakeBuyBTCOfferWithNationalBankAcctTest;
@ -97,4 +98,13 @@ public class TradeTest extends AbstractTradeTest {
test.testBobsConfirmPaymentReceived(testInfo);
test.testAlicesBtcWithdrawalToExternalAddress(testInfo);
}
@Test
@Order(6)
public void testBsqSwapTradeTest(final TestInfo testInfo) {
BsqSwapTradeTest test = new BsqSwapTradeTest();
test.createBsqSwapBsqPaymentAccounts();
test.testAliceCreateBsqSwapBuyOffer();
test.testBobTakesBsqSwapOffer();
}
}

View file

@ -17,8 +17,9 @@
package bisq.apitest.scenario.bot.script;
import bisq.core.util.JsonUtil;
import bisq.common.file.JsonFileManager;
import bisq.common.util.Utilities;
import joptsimple.BuiltinHelpFormatter;
import joptsimple.OptionParser;
@ -214,7 +215,7 @@ public class BotScriptGenerator {
}
private String generateBotScriptTemplate() {
return Utilities.objectToJson(new BotScript(
return JsonUtil.objectToJson(new BotScript(
useTestHarness,
botPaymentMethodId,
countryCode,

View file

@ -32,7 +32,7 @@ configure(subprojects) {
ext { // in alphabetical order
bcVersion = '1.63'
bitcoinjVersion = '3186b20'
bitcoinjVersion = '42bbae9'
codecVersion = '1.13'
easybindVersion = '1.0.3'
easyVersion = '4.0.1'

View file

@ -24,8 +24,8 @@ import java.util.function.Function;
import static bisq.cli.ColumnHeaderConstants.COL_HEADER_DIRECTION;
import static java.lang.String.format;
import static protobuf.OfferPayload.Direction.BUY;
import static protobuf.OfferPayload.Direction.SELL;
import static protobuf.OfferDirection.BUY;
import static protobuf.OfferDirection.SELL;
class DirectionFormat {

View file

@ -20,12 +20,17 @@ package bisq.cli;
import bisq.proto.grpc.AddressBalanceInfo;
import bisq.proto.grpc.BalancesInfo;
import bisq.proto.grpc.BsqBalanceInfo;
import bisq.proto.grpc.BsqSwapOfferInfo;
import bisq.proto.grpc.BsqSwapTradeInfo;
import bisq.proto.grpc.BtcBalanceInfo;
import bisq.proto.grpc.CreateBsqSwapOfferRequest;
import bisq.proto.grpc.GetMethodHelpRequest;
import bisq.proto.grpc.GetVersionRequest;
import bisq.proto.grpc.OfferInfo;
import bisq.proto.grpc.RegisterDisputeAgentRequest;
import bisq.proto.grpc.StopRequest;
import bisq.proto.grpc.TakeBsqSwapOfferReply;
import bisq.proto.grpc.TakeBsqSwapOfferRequest;
import bisq.proto.grpc.TakeOfferReply;
import bisq.proto.grpc.TradeInfo;
import bisq.proto.grpc.TxFeeRateInfo;
@ -137,6 +142,21 @@ public final class GrpcClient {
return walletsServiceRequest.getTransaction(txId);
}
public BsqSwapOfferInfo createBsqSwapOffer(String direction,
long amount,
long minAmount,
String fixedPrice,
String paymentAcctId) {
var request = CreateBsqSwapOfferRequest.newBuilder()
.setDirection(direction)
.setAmount(amount)
.setMinAmount(minAmount)
.setPrice(fixedPrice)
.setPaymentAccountId(paymentAcctId)
.build();
return grpcStubs.offersService.createBsqSwapOffer(request).getBsqSwapOffer();
}
public OfferInfo createFixedPricedOffer(String direction,
String currencyCode,
long amount,
@ -243,14 +263,26 @@ public final class GrpcClient {
offersServiceRequest.cancelOffer(offerId);
}
public BsqSwapOfferInfo getBsqSwapOffer(String offerId) {
return offersServiceRequest.getBsqSwapOffer(offerId);
}
public OfferInfo getOffer(String offerId) {
return offersServiceRequest.getOffer(offerId);
}
public BsqSwapOfferInfo getMyBsqSwapOffer(String offerId) {
return offersServiceRequest.getMyBsqSwapOffer(offerId);
}
public OfferInfo getMyOffer(String offerId) {
return offersServiceRequest.getMyOffer(offerId);
}
public List<BsqSwapOfferInfo> getBsqSwapOffers(String direction, String currencyCode) {
return offersServiceRequest.getBsqSwapOffers(direction, currencyCode);
}
public List<OfferInfo> getOffers(String direction, String currencyCode) {
return offersServiceRequest.getOffers(direction, currencyCode);
}
@ -271,6 +303,14 @@ public final class GrpcClient {
return offersServiceRequest.getBsqOffersSortedByDate();
}
public List<BsqSwapOfferInfo> getBsqSwapOffersSortedByDate() {
return offersServiceRequest.getBsqSwapOffersSortedByDate();
}
public List<BsqSwapOfferInfo> getMyBsqSwapOffers(String direction, String currencyCode) {
return offersServiceRequest.getMyBsqSwapOffers(direction, currencyCode);
}
public List<OfferInfo> getMyOffers(String direction, String currencyCode) {
return offersServiceRequest.getMyOffers(direction, currencyCode);
}
@ -291,22 +331,53 @@ public final class GrpcClient {
return offersServiceRequest.getMyBsqOffersSortedByDate();
}
public List<BsqSwapOfferInfo> getMyBsqSwapBsqOffersSortedByDate() {
return offersServiceRequest.getMyBsqSwapOffersSortedByDate();
}
public OfferInfo getMostRecentOffer(String direction, String currencyCode) {
return offersServiceRequest.getMostRecentOffer(direction, currencyCode);
}
public List<BsqSwapOfferInfo> sortBsqSwapOffersByDate(List<BsqSwapOfferInfo> offerInfoList) {
return offersServiceRequest.sortBsqSwapOffersByDate(offerInfoList);
}
public List<OfferInfo> sortOffersByDate(List<OfferInfo> offerInfoList) {
return offersServiceRequest.sortOffersByDate(offerInfoList);
}
public TakeBsqSwapOfferReply getTakeBsqSwapOfferReply(String offerId,
String paymentAccountId,
String takerFeeCurrencyCode) {
var request = TakeBsqSwapOfferRequest.newBuilder()
.setOfferId(offerId)
.setPaymentAccountId(paymentAccountId)
.setTakerFeeCurrencyCode(takerFeeCurrencyCode)
.build();
return grpcStubs.tradesService.takeBsqSwapOffer(request);
}
public TakeOfferReply getTakeOfferReply(String offerId, String paymentAccountId, String takerFeeCurrencyCode) {
return tradesServiceRequest.getTakeOfferReply(offerId, paymentAccountId, takerFeeCurrencyCode);
}
public BsqSwapTradeInfo takeBsqSwapOffer(String offerId, String paymentAccountId, String takerFeeCurrencyCode) {
var reply = getTakeBsqSwapOfferReply(offerId, paymentAccountId, takerFeeCurrencyCode);
if (reply.hasBsqSwapTrade())
return reply.getBsqSwapTrade();
else
throw new IllegalStateException(reply.getFailureReason().getDescription());
}
public TradeInfo takeOffer(String offerId, String paymentAccountId, String takerFeeCurrencyCode) {
return tradesServiceRequest.takeOffer(offerId, paymentAccountId, takerFeeCurrencyCode);
}
public BsqSwapTradeInfo getBsqSwapTrade(String tradeId) {
return tradesServiceRequest.getBsqSwapTrade(tradeId);
}
public TradeInfo getTrade(String tradeId) {
return tradesServiceRequest.getTrade(tradeId);
}

View file

@ -30,6 +30,7 @@ import lombok.Getter;
import static bisq.cli.opts.OptLabel.OPT_HELP;
@SuppressWarnings("unchecked")
abstract class AbstractMethodOptionParser implements MethodOpts {
// The full command line args passed to CliMain.main(String[] args).
@ -53,7 +54,6 @@ abstract class AbstractMethodOptionParser implements MethodOpts {
public AbstractMethodOptionParser parse() {
try {
options = parser.parse(new ArgumentList(args).getMethodArguments());
//noinspection unchecked
nonOptionArguments = (List<String>) options.nonOptionArguments();
return this;
} catch (OptionException ex) {

View file

@ -20,10 +20,7 @@ package bisq.cli.opts;
import joptsimple.OptionSpec;
import static bisq.cli.opts.OptLabel.OPT_ACCOUNT_NAME;
import static bisq.cli.opts.OptLabel.OPT_ADDRESS;
import static bisq.cli.opts.OptLabel.OPT_CURRENCY_CODE;
import static bisq.cli.opts.OptLabel.OPT_TRADE_INSTANT;
import static bisq.cli.opts.OptLabel.*;
public class CreateCryptoCurrencyPaymentAcctOptionParser extends AbstractMethodOptionParser implements MethodOpts {
@ -41,6 +38,11 @@ public class CreateCryptoCurrencyPaymentAcctOptionParser extends AbstractMethodO
.ofType(boolean.class)
.defaultsTo(Boolean.FALSE);
final OptionSpec<Boolean> tradeBsqSwapOpt = parser.accepts(OPT_TRADE_BSQ_SWAP, "create trade bsq swap account")
.withOptionalArg()
.ofType(boolean.class)
.defaultsTo(Boolean.FALSE);
public CreateCryptoCurrencyPaymentAcctOptionParser(String[] args) {
super(args);
}
@ -82,4 +84,8 @@ public class CreateCryptoCurrencyPaymentAcctOptionParser extends AbstractMethodO
public boolean getIsTradeInstant() {
return options.valueOf(tradeInstantOpt);
}
public boolean getIsTradeBsqSwap() {
return options.valueOf(tradeBsqSwapOpt);
}
}

View file

@ -44,6 +44,7 @@ public class OptLabel {
public final static String OPT_REGISTRATION_KEY = "registration-key";
public final static String OPT_SECURITY_DEPOSIT = "security-deposit";
public final static String OPT_SHOW_CONTRACT = "show-contract";
public final static String OPT_TRADE_BSQ_SWAP = "trade-bsq-swap";
public final static String OPT_TRADE_ID = "trade-id";
public final static String OPT_TRADE_INSTANT = "trade-instant";
public final static String OPT_TIMEOUT = "timeout";

View file

@ -17,6 +17,7 @@
package bisq.cli.request;
import bisq.proto.grpc.BsqSwapOfferInfo;
import bisq.proto.grpc.CancelOfferRequest;
import bisq.proto.grpc.CreateOfferRequest;
import bisq.proto.grpc.EditOfferRequest;
@ -38,8 +39,8 @@ import static bisq.proto.grpc.EditOfferRequest.EditType.MKT_PRICE_MARGIN_ONLY;
import static bisq.proto.grpc.EditOfferRequest.EditType.TRIGGER_PRICE_ONLY;
import static java.util.Comparator.comparing;
import static java.util.stream.Collectors.toList;
import static protobuf.OfferPayload.Direction.BUY;
import static protobuf.OfferPayload.Direction.SELL;
import static protobuf.OfferDirection.BUY;
import static protobuf.OfferDirection.SELL;
@ -207,6 +208,13 @@ public class OffersServiceRequest {
grpcStubs.offersService.cancelOffer(request);
}
public BsqSwapOfferInfo getBsqSwapOffer(String offerId) {
var request = GetOfferRequest.newBuilder()
.setId(offerId)
.build();
return grpcStubs.offersService.getBsqSwapOffer(request).getBsqSwapOffer();
}
public OfferInfo getOffer(String offerId) {
var request = GetOfferRequest.newBuilder()
.setId(offerId)
@ -214,6 +222,14 @@ public class OffersServiceRequest {
return grpcStubs.offersService.getOffer(request).getOffer();
}
public BsqSwapOfferInfo getMyBsqSwapOffer(String offerId) {
var request = GetMyOfferRequest.newBuilder()
.setId(offerId)
.build();
return grpcStubs.offersService.getMyBsqSwapOffer(request).getBsqSwapOffer();
}
public OfferInfo getMyOffer(String offerId) {
var request = GetMyOfferRequest.newBuilder()
.setId(offerId)
@ -221,6 +237,15 @@ public class OffersServiceRequest {
return grpcStubs.offersService.getMyOffer(request).getOffer();
}
public List<BsqSwapOfferInfo> getBsqSwapOffers(String direction, String currencyCode) {
var request = GetOffersRequest.newBuilder()
.setDirection(direction)
.setCurrencyCode(currencyCode)
.build();
return grpcStubs.offersService.getBsqSwapOffers(request).getBsqSwapOffersList();
}
public List<OfferInfo> getOffers(String direction, String currencyCode) {
if (isSupportedCryptoCurrency(currencyCode)) {
return getCryptoCurrencyOffers(direction, currencyCode);
@ -251,6 +276,13 @@ public class OffersServiceRequest {
return offers.isEmpty() ? offers : sortOffersByDate(offers);
}
public List<BsqSwapOfferInfo> getBsqSwapOffersSortedByDate() {
ArrayList<BsqSwapOfferInfo> offers = new ArrayList<>();
offers.addAll(getBsqSwapOffers(BUY.name(), "BSQ"));
offers.addAll(getBsqSwapOffers(SELL.name(), "BSQ"));
return sortBsqSwapOffersByDate(offers);
}
public List<OfferInfo> getBsqOffersSortedByDate() {
ArrayList<OfferInfo> offers = new ArrayList<>();
offers.addAll(getCryptoCurrencyOffers(BUY.name(), "BSQ"));
@ -258,6 +290,14 @@ public class OffersServiceRequest {
return sortOffersByDate(offers);
}
public List<BsqSwapOfferInfo> getMyBsqSwapOffers(String direction, String currencyCode) {
var request = GetMyOffersRequest.newBuilder()
.setDirection(direction)
.setCurrencyCode(currencyCode)
.build();
return grpcStubs.offersService.getMyBsqSwapOffers(request).getBsqSwapOffersList();
}
public List<OfferInfo> getMyOffers(String direction, String currencyCode) {
if (isSupportedCryptoCurrency(currencyCode)) {
return getMyCryptoCurrencyOffers(direction, currencyCode);
@ -295,11 +335,25 @@ public class OffersServiceRequest {
return sortOffersByDate(offers);
}
public List<BsqSwapOfferInfo> getMyBsqSwapOffersSortedByDate() {
ArrayList<BsqSwapOfferInfo> offers = new ArrayList<>();
offers.addAll(getMyBsqSwapOffers(BUY.name(), "BSQ"));
offers.addAll(getMyBsqSwapOffers(SELL.name(), "BSQ"));
return sortBsqSwapOffersByDate(offers);
}
public OfferInfo getMostRecentOffer(String direction, String currencyCode) {
List<OfferInfo> offers = getOffersSortedByDate(direction, currencyCode);
return offers.isEmpty() ? null : offers.get(offers.size() - 1);
}
public List<BsqSwapOfferInfo> sortBsqSwapOffersByDate(List<BsqSwapOfferInfo> offerInfoList) {
return offerInfoList.stream()
.sorted(comparing(BsqSwapOfferInfo::getDate))
.collect(toList());
}
public List<OfferInfo> sortOffersByDate(List<OfferInfo> offerInfoList) {
return offerInfoList.stream()
.sorted(comparing(OfferInfo::getDate))

View file

@ -17,6 +17,7 @@
package bisq.cli.request;
import bisq.proto.grpc.BsqSwapTradeInfo;
import bisq.proto.grpc.ConfirmPaymentReceivedRequest;
import bisq.proto.grpc.ConfirmPaymentStartedRequest;
import bisq.proto.grpc.GetTradeRequest;
@ -55,6 +56,13 @@ public class TradesServiceRequest {
throw new IllegalStateException(reply.getFailureReason().getDescription());
}
public BsqSwapTradeInfo getBsqSwapTrade(String tradeId) {
var request = GetTradeRequest.newBuilder()
.setTradeId(tradeId)
.build();
return grpcStubs.tradesService.getBsqSwapTrade(request).getBsqSwapTrade();
}
public TradeInfo getTrade(String tradeId) {
var request = GetTradeRequest.newBuilder()
.setTradeId(tradeId)

View file

@ -42,5 +42,6 @@ public enum Capability {
REFUND_AGENT, // Supports refund agents
TRADE_STATISTICS_HASH_UPDATE, // We changed the hash method in 1.2.0 and that requires update to 1.2.2 for handling it correctly, otherwise the seed nodes have to process too much data.
NO_ADDRESS_PRE_FIX, // At 1.4.0 we removed the prefix filter for mailbox messages. If a peer has that capability we do not sent the prefix.
TRADE_STATISTICS_3 // We used a new reduced trade statistics model from v1.4.0 on
TRADE_STATISTICS_3, // We used a new reduced trade statistics model from v1.4.0 on
BSQ_SWAP_OFFER // Supports new message type BsqSwapOffer
}

View file

@ -65,7 +65,7 @@ public class ConfigFileEditor {
if (ConfigFileOption.isOption(line)) {
ConfigFileOption option = ConfigFileOption.parse(line);
if (option.name.equals(name)) {
log.warn("Cleared existing config file option '{}'", option);
log.debug("Cleared existing config file option '{}'", option);
continue;
}
}

View file

@ -0,0 +1,181 @@
/*
* 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.common.crypto;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.primitives.Longs;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;
import java.util.concurrent.CompletableFuture;
import java.util.function.BiFunction;
import lombok.extern.slf4j.Slf4j;
/**
* HashCash implementation for proof of work
* It doubles required work by difficulty increase (adding one leading zero).
*
* See https://www.hashcash.org/papers/hashcash.pdf
*/
@Slf4j
public class HashCashService {
// Default validations. Custom implementations might use tolerance.
private static final BiFunction<byte[], byte[], Boolean> isChallengeValid = Arrays::equals;
private static final BiFunction<Integer, Integer, Boolean> isDifficultyValid = Integer::equals;
public static CompletableFuture<ProofOfWork> mint(byte[] payload,
byte[] challenge,
int difficulty) {
return HashCashService.mint(payload,
challenge,
difficulty,
HashCashService::testDifficulty);
}
public static boolean verify(ProofOfWork proofOfWork) {
return verify(proofOfWork,
proofOfWork.getChallenge(),
proofOfWork.getNumLeadingZeros());
}
public static boolean verify(ProofOfWork proofOfWork,
byte[] controlChallenge,
int controlDifficulty) {
return HashCashService.verify(proofOfWork,
controlChallenge,
controlDifficulty,
HashCashService::testDifficulty);
}
public static boolean verify(ProofOfWork proofOfWork,
byte[] controlChallenge,
int controlDifficulty,
BiFunction<byte[], byte[], Boolean> challengeValidation,
BiFunction<Integer, Integer, Boolean> difficultyValidation) {
return HashCashService.verify(proofOfWork,
controlChallenge,
controlDifficulty,
challengeValidation,
difficultyValidation,
HashCashService::testDifficulty);
}
private static boolean testDifficulty(byte[] result, long difficulty) {
return HashCashService.numberOfLeadingZeros(result) > difficulty;
}
///////////////////////////////////////////////////////////////////////////////////////////
// Generic
///////////////////////////////////////////////////////////////////////////////////////////
static CompletableFuture<ProofOfWork> mint(byte[] payload,
byte[] challenge,
int difficulty,
BiFunction<byte[], Integer, Boolean> testDifficulty) {
return CompletableFuture.supplyAsync(() -> {
long ts = System.currentTimeMillis();
byte[] result;
long counter = 0;
do {
result = toSha256Hash(payload, challenge, ++counter);
}
while (!testDifficulty.apply(result, difficulty));
ProofOfWork proofOfWork = new ProofOfWork(payload, counter, challenge, difficulty, System.currentTimeMillis() - ts);
log.info("Completed minting proofOfWork: {}", proofOfWork);
return proofOfWork;
});
}
static boolean verify(ProofOfWork proofOfWork,
byte[] controlChallenge,
int controlDifficulty,
BiFunction<byte[], Integer, Boolean> testDifficulty) {
return verify(proofOfWork,
controlChallenge,
controlDifficulty,
HashCashService.isChallengeValid,
HashCashService.isDifficultyValid,
testDifficulty);
}
static boolean verify(ProofOfWork proofOfWork,
byte[] controlChallenge,
int controlDifficulty,
BiFunction<byte[], byte[], Boolean> challengeValidation,
BiFunction<Integer, Integer, Boolean> difficultyValidation,
BiFunction<byte[], Integer, Boolean> testDifficulty) {
return challengeValidation.apply(proofOfWork.getChallenge(), controlChallenge) &&
difficultyValidation.apply(proofOfWork.getNumLeadingZeros(), controlDifficulty) &&
verify(proofOfWork, testDifficulty);
}
private static boolean verify(ProofOfWork proofOfWork, BiFunction<byte[], Integer, Boolean> testDifficulty) {
byte[] hash = HashCashService.toSha256Hash(proofOfWork.getPayload(),
proofOfWork.getChallenge(),
proofOfWork.getCounter());
return testDifficulty.apply(hash, proofOfWork.getNumLeadingZeros());
}
///////////////////////////////////////////////////////////////////////////////////////////
// Utils
///////////////////////////////////////////////////////////////////////////////////////////
public static byte[] getBytes(String value) {
return value.getBytes(StandardCharsets.UTF_8);
}
@VisibleForTesting
static int numberOfLeadingZeros(byte[] bytes) {
int numberOfLeadingZeros = 0;
for (int i = 0; i < bytes.length; i++) {
numberOfLeadingZeros += numberOfLeadingZeros(bytes[i]);
if (numberOfLeadingZeros < 8 * (i + 1)) {
break;
}
}
return numberOfLeadingZeros;
}
private static byte[] toSha256Hash(byte[] payload, byte[] challenge, long counter) {
byte[] preImage = org.bouncycastle.util.Arrays.concatenate(payload,
challenge,
Longs.toByteArray(counter));
return Hash.getSha256Hash(preImage);
}
// Borrowed from Integer.numberOfLeadingZeros and adjusted for byte
@VisibleForTesting
static int numberOfLeadingZeros(byte i) {
if (i <= 0)
return i == 0 ? 8 : 0;
int n = 7;
if (i >= 1 << 4) {
n -= 4;
i >>>= 4;
}
if (i >= 1 << 2) {
n -= 2;
i >>>= 2;
}
return n - (i >>> 1);
}
}

View file

@ -0,0 +1,122 @@
/*
* 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.common.crypto;
import bisq.common.proto.network.NetworkPayload;
import com.google.protobuf.ByteString;
import java.math.BigInteger;
import lombok.EqualsAndHashCode;
import lombok.Getter;
@EqualsAndHashCode
public final class ProofOfWork implements NetworkPayload {
@Getter
private final byte[] payload;
@Getter
private final long counter;
@Getter
private final byte[] challenge;
// We want to support BigInteger value for difficulty as well so we store it as byte array
private final byte[] difficulty;
@Getter
private final long duration;
public ProofOfWork(byte[] payload,
long counter,
byte[] challenge,
int difficulty,
long duration) {
this(payload,
counter,
challenge,
BigInteger.valueOf(difficulty).toByteArray(),
duration);
}
public ProofOfWork(byte[] payload,
long counter,
byte[] challenge,
BigInteger difficulty,
long duration) {
this(payload,
counter,
challenge,
difficulty.toByteArray(),
duration);
}
public ProofOfWork(byte[] payload,
long counter,
byte[] challenge,
byte[] difficulty,
long duration) {
this.payload = payload;
this.counter = counter;
this.challenge = challenge;
this.difficulty = difficulty;
this.duration = duration;
}
public int getNumLeadingZeros() {
return new BigInteger(difficulty).intValue();
}
public BigInteger getTarget() {
return new BigInteger(difficulty);
}
///////////////////////////////////////////////////////////////////////////////////////////
// PROTO BUFFER
///////////////////////////////////////////////////////////////////////////////////////////
@Override
public protobuf.ProofOfWork toProtoMessage() {
return protobuf.ProofOfWork.newBuilder()
.setPayload(ByteString.copyFrom(payload))
.setCounter(counter)
.setChallenge(ByteString.copyFrom(challenge))
.setDifficulty(ByteString.copyFrom(difficulty))
.setDuration(duration)
.build();
}
public static ProofOfWork fromProto(protobuf.ProofOfWork proto) {
return new ProofOfWork(
proto.getPayload().toByteArray(),
proto.getCounter(),
proto.getChallenge().toByteArray(),
proto.getDifficulty().toByteArray(),
proto.getDuration()
);
}
@Override
public String toString() {
return "ProofOfWork{" +
",\r\n counter=" + counter +
",\r\n numLeadingZeros=" + getNumLeadingZeros() +
",\r\n target=" + getTarget() +
",\r\n duration=" + duration +
"\r\n}";
}
}

View file

@ -0,0 +1,156 @@
/*
* 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.common.crypto;
import com.google.common.primitives.Longs;
import java.math.BigInteger;
import java.util.Arrays;
import java.util.concurrent.CompletableFuture;
import java.util.function.BiFunction;
import lombok.extern.slf4j.Slf4j;
/**
* Bitcoin-like proof of work implementation. Differs from original hashcash by using BigInteger for comparing
* the hash result with the target difficulty to gain more fine grained control for difficulty adjustment.
* This class provides a convenience method getDifficultyAsBigInteger(numLeadingZeros) to get values which are
* equivalent to the hashcash difficulty values.
*
* See https://en.wikipedia.org/wiki/Hashcash"
* "Unlike hashcash, Bitcoin's difficulty target does not specify a minimum number of leading zeros in the hash.
* Instead, the hash is interpreted as a (very large) integer, and this integer must be less than the target integer."
*/
@Slf4j
public class ProofOfWorkService {
// Default validations. Custom implementations might use tolerance.
private static final BiFunction<byte[], byte[], Boolean> isChallengeValid = Arrays::equals;
private static final BiFunction<BigInteger, BigInteger, Boolean> isTargetValid = BigInteger::equals;
public static CompletableFuture<ProofOfWork> mint(byte[] payload,
byte[] challenge,
BigInteger target) {
return mint(payload,
challenge,
target,
ProofOfWorkService::testTarget);
}
public static boolean verify(ProofOfWork proofOfWork) {
return verify(proofOfWork,
proofOfWork.getChallenge(),
proofOfWork.getTarget());
}
public static boolean verify(ProofOfWork proofOfWork,
byte[] controlChallenge,
BigInteger controlTarget) {
return verify(proofOfWork,
controlChallenge,
controlTarget,
ProofOfWorkService::testTarget);
}
public static boolean verify(ProofOfWork proofOfWork,
byte[] controlChallenge,
BigInteger controlTarget,
BiFunction<byte[], byte[], Boolean> challengeValidation,
BiFunction<BigInteger, BigInteger, Boolean> targetValidation) {
return verify(proofOfWork,
controlChallenge,
controlTarget,
challengeValidation,
targetValidation,
ProofOfWorkService::testTarget);
}
public static BigInteger getTarget(int numLeadingZeros) {
return BigInteger.TWO.pow(255 - numLeadingZeros).subtract(BigInteger.ONE);
}
private static boolean testTarget(byte[] result, BigInteger target) {
return getUnsignedBigInteger(result).compareTo(target) < 0;
}
///////////////////////////////////////////////////////////////////////////////////////////
// Generic
///////////////////////////////////////////////////////////////////////////////////////////
static CompletableFuture<ProofOfWork> mint(byte[] payload,
byte[] challenge,
BigInteger target,
BiFunction<byte[], BigInteger, Boolean> testTarget) {
return CompletableFuture.supplyAsync(() -> {
long ts = System.currentTimeMillis();
byte[] result;
long counter = 0;
do {
result = toSha256Hash(payload, challenge, ++counter);
}
while (!testTarget.apply(result, target));
return new ProofOfWork(payload, counter, challenge, target, System.currentTimeMillis() - ts);
});
}
static boolean verify(ProofOfWork proofOfWork,
byte[] controlChallenge,
BigInteger controlTarget,
BiFunction<byte[], BigInteger, Boolean> testTarget) {
return verify(proofOfWork,
controlChallenge,
controlTarget,
ProofOfWorkService.isChallengeValid,
ProofOfWorkService.isTargetValid,
testTarget);
}
static boolean verify(ProofOfWork proofOfWork,
byte[] controlChallenge,
BigInteger controlTarget,
BiFunction<byte[], byte[], Boolean> challengeValidation,
BiFunction<BigInteger, BigInteger, Boolean> targetValidation,
BiFunction<byte[], BigInteger, Boolean> testTarget) {
return challengeValidation.apply(proofOfWork.getChallenge(), controlChallenge) &&
targetValidation.apply(proofOfWork.getTarget(), controlTarget) &&
verify(proofOfWork, testTarget);
}
private static boolean verify(ProofOfWork proofOfWork, BiFunction<byte[], BigInteger, Boolean> testTarget) {
byte[] hash = toSha256Hash(proofOfWork.getPayload(), proofOfWork.getChallenge(), proofOfWork.getCounter());
return testTarget.apply(hash, proofOfWork.getTarget());
}
///////////////////////////////////////////////////////////////////////////////////////////
// Utils
///////////////////////////////////////////////////////////////////////////////////////////
private static BigInteger getUnsignedBigInteger(byte[] result) {
return new BigInteger(1, result);
}
private static byte[] toSha256Hash(byte[] payload, byte[] challenge, long counter) {
byte[] preImage = org.bouncycastle.util.Arrays.concatenate(payload,
challenge,
Longs.toByteArray(counter));
return Hash.getSha256Hash(preImage);
}
}

View file

@ -19,11 +19,6 @@ package bisq.common.util;
import org.bitcoinj.core.Utils;
import com.google.gson.ExclusionStrategy;
import com.google.gson.FieldAttributes;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.common.base.Splitter;
import com.google.common.util.concurrent.FutureCallback;
import com.google.common.util.concurrent.ListeningExecutorService;
@ -87,15 +82,6 @@ import static com.google.common.base.Preconditions.checkNotNull;
@Slf4j
public class Utilities {
public static String objectToJson(Object object) {
Gson gson = new GsonBuilder()
.setExclusionStrategies(new AnnotationExclusionStrategy())
/*.excludeFieldsWithModifiers(Modifier.TRANSIENT)*/
/* .setFieldNamingPolicy(FieldNamingPolicy.UPPER_CAMEL_CASE)*/
.setPrettyPrinting()
.create();
return gson.toJson(object);
}
public static ExecutorService getSingleThreadExecutor(String name) {
final ThreadFactory threadFactory = new ThreadFactoryBuilder()
@ -449,18 +435,6 @@ public class Utilities {
return new File(Utilities.class.getProtectionDomain().getCodeSource().getLocation().toURI()).getPath();
}
private static class AnnotationExclusionStrategy implements ExclusionStrategy {
@Override
public boolean shouldSkipField(FieldAttributes f) {
return f.getAnnotation(JsonExclude.class) != null;
}
@Override
public boolean shouldSkipClass(Class<?> clazz) {
return false;
}
}
public static String toTruncatedString(Object message) {
return toTruncatedString(message, 200, true);
}

View file

@ -0,0 +1,84 @@
package bisq.common.crypto;
import org.apache.commons.lang3.RandomStringUtils;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
import java.util.concurrent.ExecutionException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.junit.Test;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
public class HashCashServiceTest {
private final static Logger log = LoggerFactory.getLogger(HashCashServiceTest.class);
@Test
public void testNumberOfLeadingZeros() {
assertEquals(8, HashCashService.numberOfLeadingZeros((byte) 0x0));
assertEquals(0, HashCashService.numberOfLeadingZeros((byte) 0xFF));
assertEquals(6, HashCashService.numberOfLeadingZeros((byte) 0x2));
assertEquals(2, HashCashService.numberOfLeadingZeros(Byte.parseByte("00100000", 2)));
assertEquals(1, HashCashService.numberOfLeadingZeros(new byte[]{Byte.parseByte("01000000", 2), Byte.parseByte("00000000", 2)}));
assertEquals(9, HashCashService.numberOfLeadingZeros(new byte[]{Byte.parseByte("00000000", 2), Byte.parseByte("01000000", 2)}));
assertEquals(17, HashCashService.numberOfLeadingZeros(new byte[]{Byte.parseByte("00000000", 2), Byte.parseByte("00000000", 2), Byte.parseByte("01000000", 2)}));
assertEquals(9, HashCashService.numberOfLeadingZeros(new byte[]{Byte.parseByte("00000000", 2), Byte.parseByte("01010000", 2)}));
}
// @Ignore
@Test
public void testDiffIncrease() throws ExecutionException, InterruptedException {
StringBuilder stringBuilder = new StringBuilder();
for (int i = 0; i < 12; i++) {
run(i, stringBuilder);
}
log.info(stringBuilder.toString());
//Test result on a 4 GHz Intel Core i7:
//Minting 1000 tokens with 0 leading zeros took 0.281 ms per token and 2 iterations in average. Verification took 0.014 ms per token.
//Minting 1000 tokens with 1 leading zeros took 0.058 ms per token and 4 iterations in average. Verification took 0.005 ms per token.
//Minting 1000 tokens with 2 leading zeros took 0.081 ms per token and 8 iterations in average. Verification took 0.004 ms per token.
//Minting 1000 tokens with 3 leading zeros took 0.178 ms per token and 16 iterations in average. Verification took 0.003 ms per token.
//Minting 1000 tokens with 4 leading zeros took 0.133 ms per token and 34 iterations in average. Verification took 0.004 ms per token.
//Minting 1000 tokens with 5 leading zeros took 0.214 ms per token and 64 iterations in average. Verification took 0.003 ms per token.
//Minting 1000 tokens with 6 leading zeros took 0.251 ms per token and 126 iterations in average. Verification took 0.002 ms per token.
//Minting 1000 tokens with 7 leading zeros took 0.396 ms per token and 245 iterations in average. Verification took 0.002 ms per token.
//Minting 1000 tokens with 8 leading zeros took 0.835 ms per token and 529 iterations in average. Verification took 0.002 ms per token.
//Minting 1000 tokens with 9 leading zeros took 1.585 ms per token and 1013 iterations in average. Verification took 0.001 ms per token.
//Minting 1000 tokens with 10 leading zeros took 3.219 ms per token and 2112 iterations in average. Verification took 0.002 ms per token.
//Minting 1000 tokens with 11 leading zeros took 6.213 ms per token and 4123 iterations in average. Verification took 0.002 ms per token.
//Minting 1000 tokens with 12 leading zeros took 13.3 ms per token and 8871 iterations in average. Verification took 0.002 ms per token.
//Minting 1000 tokens with 13 leading zeros took 25.276 ms per token and 16786 iterations in average. Verification took 0.002 ms per token.
}
private void run(int difficulty, StringBuilder stringBuilder) throws ExecutionException, InterruptedException {
int numTokens = 1000;
byte[] payload = RandomStringUtils.random(50, true, true).getBytes(StandardCharsets.UTF_8);
long ts = System.currentTimeMillis();
List<ProofOfWork> tokens = new ArrayList<>();
for (int i = 0; i < numTokens; i++) {
byte[] challenge = UUID.randomUUID().toString().getBytes(StandardCharsets.UTF_8);
tokens.add(HashCashService.mint(payload, challenge, difficulty).get());
}
double size = tokens.size();
long ts2 = System.currentTimeMillis();
long averageCounter = Math.round(tokens.stream().mapToLong(ProofOfWork::getCounter).average().orElse(0));
boolean allValid = tokens.stream().allMatch(HashCashService::verify);
assertTrue(allValid);
double time1 = (System.currentTimeMillis() - ts) / size;
double time2 = (System.currentTimeMillis() - ts2) / size;
stringBuilder.append("\nMinting ").append(numTokens)
.append(" tokens with ").append(difficulty)
.append(" leading zeros took ").append(time1)
.append(" ms per token and ").append(averageCounter)
.append(" iterations in average. Verification took ").append(time2)
.append(" ms per token.");
}
}

View file

@ -0,0 +1,81 @@
package bisq.common.crypto;
import org.apache.commons.lang3.RandomStringUtils;
import java.nio.charset.StandardCharsets;
import java.math.BigInteger;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
import java.util.concurrent.ExecutionException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.junit.Test;
import static org.junit.Assert.assertTrue;
public class ProofOfWorkServiceTest {
private final static Logger log = LoggerFactory.getLogger(ProofOfWorkServiceTest.class);
// @Ignore
@Test
public void testDiffIncrease() throws ExecutionException, InterruptedException {
StringBuilder stringBuilder = new StringBuilder();
for (int i = 0; i < 12; i++) {
run(i, stringBuilder);
}
log.info(stringBuilder.toString());
//Test result on a 4 GHz Intel Core i7:
//Minting 1000 tokens with 0 leading zeros took 0.279 ms per token and 2 iterations in average. Verification took 0.025 ms per token.
//Minting 1000 tokens with 1 leading zeros took 0.063 ms per token and 4 iterations in average. Verification took 0.007 ms per token.
//Minting 1000 tokens with 2 leading zeros took 0.074 ms per token and 8 iterations in average. Verification took 0.004 ms per token.
//Minting 1000 tokens with 3 leading zeros took 0.117 ms per token and 16 iterations in average. Verification took 0.003 ms per token.
//Minting 1000 tokens with 4 leading zeros took 0.116 ms per token and 33 iterations in average. Verification took 0.003 ms per token.
//Minting 1000 tokens with 5 leading zeros took 0.204 ms per token and 65 iterations in average. Verification took 0.003 ms per token.
//Minting 1000 tokens with 6 leading zeros took 0.23 ms per token and 131 iterations in average. Verification took 0.002 ms per token.
//Minting 1000 tokens with 7 leading zeros took 0.445 ms per token and 270 iterations in average. Verification took 0.002 ms per token.
//Minting 1000 tokens with 8 leading zeros took 0.856 ms per token and 530 iterations in average. Verification took 0.002 ms per token.
//Minting 1000 tokens with 9 leading zeros took 1.629 ms per token and 988 iterations in average. Verification took 0.002 ms per token.
//Minting 1000 tokens with 10 leading zeros took 3.291 ms per token and 2103 iterations in average. Verification took 0.002 ms per token.
//Minting 1000 tokens with 11 leading zeros took 6.259 ms per token and 4009 iterations in average. Verification took 0.001 ms per token.
//Minting 1000 tokens with 12 leading zeros took 13.845 ms per token and 8254 iterations in average. Verification took 0.002 ms per token.
//Minting 1000 tokens with 13 leading zeros took 26.052 ms per token and 16645 iterations in average. Verification took 0.002 ms per token.
//Minting 100 tokens with 14 leading zeros took 69.14 ms per token and 40917 iterations in average. Verification took 0.06 ms per token.
//Minting 100 tokens with 15 leading zeros took 102.14 ms per token and 65735 iterations in average. Verification took 0.01 ms per token.
//Minting 100 tokens with 16 leading zeros took 209.44 ms per token and 135137 iterations in average. Verification took 0.01 ms per token.
//Minting 100 tokens with 17 leading zeros took 409.46 ms per token and 263751 iterations in average. Verification took 0.01 ms per token.
//Minting 100 tokens with 18 leading zeros took 864.21 ms per token and 555671 iterations in average. Verification took 0.0 ms per token.
//Minting 100 tokens with 19 leading zeros took 1851.33 ms per token and 1097760 iterations in average. Verification took 0.0 ms per token.
}
private void run(int numLeadingZeros, StringBuilder stringBuilder) throws ExecutionException, InterruptedException {
int numTokens = 1000;
BigInteger target = ProofOfWorkService.getTarget(numLeadingZeros);
byte[] payload = RandomStringUtils.random(50, true, true).getBytes(StandardCharsets.UTF_8);
long ts = System.currentTimeMillis();
List<ProofOfWork> tokens = new ArrayList<>();
for (int i = 0; i < numTokens; i++) {
byte[] challenge = UUID.randomUUID().toString().getBytes(StandardCharsets.UTF_8);
tokens.add(ProofOfWorkService.mint(payload, challenge, target).get());
}
double size = tokens.size();
long ts2 = System.currentTimeMillis();
long averageCounter = Math.round(tokens.stream().mapToLong(ProofOfWork::getCounter).average().orElse(0));
boolean allValid = tokens.stream().allMatch(ProofOfWorkService::verify);
assertTrue(allValid);
double time1 = (System.currentTimeMillis() - ts) / size;
double time2 = (System.currentTimeMillis() - ts2) / size;
stringBuilder.append("\nMinting ").append(numTokens)
.append(" tokens with ").append(numLeadingZeros)
.append(" leading zeros took ").append(time1)
.append(" ms per token and ").append(averageCounter)
.append(" iterations in average. Verification took ").append(time2)
.append(" ms per token.");
}
}

View file

@ -23,7 +23,7 @@ import bisq.core.filter.FilterManager;
import bisq.core.locale.CurrencyUtil;
import bisq.core.locale.Res;
import bisq.core.offer.Offer;
import bisq.core.offer.OfferPayload;
import bisq.core.offer.OfferDirection;
import bisq.core.offer.OfferRestrictions;
import bisq.core.payment.AssetAccount;
import bisq.core.payment.ChargeBackRisk;
@ -33,9 +33,9 @@ import bisq.core.payment.payload.PaymentMethod;
import bisq.core.support.dispute.Dispute;
import bisq.core.support.dispute.DisputeResult;
import bisq.core.support.dispute.arbitration.TraderDataItem;
import bisq.core.trade.Contract;
import bisq.core.trade.Trade;
import bisq.core.trade.protocol.TradingPeer;
import bisq.core.trade.model.bisq_v1.Contract;
import bisq.core.trade.model.bisq_v1.Trade;
import bisq.core.trade.protocol.bisq_v1.model.TradingPeer;
import bisq.core.user.User;
import bisq.network.p2p.BootstrapListener;
@ -308,7 +308,7 @@ public class AccountAgeWitnessService {
}
private Optional<AccountAgeWitness> findTradePeerWitness(Trade trade) {
TradingPeer tradingPeer = trade.getProcessModel().getTradingPeer();
TradingPeer tradingPeer = trade.getProcessModel().getTradePeer();
return (tradingPeer == null ||
tradingPeer.getPaymentAccountPayload() == null ||
tradingPeer.getPubKeyRing() == null) ?
@ -421,11 +421,11 @@ public class AccountAgeWitnessService {
String currencyCode,
AccountAgeWitness accountAgeWitness,
AccountAge accountAgeCategory,
OfferPayload.Direction direction,
OfferDirection direction,
PaymentMethod paymentMethod) {
if (CurrencyUtil.isCryptoCurrency(currencyCode) ||
!PaymentMethod.hasChargebackRisk(paymentMethod, currencyCode) ||
direction == OfferPayload.Direction.SELL) {
direction == OfferDirection.SELL) {
return maxTradeLimit.value;
}
@ -500,7 +500,7 @@ public class AccountAgeWitnessService {
return getAccountAge(getMyWitness(paymentAccountPayload), new Date());
}
public long getMyTradeLimit(PaymentAccount paymentAccount, String currencyCode, OfferPayload.Direction direction) {
public long getMyTradeLimit(PaymentAccount paymentAccount, String currencyCode, OfferDirection direction) {
if (paymentAccount == null)
return 0;
@ -564,7 +564,7 @@ public class AccountAgeWitnessService {
return false;
// Check if the peers trade limit is not less than the trade amount
if (!verifyPeersTradeLimit(trade.getOffer(), trade.getTradeAmount(), peersWitness, peersCurrentDate,
if (!verifyPeersTradeLimit(trade.getOffer(), trade.getAmount(), peersWitness, peersCurrentDate,
errorMessageHandler)) {
log.error("verifyPeersTradeLimit failed: peersPaymentAccountPayload {}", peersPaymentAccountPayload);
return false;
@ -641,13 +641,12 @@ public class AccountAgeWitnessService {
ErrorMessageHandler errorMessageHandler) {
checkNotNull(offer);
final String currencyCode = offer.getCurrencyCode();
final Coin defaultMaxTradeLimit = PaymentMethod.getPaymentMethodById(
offer.getOfferPayload().getPaymentMethodId()).getMaxTradeLimitAsCoin(currencyCode);
final Coin defaultMaxTradeLimit = offer.getPaymentMethod().getMaxTradeLimitAsCoin(currencyCode);
long peersCurrentTradeLimit = defaultMaxTradeLimit.value;
if (!hasTradeLimitException(peersWitness)) {
final long accountSignAge = getWitnessSignAge(peersWitness, peersCurrentDate);
AccountAge accountAgeCategory = getPeersAccountAgeCategory(accountSignAge);
OfferPayload.Direction direction = offer.isMyOffer(keyRing) ?
OfferDirection direction = offer.isMyOffer(keyRing) ?
offer.getMirroredDirection() : offer.getDirection();
peersCurrentTradeLimit = getTradeLimit(defaultMaxTradeLimit, currencyCode, peersWitness,
accountAgeCategory, direction, offer.getPaymentMethod());
@ -731,9 +730,9 @@ public class AccountAgeWitnessService {
public Optional<SignedWitness> traderSignAndPublishPeersAccountAgeWitness(Trade trade) {
AccountAgeWitness peersWitness = findTradePeerWitness(trade).orElse(null);
Coin tradeAmount = trade.getTradeAmount();
checkNotNull(trade.getProcessModel().getTradingPeer().getPubKeyRing(), "Peer must have a keyring");
PublicKey peersPubKey = trade.getProcessModel().getTradingPeer().getPubKeyRing().getSignaturePubKey();
Coin tradeAmount = trade.getAmount();
checkNotNull(trade.getProcessModel().getTradePeer().getPubKeyRing(), "Peer must have a keyring");
PublicKey peersPubKey = trade.getProcessModel().getTradePeer().getPubKeyRing().getSignaturePubKey();
checkNotNull(peersWitness, "Not able to find peers witness, unable to sign for trade {}",
trade.toString());
checkNotNull(tradeAmount, "Trade amount must not be null");
@ -926,7 +925,7 @@ public class AccountAgeWitnessService {
return accountIsSigner(myWitness) &&
!peerHasSignedWitness(trade) &&
tradeAmountIsSufficient(trade.getTradeAmount());
tradeAmountIsSufficient(trade.getAmount());
}
public String getSignInfoFromAccount(PaymentAccount paymentAccount) {

View file

@ -20,7 +20,7 @@ package bisq.core.account.witness;
import bisq.core.account.sign.SignedWitness;
import bisq.core.account.sign.SignedWitnessService;
import bisq.core.payment.payload.PaymentAccountPayload;
import bisq.core.trade.Trade;
import bisq.core.trade.model.bisq_v1.Trade;
import bisq.network.p2p.storage.P2PDataStorage;
@ -143,7 +143,7 @@ public class AccountAgeWitnessUtils {
}
boolean isSignWitnessTrade = accountAgeWitnessService.accountIsSigner(witness) &&
!accountAgeWitnessService.peerHasSignedWitness(trade) &&
accountAgeWitnessService.tradeAmountIsSufficient(trade.getTradeAmount());
accountAgeWitnessService.tradeAmountIsSufficient(trade.getAmount());
log.info("AccountSigning debug log: " +
"\ntradeId: {}" +
"\nis buyer: {}" +
@ -164,8 +164,8 @@ public class AccountAgeWitnessUtils {
checkingSignTrade, // Following cases added to use same logic as in seller signing check
accountAgeWitnessService.accountIsSigner(witness),
accountAgeWitnessService.peerHasSignedWitness(trade),
trade.getTradeAmount(),
accountAgeWitnessService.tradeAmountIsSufficient(trade.getTradeAmount()),
trade.getAmount(),
accountAgeWitnessService.tradeAmountIsSufficient(trade.getAmount()),
isSignWitnessTrade);
}
}

View file

@ -25,7 +25,9 @@ import bisq.core.offer.Offer;
import bisq.core.offer.OpenOffer;
import bisq.core.payment.PaymentAccount;
import bisq.core.payment.payload.PaymentMethod;
import bisq.core.trade.Trade;
import bisq.core.trade.bisq_v1.TradeResultHandler;
import bisq.core.trade.model.bisq_v1.Trade;
import bisq.core.trade.model.bsq_swap.BsqSwapTrade;
import bisq.core.trade.statistics.TradeStatistics3;
import bisq.core.trade.statistics.TradeStatisticsManager;
@ -117,6 +119,10 @@ public class CoreApi {
// Offers
///////////////////////////////////////////////////////////////////////////////////////////
public Offer getBsqSwapOffer(String id) {
return coreOffersService.getBsqSwapOffer(id);
}
public Offer getOffer(String id) {
return coreOffersService.getOffer(id);
}
@ -125,6 +131,14 @@ public class CoreApi {
return coreOffersService.getMyOffer(id);
}
public Offer getMyBsqSwapOffer(String id) {
return coreOffersService.getMyBsqSwapOffer(id);
}
public List<Offer> getBsqSwapOffers(String direction) {
return coreOffersService.getBsqSwapOffers(direction);
}
public List<Offer> getOffers(String direction, String currencyCode) {
return coreOffersService.getOffers(direction, currencyCode);
}
@ -133,18 +147,38 @@ public class CoreApi {
return coreOffersService.getMyOffers(direction, currencyCode);
}
public void createAnPlaceOffer(String currencyCode,
String directionAsString,
String priceAsString,
boolean useMarketBasedPrice,
double marketPriceMargin,
long amountAsLong,
long minAmountAsLong,
double buyerSecurityDeposit,
long triggerPrice,
String paymentAccountId,
String makerFeeCurrencyCode,
Consumer<Offer> resultHandler) {
public List<Offer> getMyBsqSwapOffers(String direction) {
return coreOffersService.getMyBsqSwapOffers(direction);
}
public OpenOffer getMyOpenBsqSwapOffer(String id) {
return coreOffersService.getMyOpenBsqSwapOffer(id);
}
public void createAndPlaceBsqSwapOffer(String directionAsString,
long amountAsLong,
long minAmountAsLong,
String priceAsString,
Consumer<Offer> resultHandler) {
coreOffersService.createAndPlaceBsqSwapOffer(directionAsString,
amountAsLong,
minAmountAsLong,
priceAsString,
resultHandler);
}
public void createAndPlaceOffer(String currencyCode,
String directionAsString,
String priceAsString,
boolean useMarketBasedPrice,
double marketPriceMargin,
long amountAsLong,
long minAmountAsLong,
double buyerSecurityDeposit,
long triggerPrice,
String paymentAccountId,
String makerFeeCurrencyCode,
Consumer<Offer> resultHandler) {
coreOffersService.createAndPlaceOffer(currencyCode,
directionAsString,
priceAsString,
@ -206,11 +240,13 @@ public class CoreApi {
public PaymentAccount createCryptoCurrencyPaymentAccount(String accountName,
String currencyCode,
String address,
boolean tradeInstant) {
boolean tradeInstant,
boolean isBsqSwap) {
return paymentAccountsService.createCryptoCurrencyPaymentAccount(accountName,
currencyCode,
address,
tradeInstant);
tradeInstant,
isBsqSwap);
}
public List<PaymentMethod> getCryptoCurrencyPaymentMethods() {
@ -229,6 +265,19 @@ public class CoreApi {
// Trades
///////////////////////////////////////////////////////////////////////////////////////////
public void takeBsqSwapOffer(String offerId,
String paymentAccountId,
String takerFeeCurrencyCode,
TradeResultHandler<BsqSwapTrade> tradeResultHandler,
ErrorMessageHandler errorMessageHandler) {
Offer bsqSwapOffer = coreOffersService.getBsqSwapOffer(offerId);
coreTradesService.takeBsqSwapOffer(bsqSwapOffer,
paymentAccountId,
takerFeeCurrencyCode,
tradeResultHandler,
errorMessageHandler);
}
public void takeOffer(String offerId,
String paymentAccountId,
String takerFeeCurrencyCode,
@ -258,6 +307,10 @@ public class CoreApi {
coreTradesService.withdrawFunds(tradeId, address, memo);
}
public BsqSwapTrade getBsqSwapTrade(String tradeId) {
return coreTradesService.getBsqSwapTrade(tradeId);
}
public Trade getTrade(String tradeId) {
return coreTradesService.getTrade(tradeId);
}

View file

@ -19,15 +19,17 @@ package bisq.core.api;
import bisq.core.monetary.Altcoin;
import bisq.core.monetary.Price;
import bisq.core.offer.CreateOfferService;
import bisq.core.offer.MutableOfferPayloadFields;
import bisq.core.offer.Offer;
import bisq.core.offer.OfferBookService;
import bisq.core.offer.OfferFilter;
import bisq.core.offer.OfferPayload;
import bisq.core.offer.OfferDirection;
import bisq.core.offer.OfferFilterService;
import bisq.core.offer.OfferUtil;
import bisq.core.offer.OpenOffer;
import bisq.core.offer.OpenOfferManager;
import bisq.core.offer.bisq_v1.CreateOfferService;
import bisq.core.offer.bisq_v1.MutableOfferPayloadFields;
import bisq.core.offer.bisq_v1.OfferPayload;
import bisq.core.offer.bsq_swap.OpenBsqSwapOfferService;
import bisq.core.payment.PaymentAccount;
import bisq.core.provider.price.PriceFeedService;
import bisq.core.user.User;
@ -57,8 +59,7 @@ import static bisq.common.util.MathUtils.roundDoubleToLong;
import static bisq.common.util.MathUtils.scaleUpByPowerOf10;
import static bisq.core.locale.CurrencyUtil.isCryptoCurrency;
import static bisq.core.offer.Offer.State;
import static bisq.core.offer.OfferPayload.Direction;
import static bisq.core.offer.OfferPayload.Direction.BUY;
import static bisq.core.offer.OfferDirection.BUY;
import static bisq.core.offer.OpenOffer.State.AVAILABLE;
import static bisq.core.offer.OpenOffer.State.DEACTIVATED;
import static bisq.core.payment.PaymentAccountUtil.isPaymentAccountValidForOffer;
@ -85,8 +86,9 @@ class CoreOffersService {
private final CoreWalletsService coreWalletsService;
private final CreateOfferService createOfferService;
private final OfferBookService offerBookService;
private final OfferFilter offerFilter;
private final OfferFilterService offerFilterService;
private final OpenOfferManager openOfferManager;
private final OpenBsqSwapOfferService openBsqSwapOfferService;
private final OfferUtil offerUtil;
private final PriceFeedService priceFeedService;
private final User user;
@ -97,8 +99,9 @@ class CoreOffersService {
CoreWalletsService coreWalletsService,
CreateOfferService createOfferService,
OfferBookService offerBookService,
OfferFilter offerFilter,
OfferFilterService offerFilterService,
OpenOfferManager openOfferManager,
OpenBsqSwapOfferService openBsqSwapOfferService,
OfferUtil offerUtil,
PriceFeedService priceFeedService,
User user) {
@ -107,18 +110,29 @@ class CoreOffersService {
this.coreWalletsService = coreWalletsService;
this.createOfferService = createOfferService;
this.offerBookService = offerBookService;
this.offerFilter = offerFilter;
this.offerFilterService = offerFilterService;
this.openOfferManager = openOfferManager;
this.openBsqSwapOfferService = openBsqSwapOfferService;
this.offerUtil = offerUtil;
this.priceFeedService = priceFeedService;
this.user = user;
}
Offer getBsqSwapOffer(String id) {
return offerBookService.getOffers().stream()
.filter(o -> o.getId().equals(id))
.filter(o -> !o.isMyOffer(keyRing))
.filter(o -> offerFilterService.canTakeOffer(o, coreContext.isApiUser()).isValid())
.filter(o -> o.isBsqSwapOffer())
.findAny().orElseThrow(() ->
new IllegalStateException(format("offer with id '%s' not found", id)));
}
Offer getOffer(String id) {
return offerBookService.getOffers().stream()
.filter(o -> o.getId().equals(id))
.filter(o -> !o.isMyOffer(keyRing))
.filter(o -> offerFilter.canTakeOffer(o, coreContext.isApiUser()).isValid())
.filter(o -> offerFilterService.canTakeOffer(o, coreContext.isApiUser()).isValid())
.findAny().orElseThrow(() ->
new IllegalStateException(format("offer with id '%s' not found", id)));
}
@ -131,11 +145,31 @@ class CoreOffersService {
new IllegalStateException(format("offer with id '%s' not found", id)));
}
Offer getMyBsqSwapOffer(String id) {
return offerBookService.getOffers().stream()
.filter(o -> o.getId().equals(id))
.filter(o -> o.isMyOffer(keyRing))
.filter(o -> o.isBsqSwapOffer())
.findAny().orElseThrow(() ->
new IllegalStateException(format("offer with id '%s' not found", id)));
}
List<Offer> getBsqSwapOffers(String direction) {
var offers = offerBookService.getOffers().stream()
.filter(o -> !o.isMyOffer(keyRing))
.filter(o -> o.getDirection().name().equalsIgnoreCase(direction))
.filter(o -> o.isBsqSwapOffer())
.sorted(priceComparator(direction))
.collect(Collectors.toList());
return offers;
}
List<Offer> getOffers(String direction, String currencyCode) {
return offerBookService.getOffers().stream()
.filter(o -> !o.isMyOffer(keyRing))
.filter(o -> offerMatchesDirectionAndCurrency(o, direction, currencyCode))
.filter(o -> offerFilter.canTakeOffer(o, coreContext.isApiUser()).isValid())
.filter(o -> offerFilterService.canTakeOffer(o, coreContext.isApiUser()).isValid())
.sorted(priceComparator(direction))
.collect(Collectors.toList());
}
@ -148,6 +182,24 @@ class CoreOffersService {
.collect(Collectors.toList());
}
List<Offer> getMyBsqSwapOffers(String direction) {
var offers = offerBookService.getOffers().stream()
.filter(o -> o.isMyOffer(keyRing))
.filter(o -> o.getDirection().name().equalsIgnoreCase(direction))
.filter(Offer::isBsqSwapOffer)
.sorted(priceComparator(direction))
.collect(Collectors.toList());
return offers;
}
OpenOffer getMyOpenBsqSwapOffer(String id) {
return openOfferManager.getOpenOfferById(id)
.filter(open -> open.getOffer().isMyOffer(keyRing))
.filter(open -> open.getOffer().isBsqSwapOffer())
.orElseThrow(() ->
new IllegalStateException(format("openoffer with id '%s' not found", id)));
}
OpenOffer getMyOpenOffer(String id) {
return openOfferManager.getOpenOfferById(id)
.filter(open -> open.getOffer().isMyOffer(keyRing))
@ -161,7 +213,28 @@ class CoreOffersService {
.isPresent();
}
// Create and place new offer.
void createAndPlaceBsqSwapOffer(String directionAsString,
long amountAsLong,
long minAmountAsLong,
String priceAsString,
Consumer<Offer> resultHandler) {
coreWalletsService.verifyWalletsAreAvailable();
coreWalletsService.verifyEncryptedWalletIsUnlocked();
String currencyCode = "BSQ";
String offerId = OfferUtil.getRandomOfferId();
OfferDirection direction = OfferDirection.valueOf(directionAsString.toUpperCase());
Coin amount = Coin.valueOf(amountAsLong);
Coin minAmount = Coin.valueOf(minAmountAsLong);
Price price = Price.valueOf(currencyCode, priceStringToLong(priceAsString, currencyCode));
openBsqSwapOfferService.requestNewOffer(offerId,
direction,
amount,
minAmount,
price,
offer -> placeBsqSwapOffer(offer, () -> resultHandler.accept(offer)));
}
void createAndPlaceOffer(String currencyCode,
String directionAsString,
String priceAsString,
@ -183,8 +256,8 @@ class CoreOffersService {
throw new IllegalArgumentException(format("payment account with id %s not found", paymentAccountId));
String upperCaseCurrencyCode = currencyCode.toUpperCase();
String offerId = createOfferService.getRandomOfferId();
Direction direction = Direction.valueOf(directionAsString.toUpperCase());
String offerId = OfferUtil.getRandomOfferId();
OfferDirection direction = OfferDirection.valueOf(directionAsString.toUpperCase());
Price price = Price.valueOf(upperCaseCurrencyCode, priceStringToLong(priceAsString, upperCaseCurrencyCode));
Coin amount = Coin.valueOf(amountAsLong);
Coin minAmount = Coin.valueOf(minAmountAsLong);
@ -256,7 +329,7 @@ class CoreOffersService {
editedMarketPriceMargin,
editType);
Offer editedOffer = new Offer(editedPayload);
priceFeedService.setCurrencyCode(openOffer.getOffer().getOfferPayload().getCurrencyCode());
priceFeedService.setCurrencyCode(openOffer.getOffer().getCurrencyCode());
editedOffer.setPriceFeedService(priceFeedService);
editedOffer.setState(State.AVAILABLE);
openOfferManager.editOpenOfferStart(openOffer,
@ -277,6 +350,15 @@ class CoreOffersService {
log::error);
}
private void placeBsqSwapOffer(Offer offer, Runnable resultHandler) {
openBsqSwapOfferService.placeBsqSwapOffer(offer,
resultHandler,
log::error);
if (offer.getErrorMessage() != null)
throw new IllegalStateException(offer.getErrorMessage());
}
private void placeOffer(Offer offer,
double buyerSecurityDeposit,
long triggerPrice,
@ -302,7 +384,7 @@ class CoreOffersService {
// code fields. Note: triggerPrice isDeactivated fields are in OpenOffer, not
// in OfferPayload.
Offer offer = openOffer.getOffer();
String currencyCode = offer.getOfferPayload().getCurrencyCode();
String currencyCode = offer.getCurrencyCode();
boolean isEditingPrice = editType.equals(FIXED_PRICE_ONLY) || editType.equals(FIXED_PRICE_AND_ACTIVATION_STATE);
Price editedPrice;
if (isEditingPrice) {
@ -320,15 +402,15 @@ class CoreOffersService {
Objects.requireNonNull(editedPrice).getValue(),
isUsingMktPriceMargin ? exactMultiply(editedMarketPriceMargin, 0.01) : 0.00,
isUsingMktPriceMargin,
offer.getOfferPayload().getBaseCurrencyCode(),
offer.getOfferPayload().getCounterCurrencyCode(),
offer.getBaseCurrencyCode(),
offer.getCounterCurrencyCode(),
offer.getPaymentMethod().getId(),
offer.getMakerPaymentAccountId(),
offer.getOfferPayload().getCountryCode(),
offer.getOfferPayload().getAcceptedCountryCodes(),
offer.getOfferPayload().getBankId(),
offer.getOfferPayload().getAcceptedBankIds(),
offer.getOfferPayload().getExtraDataMap());
offer.getCountryCode(),
offer.getAcceptedCountryCodes(),
offer.getBankId(),
offer.getAcceptedBankIds(),
offer.getExtraDataMap());
log.info("Merging OfferPayload with {}", mutableOfferPayloadFields);
return offerUtil.getMergedOfferPayload(openOffer, mutableOfferPayloadFields);
}
@ -336,7 +418,7 @@ class CoreOffersService {
private void verifyPaymentAccountIsValidForNewOffer(Offer offer, PaymentAccount paymentAccount) {
if (!isPaymentAccountValidForOffer(offer, paymentAccount)) {
String error = format("cannot create %s offer with payment account %s",
offer.getOfferPayload().getCounterCurrencyCode(),
offer.getCounterCurrencyCode(),
paymentAccount.getId());
throw new IllegalStateException(error);
}
@ -346,7 +428,7 @@ class CoreOffersService {
String direction,
String currencyCode) {
var offerOfWantedDirection = offer.getDirection().name().equalsIgnoreCase(direction);
var offerInWantedCurrency = offer.getOfferPayload().getCounterCurrencyCode()
var offerInWantedCurrency = offer.getCounterCurrencyCode()
.equalsIgnoreCase(currencyCode);
return offerOfWantedDirection && offerInWantedCurrency;
}

View file

@ -20,6 +20,7 @@ package bisq.core.api;
import bisq.core.account.witness.AccountAgeWitnessService;
import bisq.core.api.model.PaymentAccountForm;
import bisq.core.locale.CryptoCurrency;
import bisq.core.payment.AssetAccount;
import bisq.core.payment.CryptoCurrencyAccount;
import bisq.core.payment.InstantCryptoCurrencyAccount;
import bisq.core.payment.PaymentAccount;
@ -82,7 +83,7 @@ class CorePaymentAccountsService {
List<PaymentMethod> getFiatPaymentMethods() {
return PaymentMethod.getPaymentMethods().stream()
.filter(paymentMethod -> !paymentMethod.isAsset())
.filter(PaymentMethod::isFiat)
.sorted(Comparator.comparing(PaymentMethod::getId))
.collect(Collectors.toList());
}
@ -102,7 +103,8 @@ class CorePaymentAccountsService {
PaymentAccount createCryptoCurrencyPaymentAccount(String accountName,
String currencyCode,
String address,
boolean tradeInstant) {
boolean tradeInstant,
boolean isBsqSwap) {
String bsqCode = currencyCode.toUpperCase();
if (!bsqCode.equals("BSQ"))
throw new IllegalArgumentException("api does not currently support " + currencyCode + " accounts");
@ -110,12 +112,21 @@ class CorePaymentAccountsService {
// Validate the BSQ address string but ignore the return value.
coreWalletsService.getValidBsqAddress(address);
var cryptoCurrencyAccount = tradeInstant
? (InstantCryptoCurrencyAccount) PaymentAccountFactory.getPaymentAccount(PaymentMethod.BLOCK_CHAINS_INSTANT)
: (CryptoCurrencyAccount) PaymentAccountFactory.getPaymentAccount(PaymentMethod.BLOCK_CHAINS);
// TODO Split into 2 methods: createAtomicPaymentAccount(), createCryptoCurrencyPaymentAccount().
PaymentAccount cryptoCurrencyAccount;
if (isBsqSwap) {
cryptoCurrencyAccount = PaymentAccountFactory.getPaymentAccount(PaymentMethod.BSQ_SWAP);
} else {
cryptoCurrencyAccount = tradeInstant
? (InstantCryptoCurrencyAccount) PaymentAccountFactory.getPaymentAccount(PaymentMethod.BLOCK_CHAINS_INSTANT)
: (CryptoCurrencyAccount) PaymentAccountFactory.getPaymentAccount(PaymentMethod.BLOCK_CHAINS);
}
cryptoCurrencyAccount.init();
cryptoCurrencyAccount.setAccountName(accountName);
cryptoCurrencyAccount.setAddress(address);
if (!isBsqSwap) {
((AssetAccount) cryptoCurrencyAccount).setAddress(address);
}
Optional<CryptoCurrency> cryptoCurrency = getCryptoCurrency(bsqCode);
cryptoCurrency.ifPresent(cryptoCurrencyAccount::setSingleTradeCurrency);
user.addPaymentAccount(cryptoCurrencyAccount);
@ -132,7 +143,7 @@ class CorePaymentAccountsService {
List<PaymentMethod> getCryptoCurrencyPaymentMethods() {
return PaymentMethod.getPaymentMethods().stream()
.filter(PaymentMethod::isAsset)
.filter(PaymentMethod::isAltcoin)
.sorted(Comparator.comparing(PaymentMethod::getId))
.collect(Collectors.toList());
}

View file

@ -21,14 +21,17 @@ import bisq.core.btc.model.AddressEntry;
import bisq.core.btc.wallet.BtcWalletService;
import bisq.core.offer.Offer;
import bisq.core.offer.OfferUtil;
import bisq.core.offer.takeoffer.TakeOfferModel;
import bisq.core.trade.Tradable;
import bisq.core.trade.Trade;
import bisq.core.offer.bisq_v1.TakeOfferModel;
import bisq.core.offer.bsq_swap.BsqSwapTakeOfferModel;
import bisq.core.trade.ClosedTradableManager;
import bisq.core.trade.TradeManager;
import bisq.core.trade.TradeUtil;
import bisq.core.trade.closed.ClosedTradableManager;
import bisq.core.trade.protocol.BuyerProtocol;
import bisq.core.trade.protocol.SellerProtocol;
import bisq.core.trade.bisq_v1.TradeResultHandler;
import bisq.core.trade.bisq_v1.TradeUtil;
import bisq.core.trade.model.Tradable;
import bisq.core.trade.model.bisq_v1.Trade;
import bisq.core.trade.model.bsq_swap.BsqSwapTrade;
import bisq.core.trade.protocol.bisq_v1.BuyerProtocol;
import bisq.core.trade.protocol.bisq_v1.SellerProtocol;
import bisq.core.user.User;
import bisq.core.util.validation.BtcAddressValidator;
@ -60,6 +63,7 @@ class CoreTradesService {
private final OfferUtil offerUtil;
private final ClosedTradableManager closedTradableManager;
private final TakeOfferModel takeOfferModel;
private final BsqSwapTakeOfferModel bsqSwapTakeOfferModel;
private final TradeManager tradeManager;
private final TradeUtil tradeUtil;
private final User user;
@ -71,6 +75,7 @@ class CoreTradesService {
OfferUtil offerUtil,
ClosedTradableManager closedTradableManager,
TakeOfferModel takeOfferModel,
BsqSwapTakeOfferModel bsqSwapTakeOfferModel,
TradeManager tradeManager,
TradeUtil tradeUtil,
User user) {
@ -80,11 +85,33 @@ class CoreTradesService {
this.offerUtil = offerUtil;
this.closedTradableManager = closedTradableManager;
this.takeOfferModel = takeOfferModel;
this.bsqSwapTakeOfferModel = bsqSwapTakeOfferModel;
this.tradeManager = tradeManager;
this.tradeUtil = tradeUtil;
this.user = user;
}
// todo we need to pass the intended trade amount
void takeBsqSwapOffer(Offer offer,
String paymentAccountId,
String takerFeeCurrencyCode,
TradeResultHandler<BsqSwapTrade> tradeResultHandler,
ErrorMessageHandler errorMessageHandler) {
coreWalletsService.verifyWalletsAreAvailable();
coreWalletsService.verifyEncryptedWalletIsUnlocked();
bsqSwapTakeOfferModel.initWithData(offer);
//todo use the intended trade amount
bsqSwapTakeOfferModel.applyAmount(offer.getAmount());
log.info("Initiating take {} offer, {}",
offer.isBuyOffer() ? "buy" : "sell",
bsqSwapTakeOfferModel);
bsqSwapTakeOfferModel.onTakeOffer(tradeResultHandler, log::warn, errorMessageHandler, coreContext.isApiUser());
}
void takeOffer(Offer offer,
String paymentAccountId,
String takerFeeCurrencyCode,
@ -100,7 +127,7 @@ class CoreTradesService {
throw new IllegalArgumentException(format("payment account with id '%s' not found", paymentAccountId));
var useSavingsWallet = true;
//noinspection ConstantConditions
takeOfferModel.initModel(offer, paymentAccount, useSavingsWallet);
log.info("Initiating take {} offer, {}",
offer.isBuyOffer() ? "buy" : "sell",
@ -205,6 +232,13 @@ class CoreTradesService {
});
}
BsqSwapTrade getBsqSwapTrade(String tradeId) {
coreWalletsService.verifyWalletsAreAvailable();
coreWalletsService.verifyEncryptedWalletIsUnlocked();
return tradeManager.findBsqSwapTradeById(tradeId).orElseThrow(() ->
new IllegalArgumentException(format("trade with id '%s' not found", tradeId)));
}
String getTradeRole(String tradeId) {
coreWalletsService.verifyWalletsAreAvailable();
coreWalletsService.verifyEncryptedWalletIsUnlocked();

View file

@ -82,7 +82,6 @@ import lombok.extern.slf4j.Slf4j;
import javax.annotation.Nullable;
import static bisq.common.config.BaseCurrencyNetwork.BTC_DAO_REGTEST;
import static bisq.core.btc.wallet.Restrictions.getMinNonDustOutput;
import static bisq.core.util.ParsingUtils.parseToCoin;
import static java.lang.String.format;
@ -583,14 +582,14 @@ class CoreWalletsService {
verifyWalletsAreAvailable();
verifyEncryptedWalletIsUnlocked();
var availableConfirmedBalance = bsqWalletService.getAvailableConfirmedBalance();
var availableBalance = bsqWalletService.getAvailableBalance();
var unverifiedBalance = bsqWalletService.getUnverifiedBalance();
var unconfirmedChangeBalance = bsqWalletService.getUnconfirmedChangeBalance();
var lockedForVotingBalance = bsqWalletService.getLockedForVotingBalance();
var lockupBondsBalance = bsqWalletService.getLockupBondsBalance();
var unlockingBondsBalance = bsqWalletService.getUnlockingBondsBalance();
return new BsqBalanceInfo(availableConfirmedBalance.value,
return new BsqBalanceInfo(availableBalance.value,
unverifiedBalance.value,
unconfirmedChangeBalance.value,
lockedForVotingBalance.value,

View file

@ -17,20 +17,20 @@ public class BsqBalanceInfo implements Payload {
-1);
// All balances are in BSQ satoshis.
private final long availableConfirmedBalance;
private final long availableBalance;
private final long unverifiedBalance;
private final long unconfirmedChangeBalance;
private final long lockedForVotingBalance;
private final long lockupBondsBalance;
private final long unlockingBondsBalance;
public BsqBalanceInfo(long availableConfirmedBalance,
public BsqBalanceInfo(long availableBalance,
long unverifiedBalance,
long unconfirmedChangeBalance,
long lockedForVotingBalance,
long lockupBondsBalance,
long unlockingBondsBalance) {
this.availableConfirmedBalance = availableConfirmedBalance;
this.availableBalance = availableBalance;
this.unverifiedBalance = unverifiedBalance;
this.unconfirmedChangeBalance = unconfirmedChangeBalance;
this.lockedForVotingBalance = lockedForVotingBalance;
@ -39,14 +39,14 @@ public class BsqBalanceInfo implements Payload {
}
@VisibleForTesting
public static BsqBalanceInfo valueOf(long availableConfirmedBalance,
public static BsqBalanceInfo valueOf(long availableBalance,
long unverifiedBalance,
long unconfirmedChangeBalance,
long lockedForVotingBalance,
long lockupBondsBalance,
long unlockingBondsBalance) {
// Convenience for creating a model instance instead of a proto.
return new BsqBalanceInfo(availableConfirmedBalance,
return new BsqBalanceInfo(availableBalance,
unverifiedBalance,
unconfirmedChangeBalance,
lockedForVotingBalance,
@ -58,10 +58,11 @@ public class BsqBalanceInfo implements Payload {
// PROTO BUFFER
///////////////////////////////////////////////////////////////////////////////////////////
// TODO rename availableConfirmedBalance in proto if possible
@Override
public bisq.proto.grpc.BsqBalanceInfo toProtoMessage() {
return bisq.proto.grpc.BsqBalanceInfo.newBuilder()
.setAvailableConfirmedBalance(availableConfirmedBalance)
.setAvailableConfirmedBalance(availableBalance)
.setUnverifiedBalance(unverifiedBalance)
.setUnconfirmedChangeBalance(unconfirmedChangeBalance)
.setLockedForVotingBalance(lockedForVotingBalance)
@ -83,7 +84,7 @@ public class BsqBalanceInfo implements Payload {
@Override
public String toString() {
return "BsqBalanceInfo{" +
"availableConfirmedBalance=" + availableConfirmedBalance +
"availableBalance=" + availableBalance +
", unverifiedBalance=" + unverifiedBalance +
", unconfirmedChangeBalance=" + unconfirmedChangeBalance +
", lockedForVotingBalance=" + lockedForVotingBalance +

View file

@ -0,0 +1,227 @@
/*
* 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.model;
import bisq.core.offer.Offer;
import bisq.common.Payload;
import java.util.Objects;
import lombok.EqualsAndHashCode;
import lombok.Getter;
import lombok.ToString;
@EqualsAndHashCode
@ToString
@Getter
public class BsqSwapOfferInfo implements Payload {
private final String id;
private final String direction;
private final long amount;
private final long minAmount;
private final long price;
private final String makerPaymentAccountId;
private final String paymentMethodId;
private final String paymentMethodShortName;
private final String baseCurrencyCode;
private final String counterCurrencyCode;
private final long date;
private final String ownerNodeAddress;
private final String pubKeyRing; // TODO ?
private final String versionNumber;
private final int protocolVersion;
public BsqSwapOfferInfo(BsqSwapOfferInfoBuilder builder) {
this.id = builder.id;
this.direction = builder.direction;
this.amount = builder.amount;
this.minAmount = builder.minAmount;
this.price = builder.price;
this.makerPaymentAccountId = builder.makerPaymentAccountId;
this.paymentMethodId = builder.paymentMethodId;
this.paymentMethodShortName = builder.paymentMethodShortName;
this.baseCurrencyCode = builder.baseCurrencyCode;
this.counterCurrencyCode = builder.counterCurrencyCode;
this.date = builder.date;
this.ownerNodeAddress = builder.ownerNodeAddress;
this.pubKeyRing = builder.pubKeyRing;
this.versionNumber = builder.versionNumber;
this.protocolVersion = builder.protocolVersion;
}
public static BsqSwapOfferInfo toBsqSwapOfferInfo(Offer offer) {
// TODO support triggerPrice
return getAtomicOfferInfoBuilder(offer).build();
}
private static BsqSwapOfferInfoBuilder getAtomicOfferInfoBuilder(Offer offer) {
return new BsqSwapOfferInfoBuilder()
.withId(offer.getId())
.withDirection(offer.getDirection().name())
.withAmount(offer.getAmount().value)
.withMinAmount(offer.getMinAmount().value)
.withPrice(Objects.requireNonNull(offer.getPrice()).getValue())
//.withMakerPaymentAccountId(offer.getOfferPayloadI().getMakerPaymentAccountId())
//.withPaymentMethodId(offer.getOfferPayloadI().getPaymentMethodId())
//.withPaymentMethodShortName(getPaymentMethodById(offer.getOfferPayloadI().getPaymentMethodId()).getShortName())
.withBaseCurrencyCode(offer.getOfferPayloadBase().getBaseCurrencyCode())
.withCounterCurrencyCode(offer.getOfferPayloadBase().getCounterCurrencyCode())
.withDate(offer.getDate().getTime())
.withOwnerNodeAddress(offer.getOfferPayloadBase().getOwnerNodeAddress().getFullAddress())
.withPubKeyRing(offer.getOfferPayloadBase().getPubKeyRing().toString())
.withVersionNumber(offer.getOfferPayloadBase().getVersionNr())
.withProtocolVersion(offer.getOfferPayloadBase().getProtocolVersion());
}
///////////////////////////////////////////////////////////////////////////////////////////
// PROTO BUFFER
///////////////////////////////////////////////////////////////////////////////////////////
@Override
public bisq.proto.grpc.BsqSwapOfferInfo toProtoMessage() {
return bisq.proto.grpc.BsqSwapOfferInfo.newBuilder()
.setId(id)
.setDirection(direction)
.setAmount(amount)
.setMinAmount(minAmount)
.setPrice(price)
.setBaseCurrencyCode(baseCurrencyCode)
.setCounterCurrencyCode(counterCurrencyCode)
.setDate(date)
.setOwnerNodeAddress(ownerNodeAddress)
.setPubKeyRing(pubKeyRing)
.setVersionNr(versionNumber)
.setProtocolVersion(protocolVersion)
.build();
}
public static BsqSwapOfferInfo fromProto(bisq.proto.grpc.BsqSwapOfferInfo proto) {
return new BsqSwapOfferInfoBuilder()
.withId(proto.getId())
.withDirection(proto.getDirection())
.withAmount(proto.getAmount())
.withMinAmount(proto.getMinAmount())
.withPrice(proto.getPrice())
.withBaseCurrencyCode(proto.getBaseCurrencyCode())
.withCounterCurrencyCode(proto.getCounterCurrencyCode())
.withDate(proto.getDate())
.withOwnerNodeAddress(proto.getOwnerNodeAddress())
.withPubKeyRing(proto.getPubKeyRing())
.withVersionNumber(proto.getVersionNr())
.withProtocolVersion(proto.getProtocolVersion())
.build();
}
public static class BsqSwapOfferInfoBuilder {
private String id;
private String direction;
private long amount;
private long minAmount;
private long price;
private String makerPaymentAccountId;
private String paymentMethodId;
private String paymentMethodShortName;
private String baseCurrencyCode;
private String counterCurrencyCode;
private long date;
private String ownerNodeAddress;
private String pubKeyRing;
private String versionNumber;
private int protocolVersion;
public BsqSwapOfferInfoBuilder withId(String id) {
this.id = id;
return this;
}
public BsqSwapOfferInfoBuilder withDirection(String direction) {
this.direction = direction;
return this;
}
public BsqSwapOfferInfoBuilder withAmount(long amount) {
this.amount = amount;
return this;
}
public BsqSwapOfferInfoBuilder withMinAmount(long minAmount) {
this.minAmount = minAmount;
return this;
}
public BsqSwapOfferInfoBuilder withPrice(long price) {
this.price = price;
return this;
}
public BsqSwapOfferInfoBuilder withMakerPaymentAccountId(String makerPaymentAccountId) {
this.makerPaymentAccountId = makerPaymentAccountId;
return this;
}
public BsqSwapOfferInfoBuilder withPaymentMethodId(String paymentMethodId) {
this.paymentMethodId = paymentMethodId;
return this;
}
public BsqSwapOfferInfoBuilder withPaymentMethodShortName(String paymentMethodShortName) {
this.paymentMethodShortName = paymentMethodShortName;
return this;
}
public BsqSwapOfferInfoBuilder withBaseCurrencyCode(String baseCurrencyCode) {
this.baseCurrencyCode = baseCurrencyCode;
return this;
}
public BsqSwapOfferInfoBuilder withCounterCurrencyCode(String counterCurrencyCode) {
this.counterCurrencyCode = counterCurrencyCode;
return this;
}
public BsqSwapOfferInfoBuilder withDate(long date) {
this.date = date;
return this;
}
public BsqSwapOfferInfoBuilder withOwnerNodeAddress(String ownerNodeAddress) {
this.ownerNodeAddress = ownerNodeAddress;
return this;
}
public BsqSwapOfferInfoBuilder withPubKeyRing(String pubKeyRing) {
this.pubKeyRing = pubKeyRing;
return this;
}
public BsqSwapOfferInfoBuilder withVersionNumber(String versionNumber) {
this.versionNumber = versionNumber;
return this;
}
public BsqSwapOfferInfoBuilder withProtocolVersion(int protocolVersion) {
this.protocolVersion = protocolVersion;
return this;
}
public BsqSwapOfferInfo build() {
return new BsqSwapOfferInfo(this);
}
}
}

View file

@ -0,0 +1,323 @@
/*
* 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.model;
import bisq.core.trade.model.bsq_swap.BsqSwapTrade;
import bisq.common.Payload;
import lombok.EqualsAndHashCode;
import lombok.Getter;
import lombok.ToString;
import static bisq.core.api.model.BsqSwapOfferInfo.toBsqSwapOfferInfo;
@EqualsAndHashCode
@ToString
@Getter
public class BsqSwapTradeInfo implements Payload {
private final BsqSwapOfferInfo bsqSwapOffer;
private final String tradeId;
private final String tempTradingPeerNodeAddress;
private final String peerNodeAddress;
private final String txId;
private final long bsqTradeAmount;
private final long bsqMaxTradeAmount;
private final long bsqMinTradeAmount;
private final long btcTradeAmount;
private final long btcMaxTradeAmount;
private final long btcMinTradeAmount;
private final long tradePrice;
private final long bsqMakerTradeFee;
private final long bsqTakerTradeFee;
private final long txFeePerVbyte;
private final long txFee;
private final String makerBsqAddress;
private final String makerBtcAddress;
private final String takerBsqAddress;
private final String takerBtcAddress;
private final long takeOfferDate;
private final String state;
private final String errorMessage;
public BsqSwapTradeInfo(BsqSwapTradeInfoBuilder builder) {
this.bsqSwapOffer = builder.bsqSwapOfferInfo;
this.tradeId = builder.tradeId;
this.tempTradingPeerNodeAddress = builder.tempTradingPeerNodeAddress;
this.peerNodeAddress = builder.peerNodeAddress;
this.txId = builder.txId;
this.bsqTradeAmount = builder.bsqTradeAmount;
this.bsqMaxTradeAmount = builder.bsqMaxTradeAmount;
this.bsqMinTradeAmount = builder.bsqMinTradeAmount;
this.btcTradeAmount = builder.btcTradeAmount;
this.btcMaxTradeAmount = builder.btcMaxTradeAmount;
this.btcMinTradeAmount = builder.btcMinTradeAmount;
this.tradePrice = builder.tradePrice;
this.bsqMakerTradeFee = builder.bsqMakerTradeFee;
this.bsqTakerTradeFee = builder.bsqTakerTradeFee;
this.txFeePerVbyte = builder.txFeePerVbyte;
this.txFee = builder.txFee;
this.makerBsqAddress = builder.makerBsqAddress;
this.makerBtcAddress = builder.makerBtcAddress;
this.takerBsqAddress = builder.takerBsqAddress;
this.takerBtcAddress = builder.takerBtcAddress;
this.takeOfferDate = builder.takeOfferDate;
this.state = builder.state;
this.errorMessage = builder.errorMessage;
}
public static BsqSwapTradeInfo toBsqSwapTradeInfo(BsqSwapTrade trade) {
return toBsqSwapTradeInfo(trade, null);
}
//TODO
public static BsqSwapTradeInfo toBsqSwapTradeInfo(BsqSwapTrade trade, String role) {
return new BsqSwapTradeInfoBuilder()
.withBsqSwapOffer(toBsqSwapOfferInfo(trade.getOffer()))
.withTradeId(trade.getId())
.withTempTradingPeerNodeAddress(trade.getBsqSwapProtocolModel().getTempTradingPeerNodeAddress().getFullAddress())
.withPeerNodeAddress(trade.getTradingPeerNodeAddress().getFullAddress())
.withTxId(trade.getTxId())
/* .withBsqTradeAmount(trade.getBsqSwapProtocolModel().getBsqTradeAmount())
.withBsqMaxTradeAmount(trade.getBsqSwapProtocolModel().getBsqMaxTradeAmount())
.withBsqMinTradeAmount(trade.getBsqSwapProtocolModel().getBsqMinTradeAmount())
.withBtcTradeAmount(trade.getBsqSwapProtocolModel().getBtcTradeAmount())
.withBtcMaxTradeAmount(trade.getBsqSwapProtocolModel().getBtcMaxTradeAmount())
.withBtcMinTradeAmount(trade.getBsqSwapProtocolModel().getBtcMinTradeAmount())
.withTradePrice(trade.getBsqSwapProtocolModel().getTradePrice())
.withBsqMakerTradeFee(trade.getBsqSwapProtocolModel().getBsqMakerTradeFee())
.withBsqTakerTradeFee(trade.getBsqSwapProtocolModel().getBsqTakerTradeFee())
.withTxFeePerVbyte(trade.getBsqSwapProtocolModel().getTxFeePerVbyte())
.withTxFee(trade.getBsqSwapProtocolModel().getTxFee())
.withMakerBsqAddress(trade.getBsqSwapProtocolModel().getMakerBsqAddress())
.withMakerBtcAddress(trade.getBsqSwapProtocolModel().getMakerBtcAddress())
.withTakerBsqAddress(trade.getBsqSwapProtocolModel().getTakerBsqAddress())
.withTakerBtcAddress(trade.getBsqSwapProtocolModel().getTakerBtcAddress())*/
.withTakeOfferDate(trade.getTakeOfferDate())
.withState(trade.getTradeState().name())
.withErrorMessage(trade.getErrorMessage())
.build();
}
///////////////////////////////////////////////////////////////////////////////////////////
// PROTO BUFFER
///////////////////////////////////////////////////////////////////////////////////////////
@Override
public bisq.proto.grpc.BsqSwapTradeInfo toProtoMessage() {
return bisq.proto.grpc.BsqSwapTradeInfo.newBuilder()
.setBsqSwapOfferInfo(bsqSwapOffer.toProtoMessage())
.setTradeId(tradeId)
.setTempTradingPeerNodeAddress(tempTradingPeerNodeAddress != null ? tempTradingPeerNodeAddress : "")
.setPeerNodeAddress(peerNodeAddress != null ? peerNodeAddress : "")
.setTxId(txId != null ? txId : "")
.setBsqTradeAmount(bsqTradeAmount)
.setBsqMaxTradeAmount(bsqMaxTradeAmount)
.setBsqMinTradeAmount(bsqMinTradeAmount)
.setBtcTradeAmount(btcTradeAmount)
.setBtcMaxTradeAmount(btcMaxTradeAmount)
.setBtcMinTradeAmount(btcMinTradeAmount)
.setTradePrice(tradePrice)
.setBsqMakerTradeFee(bsqMakerTradeFee)
.setBsqTakerTradeFee(bsqTakerTradeFee)
.setTxFeePerVbyte(txFeePerVbyte)
.setTxFee(txFee)
.setMakerBsqAddress(makerBsqAddress != null ? makerBsqAddress : "")
.setTakerBsqAddress(takerBsqAddress != null ? takerBsqAddress : "")
.setMakerBtcAddress(makerBtcAddress != null ? makerBtcAddress : "")
.setTakerBtcAddress(takerBtcAddress != null ? takerBtcAddress : "")
.setTakeOfferDate(takeOfferDate)
.setState(state)
.setErrorMessage(errorMessage != null ? errorMessage : "")
.build();
}
public static BsqSwapTradeInfo fromProto(bisq.proto.grpc.BsqSwapTradeInfo proto) {
return new BsqSwapTradeInfoBuilder()
.withBsqSwapOffer(BsqSwapOfferInfo.fromProto(proto.getBsqSwapOfferInfo()))
.withTradeId(proto.getTradeId())
.withTempTradingPeerNodeAddress(proto.getTempTradingPeerNodeAddress())
.withPeerNodeAddress(proto.getPeerNodeAddress())
.withTxId(proto.getTxId())
.withBsqTradeAmount(proto.getBsqTradeAmount())
.withBsqMaxTradeAmount(proto.getBsqMaxTradeAmount())
.withBsqMinTradeAmount(proto.getBsqMinTradeAmount())
.withBtcTradeAmount(proto.getBtcTradeAmount())
.withBtcMaxTradeAmount(proto.getBtcMaxTradeAmount())
.withBtcMinTradeAmount(proto.getBtcMinTradeAmount())
.withTradePrice(proto.getTradePrice())
.withBsqMakerTradeFee(proto.getBsqMakerTradeFee())
.withBsqTakerTradeFee(proto.getBsqTakerTradeFee())
.withTxFeePerVbyte(proto.getTxFeePerVbyte())
.withTxFee(proto.getTxFee())
.withMakerBsqAddress(proto.getMakerBsqAddress())
.withMakerBtcAddress(proto.getMakerBtcAddress())
.withTakerBsqAddress(proto.getTakerBsqAddress())
.withTakerBtcAddress(proto.getTakerBtcAddress())
.withTakeOfferDate(proto.getTakeOfferDate())
.withState(proto.getState())
.withErrorMessage(proto.getErrorMessage())
.build();
}
public static class BsqSwapTradeInfoBuilder {
private BsqSwapOfferInfo bsqSwapOfferInfo;
private String tradeId;
private String tempTradingPeerNodeAddress;
private String peerNodeAddress;
private String txId;
private long bsqTradeAmount;
private long bsqMaxTradeAmount;
private long bsqMinTradeAmount;
private long btcTradeAmount;
private long btcMaxTradeAmount;
private long btcMinTradeAmount;
private long tradePrice;
private long bsqMakerTradeFee;
private long bsqTakerTradeFee;
private long txFeePerVbyte;
private long txFee;
private String makerBsqAddress;
private String makerBtcAddress;
private String takerBsqAddress;
private String takerBtcAddress;
private long takeOfferDate;
private String state;
private String errorMessage;
public BsqSwapTradeInfoBuilder withBsqSwapOffer(BsqSwapOfferInfo bsqSwapOfferInfo) {
this.bsqSwapOfferInfo = bsqSwapOfferInfo;
return this;
}
public BsqSwapTradeInfoBuilder withTradeId(String tradeId) {
this.tradeId = tradeId;
return this;
}
public BsqSwapTradeInfoBuilder withTempTradingPeerNodeAddress(String tempTradingPeerNodeAddress) {
this.tempTradingPeerNodeAddress = tempTradingPeerNodeAddress;
return this;
}
public BsqSwapTradeInfoBuilder withPeerNodeAddress(String peerNodeAddress) {
this.peerNodeAddress = peerNodeAddress;
return this;
}
public BsqSwapTradeInfoBuilder withTxId(String txId) {
this.txId = txId;
return this;
}
public BsqSwapTradeInfoBuilder withBsqTradeAmount(long bsqTradeAmount) {
this.bsqTradeAmount = bsqTradeAmount;
return this;
}
public BsqSwapTradeInfoBuilder withBsqMaxTradeAmount(long bsqMaxTradeAmount) {
this.bsqMaxTradeAmount = bsqMaxTradeAmount;
return this;
}
public BsqSwapTradeInfoBuilder withBsqMinTradeAmount(long bsqMinTradeAmount) {
this.bsqMinTradeAmount = bsqMinTradeAmount;
return this;
}
public BsqSwapTradeInfoBuilder withBtcTradeAmount(long btcTradeAmount) {
this.btcTradeAmount = btcTradeAmount;
return this;
}
public BsqSwapTradeInfoBuilder withBtcMaxTradeAmount(long btcMaxTradeAmount) {
this.btcMaxTradeAmount = btcMaxTradeAmount;
return this;
}
public BsqSwapTradeInfoBuilder withBtcMinTradeAmount(long btcMinTradeAmount) {
this.btcMinTradeAmount = btcMinTradeAmount;
return this;
}
public BsqSwapTradeInfoBuilder withTradePrice(long tradePrice) {
this.tradePrice = tradePrice;
return this;
}
public BsqSwapTradeInfoBuilder withBsqMakerTradeFee(long bsqMakerTradeFee) {
this.bsqMakerTradeFee = bsqMakerTradeFee;
return this;
}
public BsqSwapTradeInfoBuilder withBsqTakerTradeFee(long bsqTakerTradeFee) {
this.bsqTakerTradeFee = bsqTakerTradeFee;
return this;
}
public BsqSwapTradeInfoBuilder withTxFeePerVbyte(long txFeePerVbyte) {
this.txFeePerVbyte = txFeePerVbyte;
return this;
}
public BsqSwapTradeInfoBuilder withTxFee(long txFee) {
this.txFee = txFee;
return this;
}
public BsqSwapTradeInfoBuilder withMakerBsqAddress(String makerBsqAddress) {
this.makerBsqAddress = makerBsqAddress;
return this;
}
public BsqSwapTradeInfoBuilder withMakerBtcAddress(String makerBtcAddress) {
this.makerBtcAddress = makerBtcAddress;
return this;
}
public BsqSwapTradeInfoBuilder withTakerBsqAddress(String takerBsqAddress) {
this.takerBsqAddress = takerBsqAddress;
return this;
}
public BsqSwapTradeInfoBuilder withTakerBtcAddress(String takerBtcAddress) {
this.takerBtcAddress = takerBtcAddress;
return this;
}
public BsqSwapTradeInfoBuilder withTakeOfferDate(long takeOfferDate) {
this.takeOfferDate = takeOfferDate;
return this;
}
public BsqSwapTradeInfoBuilder withState(String state) {
this.state = state;
return this;
}
public BsqSwapTradeInfoBuilder withErrorMessage(String errorMessage) {
this.errorMessage = errorMessage;
return this;
}
public BsqSwapTradeInfo build() {
return new BsqSwapTradeInfo(this);
}
}
}

View file

@ -144,8 +144,8 @@ public class OfferInfo implements Payload {
.withPaymentAccountId(offer.getMakerPaymentAccountId())
.withPaymentMethodId(offer.getPaymentMethod().getId())
.withPaymentMethodShortName(offer.getPaymentMethod().getShortName())
.withBaseCurrencyCode(offer.getOfferPayload().getBaseCurrencyCode())
.withCounterCurrencyCode(offer.getOfferPayload().getCounterCurrencyCode())
.withBaseCurrencyCode(offer.getBaseCurrencyCode())
.withCounterCurrencyCode(offer.getCounterCurrencyCode())
.withDate(offer.getDate().getTime())
.withState(offer.getState().name())
.withIsMyOffer(isMyOffer);

View file

@ -17,8 +17,8 @@
package bisq.core.api.model;
import bisq.core.trade.Contract;
import bisq.core.trade.Trade;
import bisq.core.trade.model.bisq_v1.Contract;
import bisq.core.trade.model.bisq_v1.Trade;
import bisq.common.Payload;
@ -34,7 +34,7 @@ import static bisq.core.api.model.PaymentAccountPayloadInfo.toPaymentAccountPayl
@Getter
public class TradeInfo implements Payload {
// The client cannot see bisq.core.trade.Trade or its fromProto method. We use the
// The client cannot see Trade or its fromProto method. We use the
// lighter weight TradeInfo proto wrapper instead, containing just enough fields to
// view and interact with trades.
@ -127,19 +127,19 @@ public class TradeInfo implements Payload {
.withDate(trade.getDate().getTime())
.withRole(role == null ? "" : role)
.withIsCurrencyForTakerFeeBtc(trade.isCurrencyForTakerFeeBtc())
.withTxFeeAsLong(trade.getTxFeeAsLong())
.withTxFeeAsLong(trade.getTradeTxFeeAsLong())
.withTakerFeeAsLong(trade.getTakerFeeAsLong())
.withTakerFeeAsLong(trade.getTakerFeeAsLong())
.withTakerFeeTxId(trade.getTakerFeeTxId())
.withDepositTxId(trade.getDepositTxId())
.withPayoutTxId(trade.getPayoutTxId())
.withTradeAmountAsLong(trade.getTradeAmountAsLong())
.withTradePrice(trade.getTradePrice().getValue())
.withTradeVolume(trade.getTradeVolume() == null ? 0 : trade.getTradeVolume().getValue())
.withTradeAmountAsLong(trade.getAmountAsLong())
.withTradePrice(trade.getPrice().getValue())
.withTradeVolume(trade.getVolume() == null ? 0 : trade.getVolume().getValue())
.withTradingPeerNodeAddress(Objects.requireNonNull(
trade.getTradingPeerNodeAddress()).getHostNameWithoutPostFix())
.withState(trade.getState().name())
.withPhase(trade.getPhase().name())
.withState(trade.getTradeState().name())
.withPhase(trade.getTradePhase().name())
.withTradePeriodState(trade.getTradePeriodState().name())
.withIsDepositPublished(trade.isDepositPublished())
.withIsDepositConfirmed(trade.isDepositConfirmed())

View file

@ -23,6 +23,7 @@ import bisq.core.btc.wallet.BtcWalletService;
import bisq.core.dao.DaoSetup;
import bisq.core.dao.node.full.RpcService;
import bisq.core.offer.OpenOfferManager;
import bisq.core.offer.bsq_swap.OpenBsqSwapOfferService;
import bisq.core.provider.price.PriceFeedService;
import bisq.core.setup.CorePersistedDataHost;
import bisq.core.setup.CoreSetup;
@ -227,6 +228,7 @@ public abstract class BisqExecutable implements GracefulShutDownHandler, BisqSet
}
try {
injector.getInstance(OpenBsqSwapOfferService.class).shutDown();
injector.getInstance(PriceFeedService.class).shutDown();
injector.getInstance(ArbitratorManager.class).shutDown();
injector.getInstance(TradeStatisticsManager.class).shutDown();

View file

@ -42,7 +42,7 @@ import bisq.core.support.dispute.arbitration.ArbitrationManager;
import bisq.core.support.dispute.mediation.MediationManager;
import bisq.core.support.dispute.refund.RefundManager;
import bisq.core.trade.TradeManager;
import bisq.core.trade.TradeTxException;
import bisq.core.trade.bisq_v1.TradeTxException;
import bisq.core.user.Preferences;
import bisq.core.user.User;
import bisq.core.util.FormattingUtils;
@ -525,6 +525,9 @@ public class BisqSetup {
// miner fee was too low and the transaction got removed from mempool and got out from our wallet after a
// resync.
openOfferManager.getObservableList().forEach(e -> {
if (e.getOffer().isBsqSwapOffer()) {
return;
}
String offerFeePaymentTxId = e.getOffer().getOfferFeePaymentTxId();
if (btcWalletService.getConfidenceForTxId(offerFeePaymentTxId) == null) {
String message = Res.get("popup.warning.openOfferWithInvalidMakerFeeTx",
@ -657,7 +660,10 @@ public class BisqSetup {
}
private void maybeShowLocalhostRunningInfo() {
maybeTriggerDisplayHandler("bitcoinLocalhostNode", displayLocalhostHandler, localBitcoinNode.shouldBeUsed());
if (Config.baseCurrencyNetwork().isMainnet()) {
maybeTriggerDisplayHandler("bitcoinLocalhostNode", displayLocalhostHandler,
localBitcoinNode.shouldBeUsed());
}
}
private void maybeShowAccountSigningStateInfo() {

View file

@ -34,7 +34,8 @@ import bisq.core.notifications.alerts.TradeEvents;
import bisq.core.notifications.alerts.market.MarketAlerts;
import bisq.core.notifications.alerts.price.PriceAlert;
import bisq.core.offer.OpenOfferManager;
import bisq.core.offer.TriggerPriceService;
import bisq.core.offer.bisq_v1.TriggerPriceService;
import bisq.core.offer.bsq_swap.OpenBsqSwapOfferService;
import bisq.core.payment.AmazonGiftCardAccount;
import bisq.core.payment.RevolutAccount;
import bisq.core.payment.TradeLimits;
@ -48,9 +49,10 @@ import bisq.core.support.dispute.mediation.mediator.MediatorManager;
import bisq.core.support.dispute.refund.RefundManager;
import bisq.core.support.dispute.refund.refundagent.RefundAgentManager;
import bisq.core.support.traderchat.TraderChatManager;
import bisq.core.trade.ClosedTradableManager;
import bisq.core.trade.TradeManager;
import bisq.core.trade.closed.ClosedTradableManager;
import bisq.core.trade.failed.FailedTradesManager;
import bisq.core.trade.bisq_v1.FailedTradesManager;
import bisq.core.trade.bsq_swap.BsqSwapTradeManager;
import bisq.core.trade.statistics.TradeStatisticsManager;
import bisq.core.trade.txproof.xmr.XmrTxProofService;
import bisq.core.user.User;
@ -84,6 +86,7 @@ public class DomainInitialisation {
private final TraderChatManager traderChatManager;
private final TradeManager tradeManager;
private final ClosedTradableManager closedTradableManager;
private final BsqSwapTradeManager bsqSwapTradeManager;
private final FailedTradesManager failedTradesManager;
private final XmrTxProofService xmrTxProofService;
private final OpenOfferManager openOfferManager;
@ -112,6 +115,7 @@ public class DomainInitialisation {
private final DaoStateSnapshotService daoStateSnapshotService;
private final TriggerPriceService triggerPriceService;
private final MempoolService mempoolService;
private final OpenBsqSwapOfferService openBsqSwapOfferService;
@Inject
public DomainInitialisation(ClockWatcher clockWatcher,
@ -122,6 +126,7 @@ public class DomainInitialisation {
TraderChatManager traderChatManager,
TradeManager tradeManager,
ClosedTradableManager closedTradableManager,
BsqSwapTradeManager bsqSwapTradeManager,
FailedTradesManager failedTradesManager,
XmrTxProofService xmrTxProofService,
OpenOfferManager openOfferManager,
@ -149,7 +154,8 @@ public class DomainInitialisation {
User user,
DaoStateSnapshotService daoStateSnapshotService,
TriggerPriceService triggerPriceService,
MempoolService mempoolService) {
MempoolService mempoolService,
OpenBsqSwapOfferService openBsqSwapOfferService) {
this.clockWatcher = clockWatcher;
this.tradeLimits = tradeLimits;
this.arbitrationManager = arbitrationManager;
@ -158,6 +164,7 @@ public class DomainInitialisation {
this.traderChatManager = traderChatManager;
this.tradeManager = tradeManager;
this.closedTradableManager = closedTradableManager;
this.bsqSwapTradeManager = bsqSwapTradeManager;
this.failedTradesManager = failedTradesManager;
this.xmrTxProofService = xmrTxProofService;
this.openOfferManager = openOfferManager;
@ -186,6 +193,7 @@ public class DomainInitialisation {
this.daoStateSnapshotService = daoStateSnapshotService;
this.triggerPriceService = triggerPriceService;
this.mempoolService = mempoolService;
this.openBsqSwapOfferService = openBsqSwapOfferService;
}
public void initDomainServices(Consumer<String> rejectedTxErrorMessageHandler,
@ -210,10 +218,12 @@ public class DomainInitialisation {
traderChatManager.onAllServicesInitialized();
closedTradableManager.onAllServicesInitialized();
bsqSwapTradeManager.onAllServicesInitialized();
failedTradesManager.onAllServicesInitialized();
xmrTxProofService.onAllServicesInitialized();
openOfferManager.onAllServicesInitialized();
openBsqSwapOfferService.onAllServicesInitialized();
balances.onAllServicesInitialized();

View file

@ -18,6 +18,8 @@
package bisq.core.app;
import bisq.core.btc.setup.WalletsSetup;
import bisq.core.filter.Filter;
import bisq.core.filter.FilterManager;
import bisq.core.locale.Res;
import bisq.core.provider.price.PriceFeedService;
import bisq.core.user.Preferences;
@ -27,6 +29,7 @@ import bisq.network.p2p.P2PServiceListener;
import bisq.network.p2p.network.CloseConnectionReason;
import bisq.network.p2p.network.Connection;
import bisq.network.p2p.network.ConnectionListener;
import bisq.network.p2p.storage.payload.ProofOfWorkPayload;
import javax.inject.Inject;
import javax.inject.Singleton;
@ -71,25 +74,31 @@ public class P2PNetworkSetup {
final BooleanProperty updatedDataReceived = new SimpleBooleanProperty();
@Getter
final BooleanProperty p2pNetworkFailed = new SimpleBooleanProperty();
final FilterManager filterManager;
@Inject
public P2PNetworkSetup(PriceFeedService priceFeedService,
P2PService p2PService,
WalletsSetup walletsSetup,
Preferences preferences) {
Preferences preferences,
FilterManager filterManager) {
this.priceFeedService = priceFeedService;
this.p2PService = p2PService;
this.walletsSetup = walletsSetup;
this.preferences = preferences;
this.filterManager = filterManager;
}
BooleanProperty init(Runnable initWalletServiceHandler, @Nullable Consumer<Boolean> displayTorNetworkSettingsHandler) {
BooleanProperty init(Runnable initWalletServiceHandler,
@Nullable Consumer<Boolean> displayTorNetworkSettingsHandler) {
StringProperty bootstrapState = new SimpleStringProperty();
StringProperty bootstrapWarning = new SimpleStringProperty();
BooleanProperty hiddenServicePublished = new SimpleBooleanProperty();
BooleanProperty initialP2PNetworkDataReceived = new SimpleBooleanProperty();
addP2PMessageFilter();
p2PNetworkInfoBinding = EasyBind.combine(bootstrapState, bootstrapWarning, p2PService.getNumConnectedPeers(),
walletsSetup.numPeersProperty(), hiddenServicePublished, initialP2PNetworkDataReceived,
(state, warning, numP2pPeers, numBtcPeers, hiddenService, dataReceived) -> {
@ -225,4 +234,13 @@ public class P2PNetworkSetup {
public void setSplashP2PNetworkAnimationVisible(boolean value) {
splashP2PNetworkAnimationVisible.set(value);
}
private void addP2PMessageFilter() {
p2PService.getP2PDataStorage().setFilterPredicate(payload -> {
Filter filter = filterManager.getFilter();
return filter == null ||
!filter.isDisablePowMessage() ||
!(payload instanceof ProofOfWorkPayload);
});
}
}

View file

@ -24,6 +24,7 @@ import bisq.core.btc.wallet.BtcWalletService;
import bisq.core.dao.DaoSetup;
import bisq.core.dao.node.full.RpcService;
import bisq.core.offer.OpenOfferManager;
import bisq.core.offer.bsq_swap.OpenBsqSwapOfferService;
import bisq.core.support.dispute.arbitration.arbitrator.ArbitratorManager;
import bisq.network.p2p.NodeAddress;
@ -87,6 +88,7 @@ public abstract class ExecutableForAppWithP2p extends BisqExecutable {
try {
if (injector != null) {
JsonFileManager.shutDownAllInstances();
injector.getInstance(OpenBsqSwapOfferService.class).shutDown();
injector.getInstance(RpcService.class).shutDown();
injector.getInstance(DaoSetup.class).shutDown();
injector.getInstance(ArbitratorManager.class).shutDown();

View file

@ -24,10 +24,10 @@ import bisq.core.offer.OpenOffer;
import bisq.core.offer.OpenOfferManager;
import bisq.core.support.dispute.Dispute;
import bisq.core.support.dispute.refund.RefundManager;
import bisq.core.trade.Trade;
import bisq.core.trade.ClosedTradableManager;
import bisq.core.trade.TradeManager;
import bisq.core.trade.closed.ClosedTradableManager;
import bisq.core.trade.failed.FailedTradesManager;
import bisq.core.trade.bisq_v1.FailedTradesManager;
import bisq.core.trade.model.bisq_v1.Trade;
import bisq.common.UserThread;

View file

@ -55,11 +55,11 @@ public class TxFeeEstimationService {
// segwit deposit tx with change vsize = 263
// segwit payout tx vsize = 169
// segwit delayed payout tx vsize = 139
public static int TYPICAL_TX_WITH_1_INPUT_VSIZE = 175;
private static int DEPOSIT_TX_VSIZE = 233;
public static final int TYPICAL_TX_WITH_1_INPUT_VSIZE = 175;
private static final int DEPOSIT_TX_VSIZE = 233;
private static int BSQ_INPUT_INCREASE = 70;
private static int MAX_ITERATIONS = 10;
private static final int BSQ_INPUT_INCREASE = 70;
private static final int MAX_ITERATIONS = 10;
private final FeeService feeService;
private final BtcWalletService btcWalletService;

View file

@ -20,7 +20,7 @@ package bisq.core.btc.listeners;
import org.bitcoinj.core.Coin;
public interface BsqBalanceListener {
void onUpdateBalances(Coin availableConfirmedBalance,
void onUpdateBalances(Coin availableBalance,
Coin availableNonBsqBalance,
Coin unverifiedBalance,
Coin unconfirmedChangeBalance,

View file

@ -19,18 +19,15 @@ package bisq.core.btc.listeners;
import org.bitcoinj.core.TransactionConfidence;
public class TxConfidenceListener {
private final String txID;
import lombok.Getter;
public TxConfidenceListener(String txID) {
this.txID = txID;
public abstract class TxConfidenceListener {
@Getter
private final String txId;
public TxConfidenceListener(String txId) {
this.txId = txId;
}
public String getTxID() {
return txID;
}
@SuppressWarnings("UnusedParameters")
public void onTransactionConfidenceChanged(TransactionConfidence confidence) {
}
abstract public void onTransactionConfidenceChanged(TransactionConfidence confidence);
}

View file

@ -17,33 +17,82 @@
package bisq.core.btc.model;
import bisq.core.btc.wallet.BtcWalletService;
import bisq.common.proto.network.NetworkPayload;
import bisq.common.proto.persistable.PersistablePayload;
import bisq.common.util.Utilities;
import com.google.protobuf.ByteString;
import org.bitcoinj.core.Transaction;
import org.bitcoinj.core.TransactionInput;
import org.bitcoinj.script.Script;
import java.util.Objects;
import lombok.EqualsAndHashCode;
import lombok.Getter;
import javax.annotation.concurrent.Immutable;
@EqualsAndHashCode
@Immutable
@Getter
public final class RawTransactionInput implements NetworkPayload, PersistablePayload {
public final long index; // Index of spending txo
public final byte[] parentTransaction; // Spending tx (fromTx)
public final long value;
// Added at Bsq swap release
// id of the org.bitcoinj.script.Script.ScriptType. Useful to know if input is segwit.
// Lowest Script.ScriptType.id value is 1, so we use 0 as value for not defined
public final int scriptTypeId;
public RawTransactionInput(TransactionInput input) {
this(input.getOutpoint().getIndex(),
Objects.requireNonNull(Objects.requireNonNull(input.getConnectedOutput()).getParentTransaction()),
Objects.requireNonNull(input.getValue()).value,
input.getConnectedOutput() != null &&
input.getConnectedOutput().getScriptPubKey() != null &&
input.getConnectedOutput().getScriptPubKey().getScriptType() != null ?
input.getConnectedOutput().getScriptPubKey().getScriptType().id : -1);
}
// Does not set the scriptTypeId. Use RawTransactionInput(TransactionInput input) for any new code.
@Deprecated
public RawTransactionInput(long index, byte[] parentTransaction, long value) {
this(index, parentTransaction, value, 0);
}
private RawTransactionInput(long index, Transaction parentTransaction, long value, int scriptTypeId) {
this(index,
parentTransaction.bitcoinSerialize(scriptTypeId == Script.ScriptType.P2WPKH.id ||
scriptTypeId == Script.ScriptType.P2WSH.id),
value, scriptTypeId);
}
///////////////////////////////////////////////////////////////////////////////////////////
// PROTO BUFFER
///////////////////////////////////////////////////////////////////////////////////////////
/**
* Holds the relevant data for the connected output for a tx input.
* @param index the index of the parentTransaction
* @param parentTransaction the spending output tx, not the parent tx of the input
* @param value the number of satoshis being spent
* @param scriptTypeId The id of the org.bitcoinj.script.Script.ScriptType of the spending output
* If not set it is 0.
*/
public RawTransactionInput(long index, byte[] parentTransaction, long value) {
private RawTransactionInput(long index,
byte[] parentTransaction,
long value,
int scriptTypeId) {
this.index = index;
this.parentTransaction = parentTransaction;
this.value = value;
this.scriptTypeId = scriptTypeId;
}
@Override
@ -52,11 +101,36 @@ public final class RawTransactionInput implements NetworkPayload, PersistablePay
.setIndex(index)
.setParentTransaction(ByteString.copyFrom(parentTransaction))
.setValue(value)
.setScriptTypeId(scriptTypeId)
.build();
}
public static RawTransactionInput fromProto(protobuf.RawTransactionInput proto) {
return new RawTransactionInput(proto.getIndex(), proto.getParentTransaction().toByteArray(), proto.getValue());
return new RawTransactionInput(proto.getIndex(),
proto.getParentTransaction().toByteArray(),
proto.getValue(),
proto.getScriptTypeId());
}
///////////////////////////////////////////////////////////////////////////////////////////
// API
///////////////////////////////////////////////////////////////////////////////////////////
public boolean isSegwit() {
return isP2WPKH() || isP2WSH();
}
public boolean isP2WPKH() {
return scriptTypeId == Script.ScriptType.P2WPKH.id;
}
public boolean isP2WSH() {
return scriptTypeId == Script.ScriptType.P2WSH.id;
}
public String getParentTxId(BtcWalletService btcWalletService) {
return btcWalletService.getTxFromSerializedTx(parentTransaction).getTxId().toString();
}
@Override
@ -65,6 +139,7 @@ public final class RawTransactionInput implements NetworkPayload, PersistablePay
"index=" + index +
", parentTransaction as HEX " + Utilities.bytesAsHexString(parentTransaction) +
", value=" + value +
", scriptTypeId=" + scriptTypeId +
'}';
}
}

View file

@ -64,9 +64,6 @@ public class BtcNodes {
new BtcNode("btc1.sqrrm.net", "jygcc54etaubgdpcvzgbihjaqbc37cstpvum5sjzvka4bibkp4wrgnqd.onion", "185.25.48.184", BtcNode.DEFAULT_PORT, "@sqrrm"),
new BtcNode("btc2.sqrrm.net", "h32haomoe52ljz6qopedsocvotvoj5lm2zmecfhdhawb3flbsf64l2qd.onion", "81.171.22.143", BtcNode.DEFAULT_PORT, "@sqrrm"),
// KanoczTomas
// new BtcNode("btc.ispol.sk", "mbm6ffx6j5ygi2ck.onion", "193.58.196.212", BtcNode.DEFAULT_PORT, "@KanoczTomas"),
// Devin Bileck
new BtcNode("btc1.bisq.services", "devinbtctu7uctl7hly2juu3thbgeivfnvw3ckj3phy6nyvpnx66yeyd.onion", "172.105.21.216", BtcNode.DEFAULT_PORT, "@devinbileck"),
new BtcNode("btc2.bisq.services", "devinbtcyk643iruzfpaxw3on2jket7rbjmwygm42dmdyub3ietrbmid.onion", "173.255.240.205", BtcNode.DEFAULT_PORT, "@devinbileck"),
@ -77,9 +74,6 @@ public class BtcNodes {
new BtcNode("node140.hnl.wiz.biz", "jto2jfbsxhb6yvhcrrjddrgbakte6tgsy3c3z3prss64gndgvovvosyd.onion", "103.99.168.140", BtcNode.DEFAULT_PORT, "@wiz"),
new BtcNode("node210.fmt.wiz.biz", "rfqmn3qe36uaptkxhdvi74p4hyrzhir6vhmzb2hqryxodig4gue2zbyd.onion", "103.99.170.210", BtcNode.DEFAULT_PORT, "@wiz"),
new BtcNode("node220.fmt.wiz.biz", "azbpsh4arqlm6442wfimy7qr65bmha2zhgjg7wbaji6vvaug53hur2qd.onion", "103.99.170.220", BtcNode.DEFAULT_PORT, "@wiz")
// Rob Kaandorp
// new BtcNode(null, "2pj2o2mrawj7yotg.onion", null, BtcNode.DEFAULT_PORT, "@robkaandorp") // cannot provide IP because no static IP
) :
new ArrayList<>();
}

View file

@ -26,6 +26,8 @@ import org.bitcoinj.core.TransactionOutput;
import javax.inject.Inject;
import lombok.Getter;
import lombok.Setter;
import lombok.extern.slf4j.Slf4j;
/**
@ -36,9 +38,13 @@ import lombok.extern.slf4j.Slf4j;
public class BsqCoinSelector extends BisqDefaultCoinSelector {
private final DaoStateService daoStateService;
private final UnconfirmedBsqChangeOutputListService unconfirmedBsqChangeOutputListService;
@Setter
@Getter
private boolean allowSpendMyOwnUnconfirmedTxOutputs = true;
@Inject
public BsqCoinSelector(DaoStateService daoStateService, UnconfirmedBsqChangeOutputListService unconfirmedBsqChangeOutputListService) {
public BsqCoinSelector(DaoStateService daoStateService,
UnconfirmedBsqChangeOutputListService unconfirmedBsqChangeOutputListService) {
// permitForeignPendingTx is not relevant here as we do not support pending foreign utxos anyway.
super(false);
this.daoStateService = daoStateService;
@ -53,17 +59,19 @@ public class BsqCoinSelector extends BisqDefaultCoinSelector {
return false;
// If it is a normal confirmed BSQ output we use the default lookup at the daoState
if (daoStateService.isTxOutputSpendable(new TxOutputKey(parentTransaction.getTxId().toString(), output.getIndex())))
TxOutputKey txOutputKey = new TxOutputKey(parentTransaction.getTxId().toString(), output.getIndex());
if (daoStateService.isTxOutputSpendable(txOutputKey))
return true;
// It might be that it is an unconfirmed change output which we allow to be used for spending without requiring a confirmation.
// We check if we have the output in the dao state, if so we have a confirmed but unspendable output (e.g. confiscated).
if (daoStateService.getTxOutput(new TxOutputKey(parentTransaction.getTxId().toString(), output.getIndex())).isPresent())
if (daoStateService.getTxOutput(txOutputKey).isPresent())
return false;
// If we have set the isUnconfirmedSpendable flag to true (default) we check for unconfirmed own change outputs.
// Only if it's not existing yet in the dao state (unconfirmed) we use our unconfirmedBsqChangeOutputList to
// check if it is an own change output.
return unconfirmedBsqChangeOutputListService.hasTransactionOutput(output);
return allowSpendMyOwnUnconfirmedTxOutputs && unconfirmedBsqChangeOutputListService.hasTransactionOutput(output);
}
// For BSQ we do not check for dust attack utxos as they are 5.46 BSQ and a considerable value.

View file

@ -42,7 +42,7 @@ public class BsqTransferService {
Transaction preparedSendTx = bsqWalletService.getPreparedSendBsqTx(address.toString(), receiverAmount);
Transaction txWithBtcFee = btcWalletService.completePreparedSendBsqTx(preparedSendTx, txFeePerVbyte);
Transaction signedTx = bsqWalletService.signTx(txWithBtcFee);
Transaction signedTx = bsqWalletService.signTxAndVerifyNoDustOutputs(txWithBtcFee);
return new BsqTransferModel(address,
receiverAmount,

View file

@ -22,6 +22,7 @@ import bisq.core.btc.exceptions.InsufficientBsqException;
import bisq.core.btc.exceptions.TransactionVerificationException;
import bisq.core.btc.exceptions.WalletException;
import bisq.core.btc.listeners.BsqBalanceListener;
import bisq.core.btc.model.RawTransactionInput;
import bisq.core.btc.setup.WalletsSetup;
import bisq.core.dao.DaoKillSwitch;
import bisq.core.dao.state.DaoStateListener;
@ -34,8 +35,10 @@ import bisq.core.dao.state.model.blockchain.TxType;
import bisq.core.dao.state.unconfirmed.UnconfirmedBsqChangeOutputListService;
import bisq.core.provider.fee.FeeService;
import bisq.core.user.Preferences;
import bisq.core.util.coin.BsqFormatter;
import bisq.common.UserThread;
import bisq.common.util.Tuple2;
import org.bitcoinj.core.Address;
import org.bitcoinj.core.AddressFormatException;
@ -95,15 +98,20 @@ public class BsqWalletService extends WalletService implements DaoStateListener
private final CopyOnWriteArraySet<BsqBalanceListener> bsqBalanceListeners = new CopyOnWriteArraySet<>();
private final List<WalletTransactionsChangeListener> walletTransactionsChangeListeners = new ArrayList<>();
private boolean updateBsqWalletTransactionsPending;
@Getter
private final BsqFormatter bsqFormatter;
// balance of non BSQ satoshis
@Getter
private Coin availableNonBsqBalance = Coin.ZERO;
@Getter
private Coin availableConfirmedBalance = Coin.ZERO;
private Coin availableBalance = Coin.ZERO;
@Getter
private Coin unverifiedBalance = Coin.ZERO;
@Getter
private Coin verifiedBalance = Coin.ZERO;
@Getter
private Coin unconfirmedChangeBalance = Coin.ZERO;
@Getter
private Coin lockedForVotingBalance = Coin.ZERO;
@ -125,7 +133,8 @@ public class BsqWalletService extends WalletService implements DaoStateListener
UnconfirmedBsqChangeOutputListService unconfirmedBsqChangeOutputListService,
Preferences preferences,
FeeService feeService,
DaoKillSwitch daoKillSwitch) {
DaoKillSwitch daoKillSwitch,
BsqFormatter bsqFormatter) {
super(walletsSetup,
preferences,
feeService);
@ -135,6 +144,7 @@ public class BsqWalletService extends WalletService implements DaoStateListener
this.daoStateService = daoStateService;
this.unconfirmedBsqChangeOutputListService = unconfirmedBsqChangeOutputListService;
this.daoKillSwitch = daoKillSwitch;
this.bsqFormatter = bsqFormatter;
nonBsqCoinSelector.setPreferences(preferences);
@ -284,18 +294,20 @@ public class BsqWalletService extends WalletService implements DaoStateListener
.mapToLong(TxOutput::getValue)
.sum());
availableConfirmedBalance = bsqCoinSelector.select(NetworkParameters.MAX_MONEY,
availableBalance = bsqCoinSelector.select(NetworkParameters.MAX_MONEY,
wallet.calculateAllSpendCandidates()).valueGathered;
if (availableConfirmedBalance.isNegative())
availableConfirmedBalance = Coin.ZERO;
if (availableBalance.isNegative())
availableBalance = Coin.ZERO;
unconfirmedChangeBalance = unconfirmedBsqChangeOutputListService.getBalance();
availableNonBsqBalance = nonBsqCoinSelector.select(NetworkParameters.MAX_MONEY,
wallet.calculateAllSpendCandidates()).valueGathered;
bsqBalanceListeners.forEach(e -> e.onUpdateBalances(availableConfirmedBalance, availableNonBsqBalance, unverifiedBalance,
verifiedBalance = availableBalance.subtract(unconfirmedChangeBalance);
bsqBalanceListeners.forEach(e -> e.onUpdateBalances(availableBalance, availableNonBsqBalance, unverifiedBalance,
unconfirmedChangeBalance, lockedForVotingBalance, lockupBondsBalance, unlockingBondsBalance));
log.info("updateBsqBalance took {} ms", System.currentTimeMillis() - ts);
}
@ -481,28 +493,10 @@ public class BsqWalletService extends WalletService implements DaoStateListener
// Sign tx
///////////////////////////////////////////////////////////////////////////////////////////
public Transaction signTx(Transaction tx) throws WalletException, TransactionVerificationException {
for (int i = 0; i < tx.getInputs().size(); i++) {
TransactionInput txIn = tx.getInputs().get(i);
TransactionOutput connectedOutput = txIn.getConnectedOutput();
if (connectedOutput != null && connectedOutput.isMine(wallet)) {
signTransactionInput(wallet, aesKey, tx, txIn, i);
checkScriptSig(tx, txIn, i);
}
}
for (TransactionOutput txo : tx.getOutputs()) {
Coin value = txo.getValue();
// OpReturn outputs have value 0
if (value.isPositive()) {
checkArgument(Restrictions.isAboveDust(txo.getValue()),
"An output value is below dust limit. Transaction=" + tx);
}
}
checkWalletConsistency(wallet);
verifyTransaction(tx);
printTx("BSQ wallet: Signed Tx", tx);
public Transaction signTxAndVerifyNoDustOutputs(Transaction tx)
throws WalletException, TransactionVerificationException {
WalletService.signTx(wallet, aesKey, tx);
WalletService.verifyNonDustTxo(tx);
return tx;
}
@ -540,6 +534,7 @@ public class BsqWalletService extends WalletService implements DaoStateListener
return getPreparedSendTx(receiverAddress, receiverAmount, bsqCoinSelector);
}
///////////////////////////////////////////////////////////////////////////////////////////
// Send BTC (non-BSQ) with BTC fee (e.g. the issuance output from a lost comp. request)
///////////////////////////////////////////////////////////////////////////////////////////
@ -732,6 +727,46 @@ public class BsqWalletService extends WalletService implements DaoStateListener
}
///////////////////////////////////////////////////////////////////////////////////////////
// BsqSwap tx
///////////////////////////////////////////////////////////////////////////////////////////
public Tuple2<List<RawTransactionInput>, Coin> getBuyersBsqInputsForBsqSwapTx(Coin required)
throws InsufficientBsqException {
daoKillSwitch.assertDaoIsNotDisabled();
// As unconfirmed BSQ inputs cannot be verified by the peer we can only use confirmed BSQ.
boolean prev = bsqCoinSelector.isAllowSpendMyOwnUnconfirmedTxOutputs();
bsqCoinSelector.setAllowSpendMyOwnUnconfirmedTxOutputs(false);
CoinSelection coinSelection = bsqCoinSelector.select(required, wallet.calculateAllSpendCandidates());
Coin change;
try {
change = bsqCoinSelector.getChange(required, coinSelection);
} catch (InsufficientMoneyException e) {
throw new InsufficientBsqException(e.missing);
} finally {
bsqCoinSelector.setAllowSpendMyOwnUnconfirmedTxOutputs(prev);
}
Transaction dummyTx = new Transaction(params);
coinSelection.gathered.forEach(dummyTx::addInput);
List<RawTransactionInput> inputs = dummyTx.getInputs().stream()
.map(RawTransactionInput::new)
.collect(Collectors.toList());
return new Tuple2<>(inputs, change);
}
public void signBsqSwapTransaction(Transaction transaction, List<TransactionInput> myInputs)
throws TransactionVerificationException {
for (TransactionInput input : myInputs) {
TransactionOutput connectedOutput = input.getConnectedOutput();
checkNotNull(connectedOutput, "connectedOutput must not be null");
checkArgument(connectedOutput.isMine(wallet), "connectedOutput is not mine");
signTransactionInput(wallet, aesKey, transaction, input, input.getIndex());
checkScriptSig(transaction, input, input.getIndex());
}
}
///////////////////////////////////////////////////////////////////////////////////////////
// Blind vote tx
///////////////////////////////////////////////////////////////////////////////////////////
@ -781,6 +816,7 @@ public class BsqWalletService extends WalletService implements DaoStateListener
return tx;
}
///////////////////////////////////////////////////////////////////////////////////////////
// Unlock bond tx
///////////////////////////////////////////////////////////////////////////////////////////

View file

@ -23,6 +23,7 @@ import bisq.core.btc.exceptions.TransactionVerificationException;
import bisq.core.btc.exceptions.WalletException;
import bisq.core.btc.model.AddressEntry;
import bisq.core.btc.model.AddressEntryList;
import bisq.core.btc.model.RawTransactionInput;
import bisq.core.btc.setup.WalletsSetup;
import bisq.core.btc.wallet.http.MemPoolSpaceTxBroadcaster;
import bisq.core.provider.fee.FeeService;
@ -47,6 +48,7 @@ import org.bitcoinj.crypto.KeyCrypterScrypt;
import org.bitcoinj.script.Script;
import org.bitcoinj.script.ScriptBuilder;
import org.bitcoinj.script.ScriptPattern;
import org.bitcoinj.wallet.CoinSelection;
import org.bitcoinj.wallet.SendRequest;
import org.bitcoinj.wallet.Wallet;
@ -61,6 +63,7 @@ import org.bouncycastle.crypto.params.KeyParameter;
import java.util.Arrays;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
import java.util.stream.Collectors;
@ -1330,4 +1333,30 @@ public class BtcWalletService extends WalletService {
return resultTx;
}
///////////////////////////////////////////////////////////////////////////////////////////
// Find inputs and change
///////////////////////////////////////////////////////////////////////////////////////////
public Tuple2<List<RawTransactionInput>, Coin> getInputsAndChange(Coin required) throws InsufficientMoneyException {
BtcCoinSelector coinSelector = new BtcCoinSelector(walletsSetup.getAddressesByContext(AddressEntry.Context.AVAILABLE),
preferences.getIgnoreDustThreshold());
CoinSelection coinSelection = coinSelector.select(required, Objects.requireNonNull(wallet).calculateAllSpendCandidates());
Coin change;
try {
change = coinSelector.getChange(required, coinSelection);
} catch (InsufficientMoneyException e) {
log.error("Missing funds in getSellersBtcInputsForBsqSwapTx. missing={}", e.missing);
throw new InsufficientMoneyException(e.missing);
}
Transaction dummyTx = new Transaction(params);
coinSelection.gathered.forEach(dummyTx::addInput);
List<RawTransactionInput> inputs = dummyTx.getInputs().stream()
.map(RawTransactionInput::new)
.collect(Collectors.toList());
return new Tuple2<>(inputs, change);
}
}

View file

@ -63,13 +63,12 @@ import org.bouncycastle.crypto.params.KeyParameter;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.stream.Collectors;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.jetbrains.annotations.NotNull;
import javax.annotation.Nullable;
import static com.google.common.base.Preconditions.checkArgument;
@ -891,8 +890,7 @@ public class TradeWalletService {
input.setScriptSig(inputScript);
} else {
input.setScriptSig(ScriptBuilder.createEmpty());
TransactionWitness witness = TransactionWitness.redeemP2WSH(redeemScript, sellerTxSig, buyerTxSig);
input.setWitness(witness);
input.setWitness(TransactionWitness.redeemP2WSH(redeemScript, sellerTxSig, buyerTxSig));
}
WalletService.printTx("payoutTx", payoutTx);
WalletService.verifyTransaction(payoutTx);
@ -971,8 +969,7 @@ public class TradeWalletService {
input.setScriptSig(inputScript);
} else {
input.setScriptSig(ScriptBuilder.createEmpty());
TransactionWitness witness = TransactionWitness.redeemP2WSH(redeemScript, sellerTxSig, buyerTxSig);
input.setWitness(witness);
input.setWitness(TransactionWitness.redeemP2WSH(redeemScript, sellerTxSig, buyerTxSig));
}
WalletService.printTx("mediated payoutTx", payoutTx);
WalletService.verifyTransaction(payoutTx);
@ -1059,8 +1056,7 @@ public class TradeWalletService {
input.setScriptSig(inputScript);
} else {
input.setScriptSig(ScriptBuilder.createEmpty());
TransactionWitness witness = TransactionWitness.redeemP2WSH(redeemScript, arbitratorTxSig, tradersTxSig);
input.setWitness(witness);
input.setWitness(TransactionWitness.redeemP2WSH(redeemScript, arbitratorTxSig, tradersTxSig));
}
WalletService.printTx("disputed payoutTx", payoutTx);
WalletService.verifyTransaction(payoutTx);
@ -1077,14 +1073,14 @@ public class TradeWalletService {
///////////////////////////////////////////////////////////////////////////////////////////
public Tuple2<String, String> emergencyBuildPayoutTxFrom2of2MultiSig(String depositTxHex,
Coin buyerPayoutAmount,
Coin sellerPayoutAmount,
Coin txFee,
String buyerAddressString,
String sellerAddressString,
String buyerPubKeyAsHex,
String sellerPubKeyAsHex,
boolean hashedMultiSigOutputIsLegacy) {
Coin buyerPayoutAmount,
Coin sellerPayoutAmount,
Coin txFee,
String buyerAddressString,
String sellerAddressString,
String buyerPubKeyAsHex,
String sellerPubKeyAsHex,
boolean hashedMultiSigOutputIsLegacy) {
byte[] buyerPubKey = ECKey.fromPublicOnly(Utils.HEX.decode(buyerPubKeyAsHex)).getPubKey();
byte[] sellerPubKey = ECKey.fromPublicOnly(Utils.HEX.decode(sellerPubKeyAsHex)).getPubKey();
Script redeemScript = get2of2MultiSigRedeemScript(buyerPubKey, sellerPubKey);
@ -1105,7 +1101,10 @@ public class TradeWalletService {
return new Tuple2<>(redeemScriptHex, unsignedTxHex);
}
public String emergencyGenerateSignature(String rawTxHex, String redeemScriptHex, Coin inputValue, String myPrivKeyAsHex)
public String emergencyGenerateSignature(String rawTxHex,
String redeemScriptHex,
Coin inputValue,
String myPrivKeyAsHex)
throws IllegalArgumentException {
boolean hashedMultiSigOutputIsLegacy = true;
if (rawTxHex.startsWith("010000000001"))
@ -1129,10 +1128,10 @@ public class TradeWalletService {
}
public Tuple2<String, String> emergencyApplySignatureToPayoutTxFrom2of2MultiSig(String unsignedTxHex,
String redeemScriptHex,
String buyerSignatureAsHex,
String sellerSignatureAsHex,
boolean hashedMultiSigOutputIsLegacy)
String redeemScriptHex,
String buyerSignatureAsHex,
String sellerSignatureAsHex,
boolean hashedMultiSigOutputIsLegacy)
throws AddressFormatException, SignatureDecodeException {
Transaction payoutTx = new Transaction(params, Utils.HEX.decode(unsignedTxHex));
TransactionSignature buyerTxSig = TransactionSignature.decodeFromBitcoin(Utils.HEX.decode(buyerSignatureAsHex), true, true);
@ -1146,8 +1145,7 @@ public class TradeWalletService {
input.setScriptSig(inputScript);
} else {
input.setScriptSig(ScriptBuilder.createEmpty());
TransactionWitness witness = TransactionWitness.redeemP2WSH(redeemScript, sellerTxSig, buyerTxSig);
input.setWitness(witness);
input.setWitness(TransactionWitness.redeemP2WSH(redeemScript, sellerTxSig, buyerTxSig));
}
String txId = payoutTx.getTxId().toString();
String signedTxHex = Utils.HEX.encode(payoutTx.bitcoinSerialize(!hashedMultiSigOutputIsLegacy));
@ -1163,6 +1161,96 @@ public class TradeWalletService {
broadcastTx(payoutTx, callback, 20);
}
///////////////////////////////////////////////////////////////////////////////////////////
// BsqSwap tx
///////////////////////////////////////////////////////////////////////////////////////////
public Transaction sellerBuildBsqSwapTx(List<RawTransactionInput> buyersBsqInputs,
List<RawTransactionInput> sellersBtcInputs,
Coin sellersBsqPayoutAmount,
String sellersBsqPayoutAddress,
@Nullable Coin buyersBsqChangeAmount,
@Nullable String buyersBsqChangeAddress,
Coin buyersBtcPayoutAmount,
String buyersBtcPayoutAddress,
@Nullable Coin sellersBtcChangeAmount,
@Nullable String sellersBtcChangeAddress) throws AddressFormatException {
Transaction transaction = new Transaction(params);
List<TransactionInput> sellersBtcTransactionInput = sellersBtcInputs.stream()
.map(rawInput -> getTransactionInput(transaction, new byte[]{}, rawInput))
.collect(Collectors.toList());
return buildBsqSwapTx(buyersBsqInputs,
sellersBtcTransactionInput,
sellersBsqPayoutAmount,
sellersBsqPayoutAddress,
buyersBsqChangeAmount,
buyersBsqChangeAddress,
buyersBtcPayoutAmount,
buyersBtcPayoutAddress,
sellersBtcChangeAmount,
sellersBtcChangeAddress,
transaction);
}
public Transaction buyerBuildBsqSwapTx(List<RawTransactionInput> buyersBsqInputs,
List<TransactionInput> sellersBtcInputs,
Coin sellersBsqPayoutAmount,
String sellersBsqPayoutAddress,
@Nullable Coin buyersBsqChangeAmount,
@Nullable String buyersBsqChangeAddress,
Coin buyersBtcPayoutAmount,
String buyersBtcPayoutAddress,
@Nullable Coin sellersBtcChangeAmount,
@Nullable String sellersBtcChangeAddress) throws AddressFormatException {
Transaction transaction = new Transaction(params);
return buildBsqSwapTx(buyersBsqInputs,
sellersBtcInputs,
sellersBsqPayoutAmount,
sellersBsqPayoutAddress,
buyersBsqChangeAmount,
buyersBsqChangeAddress,
buyersBtcPayoutAmount,
buyersBtcPayoutAddress,
sellersBtcChangeAmount,
sellersBtcChangeAddress,
transaction);
}
private Transaction buildBsqSwapTx(List<RawTransactionInput> buyersBsqInputs,
List<TransactionInput> sellersBtcInputs,
Coin sellersBsqPayoutAmount,
String sellersBsqPayoutAddress,
@Nullable Coin buyersBsqChangeAmount,
@Nullable String buyersBsqChangeAddress,
Coin buyersBtcPayoutAmount,
String buyersBtcPayoutAddress,
@Nullable Coin sellersBtcChangeAmount,
@Nullable String sellersBtcChangeAddress,
Transaction transaction) throws AddressFormatException {
buyersBsqInputs.forEach(rawInput -> transaction.addInput(getTransactionInput(transaction, new byte[]{}, rawInput)));
sellersBtcInputs.forEach(transaction::addInput);
transaction.addOutput(sellersBsqPayoutAmount, Address.fromString(params, sellersBsqPayoutAddress));
if (buyersBsqChangeAmount != null && buyersBsqChangeAmount.isPositive())
transaction.addOutput(buyersBsqChangeAmount, Address.fromString(params, Objects.requireNonNull(buyersBsqChangeAddress)));
transaction.addOutput(buyersBtcPayoutAmount, Address.fromString(params, buyersBtcPayoutAddress));
if (sellersBtcChangeAmount != null && sellersBtcChangeAmount.isPositive())
transaction.addOutput(sellersBtcChangeAmount, Address.fromString(params, Objects.requireNonNull(sellersBtcChangeAddress)));
return transaction;
}
public void signBsqSwapTransaction(Transaction transaction, List<TransactionInput> myInputs)
throws SigningException {
for (TransactionInput input : myInputs) {
signInput(transaction, input, input.getIndex());
}
}
///////////////////////////////////////////////////////////////////////////////////////////
// Broadcast tx
@ -1207,7 +1295,12 @@ public class TradeWalletService {
// Private methods
///////////////////////////////////////////////////////////////////////////////////////////
private RawTransactionInput getRawInputFromTransactionInput(@NotNull TransactionInput input) {
// This method might be replace by RawTransactionInput constructor taking the TransactionInput as param.
// As we used segwit=false for the bitcoinSerialize method here we still keep it to not risk to break anything,
// though it very likely should be fine to replace it with the RawTransactionInput constructor call.
@Deprecated
private RawTransactionInput getRawInputFromTransactionInput(TransactionInput input) {
checkNotNull(input, "input must not be null");
checkNotNull(input.getConnectedOutput(), "input.getConnectedOutput() must not be null");
checkNotNull(input.getConnectedOutput().getParentTransaction(),
"input.getConnectedOutput().getParentTransaction() must not be null");
@ -1222,10 +1315,13 @@ public class TradeWalletService {
input.getValue().value);
}
private TransactionInput getTransactionInput(Transaction depositTx,
private TransactionInput getTransactionInput(Transaction parentTransaction,
byte[] scriptProgram,
RawTransactionInput rawTransactionInput) {
return new TransactionInput(params, depositTx, scriptProgram, getConnectedOutPoint(rawTransactionInput),
return new TransactionInput(params,
parentTransaction,
scriptProgram,
getConnectedOutPoint(rawTransactionInput),
Coin.valueOf(rawTransactionInput.value));
}
@ -1239,7 +1335,6 @@ public class TradeWalletService {
checkNotNull(getConnectedOutPoint(rawTransactionInput).getConnectedOutput()).getScriptPubKey());
}
// TODO: Once we have removed legacy arbitrator from dispute domain we can remove that method as well.
// Atm it is still used by traderSignAndFinalizeDisputedPayoutTx which is used by ArbitrationManager.
@ -1299,7 +1394,6 @@ public class TradeWalletService {
private void signInput(Transaction transaction, TransactionInput input, int inputIndex) throws SigningException {
checkNotNull(input.getConnectedOutput(), "input.getConnectedOutput() must not be null");
Script scriptPubKey = input.getConnectedOutput().getScriptPubKey();
checkNotNull(wallet);
ECKey sigKey = input.getOutpoint().getConnectedKey(wallet);
checkNotNull(sigKey, "signInput: sigKey must not be null. input.getOutpoint()=" +
input.getOutpoint().toString());

View file

@ -102,6 +102,7 @@ import org.jetbrains.annotations.NotNull;
import javax.annotation.Nullable;
import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkNotNull;
import static com.google.common.base.Preconditions.checkState;
@ -278,6 +279,31 @@ public abstract class WalletService {
// Sign tx
///////////////////////////////////////////////////////////////////////////////////////////
public static void signTx(Wallet wallet,
KeyParameter aesKey,
Transaction tx)
throws WalletException, TransactionVerificationException {
for (int i = 0; i < tx.getInputs().size(); i++) {
TransactionInput input = tx.getInput(i);
TransactionOutput connectedOutput = input.getConnectedOutput();
if (connectedOutput == null) {
log.error("connectedOutput is null");
continue;
}
if (!connectedOutput.isMine(wallet)) {
log.error("connectedOutput is not mine");
continue;
}
signTransactionInput(wallet, aesKey, tx, input, i);
checkScriptSig(tx, input, i);
}
checkWalletConsistency(wallet);
verifyTransaction(tx);
printTx("Signed Tx", tx);
}
public static void signTransactionInput(Wallet wallet,
KeyParameter aesKey,
Transaction tx,
@ -357,12 +383,13 @@ public abstract class WalletService {
txIn.setScriptSig(ScriptBuilder.createEmpty());
txIn.setWitness(TransactionWitness.redeemP2WPKH(txSig, key));
} catch (ECKey.KeyIsEncryptedException e1) {
log.error(e1.toString());
throw e1;
} catch (ECKey.MissingPrivateKeyException e1) {
log.warn("No private key in keypair for input {}", index);
}
} else {
// log.error("Unexpected script type.");
log.error("Unexpected script type.");
throw new RuntimeException("Unexpected script type.");
}
} else {
@ -374,6 +401,23 @@ public abstract class WalletService {
}
///////////////////////////////////////////////////////////////////////////////////////////
// Dust
///////////////////////////////////////////////////////////////////////////////////////////
public static void verifyNonDustTxo(Transaction tx) {
for (TransactionOutput txo : tx.getOutputs()) {
Coin value = txo.getValue();
// OpReturn outputs have value 0
if (value.isPositive()) {
checkArgument(Restrictions.isAboveDust(txo.getValue()),
"An output value is below dust limit. Transaction=" + tx);
}
}
}
///////////////////////////////////////////////////////////////////////////////////////////
// Broadcast tx
///////////////////////////////////////////////////////////////////////////////////////////
@ -482,7 +526,7 @@ public abstract class WalletService {
// Balance
///////////////////////////////////////////////////////////////////////////////////////////
public Coin getAvailableConfirmedBalance() {
public Coin getAvailableBalance() {
return wallet != null ? wallet.getBalance(Wallet.BalanceType.AVAILABLE) : Coin.ZERO;
}
@ -546,6 +590,10 @@ public abstract class WalletService {
return getNumTxOutputsForAddress(address) == 0;
}
public boolean isMine(TransactionOutput transactionOutput) {
return transactionOutput.isMine(wallet);
}
// BISQ issue #4039: Prevent dust outputs from being created.
// Check the outputs of a proposed transaction. If any are below the dust threshold,
// add up the dust, log the details, and return the cumulative dust amount.
@ -818,11 +866,15 @@ public abstract class WalletService {
return maybeAddTxToWallet(transaction.bitcoinSerialize(), wallet, source);
}
///////////////////////////////////////////////////////////////////////////////////////////
// bisqWalletEventListener
///////////////////////////////////////////////////////////////////////////////////////////
public class BisqWalletListener implements WalletCoinsReceivedEventListener, WalletCoinsSentEventListener, WalletReorganizeEventListener, TransactionConfidenceEventListener {
public class BisqWalletListener implements WalletCoinsReceivedEventListener,
WalletCoinsSentEventListener,
WalletReorganizeEventListener,
TransactionConfidenceEventListener {
@Override
public void onCoinsReceived(Wallet wallet, Transaction tx, Coin prevBalance, Coin newBalance) {
notifyBalanceListeners(tx);
@ -848,7 +900,7 @@ public abstract class WalletService {
.filter(txConfidenceListener -> tx != null &&
tx.getTxId().toString() != null &&
txConfidenceListener != null &&
tx.getTxId().toString().equals(txConfidenceListener.getTxID()))
tx.getTxId().toString().equals(txConfidenceListener.getTxId()))
.forEach(txConfidenceListener ->
txConfidenceListener.onTransactionConfidenceChanged(tx.getConfidence()));
}
@ -859,7 +911,7 @@ public abstract class WalletService {
if (balanceListener.getAddress() != null)
balance = getBalanceForAddress(balanceListener.getAddress());
else
balance = getAvailableConfirmedBalance();
balance = getAvailableBalance();
balanceListener.onBalanceChanged(balance, tx);
}

View file

@ -60,6 +60,7 @@ import bisq.core.dao.state.model.blockchain.BaseTxOutput;
import bisq.core.dao.state.model.blockchain.Block;
import bisq.core.dao.state.model.blockchain.Tx;
import bisq.core.dao.state.model.blockchain.TxOutput;
import bisq.core.dao.state.model.blockchain.TxOutputKey;
import bisq.core.dao.state.model.blockchain.TxType;
import bisq.core.dao.state.model.governance.Ballot;
import bisq.core.dao.state.model.governance.BondedRoleType;
@ -649,6 +650,14 @@ public class DaoFacade implements DaoSetupService {
return daoStateService.getUnspentTxOutputs();
}
public boolean isTxOutputSpendable(TxOutputKey txOutputKey) {
return daoStateService.isTxOutputSpendable(txOutputKey);
}
public long getUnspentTxOutputValue(TxOutputKey key) {
return daoStateService.getUnspentTxOutputValue(key);
}
public int getNumTxs() {
return daoStateService.getNumTxs();
}
@ -796,4 +805,8 @@ public class DaoFacade implements DaoSetupService {
return allPastParamValues;
}
public boolean isParseBlockChainComplete() {
return daoStateService.isParseBlockChainComplete();
}
}

View file

@ -205,7 +205,7 @@ public class AssetService implements DaoSetupService, DaoStateListener {
// We add the BTC inputs for the miner fee.
Transaction txWithBtcFee = btcWalletService.completePreparedBurnBsqTx(preparedBurnFeeTx, opReturnData);
// We sign the BSQ inputs of the final tx.
Transaction transaction = bsqWalletService.signTx(txWithBtcFee);
Transaction transaction = bsqWalletService.signTxAndVerifyNoDustOutputs(txWithBtcFee);
log.info("Asset listing fee tx: " + transaction);
return transaction;
} catch (WalletException | TransactionVerificationException e) {

View file

@ -356,7 +356,7 @@ public class MyBlindVoteListService implements PersistedDataHost, DaoStateListen
throws InsufficientMoneyException, WalletException, TransactionVerificationException {
Transaction preparedTx = bsqWalletService.getPreparedBlindVoteTx(fee, stake);
Transaction txWithBtcFee = btcWalletService.completePreparedBlindVoteTx(preparedTx, opReturnData);
return bsqWalletService.signTx(txWithBtcFee);
return bsqWalletService.signTxAndVerifyNoDustOutputs(txWithBtcFee);
}
private void maybeRePublishMyBlindVote() {

View file

@ -104,7 +104,7 @@ public class LockupTxService {
byte[] opReturnData = BondConsensus.getLockupOpReturnData(lockTime, lockupReason, hash);
Transaction preparedTx = bsqWalletService.getPreparedLockupTx(lockupAmount);
Transaction txWithBtcFee = btcWalletService.completePreparedBsqTx(preparedTx, opReturnData);
Transaction transaction = bsqWalletService.signTx(txWithBtcFee);
Transaction transaction = bsqWalletService.signTxAndVerifyNoDustOutputs(txWithBtcFee);
log.info("Lockup tx: " + transaction);
return transaction;
}

View file

@ -104,7 +104,7 @@ public class UnlockTxService {
TxOutput lockupTxOutput = optionalLockupTxOutput.get();
Transaction preparedTx = bsqWalletService.getPreparedUnlockTx(lockupTxOutput);
Transaction txWithBtcFee = btcWalletService.completePreparedBsqTx(preparedTx, null);
Transaction transaction = bsqWalletService.signTx(txWithBtcFee);
Transaction transaction = bsqWalletService.signTxAndVerifyNoDustOutputs(txWithBtcFee);
log.info("Unlock tx: " + transaction);
return transaction;
}

View file

@ -140,7 +140,7 @@ public class ProofOfBurnService implements DaoSetupService, DaoStateListener {
// We add the BTC inputs for the miner fee.
Transaction txWithBtcFee = btcWalletService.completePreparedBurnBsqTx(preparedBurnFeeTx, opReturnData);
// We sign the BSQ inputs of the final tx.
Transaction transaction = bsqWalletService.signTx(txWithBtcFee);
Transaction transaction = bsqWalletService.signTxAndVerifyNoDustOutputs(txWithBtcFee);
log.info("Proof of burn tx: " + transaction);
return transaction;
} catch (WalletException | TransactionVerificationException e) {

View file

@ -99,7 +99,7 @@ public abstract class BaseProposalFactory<R extends Proposal> {
Transaction txWithBtcFee = completeTx(preparedBurnFeeTx, opReturnData, proposal);
// We sign the BSQ inputs of the final tx.
Transaction transaction = bsqWalletService.signTx(txWithBtcFee);
Transaction transaction = bsqWalletService.signTxAndVerifyNoDustOutputs(txWithBtcFee);
log.info("Proposal tx: " + transaction);
return transaction;
} catch (WalletException | TransactionVerificationException e) {

View file

@ -270,6 +270,6 @@ public class VoteRevealService implements DaoStateListener, DaoSetupService {
throws InsufficientMoneyException, WalletException, TransactionVerificationException {
Transaction preparedTx = bsqWalletService.getPreparedVoteRevealTx(stakeTxOutput);
Transaction txWithBtcFee = btcWalletService.completePreparedVoteRevealTx(preparedTx, opReturnData);
return bsqWalletService.signTx(txWithBtcFee);
return bsqWalletService.signTxAndVerifyNoDustOutputs(txWithBtcFee);
}
}

View file

@ -25,6 +25,7 @@ import bisq.core.dao.state.model.blockchain.PubKeyScript;
import bisq.core.dao.state.model.blockchain.Tx;
import bisq.core.dao.state.model.blockchain.TxOutput;
import bisq.core.dao.state.model.blockchain.TxType;
import bisq.core.util.JsonUtil;
import bisq.common.config.Config;
import bisq.common.file.FileUtil;
@ -154,9 +155,9 @@ public class ExportJsonFilesService implements DaoSetupService {
JsonBlocks jsonBlocks = new JsonBlocks(daoState.getChainHeight(), jsonBlockList);
ListenableFuture<Void> future = executor.submit(() -> {
bsqStateFileManager.writeToDisc(Utilities.objectToJson(jsonBlocks), "blocks");
allJsonTxOutputs.forEach(jsonTxOutput -> txOutputFileManager.writeToDisc(Utilities.objectToJson(jsonTxOutput), jsonTxOutput.getId()));
jsonTxs.forEach(jsonTx -> txFileManager.writeToDisc(Utilities.objectToJson(jsonTx), jsonTx.getId()));
bsqStateFileManager.writeToDisc(JsonUtil.objectToJson(jsonBlocks), "blocks");
allJsonTxOutputs.forEach(jsonTxOutput -> txOutputFileManager.writeToDisc(JsonUtil.objectToJson(jsonTxOutput), jsonTxOutput.getId()));
jsonTxs.forEach(jsonTx -> txFileManager.writeToDisc(JsonUtil.objectToJson(jsonTx), jsonTx.getId()));
GcUtil.maybeReleaseMemory();

View file

@ -21,6 +21,7 @@ import bisq.core.dao.DaoSetupService;
import bisq.core.dao.governance.bond.BondConsensus;
import bisq.core.dao.governance.param.Param;
import bisq.core.dao.state.model.DaoState;
import bisq.core.dao.state.model.blockchain.BaseTxOutput;
import bisq.core.dao.state.model.blockchain.Block;
import bisq.core.dao.state.model.blockchain.SpentInfo;
import bisq.core.dao.state.model.blockchain.Tx;
@ -484,6 +485,12 @@ public class DaoStateService implements DaoSetupService {
return Optional.ofNullable(getUnspentTxOutputMap().getOrDefault(key, null));
}
public long getUnspentTxOutputValue(TxOutputKey key) {
return getUnspentTxOutput(key)
.map(BaseTxOutput::getValue)
.orElse(0L);
}
public boolean isTxOutputSpendable(TxOutputKey key) {
if (!isUnspent(key))
return false;
@ -492,7 +499,12 @@ public class DaoStateService implements DaoSetupService {
// The above isUnspent call satisfies optionalTxOutput.isPresent()
checkArgument(optionalTxOutput.isPresent(), "optionalTxOutput must be present");
TxOutput txOutput = optionalTxOutput.get();
return isTxOutputSpendable(txOutput);
}
public boolean isTxOutputSpendable(TxOutput txOutput) {
// OP_RETURN_OUTPUTs are actually not spendable but as we have no value on them
// they would not be used anyway.
switch (txOutput.getTxOutputType()) {
case UNDEFINED_OUTPUT:
return false;

View file

@ -39,13 +39,15 @@ import java.util.Set;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
import lombok.Value;
import lombok.EqualsAndHashCode;
import lombok.Getter;
import lombok.extern.slf4j.Slf4j;
import javax.annotation.Nullable;
@Slf4j
@Value
@Getter
@EqualsAndHashCode
public final class Filter implements ProtectedStoragePayload, ExpirablePayload {
public static final long TTL = TimeUnit.DAYS.toMillis(180);
@ -101,6 +103,12 @@ public final class Filter implements ProtectedStoragePayload, ExpirablePayload {
// added at v1.6.0
private final boolean disableMempoolValidation;
// added at BsqSwap release
private final boolean disablePowMessage;
// Number of leading zeros for pow for BSQ swap offers. Difficulty of 8 requires 0.856 ms in average, 15 about 100 ms.
// See ProofOfWorkTest for more info.
private final int powDifficulty;
// After we have created the signature from the filter data we clone it and apply the signature
static Filter cloneWithSig(Filter filter, String signatureAsBase64) {
return new Filter(filter.getBannedOfferIds(),
@ -130,7 +138,9 @@ public final class Filter implements ProtectedStoragePayload, ExpirablePayload {
filter.getBannedAutoConfExplorers(),
filter.getNodeAddressesBannedFromNetwork(),
filter.isDisableMempoolValidation(),
filter.isDisableApi());
filter.isDisableApi(),
filter.isDisablePowMessage(),
filter.getPowDifficulty());
}
// Used for signature verification as we created the sig without the signatureAsBase64 field we set it to null again
@ -162,7 +172,9 @@ public final class Filter implements ProtectedStoragePayload, ExpirablePayload {
filter.getBannedAutoConfExplorers(),
filter.getNodeAddressesBannedFromNetwork(),
filter.isDisableMempoolValidation(),
filter.isDisableApi());
filter.isDisableApi(),
filter.isDisablePowMessage(),
filter.getPowDifficulty());
}
public Filter(List<String> bannedOfferIds,
@ -189,7 +201,9 @@ public final class Filter implements ProtectedStoragePayload, ExpirablePayload {
List<String> bannedAutoConfExplorers,
Set<String> nodeAddressesBannedFromNetwork,
boolean disableMempoolValidation,
boolean disableApi) {
boolean disableApi,
boolean disablePowMessage,
int powDifficulty) {
this(bannedOfferIds,
nodeAddressesBannedFromTrading,
bannedPaymentAccounts,
@ -217,7 +231,9 @@ public final class Filter implements ProtectedStoragePayload, ExpirablePayload {
bannedAutoConfExplorers,
nodeAddressesBannedFromNetwork,
disableMempoolValidation,
disableApi);
disableApi,
disablePowMessage,
powDifficulty);
}
@ -253,7 +269,9 @@ public final class Filter implements ProtectedStoragePayload, ExpirablePayload {
List<String> bannedAutoConfExplorers,
Set<String> nodeAddressesBannedFromNetwork,
boolean disableMempoolValidation,
boolean disableApi) {
boolean disableApi,
boolean disablePowMessage,
int powDifficulty) {
this.bannedOfferIds = bannedOfferIds;
this.nodeAddressesBannedFromTrading = nodeAddressesBannedFromTrading;
this.bannedPaymentAccounts = bannedPaymentAccounts;
@ -282,6 +300,8 @@ public final class Filter implements ProtectedStoragePayload, ExpirablePayload {
this.nodeAddressesBannedFromNetwork = nodeAddressesBannedFromNetwork;
this.disableMempoolValidation = disableMempoolValidation;
this.disableApi = disableApi;
this.disablePowMessage = disablePowMessage;
this.powDifficulty = powDifficulty;
// ownerPubKeyBytes can be null when called from tests
if (ownerPubKeyBytes != null) {
@ -322,7 +342,9 @@ public final class Filter implements ProtectedStoragePayload, ExpirablePayload {
.addAllBannedAutoConfExplorers(bannedAutoConfExplorers)
.addAllNodeAddressesBannedFromNetwork(nodeAddressesBannedFromNetwork)
.setDisableMempoolValidation(disableMempoolValidation)
.setDisableApi(disableApi);
.setDisableApi(disableApi)
.setDisablePowMessage(disablePowMessage)
.setPowDifficulty(powDifficulty);
Optional.ofNullable(signatureAsBase64).ifPresent(builder::setSignatureAsBase64);
Optional.ofNullable(extraDataMap).ifPresent(builder::putAllExtraData);
@ -363,7 +385,9 @@ public final class Filter implements ProtectedStoragePayload, ExpirablePayload {
ProtoUtil.protocolStringListToList(proto.getBannedAutoConfExplorersList()),
ProtoUtil.protocolStringListToSet(proto.getNodeAddressesBannedFromNetworkList()),
proto.getDisableMempoolValidation(),
proto.getDisableApi()
proto.getDisableApi(),
proto.getDisablePowMessage(),
proto.getPowDifficulty()
);
}
@ -409,6 +433,8 @@ public final class Filter implements ProtectedStoragePayload, ExpirablePayload {
",\n nodeAddressesBannedFromNetwork=" + nodeAddressesBannedFromNetwork +
",\n disableMempoolValidation=" + disableMempoolValidation +
",\n disableApi=" + disableApi +
",\n disablePowMessage=" + disablePowMessage +
",\n powDifficulty=" + powDifficulty +
"\n}";
}
}

View file

@ -19,6 +19,7 @@ package bisq.core.filter;
import bisq.core.btc.nodes.BtcNodes;
import bisq.core.locale.Res;
import bisq.core.offer.Offer;
import bisq.core.payment.payload.PaymentAccountPayload;
import bisq.core.payment.payload.PaymentMethod;
import bisq.core.provider.ProvidersRepository;
@ -36,6 +37,7 @@ import bisq.common.app.DevEnv;
import bisq.common.app.Version;
import bisq.common.config.Config;
import bisq.common.config.ConfigFileEditor;
import bisq.common.crypto.HashCashService;
import bisq.common.crypto.KeyRing;
import org.bitcoinj.core.ECKey;
@ -55,6 +57,7 @@ import java.nio.charset.StandardCharsets;
import java.math.BigInteger;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Date;
@ -62,6 +65,7 @@ import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.function.BiFunction;
import java.util.function.Consumer;
import java.lang.reflect.Method;
@ -70,6 +74,7 @@ import lombok.extern.slf4j.Slf4j;
import javax.annotation.Nullable;
import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkNotNull;
import static org.bitcoinj.core.Utils.HEX;
@ -82,6 +87,11 @@ public class FilterManager {
private static final String BANNED_SEED_NODES = "bannedSeedNodes";
private static final String BANNED_BTC_NODES = "bannedBtcNodes";
private final BiFunction<byte[], byte[], Boolean> challengeValidation = Arrays::equals;
// We only require a new pow if difficulty has increased
private final BiFunction<Integer, Integer, Boolean> difficultyValidation =
(value, controlValue) -> value - controlValue >= 0;
///////////////////////////////////////////////////////////////////////////////////////////
// Listener
@ -476,6 +486,20 @@ public class FilterManager {
.anyMatch(e -> e.equals(witnessSignerPubKeyAsHex));
}
public boolean isProofOfWorkValid(Offer offer) {
Filter filter = getFilter();
if (filter == null) {
return true;
}
checkArgument(offer.getBsqSwapOfferPayload().isPresent(),
"Offer payload must be BsqSwapOfferPayload");
return HashCashService.verify(offer.getBsqSwapOfferPayload().get().getProofOfWork(),
HashCashService.getBytes(offer.getId() + offer.getOwnerNodeAddress().toString()),
filter.getPowDifficulty(),
challengeValidation,
difficultyValidation);
}
///////////////////////////////////////////////////////////////////////////////////////////
// Private
@ -499,13 +523,13 @@ public class FilterManager {
if (currentFilter != null) {
if (currentFilter.getCreationDate() > newFilter.getCreationDate()) {
log.warn("We received a new filter from the network but the creation date is older than the " +
log.debug("We received a new filter from the network but the creation date is older than the " +
"filter we have already. We ignore the new filter.");
addToInvalidFilters(newFilter);
return;
} else {
log.warn("We received a new filter from the network and the creation date is newer than the " +
log.debug("We received a new filter from the network and the creation date is newer than the " +
"filter we have already. We ignore the old filter.");
addToInvalidFilters(currentFilter);
}

View file

@ -120,13 +120,13 @@ public class Res {
.replace("bitcoin", baseCurrencyNameLowerCase);
} catch (MissingResourceException e) {
log.warn("Missing resource for key: {}", key);
e.printStackTrace();
if (DevEnv.isDevMode())
if (DevEnv.isDevMode()) {
e.printStackTrace();
UserThread.runAfter(() -> {
// We delay a bit to not throw while UI is not ready
throw new RuntimeException("Missing resource for key: " + key);
}, 1);
}
return key;
}
}

View file

@ -21,8 +21,8 @@ import bisq.core.locale.Res;
import bisq.core.notifications.MobileMessage;
import bisq.core.notifications.MobileMessageType;
import bisq.core.notifications.MobileNotificationService;
import bisq.core.trade.Trade;
import bisq.core.trade.TradeManager;
import bisq.core.trade.model.bisq_v1.Trade;
import bisq.common.crypto.KeyRing;
import bisq.common.crypto.PubKeyRing;

View file

@ -26,7 +26,7 @@ import bisq.core.notifications.MobileMessageType;
import bisq.core.notifications.MobileNotificationService;
import bisq.core.offer.Offer;
import bisq.core.offer.OfferBookService;
import bisq.core.offer.OfferPayload;
import bisq.core.offer.OfferDirection;
import bisq.core.provider.price.MarketPrice;
import bisq.core.provider.price.PriceFeedService;
import bisq.core.user.User;
@ -109,7 +109,7 @@ public class MarketAlerts {
// % price get multiplied by 10000 to have 0.12% be converted to 12. For fixed price we have precision of 8 for
// altcoins and precision of 4 for fiat.
private String getAlertId(Offer offer) {
double price = offer.isUseMarketBasedPrice() ? offer.getMarketPriceMargin() * 10000 : offer.getOfferPayload().getPrice();
double price = offer.isUseMarketBasedPrice() ? offer.getMarketPriceMargin() * 10000 : offer.getFixedPrice();
String priceString = String.valueOf((long) price);
return offer.getId() + "|" + priceString;
}
@ -119,7 +119,7 @@ public class MarketAlerts {
MarketPrice marketPrice = priceFeedService.getMarketPrice(currencyCode);
Price offerPrice = offer.getPrice();
if (marketPrice != null && offerPrice != null) {
boolean isSellOffer = offer.getDirection() == OfferPayload.Direction.SELL;
boolean isSellOffer = offer.getDirection() == OfferDirection.SELL;
String shortOfferId = offer.getShortId();
boolean isFiatCurrency = CurrencyUtil.isFiatCurrency(currencyCode);
String alertId = getAlertId(offer);

View file

@ -24,6 +24,9 @@ import bisq.core.monetary.Price;
import bisq.core.monetary.Volume;
import bisq.core.offer.availability.OfferAvailabilityModel;
import bisq.core.offer.availability.OfferAvailabilityProtocol;
import bisq.core.offer.bisq_v1.MarketPriceNotAvailableException;
import bisq.core.offer.bisq_v1.OfferPayload;
import bisq.core.offer.bsq_swap.BsqSwapOfferPayload;
import bisq.core.payment.payload.PaymentMethod;
import bisq.core.provider.price.MarketPrice;
import bisq.core.provider.price.PriceFeedService;
@ -75,7 +78,6 @@ public class Offer implements NetworkPayload, PersistablePayload {
// from one provider.
private final static double PRICE_TOLERANCE = 0.01;
///////////////////////////////////////////////////////////////////////////////////////////
// Enums
///////////////////////////////////////////////////////////////////////////////////////////
@ -94,7 +96,7 @@ public class Offer implements NetworkPayload, PersistablePayload {
///////////////////////////////////////////////////////////////////////////////////////////
@Getter
private final OfferPayload offerPayload;
private final OfferPayloadBase offerPayloadBase;
@JsonExclude
@Getter
final transient private ObjectProperty<Offer.State> stateProperty = new SimpleObjectProperty<>(Offer.State.UNKNOWN);
@ -119,8 +121,8 @@ public class Offer implements NetworkPayload, PersistablePayload {
// Constructor
///////////////////////////////////////////////////////////////////////////////////////////
public Offer(OfferPayload offerPayload) {
this.offerPayload = offerPayload;
public Offer(OfferPayloadBase offerPayloadBase) {
this.offerPayloadBase = offerPayloadBase;
}
@ -130,11 +132,21 @@ public class Offer implements NetworkPayload, PersistablePayload {
@Override
public protobuf.Offer toProtoMessage() {
return protobuf.Offer.newBuilder().setOfferPayload(offerPayload.toProtoMessage().getOfferPayload()).build();
if (isBsqSwapOffer()) {
return protobuf.Offer.newBuilder().setBsqSwapOfferPayload(((BsqSwapOfferPayload) offerPayloadBase)
.toProtoMessage().getBsqSwapOfferPayload()).build();
} else {
return protobuf.Offer.newBuilder().setOfferPayload(((OfferPayload) offerPayloadBase)
.toProtoMessage().getOfferPayload()).build();
}
}
public static Offer fromProto(protobuf.Offer proto) {
return new Offer(OfferPayload.fromProto(proto.getOfferPayload()));
if (proto.hasOfferPayload()) {
return new Offer(OfferPayload.fromProto(proto.getOfferPayload()));
} else {
return new Offer(BsqSwapOfferPayload.fromProto(proto.getBsqSwapOfferPayload()));
}
}
@ -166,43 +178,53 @@ public class Offer implements NetworkPayload, PersistablePayload {
@Nullable
public Price getPrice() {
String currencyCode = getCurrencyCode();
if (offerPayload.isUseMarketBasedPrice()) {
checkNotNull(priceFeedService, "priceFeed must not be null");
MarketPrice marketPrice = priceFeedService.getMarketPrice(currencyCode);
if (marketPrice != null && marketPrice.isRecentExternalPriceAvailable()) {
double factor;
double marketPriceMargin = offerPayload.getMarketPriceMargin();
if (CurrencyUtil.isCryptoCurrency(currencyCode)) {
factor = getDirection() == OfferPayload.Direction.SELL ?
1 - marketPriceMargin : 1 + marketPriceMargin;
} else {
factor = getDirection() == OfferPayload.Direction.BUY ?
1 - marketPriceMargin : 1 + marketPriceMargin;
}
double marketPriceAsDouble = marketPrice.getPrice();
double targetPriceAsDouble = marketPriceAsDouble * factor;
try {
int precision = CurrencyUtil.isCryptoCurrency(currencyCode) ?
Altcoin.SMALLEST_UNIT_EXPONENT :
Fiat.SMALLEST_UNIT_EXPONENT;
double scaled = MathUtils.scaleUpByPowerOf10(targetPriceAsDouble, precision);
final long roundedToLong = MathUtils.roundDoubleToLong(scaled);
return Price.valueOf(currencyCode, roundedToLong);
} catch (Exception e) {
log.error("Exception at getPrice / parseToFiat: " + e.toString() + "\n" +
"That case should never happen.");
return null;
}
Optional<OfferPayload> optionalOfferPayload = getOfferPayload();
if (!optionalOfferPayload.isPresent()) {
return Price.valueOf(currencyCode, offerPayloadBase.getPrice());
}
OfferPayload offerPayload = optionalOfferPayload.get();
if (!offerPayload.isUseMarketBasedPrice()) {
return Price.valueOf(currencyCode, offerPayloadBase.getPrice());
}
checkNotNull(priceFeedService, "priceFeed must not be null");
MarketPrice marketPrice = priceFeedService.getMarketPrice(currencyCode);
if (marketPrice != null && marketPrice.isRecentExternalPriceAvailable()) {
double factor;
double marketPriceMargin = offerPayload.getMarketPriceMargin();
if (CurrencyUtil.isCryptoCurrency(currencyCode)) {
factor = getDirection() == OfferDirection.SELL ?
1 - marketPriceMargin : 1 + marketPriceMargin;
} else {
log.trace("We don't have a market price. " +
"That case could only happen if you don't have a price feed.");
factor = getDirection() == OfferDirection.BUY ?
1 - marketPriceMargin : 1 + marketPriceMargin;
}
double marketPriceAsDouble = marketPrice.getPrice();
double targetPriceAsDouble = marketPriceAsDouble * factor;
try {
int precision = CurrencyUtil.isCryptoCurrency(currencyCode) ?
Altcoin.SMALLEST_UNIT_EXPONENT :
Fiat.SMALLEST_UNIT_EXPONENT;
double scaled = MathUtils.scaleUpByPowerOf10(targetPriceAsDouble, precision);
final long roundedToLong = MathUtils.roundDoubleToLong(scaled);
return Price.valueOf(currencyCode, roundedToLong);
} catch (Exception e) {
log.error("Exception at getPrice / parseToFiat: " + e + "\n" +
"That case should never happen.");
return null;
}
} else {
return Price.valueOf(currencyCode, offerPayload.getPrice());
log.trace("We don't have a market price. " +
"That case could only happen if you don't have a price feed.");
return null;
}
}
public long getFixedPrice() {
return offerPayloadBase.getPrice();
}
public void checkTradePriceTolerance(long takersTradePrice) throws TradePriceOutOfToleranceException,
MarketPriceNotAvailableException, IllegalArgumentException {
Price tradePrice = Price.valueOf(getCurrencyCode(), takersTradePrice);
@ -234,17 +256,16 @@ public class Offer implements NetworkPayload, PersistablePayload {
@Nullable
public Volume getVolumeByAmount(Coin amount) {
Price price = getPrice();
if (price != null && amount != null) {
Volume volumeByAmount = price.getVolumeByAmount(amount);
if (offerPayload.getPaymentMethodId().equals(PaymentMethod.HAL_CASH_ID))
volumeByAmount = VolumeUtil.getAdjustedVolumeForHalCash(volumeByAmount);
else if (CurrencyUtil.isFiatCurrency(offerPayload.getCurrencyCode()))
volumeByAmount = VolumeUtil.getRoundedFiatVolume(volumeByAmount);
return volumeByAmount;
} else {
if (price == null || amount == null) {
return null;
}
Volume volumeByAmount = price.getVolumeByAmount(amount);
if (offerPayloadBase.getPaymentMethodId().equals(PaymentMethod.HAL_CASH_ID))
volumeByAmount = VolumeUtil.getAdjustedVolumeForHalCash(volumeByAmount);
else if (CurrencyUtil.isFiatCurrency(offerPayloadBase.getCurrencyCode()))
volumeByAmount = VolumeUtil.getRoundedFiatVolume(volumeByAmount);
return volumeByAmount;
}
public void resetState() {
@ -265,7 +286,7 @@ public class Offer implements NetworkPayload, PersistablePayload {
}
public void setOfferFeePaymentTxId(String offerFeePaymentTxID) {
offerPayload.setOfferFeePaymentTxId(offerFeePaymentTxID);
getOfferPayload().ifPresent(p -> p.setOfferFeePaymentTxId(offerFeePaymentTxID));
}
public void setErrorMessage(String errorMessage) {
@ -279,52 +300,52 @@ public class Offer implements NetworkPayload, PersistablePayload {
// converted payload properties
public Coin getTxFee() {
return Coin.valueOf(offerPayload.getTxFee());
return Coin.valueOf(getOfferPayload().map(OfferPayload::getTxFee).orElse(0L));
}
public Coin getMakerFee() {
return Coin.valueOf(offerPayload.getMakerFee());
return getOfferPayload().map(OfferPayload::getMakerFee).map(Coin::valueOf).orElse(Coin.ZERO);
}
public boolean isCurrencyForMakerFeeBtc() {
return offerPayload.isCurrencyForMakerFeeBtc();
return getOfferPayload().map(OfferPayload::isCurrencyForMakerFeeBtc).orElse(false);
}
public Coin getBuyerSecurityDeposit() {
return Coin.valueOf(offerPayload.getBuyerSecurityDeposit());
return Coin.valueOf(getOfferPayload().map(OfferPayload::getBuyerSecurityDeposit).orElse(0L));
}
public Coin getSellerSecurityDeposit() {
return Coin.valueOf(offerPayload.getSellerSecurityDeposit());
return Coin.valueOf(getOfferPayload().map(OfferPayload::getSellerSecurityDeposit).orElse(0L));
}
public Coin getMaxTradeLimit() {
return Coin.valueOf(offerPayload.getMaxTradeLimit());
return getOfferPayload().map(OfferPayload::getMaxTradeLimit).map(Coin::valueOf).orElse(Coin.ZERO);
}
public Coin getAmount() {
return Coin.valueOf(offerPayload.getAmount());
return Coin.valueOf(offerPayloadBase.getAmount());
}
public Coin getMinAmount() {
return Coin.valueOf(offerPayload.getMinAmount());
return Coin.valueOf(offerPayloadBase.getMinAmount());
}
public boolean isRange() {
return offerPayload.getAmount() != offerPayload.getMinAmount();
return offerPayloadBase.getAmount() != offerPayloadBase.getMinAmount();
}
public Date getDate() {
return new Date(offerPayload.getDate());
return new Date(offerPayloadBase.getDate());
}
public PaymentMethod getPaymentMethod() {
return PaymentMethod.getPaymentMethodById(offerPayload.getPaymentMethodId());
return PaymentMethod.getPaymentMethodById(offerPayloadBase.getPaymentMethodId());
}
// utils
public String getShortId() {
return Utilities.getShortId(offerPayload.getId());
return Utilities.getShortId(offerPayloadBase.getId());
}
@Nullable
@ -338,18 +359,17 @@ public class Offer implements NetworkPayload, PersistablePayload {
}
public boolean isBuyOffer() {
return getDirection() == OfferPayload.Direction.BUY;
return getDirection() == OfferDirection.BUY;
}
public OfferPayload.Direction getMirroredDirection() {
return getDirection() == OfferPayload.Direction.BUY ? OfferPayload.Direction.SELL : OfferPayload.Direction.BUY;
public OfferDirection getMirroredDirection() {
return getDirection() == OfferDirection.BUY ? OfferDirection.SELL : OfferDirection.BUY;
}
public boolean isMyOffer(KeyRing keyRing) {
return getPubKeyRing().equals(keyRing.getPubKeyRing());
}
public Optional<String> getAccountAgeWitnessHashAsHex() {
Map<String, String> extraDataMap = getExtraDataMap();
if (extraDataMap != null && extraDataMap.containsKey(OfferPayload.ACCOUNT_AGE_WITNESS_HASH))
@ -400,32 +420,32 @@ public class Offer implements NetworkPayload, PersistablePayload {
// Delegate Getter (boilerplate code generated via IntelliJ generate delegate feature)
///////////////////////////////////////////////////////////////////////////////////////////
public OfferPayload.Direction getDirection() {
return offerPayload.getDirection();
public OfferDirection getDirection() {
return offerPayloadBase.getDirection();
}
public String getId() {
return offerPayload.getId();
return offerPayloadBase.getId();
}
@Nullable
public List<String> getAcceptedBankIds() {
return offerPayload.getAcceptedBankIds();
return getOfferPayload().map(OfferPayload::getAcceptedBankIds).orElse(null);
}
@Nullable
public String getBankId() {
return offerPayload.getBankId();
return getOfferPayload().map(OfferPayload::getBankId).orElse(null);
}
@Nullable
public List<String> getAcceptedCountryCodes() {
return offerPayload.getAcceptedCountryCodes();
return getOfferPayload().map(OfferPayload::getAcceptedCountryCodes).orElse(null);
}
@Nullable
public String getCountryCode() {
return offerPayload.getCountryCode();
return getOfferPayload().map(OfferPayload::getCountryCode).orElse(null);
}
public String getCurrencyCode() {
@ -433,89 +453,84 @@ public class Offer implements NetworkPayload, PersistablePayload {
return currencyCode;
}
currencyCode = offerPayload.getBaseCurrencyCode().equals("BTC") ?
offerPayload.getCounterCurrencyCode() :
offerPayload.getBaseCurrencyCode();
currencyCode = getBaseCurrencyCode().equals("BTC") ?
getCounterCurrencyCode() :
getBaseCurrencyCode();
return currencyCode;
}
public String getCounterCurrencyCode() {
return offerPayloadBase.getCounterCurrencyCode();
}
public String getBaseCurrencyCode() {
return offerPayloadBase.getBaseCurrencyCode();
}
public String getPaymentMethodId() {
return offerPayloadBase.getPaymentMethodId();
}
public long getProtocolVersion() {
return offerPayload.getProtocolVersion();
return offerPayloadBase.getProtocolVersion();
}
public boolean isUseMarketBasedPrice() {
return offerPayload.isUseMarketBasedPrice();
return getOfferPayload().map(OfferPayload::isUseMarketBasedPrice).orElse(false);
}
public double getMarketPriceMargin() {
return offerPayload.getMarketPriceMargin();
return getOfferPayload().map(OfferPayload::getMarketPriceMargin).orElse(0D);
}
public NodeAddress getMakerNodeAddress() {
return offerPayload.getOwnerNodeAddress();
return offerPayloadBase.getOwnerNodeAddress();
}
public PubKeyRing getPubKeyRing() {
return offerPayload.getPubKeyRing();
return offerPayloadBase.getPubKeyRing();
}
public String getMakerPaymentAccountId() {
return offerPayload.getMakerPaymentAccountId();
return offerPayloadBase.getMakerPaymentAccountId();
}
public String getOfferFeePaymentTxId() {
return offerPayload.getOfferFeePaymentTxId();
return getOfferPayload().map(OfferPayload::getOfferFeePaymentTxId).orElse(null);
}
public String getVersionNr() {
return offerPayload.getVersionNr();
return offerPayloadBase.getVersionNr();
}
public long getMaxTradePeriod() {
return offerPayload.getMaxTradePeriod();
return getOfferPayload().map(OfferPayload::getMaxTradePeriod).orElse(0L);
}
public NodeAddress getOwnerNodeAddress() {
return offerPayload.getOwnerNodeAddress();
return offerPayloadBase.getOwnerNodeAddress();
}
// Yet unused
public PublicKey getOwnerPubKey() {
return offerPayload.getOwnerPubKey();
return offerPayloadBase.getOwnerPubKey();
}
@Nullable
public Map<String, String> getExtraDataMap() {
return offerPayload.getExtraDataMap();
return offerPayloadBase.getExtraDataMap();
}
public boolean isUseAutoClose() {
return offerPayload.isUseAutoClose();
}
public long getBlockHeightAtOfferCreation() {
return offerPayload.getBlockHeightAtOfferCreation();
}
@Nullable
public String getHashOfChallenge() {
return offerPayload.getHashOfChallenge();
}
public boolean isPrivateOffer() {
return offerPayload.isPrivateOffer();
}
public long getUpperClosePrice() {
return offerPayload.getUpperClosePrice();
}
public long getLowerClosePrice() {
return offerPayload.getLowerClosePrice();
return getOfferPayload().map(OfferPayload::isUseAutoClose).orElse(false);
}
public boolean isUseReOpenAfterAutoClose() {
return offerPayload.isUseReOpenAfterAutoClose();
return getOfferPayload().map(OfferPayload::isUseReOpenAfterAutoClose).orElse(false);
}
public boolean isBsqSwapOffer() {
return getOfferPayloadBase() instanceof BsqSwapOfferPayload;
}
public boolean isXmrAutoConf() {
@ -533,6 +548,24 @@ public class Offer implements NetworkPayload, PersistablePayload {
return getCurrencyCode().equals("XMR");
}
public Optional<OfferPayload> getOfferPayload() {
if (offerPayloadBase instanceof OfferPayload) {
return Optional.of((OfferPayload) offerPayloadBase);
}
return Optional.empty();
}
public Optional<BsqSwapOfferPayload> getBsqSwapOfferPayload() {
if (offerPayloadBase instanceof BsqSwapOfferPayload) {
return Optional.of((BsqSwapOfferPayload) offerPayloadBase);
}
return Optional.empty();
}
public byte[] getOfferPayloadHash() {
return offerPayloadBase.getHash();
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
@ -540,7 +573,8 @@ public class Offer implements NetworkPayload, PersistablePayload {
Offer offer = (Offer) o;
if (offerPayload != null ? !offerPayload.equals(offer.offerPayload) : offer.offerPayload != null) return false;
if (offerPayloadBase != null ? !offerPayloadBase.equals(offer.offerPayloadBase) : offer.offerPayloadBase != null)
return false;
//noinspection SimplifiableIfStatement
if (getState() != offer.getState()) return false;
return !(getErrorMessage() != null ? !getErrorMessage().equals(offer.getErrorMessage()) : offer.getErrorMessage() != null);
@ -549,7 +583,7 @@ public class Offer implements NetworkPayload, PersistablePayload {
@Override
public int hashCode() {
int result = offerPayload != null ? offerPayload.hashCode() : 0;
int result = offerPayloadBase != null ? offerPayloadBase.hashCode() : 0;
result = 31 * result + (getState() != null ? getState().hashCode() : 0);
result = 31 * result + (getErrorMessage() != null ? getErrorMessage().hashCode() : 0);
return result;
@ -560,7 +594,7 @@ public class Offer implements NetworkPayload, PersistablePayload {
return "Offer{" +
"getErrorMessage()='" + getErrorMessage() + '\'' +
", state=" + getState() +
", offerPayload=" + offerPayload +
", offerPayloadBase=" + offerPayloadBase +
'}';
}
}

View file

@ -20,6 +20,7 @@ package bisq.core.offer;
import bisq.core.filter.FilterManager;
import bisq.core.locale.Res;
import bisq.core.provider.price.PriceFeedService;
import bisq.core.util.JsonUtil;
import bisq.network.p2p.BootstrapListener;
import bisq.network.p2p.P2PService;
@ -31,7 +32,6 @@ import bisq.common.config.Config;
import bisq.common.file.JsonFileManager;
import bisq.common.handlers.ErrorMessageHandler;
import bisq.common.handlers.ResultHandler;
import bisq.common.util.Utilities;
import javax.inject.Inject;
import javax.inject.Named;
@ -87,9 +87,9 @@ public class OfferBookService {
@Override
public void onAdded(Collection<ProtectedStorageEntry> protectedStorageEntries) {
protectedStorageEntries.forEach(protectedStorageEntry -> offerBookChangedListeners.forEach(listener -> {
if (protectedStorageEntry.getProtectedStoragePayload() instanceof OfferPayload) {
OfferPayload offerPayload = (OfferPayload) protectedStorageEntry.getProtectedStoragePayload();
Offer offer = new Offer(offerPayload);
if (protectedStorageEntry.getProtectedStoragePayload() instanceof OfferPayloadBase) {
OfferPayloadBase offerPayloadBase = (OfferPayloadBase) protectedStorageEntry.getProtectedStoragePayload();
Offer offer = new Offer(offerPayloadBase);
offer.setPriceFeedService(priceFeedService);
listener.onAdded(offer);
}
@ -99,9 +99,9 @@ public class OfferBookService {
@Override
public void onRemoved(Collection<ProtectedStorageEntry> protectedStorageEntries) {
protectedStorageEntries.forEach(protectedStorageEntry -> offerBookChangedListeners.forEach(listener -> {
if (protectedStorageEntry.getProtectedStoragePayload() instanceof OfferPayload) {
OfferPayload offerPayload = (OfferPayload) protectedStorageEntry.getProtectedStoragePayload();
Offer offer = new Offer(offerPayload);
if (protectedStorageEntry.getProtectedStoragePayload() instanceof OfferPayloadBase) {
OfferPayloadBase offerPayloadBase = (OfferPayloadBase) protectedStorageEntry.getProtectedStoragePayload();
Offer offer = new Offer(offerPayloadBase);
offer.setPriceFeedService(priceFeedService);
listener.onRemoved(offer);
}
@ -141,7 +141,7 @@ public class OfferBookService {
return;
}
boolean result = p2PService.addProtectedStorageEntry(offer.getOfferPayload());
boolean result = p2PService.addProtectedStorageEntry(offer.getOfferPayloadBase());
if (result) {
resultHandler.handleResult();
} else {
@ -149,7 +149,7 @@ public class OfferBookService {
}
}
public void refreshTTL(OfferPayload offerPayload,
public void refreshTTL(OfferPayloadBase offerPayloadBase,
ResultHandler resultHandler,
ErrorMessageHandler errorMessageHandler) {
if (filterManager.requireUpdateToNewVersionForTrading()) {
@ -157,7 +157,7 @@ public class OfferBookService {
return;
}
boolean result = p2PService.refreshTTL(offerPayload);
boolean result = p2PService.refreshTTL(offerPayloadBase);
if (result) {
resultHandler.handleResult();
} else {
@ -171,16 +171,16 @@ public class OfferBookService {
addOffer(offer, resultHandler, errorMessageHandler);
}
public void deactivateOffer(OfferPayload offerPayload,
public void deactivateOffer(OfferPayloadBase offerPayloadBase,
@Nullable ResultHandler resultHandler,
@Nullable ErrorMessageHandler errorMessageHandler) {
removeOffer(offerPayload, resultHandler, errorMessageHandler);
removeOffer(offerPayloadBase, resultHandler, errorMessageHandler);
}
public void removeOffer(OfferPayload offerPayload,
public void removeOffer(OfferPayloadBase offerPayloadBase,
@Nullable ResultHandler resultHandler,
@Nullable ErrorMessageHandler errorMessageHandler) {
if (p2PService.removeData(offerPayload)) {
if (p2PService.removeData(offerPayloadBase)) {
if (resultHandler != null)
resultHandler.handleResult();
} else {
@ -191,18 +191,18 @@ public class OfferBookService {
public List<Offer> getOffers() {
return p2PService.getDataMap().values().stream()
.filter(data -> data.getProtectedStoragePayload() instanceof OfferPayload)
.filter(data -> data.getProtectedStoragePayload() instanceof OfferPayloadBase)
.map(data -> {
OfferPayload offerPayload = (OfferPayload) data.getProtectedStoragePayload();
Offer offer = new Offer(offerPayload);
OfferPayloadBase offerPayloadBase = (OfferPayloadBase) data.getProtectedStoragePayload();
Offer offer = new Offer(offerPayloadBase);
offer.setPriceFeedService(priceFeedService);
return offer;
})
.collect(Collectors.toList());
}
public void removeOfferAtShutDown(OfferPayload offerPayload) {
removeOffer(offerPayload, null, null);
public void removeOfferAtShutDown(OfferPayloadBase offerPayloadBase) {
removeOffer(offerPayloadBase, null, null);
}
public boolean isBootstrapped() {
@ -243,6 +243,6 @@ public class OfferBookService {
})
.filter(Objects::nonNull)
.collect(Collectors.toList());
jsonFileManager.writeToDiscThreaded(Utilities.objectToJson(offerForJsonList), "offers_statistics");
jsonFileManager.writeToDiscThreaded(JsonUtil.objectToJson(offerForJsonList), "offers_statistics");
}
}

View file

@ -0,0 +1,33 @@
/*
* 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.offer;
import bisq.common.proto.ProtoUtil;
public enum OfferDirection {
BUY,
SELL;
public static OfferDirection fromProto(protobuf.OfferDirection direction) {
return ProtoUtil.enumFromProto(OfferDirection.class, direction.name());
}
public static protobuf.OfferDirection toProtoMessage(OfferDirection direction) {
return protobuf.OfferDirection.valueOf(direction.name());
}
}

View file

@ -25,6 +25,7 @@ import bisq.core.payment.PaymentAccountUtil;
import bisq.core.user.Preferences;
import bisq.core.user.User;
import bisq.common.app.DevEnv;
import bisq.common.app.Version;
import org.bitcoinj.core.Coin;
@ -43,7 +44,7 @@ import lombok.extern.slf4j.Slf4j;
@Slf4j
@Singleton
public class OfferFilter {
public class OfferFilterService {
private final User user;
private final Preferences preferences;
private final FilterManager filterManager;
@ -52,10 +53,10 @@ public class OfferFilter {
private final Map<String, Boolean> myInsufficientTradeLimitCache = new HashMap<>();
@Inject
public OfferFilter(User user,
Preferences preferences,
FilterManager filterManager,
AccountAgeWitnessService accountAgeWitnessService) {
public OfferFilterService(User user,
Preferences preferences,
FilterManager filterManager,
AccountAgeWitnessService accountAgeWitnessService) {
this.user = user;
this.preferences = preferences;
this.filterManager = filterManager;
@ -80,7 +81,8 @@ public class OfferFilter {
IS_NODE_ADDRESS_BANNED,
REQUIRE_UPDATE_TO_NEW_VERSION,
IS_INSUFFICIENT_COUNTERPARTY_TRADE_LIMIT,
IS_MY_INSUFFICIENT_TRADE_LIMIT;
IS_MY_INSUFFICIENT_TRADE_LIMIT,
HIDE_BSQ_SWAPS_DUE_DAO_DEACTIVATED;
@Getter
private final boolean isValid;
@ -128,6 +130,9 @@ public class OfferFilter {
if (isMyInsufficientTradeLimit(offer)) {
return Result.IS_MY_INSUFFICIENT_TRADE_LIMIT;
}
if (!DevEnv.isDaoActivated() && offer.isBsqSwapOffer()) {
return Result.HIDE_BSQ_SWAPS_DUE_DAO_DEACTIVATED;
}
return Result.VALID;
}

View file

@ -40,7 +40,7 @@ import javax.annotation.Nullable;
public class OfferForJson {
private static final Logger log = LoggerFactory.getLogger(OfferForJson.class);
public final OfferPayload.Direction direction;
public final OfferDirection direction;
public final String currencyCode;
public final long minAmount;
public final long amount;
@ -53,7 +53,7 @@ public class OfferForJson {
// primaryMarket fields are based on industry standard where primaryMarket is always in the focus (in the app BTC is always in the focus - will be changed in a larger refactoring once)
public String currencyPair;
public OfferPayload.Direction primaryMarketDirection;
public OfferDirection primaryMarketDirection;
public String priceDisplayString;
public String primaryMarketAmountDisplayString;
@ -75,7 +75,7 @@ public class OfferForJson {
transient private final MonetaryFormat coinFormat = MonetaryFormat.BTC;
public OfferForJson(OfferPayload.Direction direction,
public OfferForJson(OfferDirection direction,
String currencyCode,
Coin minAmount,
Coin amount,
@ -104,7 +104,7 @@ public class OfferForJson {
try {
final Price price = getPrice();
if (CurrencyUtil.isCryptoCurrency(currencyCode)) {
primaryMarketDirection = direction == OfferPayload.Direction.BUY ? OfferPayload.Direction.SELL : OfferPayload.Direction.BUY;
primaryMarketDirection = direction == OfferDirection.BUY ? OfferDirection.SELL : OfferDirection.BUY;
currencyPair = currencyCode + "/" + Res.getBaseCurrencyCode();
// int precision = 8;

View file

@ -0,0 +1,147 @@
/*
* 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.offer;
import bisq.network.p2p.NodeAddress;
import bisq.network.p2p.storage.payload.ExpirablePayload;
import bisq.network.p2p.storage.payload.ProtectedStoragePayload;
import bisq.network.p2p.storage.payload.RequiresOwnerIsOnlinePayload;
import bisq.common.crypto.Hash;
import bisq.common.crypto.PubKeyRing;
import bisq.common.util.Hex;
import bisq.common.util.JsonExclude;
import java.security.PublicKey;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import lombok.EqualsAndHashCode;
import lombok.Getter;
import javax.annotation.Nullable;
@EqualsAndHashCode(exclude = {"hash"})
@Getter
public abstract class OfferPayloadBase implements ProtectedStoragePayload, ExpirablePayload, RequiresOwnerIsOnlinePayload {
public static final long TTL = TimeUnit.MINUTES.toMillis(9);
protected final String id;
protected final long date;
// For fiat offer the baseCurrencyCode is BTC and the counterCurrencyCode is the fiat currency
// For altcoin offers it is the opposite. baseCurrencyCode is the altcoin and the counterCurrencyCode is BTC.
protected final String baseCurrencyCode;
protected final String counterCurrencyCode;
// price if fixed price is used (usePercentageBasedPrice = false), otherwise 0
protected final long price;
protected final long amount;
protected final long minAmount;
protected final String paymentMethodId;
protected final String makerPaymentAccountId;
protected final NodeAddress ownerNodeAddress;
protected final OfferDirection direction;
protected final String versionNr;
protected final int protocolVersion;
@JsonExclude
protected final PubKeyRing pubKeyRing;
// cache
protected transient byte[] hash;
@Nullable
protected final Map<String, String> extraDataMap;
public OfferPayloadBase(String id,
long date,
NodeAddress ownerNodeAddress,
PubKeyRing pubKeyRing,
String baseCurrencyCode,
String counterCurrencyCode,
OfferDirection direction,
long price,
long amount,
long minAmount,
String paymentMethodId,
String makerPaymentAccountId,
@Nullable Map<String, String> extraDataMap,
String versionNr,
int protocolVersion) {
this.id = id;
this.date = date;
this.ownerNodeAddress = ownerNodeAddress;
this.pubKeyRing = pubKeyRing;
this.baseCurrencyCode = baseCurrencyCode;
this.counterCurrencyCode = counterCurrencyCode;
this.direction = direction;
this.price = price;
this.amount = amount;
this.minAmount = minAmount;
this.paymentMethodId = paymentMethodId;
this.makerPaymentAccountId = makerPaymentAccountId;
this.extraDataMap = extraDataMap;
this.versionNr = versionNr;
this.protocolVersion = protocolVersion;
}
public byte[] getHash() {
if (this.hash == null) {
this.hash = Hash.getSha256Hash(this.toProtoMessage().toByteArray());
}
return this.hash;
}
@Override
public PublicKey getOwnerPubKey() {
return pubKeyRing.getSignaturePubKey();
}
// In the offer we support base and counter currency
// Fiat offers have base currency BTC and counterCurrency Fiat
// Altcoins have base currency Altcoin and counterCurrency BTC
// The rest of the app does not support yet that concept of base currency and counter currencies
// so we map here for convenience
public String getCurrencyCode() {
return getBaseCurrencyCode().equals("BTC") ? getCounterCurrencyCode() : getBaseCurrencyCode();
}
@Override
public long getTTL() {
return TTL;
}
@Override
public String toString() {
return "OfferPayloadBase{" +
"\r\n id='" + id + '\'' +
",\r\n date=" + date +
",\r\n baseCurrencyCode='" + baseCurrencyCode + '\'' +
",\r\n counterCurrencyCode='" + counterCurrencyCode + '\'' +
",\r\n price=" + price +
",\r\n amount=" + amount +
",\r\n minAmount=" + minAmount +
",\r\n paymentMethodId='" + paymentMethodId + '\'' +
",\r\n makerPaymentAccountId='" + makerPaymentAccountId + '\'' +
",\r\n ownerNodeAddress=" + ownerNodeAddress +
",\r\n direction=" + direction +
",\r\n versionNr='" + versionNr + '\'' +
",\r\n protocolVersion=" + protocolVersion +
",\r\n pubKeyRing=" + pubKeyRing +
",\r\n hash=" + (hash != null ? Hex.encode(hash) : "null") +
",\r\n extraDataMap=" + extraDataMap +
"\r\n}";
}
}

View file

@ -17,6 +17,8 @@
package bisq.core.offer;
import bisq.core.offer.bisq_v1.OfferPayload;
import bisq.common.app.Capabilities;
import bisq.common.app.Capability;
import bisq.common.config.Config;
@ -40,7 +42,7 @@ public class OfferRestrictions {
public static Coin TOLERATED_SMALL_TRADE_AMOUNT = Coin.parseCoin("0.01");
static boolean hasOfferMandatoryCapability(Offer offer, Capability mandatoryCapability) {
Map<String, String> extraDataMap = offer.getOfferPayload().getExtraDataMap();
Map<String, String> extraDataMap = offer.getExtraDataMap();
if (extraDataMap != null && extraDataMap.containsKey(OfferPayload.CAPABILITIES)) {
String commaSeparatedOrdinals = extraDataMap.get(OfferPayload.CAPABILITIES);
Capabilities capabilities = Capabilities.fromStringList(commaSeparatedOrdinals);

View file

@ -25,9 +25,12 @@ import bisq.core.locale.CurrencyUtil;
import bisq.core.locale.Res;
import bisq.core.monetary.Price;
import bisq.core.monetary.Volume;
import bisq.core.offer.bisq_v1.MutableOfferPayloadFields;
import bisq.core.offer.bisq_v1.OfferPayload;
import bisq.core.payment.CashByMailAccount;
import bisq.core.payment.F2FAccount;
import bisq.core.payment.PaymentAccount;
import bisq.core.payment.payload.PaymentMethod;
import bisq.core.provider.fee.FeeService;
import bisq.core.provider.price.MarketPrice;
import bisq.core.provider.price.PriceFeedService;
@ -42,8 +45,10 @@ import bisq.core.util.coin.CoinUtil;
import bisq.network.p2p.P2PService;
import bisq.common.app.Capabilities;
import bisq.common.app.Version;
import bisq.common.util.MathUtils;
import bisq.common.util.Tuple2;
import bisq.common.util.Utilities;
import org.bitcoinj.core.Coin;
import org.bitcoinj.core.Transaction;
@ -57,6 +62,7 @@ import javax.inject.Singleton;
import java.util.HashMap;
import java.util.Map;
import java.util.Optional;
import java.util.UUID;
import java.util.function.Predicate;
import lombok.extern.slf4j.Slf4j;
@ -69,7 +75,7 @@ import static bisq.core.btc.wallet.Restrictions.getMaxBuyerSecurityDepositAsPerc
import static bisq.core.btc.wallet.Restrictions.getMinBuyerSecurityDepositAsPercent;
import static bisq.core.btc.wallet.Restrictions.getMinNonDustOutput;
import static bisq.core.btc.wallet.Restrictions.isDust;
import static bisq.core.offer.OfferPayload.*;
import static bisq.core.offer.bisq_v1.OfferPayload.*;
import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkNotNull;
import static java.lang.String.format;
@ -112,6 +118,40 @@ public class OfferUtil {
this.tradeStatisticsManager = tradeStatisticsManager;
}
public static String getRandomOfferId() {
return Utilities.getRandomPrefix(5, 8) + "-" +
UUID.randomUUID() + "-" +
getStrippedVersion();
}
public static String getStrippedVersion() {
return Version.VERSION.replace(".", "");
}
// We add a counter at the end of the offer id signalling the number of times that offer has
// been mutated ether due edit or due pow adjustments.
public static String getOfferIdWithMutationCounter(String id) {
String[] split = id.split("-");
String base = id;
int counter = 0;
if (split.length > 7) {
String counterString = split[7];
int endIndex = id.length() - counterString.length() - 1;
base = id.substring(0, endIndex);
try {
counter = Integer.parseInt(counterString);
} catch (Exception ignore) {
}
}
counter++;
return base + "-" + counter;
}
public static String getVersionFromId(String id) {
String[] split = id.split("-");
return split[6];
}
public void maybeSetFeePaymentCurrencyPreference(String feeCurrencyCode) {
if (!feeCurrencyCode.isEmpty()) {
if (!isValidFeePaymentCurrencyCode.test(feeCurrencyCode))
@ -132,13 +172,13 @@ public class OfferUtil {
* @return {@code true} for an offer to buy BTC from the taker, {@code false} for an
* offer to sell BTC to the taker
*/
public boolean isBuyOffer(Direction direction) {
return direction == Direction.BUY;
public boolean isBuyOffer(OfferDirection direction) {
return direction == OfferDirection.BUY;
}
public long getMaxTradeLimit(PaymentAccount paymentAccount,
String currencyCode,
Direction direction) {
OfferDirection direction) {
return paymentAccount != null
? accountAgeWitnessService.getMyTradeLimit(paymentAccount, currencyCode, direction)
: 0;
@ -181,7 +221,7 @@ public class OfferUtil {
// We have to keep a minimum amount of BSQ == bitcoin dust limit, otherwise there
// would be dust violations for change UTXOs; essentially means the minimum usable
// balance of BSQ is 5.46.
Coin usableBsqBalance = bsqWalletService.getAvailableConfirmedBalance().subtract(getMinNonDustOutput());
Coin usableBsqBalance = bsqWalletService.getAvailableBalance().subtract(getMinNonDustOutput());
return usableBsqBalance.isNegative() ? Coin.ZERO : usableBsqBalance;
}
@ -240,7 +280,7 @@ public class OfferUtil {
* @return {@code true} if the balance is sufficient, {@code false} otherwise
*/
public boolean isBsqForMakerFeeAvailable(@Nullable Coin amount) {
Coin availableBalance = bsqWalletService.getAvailableConfirmedBalance();
Coin availableBalance = bsqWalletService.getAvailableBalance();
Coin makerFee = CoinUtil.getMakerFee(false, amount);
// If we don't know yet the maker fee (amount is not set) we return true,
@ -274,7 +314,7 @@ public class OfferUtil {
}
public boolean isBsqForTakerFeeAvailable(@Nullable Coin amount) {
Coin availableBalance = bsqWalletService.getAvailableConfirmedBalance();
Coin availableBalance = bsqWalletService.getAvailableBalance();
Coin takerFee = getTakerFee(false, amount);
// If we don't know yet the maker fee (amount is not set) we return true,
@ -291,7 +331,7 @@ public class OfferUtil {
}
public boolean isBlockChainPaymentMethod(Offer offer) {
return offer != null && offer.getPaymentMethod().isAsset();
return offer != null && offer.getPaymentMethod().isBlockchain();
}
public Optional<Volume> getFeeInUserFiatCurrency(Coin makerFee,
@ -313,7 +353,7 @@ public class OfferUtil {
public Map<String, String> getExtraDataMap(PaymentAccount paymentAccount,
String currencyCode,
Direction direction) {
OfferDirection direction) {
Map<String, String> extraDataMap = new HashMap<>();
if (CurrencyUtil.isFiatCurrency(currencyCode)) {
String myWitnessHashAsHex = accountAgeWitnessService
@ -336,7 +376,7 @@ public class OfferUtil {
extraDataMap.put(CAPABILITIES, Capabilities.app.toStringList());
if (currencyCode.equals("XMR") && direction == Direction.SELL) {
if (currencyCode.equals("XMR") && direction == OfferDirection.SELL) {
preferences.getAutoConfirmSettingsList().stream()
.filter(e -> e.getCurrencyCode().equals("XMR"))
.filter(AutoConfirmSettings::isEnabled)
@ -350,17 +390,21 @@ public class OfferUtil {
PaymentAccount paymentAccount,
String currencyCode,
Coin makerFeeAsCoin) {
validateBasicOfferData(paymentAccount.getPaymentMethod(), currencyCode);
checkNotNull(makerFeeAsCoin, "makerFee must not be null");
checkNotNull(p2PService.getAddress(), "Address must not be null");
checkArgument(buyerSecurityDeposit <= getMaxBuyerSecurityDepositAsPercent(),
"securityDeposit must not exceed " +
getMaxBuyerSecurityDepositAsPercent());
checkArgument(buyerSecurityDeposit >= getMinBuyerSecurityDepositAsPercent(),
"securityDeposit must not be less than " +
getMinBuyerSecurityDepositAsPercent());
}
public void validateBasicOfferData(PaymentMethod paymentMethod, String currencyCode) {
checkNotNull(p2PService.getAddress(), "Address must not be null");
checkArgument(!filterManager.isCurrencyBanned(currencyCode),
Res.get("offerbook.warning.currencyBanned"));
checkArgument(!filterManager.isPaymentMethodBanned(paymentAccount.getPaymentMethod()),
checkArgument(!filterManager.isPaymentMethodBanned(paymentMethod),
Res.get("offerbook.warning.paymentMethodBanned"));
}
@ -370,45 +414,45 @@ public class OfferUtil {
// Immutable fields are sourced from the original openOffer param.
public OfferPayload getMergedOfferPayload(OpenOffer openOffer,
MutableOfferPayloadFields mutableOfferPayloadFields) {
OfferPayload originalOfferPayload = openOffer.getOffer().getOfferPayload();
return new OfferPayload(originalOfferPayload.getId(),
originalOfferPayload.getDate(),
originalOfferPayload.getOwnerNodeAddress(),
originalOfferPayload.getPubKeyRing(),
originalOfferPayload.getDirection(),
OfferPayload original = openOffer.getOffer().getOfferPayload().orElseThrow();
return new OfferPayload(original.getId(),
original.getDate(),
original.getOwnerNodeAddress(),
original.getPubKeyRing(),
original.getDirection(),
mutableOfferPayloadFields.getPrice(),
mutableOfferPayloadFields.getMarketPriceMargin(),
mutableOfferPayloadFields.isUseMarketBasedPrice(),
originalOfferPayload.getAmount(),
originalOfferPayload.getMinAmount(),
original.getAmount(),
original.getMinAmount(),
mutableOfferPayloadFields.getBaseCurrencyCode(),
mutableOfferPayloadFields.getCounterCurrencyCode(),
originalOfferPayload.getArbitratorNodeAddresses(),
originalOfferPayload.getMediatorNodeAddresses(),
original.getArbitratorNodeAddresses(),
original.getMediatorNodeAddresses(),
mutableOfferPayloadFields.getPaymentMethodId(),
mutableOfferPayloadFields.getMakerPaymentAccountId(),
originalOfferPayload.getOfferFeePaymentTxId(),
original.getOfferFeePaymentTxId(),
mutableOfferPayloadFields.getCountryCode(),
mutableOfferPayloadFields.getAcceptedCountryCodes(),
mutableOfferPayloadFields.getBankId(),
mutableOfferPayloadFields.getAcceptedBankIds(),
originalOfferPayload.getVersionNr(),
originalOfferPayload.getBlockHeightAtOfferCreation(),
originalOfferPayload.getTxFee(),
originalOfferPayload.getMakerFee(),
originalOfferPayload.isCurrencyForMakerFeeBtc(),
originalOfferPayload.getBuyerSecurityDeposit(),
originalOfferPayload.getSellerSecurityDeposit(),
originalOfferPayload.getMaxTradeLimit(),
originalOfferPayload.getMaxTradePeriod(),
originalOfferPayload.isUseAutoClose(),
originalOfferPayload.isUseReOpenAfterAutoClose(),
originalOfferPayload.getLowerClosePrice(),
originalOfferPayload.getUpperClosePrice(),
originalOfferPayload.isPrivateOffer(),
originalOfferPayload.getHashOfChallenge(),
original.getVersionNr(),
original.getBlockHeightAtOfferCreation(),
original.getTxFee(),
original.getMakerFee(),
original.isCurrencyForMakerFeeBtc(),
original.getBuyerSecurityDeposit(),
original.getSellerSecurityDeposit(),
original.getMaxTradeLimit(),
original.getMaxTradePeriod(),
original.isUseAutoClose(),
original.isUseReOpenAfterAutoClose(),
original.getLowerClosePrice(),
original.getUpperClosePrice(),
original.isPrivateOffer(),
original.getHashOfChallenge(),
mutableOfferPayloadFields.getExtraDataMap(),
originalOfferPayload.getProtocolVersion());
original.getProtocolVersion());
}
private Optional<Volume> getFeeInUserFiatCurrency(Coin makerFee,
@ -482,7 +526,7 @@ public class OfferUtil {
}
} else {
errorMsg = "The maker fee tx is invalid as it does not has at least 2 outputs." + extraString +
"\nMakerFeeTx=" + makerFeeTx.toString();
"\nMakerFeeTx=" + makerFeeTx;
}
if (errorMsg == null) {

View file

@ -17,7 +17,8 @@
package bisq.core.offer;
import bisq.core.trade.Tradable;
import bisq.core.offer.bsq_swap.BsqSwapOfferPayload;
import bisq.core.trade.model.Tradable;
import bisq.network.p2p.NodeAddress;
@ -35,12 +36,13 @@ import lombok.extern.slf4j.Slf4j;
import javax.annotation.Nullable;
@EqualsAndHashCode
import static com.google.common.base.Preconditions.checkArgument;
@EqualsAndHashCode(exclude = {"bsqSwapOfferHasMissingFunds", "state", "timeoutTimer", "mempoolStatus"})
@Slf4j
public final class OpenOffer implements Tradable {
// Timeout for offer reservation during takeoffer process. If deposit tx is not completed in that time we reset the offer to AVAILABLE state.
private static final long TIMEOUT = 60;
transient private Timer timeoutTimer;
public enum State {
AVAILABLE,
@ -54,10 +56,11 @@ public final class OpenOffer implements Tradable {
private final Offer offer;
@Getter
private State state;
// TODO Not used. Could be removed?
@Getter
@Setter
@Nullable
private NodeAddress arbitratorNodeAddress;
private final NodeAddress arbitratorNodeAddress;
@Getter
@Setter
@Nullable
@ -76,15 +79,29 @@ public final class OpenOffer implements Tradable {
@Getter
@Setter
transient private long mempoolStatus = -1;
transient private Timer timeoutTimer;
// Added at BsqSwap release. We do not persist that field
@Getter
@Setter
transient private boolean bsqSwapOfferHasMissingFunds;
public OpenOffer(Offer offer) {
this(offer, 0);
}
public OpenOffer(Offer offer, State state) {
this.offer = offer;
this.triggerPrice = 0;
this.state = state;
arbitratorNodeAddress = null;
}
public OpenOffer(Offer offer, long triggerPrice) {
this.offer = offer;
this.triggerPrice = triggerPrice;
state = State.AVAILABLE;
arbitratorNodeAddress = null;
}
///////////////////////////////////////////////////////////////////////////////////////////
@ -131,9 +148,8 @@ public final class OpenOffer implements Tradable {
proto.getTriggerPrice());
}
///////////////////////////////////////////////////////////////////////////////////////////
// Getters
// Tradable
///////////////////////////////////////////////////////////////////////////////////////////
@Override
@ -151,6 +167,11 @@ public final class OpenOffer implements Tradable {
return offer.getShortId();
}
///////////////////////////////////////////////////////////////////////////////////////////
// Misc
///////////////////////////////////////////////////////////////////////////////////////////
public void setState(State state) {
this.state = state;
@ -166,6 +187,12 @@ public final class OpenOffer implements Tradable {
return state == State.DEACTIVATED;
}
public BsqSwapOfferPayload getBsqSwapOfferPayload() {
checkArgument(getOffer().getBsqSwapOfferPayload().isPresent(),
"getBsqSwapOfferPayload must be called only when BsqSwapOfferPayload is the expected payload");
return getOffer().getBsqSwapOfferPayload().get();
}
private void startTimeout() {
stopTimeout();
@ -185,7 +212,6 @@ public final class OpenOffer implements Tradable {
}
}
@Override
public String toString() {
return "OpenOffer{" +
@ -195,6 +221,7 @@ public final class OpenOffer implements Tradable {
",\n mediatorNodeAddress=" + mediatorNodeAddress +
",\n refundAgentNodeAddress=" + refundAgentNodeAddress +
",\n triggerPrice=" + triggerPrice +
",\n bsqSwapOfferHasMissingFunds=" + bsqSwapOfferHasMissingFunds +
"\n}";
}
}

View file

@ -25,18 +25,22 @@ import bisq.core.dao.DaoFacade;
import bisq.core.exceptions.TradePriceOutOfToleranceException;
import bisq.core.filter.FilterManager;
import bisq.core.locale.Res;
import bisq.core.offer.availability.AvailabilityResult;
import bisq.core.offer.availability.DisputeAgentSelection;
import bisq.core.offer.messages.OfferAvailabilityRequest;
import bisq.core.offer.messages.OfferAvailabilityResponse;
import bisq.core.offer.placeoffer.PlaceOfferModel;
import bisq.core.offer.placeoffer.PlaceOfferProtocol;
import bisq.core.offer.availability.messages.OfferAvailabilityRequest;
import bisq.core.offer.availability.messages.OfferAvailabilityResponse;
import bisq.core.offer.bisq_v1.CreateOfferService;
import bisq.core.offer.bisq_v1.MarketPriceNotAvailableException;
import bisq.core.offer.bisq_v1.OfferPayload;
import bisq.core.offer.placeoffer.bisq_v1.PlaceOfferModel;
import bisq.core.offer.placeoffer.bisq_v1.PlaceOfferProtocol;
import bisq.core.provider.price.PriceFeedService;
import bisq.core.support.dispute.arbitration.arbitrator.ArbitratorManager;
import bisq.core.support.dispute.mediation.mediator.MediatorManager;
import bisq.core.support.dispute.refund.refundagent.RefundAgentManager;
import bisq.core.trade.TradableList;
import bisq.core.trade.closed.ClosedTradableManager;
import bisq.core.trade.handlers.TransactionResultHandler;
import bisq.core.trade.ClosedTradableManager;
import bisq.core.trade.bisq_v1.TransactionResultHandler;
import bisq.core.trade.model.TradableList;
import bisq.core.trade.statistics.TradeStatisticsManager;
import bisq.core.user.Preferences;
import bisq.core.user.User;
@ -84,19 +88,16 @@ import java.util.Set;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import lombok.Getter;
import org.jetbrains.annotations.NotNull;
import lombok.extern.slf4j.Slf4j;
import javax.annotation.Nullable;
import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkNotNull;
@Slf4j
public class OpenOfferManager implements PeerManager.Listener, DecryptedDirectMessageListener, PersistedDataHost {
private static final Logger log = LoggerFactory.getLogger(OpenOfferManager.class);
private static final long RETRY_REPUBLISH_DELAY_SEC = 10;
private static final long REPUBLISH_AGAIN_AT_STARTUP_DELAY_SEC = 30;
@ -207,8 +208,9 @@ public class OpenOfferManager implements PeerManager.Listener, DecryptedDirectMe
cleanUpAddressEntries();
openOffers.stream()
.forEach(openOffer -> OfferUtil.getInvalidMakerFeeTxErrorMessage(openOffer.getOffer(), btcWalletService)
.ifPresent(errorMsg -> invalidOffers.add(new Tuple2<>(openOffer, errorMsg))));
.forEach(openOffer ->
OfferUtil.getInvalidMakerFeeTxErrorMessage(openOffer.getOffer(), btcWalletService)
.ifPresent(errorMsg -> invalidOffers.add(new Tuple2<>(openOffer, errorMsg))));
}
private void cleanUpAddressEntries() {
@ -238,7 +240,7 @@ public class OpenOfferManager implements PeerManager.Listener, DecryptedDirectMe
log.info("Remove open offers at shutDown. Number of open offers: {}", size);
if (offerBookService.isBootstrapped() && size > 0) {
UserThread.execute(() -> openOffers.forEach(
openOffer -> offerBookService.removeOfferAtShutDown(openOffer.getOffer().getOfferPayload())
openOffer -> offerBookService.removeOfferAtShutDown(openOffer.getOffer().getOfferPayloadBase())
));
// Force broadcaster to send out immediately, otherwise we could have a 2 sec delay until the
@ -265,11 +267,9 @@ public class OpenOfferManager implements PeerManager.Listener, DecryptedDirectMe
int size = openOffers.size();
// Copy list as we remove in the loop
List<OpenOffer> openOffersList = new ArrayList<>(openOffers);
openOffersList.forEach(openOffer -> removeOpenOffer(openOffer, () -> {
}, errorMessage -> {
}));
openOffersList.forEach(this::removeOpenOffer);
if (completeHandler != null)
UserThread.runAfter(completeHandler, size * 200 + 500, TimeUnit.MILLISECONDS);
UserThread.runAfter(completeHandler, size * 200L + 500, TimeUnit.MILLISECONDS);
}
@ -372,6 +372,7 @@ public class OpenOfferManager implements PeerManager.Listener, DecryptedDirectMe
TransactionResultHandler resultHandler,
ErrorMessageHandler errorMessageHandler) {
checkNotNull(offer.getMakerFee(), "makerFee must not be null");
checkArgument(!offer.isBsqSwapOffer());
Coin reservedFundsForOffer = createOfferService.getReservedFundsForOffer(offer.getDirection(),
offer.getAmount(),
@ -394,21 +395,30 @@ public class OpenOfferManager implements PeerManager.Listener, DecryptedDirectMe
model,
transaction -> {
OpenOffer openOffer = new OpenOffer(offer, triggerPrice);
openOffers.add(openOffer);
requestPersistence();
resultHandler.handleResult(transaction);
addOpenOfferToList(openOffer);
if (!stopped) {
startPeriodicRepublishOffersTimer();
startPeriodicRefreshOffersTimer();
} else {
log.debug("We have stopped already. We ignore that placeOfferProtocol.placeOffer.onResult call.");
}
resultHandler.handleResult(transaction);
},
errorMessageHandler
);
placeOfferProtocol.placeOffer();
}
public void addOpenBsqSwapOffer(OpenOffer openOffer) {
addOpenOfferToList(openOffer);
if (!stopped) {
startPeriodicRepublishOffersTimer();
startPeriodicRefreshOffersTimer();
} else {
log.debug("We have stopped already. We ignore that placeOfferProtocol.placeOffer.onResult call.");
}
}
// Remove from offerbook
public void removeOffer(Offer offer, ResultHandler resultHandler, ErrorMessageHandler errorMessageHandler) {
Optional<OpenOffer> openOfferOptional = getOpenOfferById(offer.getId());
@ -418,7 +428,7 @@ public class OpenOfferManager implements PeerManager.Listener, DecryptedDirectMe
log.warn("Offer was not found in our list of open offers. We still try to remove it from the offerbook.");
errorMessageHandler.handleErrorMessage("Offer was not found in our list of open offers. " +
"We still try to remove it from the offerbook.");
offerBookService.removeOffer(offer.getOfferPayload(),
offerBookService.removeOffer(offer.getOfferPayloadBase(),
() -> offer.setState(Offer.State.REMOVED),
null);
}
@ -427,26 +437,36 @@ public class OpenOfferManager implements PeerManager.Listener, DecryptedDirectMe
public void activateOpenOffer(OpenOffer openOffer,
ResultHandler resultHandler,
ErrorMessageHandler errorMessageHandler) {
if (!offersToBeEdited.containsKey(openOffer.getId())) {
Offer offer = openOffer.getOffer();
offerBookService.activateOffer(offer,
() -> {
openOffer.setState(OpenOffer.State.AVAILABLE);
requestPersistence();
log.debug("activateOpenOffer, offerId={}", offer.getId());
resultHandler.handleResult();
},
errorMessageHandler);
} else {
if (offersToBeEdited.containsKey(openOffer.getId())) {
errorMessageHandler.handleErrorMessage("You can't activate an offer that is currently edited.");
return;
}
// If there is not enough funds for a BsqSwapOffer we do not publish the offer, but still apply the state change.
// Once the wallet gets funded the offer gets published automatically.
if (isBsqSwapOfferLackingFunds(openOffer)) {
openOffer.setState(OpenOffer.State.AVAILABLE);
requestPersistence();
resultHandler.handleResult();
return;
}
Offer offer = openOffer.getOffer();
offerBookService.activateOffer(offer,
() -> {
openOffer.setState(OpenOffer.State.AVAILABLE);
requestPersistence();
log.debug("activateOpenOffer, offerId={}", offer.getId());
resultHandler.handleResult();
},
errorMessageHandler);
}
public void deactivateOpenOffer(OpenOffer openOffer,
ResultHandler resultHandler,
ErrorMessageHandler errorMessageHandler) {
Offer offer = openOffer.getOffer();
offerBookService.deactivateOffer(offer.getOfferPayload(),
offerBookService.deactivateOffer(offer.getOfferPayloadBase(),
() -> {
openOffer.setState(OpenOffer.State.DEACTIVATED);
requestPersistence();
@ -456,6 +476,12 @@ public class OpenOfferManager implements PeerManager.Listener, DecryptedDirectMe
errorMessageHandler);
}
public void removeOpenOffer(OpenOffer openOffer) {
removeOpenOffer(openOffer, () -> {
}, error -> {
});
}
public void removeOpenOffer(OpenOffer openOffer,
ResultHandler resultHandler,
ErrorMessageHandler errorMessageHandler) {
@ -464,7 +490,7 @@ public class OpenOfferManager implements PeerManager.Listener, DecryptedDirectMe
if (openOffer.isDeactivated()) {
onRemoved(openOffer, resultHandler, offer);
} else {
offerBookService.removeOffer(offer.getOfferPayload(),
offerBookService.removeOffer(offer.getOfferPayloadBase(),
() -> onRemoved(openOffer, resultHandler, offer),
errorMessageHandler);
}
@ -508,18 +534,17 @@ public class OpenOfferManager implements PeerManager.Listener, DecryptedDirectMe
openOffer.getOffer().setState(Offer.State.REMOVED);
openOffer.setState(OpenOffer.State.CANCELED);
openOffers.remove(openOffer);
removeOpenOfferFromList(openOffer);
OpenOffer editedOpenOffer = new OpenOffer(editedOffer, triggerPrice);
editedOpenOffer.setState(originalState);
openOffers.add(editedOpenOffer);
addOpenOfferToList(editedOpenOffer);
if (!editedOpenOffer.isDeactivated())
republishOffer(editedOpenOffer);
maybeRepublishOffer(editedOpenOffer);
offersToBeEdited.remove(openOffer.getId());
requestPersistence();
resultHandler.handleResult();
} else {
errorMessageHandler.handleErrorMessage("There is no offer with this id existing to be published.");
@ -542,26 +567,26 @@ public class OpenOfferManager implements PeerManager.Listener, DecryptedDirectMe
}
}
private void onRemoved(@NotNull OpenOffer openOffer, ResultHandler resultHandler, Offer offer) {
private void onRemoved(OpenOffer openOffer, ResultHandler resultHandler, Offer offer) {
offer.setState(Offer.State.REMOVED);
openOffer.setState(OpenOffer.State.CANCELED);
openOffers.remove(openOffer);
closedTradableManager.add(openOffer);
removeOpenOfferFromList(openOffer);
if (!openOffer.getOffer().isBsqSwapOffer()) {
closedTradableManager.add(openOffer);
btcWalletService.resetAddressEntriesForOpenOffer(offer.getId());
}
log.info("onRemoved offerId={}", offer.getId());
btcWalletService.resetAddressEntriesForOpenOffer(offer.getId());
requestPersistence();
resultHandler.handleResult();
}
// Close openOffer after deposit published
public void closeOpenOffer(Offer offer) {
getOpenOfferById(offer.getId()).ifPresent(openOffer -> {
openOffers.remove(openOffer);
removeOpenOfferFromList(openOffer);
openOffer.setState(OpenOffer.State.CLOSED);
offerBookService.removeOffer(openOffer.getOffer().getOfferPayload(),
offerBookService.removeOffer(openOffer.getOffer().getOfferPayloadBase(),
() -> log.trace("Successful removed offer"),
log::error);
requestPersistence();
});
}
@ -632,7 +657,7 @@ public class OpenOfferManager implements PeerManager.Listener, DecryptedDirectMe
Validator.nonEmptyStringOf(request.offerId);
checkNotNull(request.getPubKeyRing());
} catch (Throwable t) {
errorMessage = "Message validation failed. Error=" + t.toString() + ", Message=" + request.toString();
errorMessage = "Message validation failed. Error=" + t + ", Message=" + request;
log.warn(errorMessage);
sendAckMessage(request, peer, false, errorMessage);
return;
@ -787,23 +812,27 @@ public class OpenOfferManager implements PeerManager.Listener, DecryptedDirectMe
ArrayList<OpenOffer> openOffersClone = new ArrayList<>(openOffers.getList());
openOffersClone.forEach(originalOpenOffer -> {
Offer originalOffer = originalOpenOffer.getOffer();
OfferPayload originalOfferPayload = originalOffer.getOfferPayload();
if (originalOffer.isBsqSwapOffer()) {
// Offer without a fee transaction don't need to be updated, they can be removed and a new
// offer created without incurring any extra costs
return;
}
OfferPayload original = originalOffer.getOfferPayload().orElseThrow();
// We added CAPABILITIES with entry for Capability.MEDIATION in v1.1.6 and
// Capability.REFUND_AGENT in v1.2.0 and want to rewrite a
// persisted offer after the user has updated to 1.2.0 so their offer will be accepted by the network.
if (originalOfferPayload.getProtocolVersion() < Version.TRADE_PROTOCOL_VERSION ||
if (original.getProtocolVersion() < Version.TRADE_PROTOCOL_VERSION ||
!OfferRestrictions.hasOfferMandatoryCapability(originalOffer, Capability.MEDIATION) ||
!OfferRestrictions.hasOfferMandatoryCapability(originalOffer, Capability.REFUND_AGENT) ||
!originalOfferPayload.getOwnerNodeAddress().equals(p2PService.getAddress())) {
!original.getOwnerNodeAddress().equals(p2PService.getAddress())) {
// - Capabilities changed?
// We rewrite our offer with the additional capabilities entry
Map<String, String> updatedExtraDataMap = new HashMap<>();
if (!OfferRestrictions.hasOfferMandatoryCapability(originalOffer, Capability.MEDIATION) ||
!OfferRestrictions.hasOfferMandatoryCapability(originalOffer, Capability.REFUND_AGENT)) {
Map<String, String> originalExtraDataMap = originalOfferPayload.getExtraDataMap();
Map<String, String> originalExtraDataMap = original.getExtraDataMap();
if (originalExtraDataMap != null) {
updatedExtraDataMap.putAll(originalExtraDataMap);
@ -814,11 +843,11 @@ public class OpenOfferManager implements PeerManager.Listener, DecryptedDirectMe
log.info("Converted offer to support new Capability.MEDIATION and Capability.REFUND_AGENT capability. id={}", originalOffer.getId());
} else {
updatedExtraDataMap = originalOfferPayload.getExtraDataMap();
updatedExtraDataMap = original.getExtraDataMap();
}
// - Protocol version changed?
int protocolVersion = originalOfferPayload.getProtocolVersion();
int protocolVersion = original.getProtocolVersion();
if (protocolVersion < Version.TRADE_PROTOCOL_VERSION) {
// We update the trade protocol version
protocolVersion = Version.TRADE_PROTOCOL_VERSION;
@ -826,48 +855,48 @@ public class OpenOfferManager implements PeerManager.Listener, DecryptedDirectMe
}
// - node address changed? (due to a faulty tor dir)
NodeAddress ownerNodeAddress = originalOfferPayload.getOwnerNodeAddress();
NodeAddress ownerNodeAddress = original.getOwnerNodeAddress();
if (!ownerNodeAddress.equals(p2PService.getAddress())) {
ownerNodeAddress = p2PService.getAddress();
log.info("Updated the owner nodeaddress of offer id={}", originalOffer.getId());
}
OfferPayload updatedPayload = new OfferPayload(originalOfferPayload.getId(),
originalOfferPayload.getDate(),
OfferPayload updatedPayload = new OfferPayload(original.getId(),
original.getDate(),
ownerNodeAddress,
originalOfferPayload.getPubKeyRing(),
originalOfferPayload.getDirection(),
originalOfferPayload.getPrice(),
originalOfferPayload.getMarketPriceMargin(),
originalOfferPayload.isUseMarketBasedPrice(),
originalOfferPayload.getAmount(),
originalOfferPayload.getMinAmount(),
originalOfferPayload.getBaseCurrencyCode(),
originalOfferPayload.getCounterCurrencyCode(),
originalOfferPayload.getArbitratorNodeAddresses(),
originalOfferPayload.getMediatorNodeAddresses(),
originalOfferPayload.getPaymentMethodId(),
originalOfferPayload.getMakerPaymentAccountId(),
originalOfferPayload.getOfferFeePaymentTxId(),
originalOfferPayload.getCountryCode(),
originalOfferPayload.getAcceptedCountryCodes(),
originalOfferPayload.getBankId(),
originalOfferPayload.getAcceptedBankIds(),
originalOfferPayload.getVersionNr(),
originalOfferPayload.getBlockHeightAtOfferCreation(),
originalOfferPayload.getTxFee(),
originalOfferPayload.getMakerFee(),
originalOfferPayload.isCurrencyForMakerFeeBtc(),
originalOfferPayload.getBuyerSecurityDeposit(),
originalOfferPayload.getSellerSecurityDeposit(),
originalOfferPayload.getMaxTradeLimit(),
originalOfferPayload.getMaxTradePeriod(),
originalOfferPayload.isUseAutoClose(),
originalOfferPayload.isUseReOpenAfterAutoClose(),
originalOfferPayload.getLowerClosePrice(),
originalOfferPayload.getUpperClosePrice(),
originalOfferPayload.isPrivateOffer(),
originalOfferPayload.getHashOfChallenge(),
original.getPubKeyRing(),
original.getDirection(),
original.getPrice(),
original.getMarketPriceMargin(),
original.isUseMarketBasedPrice(),
original.getAmount(),
original.getMinAmount(),
original.getBaseCurrencyCode(),
original.getCounterCurrencyCode(),
original.getArbitratorNodeAddresses(),
original.getMediatorNodeAddresses(),
original.getPaymentMethodId(),
original.getMakerPaymentAccountId(),
original.getOfferFeePaymentTxId(),
original.getCountryCode(),
original.getAcceptedCountryCodes(),
original.getBankId(),
original.getAcceptedBankIds(),
original.getVersionNr(),
original.getBlockHeightAtOfferCreation(),
original.getTxFee(),
original.getMakerFee(),
original.isCurrencyForMakerFeeBtc(),
original.getBuyerSecurityDeposit(),
original.getSellerSecurityDeposit(),
original.getMaxTradeLimit(),
original.getMaxTradePeriod(),
original.isUseAutoClose(),
original.isUseReOpenAfterAutoClose(),
original.getLowerClosePrice(),
original.getUpperClosePrice(),
original.isPrivateOffer(),
original.getHashOfChallenge(),
updatedExtraDataMap,
protocolVersion);
@ -878,7 +907,7 @@ public class OpenOfferManager implements PeerManager.Listener, DecryptedDirectMe
// remove old offer
originalOffer.setState(Offer.State.REMOVED);
originalOpenOffer.setState(OpenOffer.State.CANCELED);
openOffers.remove(originalOpenOffer);
removeOpenOfferFromList(originalOpenOffer);
// Create new Offer
Offer updatedOffer = new Offer(updatedPayload);
@ -887,8 +916,7 @@ public class OpenOfferManager implements PeerManager.Listener, DecryptedDirectMe
OpenOffer updatedOpenOffer = new OpenOffer(updatedOffer, originalOpenOffer.getTriggerPrice());
updatedOpenOffer.setState(originalOpenOfferState);
openOffers.add(updatedOpenOffer);
requestPersistence();
addOpenOfferToList(updatedOpenOffer);
log.info("Updating offer completed. id={}", originalOffer.getId());
}
@ -904,7 +932,6 @@ public class OpenOfferManager implements PeerManager.Listener, DecryptedDirectMe
if (stopped) {
return;
}
stopPeriodicRefreshOffersTimer();
List<OpenOffer> openOffersList = new ArrayList<>(openOffers.getList());
@ -917,15 +944,8 @@ public class OpenOfferManager implements PeerManager.Listener, DecryptedDirectMe
}
OpenOffer openOffer = list.remove(0);
if (openOffers.contains(openOffer) && !openOffer.isDeactivated()) {
// TODO It is not clear yet if it is better for the node and the network to send out all add offer
// messages in one go or to spread it over a delay. With power users who have 100-200 offers that can have
// some significant impact to user experience and the network
republishOffer(openOffer, () -> processListForRepublishOffers(list));
/* republishOffer(openOffer,
() -> UserThread.runAfter(() -> processListForRepublishOffers(list),
30, TimeUnit.MILLISECONDS));*/
if (openOffers.contains(openOffer)) {
maybeRepublishOffer(openOffer, () -> processListForRepublishOffers(list));
} else {
// If the offer was removed in the meantime or if its deactivated we skip and call
// processListForRepublishOffers again with the list where we removed the offer already.
@ -933,11 +953,18 @@ public class OpenOfferManager implements PeerManager.Listener, DecryptedDirectMe
}
}
private void republishOffer(OpenOffer openOffer) {
republishOffer(openOffer, null);
public void maybeRepublishOffer(OpenOffer openOffer) {
maybeRepublishOffer(openOffer, null);
}
private void republishOffer(OpenOffer openOffer, @Nullable Runnable completeHandler) {
private void maybeRepublishOffer(OpenOffer openOffer, @Nullable Runnable completeHandler) {
if (preventedFromPublishing(openOffer)) {
if (completeHandler != null) {
completeHandler.run();
}
return;
}
offerBookService.addOffer(openOffer.getOffer(),
() -> {
if (!stopped) {
@ -996,8 +1023,8 @@ public class OpenOfferManager implements PeerManager.Listener, DecryptedDirectMe
final OpenOffer openOffer = openOffersList.get(i);
UserThread.runAfterRandomDelay(() -> {
// we need to check if in the meantime the offer has been removed
if (openOffers.contains(openOffer) && !openOffer.isDeactivated())
refreshOffer(openOffer);
if (openOffers.contains(openOffer))
maybeRefreshOffer(openOffer);
}, minDelay, maxDelay, TimeUnit.MILLISECONDS);
}
} else {
@ -1010,8 +1037,11 @@ public class OpenOfferManager implements PeerManager.Listener, DecryptedDirectMe
log.trace("periodicRefreshOffersTimer already stated");
}
private void refreshOffer(OpenOffer openOffer) {
offerBookService.refreshTTL(openOffer.getOffer().getOfferPayload(),
private void maybeRefreshOffer(OpenOffer openOffer) {
if (preventedFromPublishing(openOffer)) {
return;
}
offerBookService.refreshTTL(openOffer.getOffer().getOfferPayloadBase(),
() -> log.debug("Successful refreshed TTL for offer"),
log::warn);
}
@ -1028,7 +1058,7 @@ public class OpenOfferManager implements PeerManager.Listener, DecryptedDirectMe
startPeriodicRepublishOffersTimer();
}
private void requestPersistence() {
public void requestPersistence() {
persistenceManager.requestPersistence();
}
@ -1057,4 +1087,23 @@ public class OpenOfferManager implements PeerManager.Listener, DecryptedDirectMe
retryRepublishOffersTimer = null;
}
}
private void addOpenOfferToList(OpenOffer openOffer) {
openOffers.add(openOffer);
requestPersistence();
}
private void removeOpenOfferFromList(OpenOffer openOffer) {
openOffers.remove(openOffer);
requestPersistence();
}
private boolean isBsqSwapOfferLackingFunds(OpenOffer openOffer) {
return openOffer.getOffer().isBsqSwapOffer() &&
openOffer.isBsqSwapOfferHasMissingFunds();
}
private boolean preventedFromPublishing(OpenOffer openOffer) {
return openOffer.isDeactivated() || openOffer.isBsqSwapOfferHasMissingFunds();
}
}

View file

@ -15,7 +15,7 @@
* along with Bisq. If not, see <http://www.gnu.org/licenses/>.
*/
package bisq.core.offer;
package bisq.core.offer.availability;
public enum AvailabilityResult {
UNKNOWN_FAILURE("cannot take offer for unknown reason"),

View file

@ -18,7 +18,7 @@
package bisq.core.offer.availability;
import bisq.core.offer.Offer;
import bisq.core.offer.messages.OfferAvailabilityResponse;
import bisq.core.offer.availability.messages.OfferAvailabilityResponse;
import bisq.core.support.dispute.mediation.mediator.MediatorManager;
import bisq.core.trade.statistics.TradeStatisticsManager;
import bisq.core.user.User;

View file

@ -18,10 +18,10 @@
package bisq.core.offer.availability;
import bisq.core.offer.Offer;
import bisq.core.offer.availability.messages.OfferAvailabilityResponse;
import bisq.core.offer.availability.messages.OfferMessage;
import bisq.core.offer.availability.tasks.ProcessOfferAvailabilityResponse;
import bisq.core.offer.availability.tasks.SendOfferAvailabilityRequest;
import bisq.core.offer.messages.OfferAvailabilityResponse;
import bisq.core.offer.messages.OfferMessage;
import bisq.core.util.Validator;
import bisq.network.p2p.AckMessage;

Some files were not shown because too many files have changed in this diff Show more