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 # 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 # 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 # 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. # first displayed in Bob's getoffers command output.

View file

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

View file

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

View file

@ -17,6 +17,8 @@
package bisq.apitest; package bisq.apitest;
import java.time.Duration;
import java.io.IOException; import java.io.IOException;
import java.util.concurrent.ExecutionException; 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.alicedaemon;
import static bisq.apitest.config.BisqAppConfig.arbdaemon; import static bisq.apitest.config.BisqAppConfig.arbdaemon;
import static bisq.apitest.config.BisqAppConfig.bobdaemon; 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.net.InetAddress.getLoopbackAddress;
import static java.util.Arrays.stream; 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) { protected static void sleep(long ms) {
try { sleepUninterruptibly(Duration.ofMillis(ms));
MILLISECONDS.sleep(ms);
} catch (InterruptedException ignored) {
// empty
}
} }
protected final String testName(TestInfo testInfo) { protected final String testName(TestInfo testInfo) {

View file

@ -31,7 +31,12 @@ import java.io.PrintWriter;
import java.util.function.Function; import java.util.function.Function;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import org.slf4j.Logger;
import javax.annotation.Nullable;
import static bisq.apitest.config.ApiTestRateMeterInterceptorConfig.getTestRateMeterInterceptorConfig; import static bisq.apitest.config.ApiTestRateMeterInterceptorConfig.getTestRateMeterInterceptorConfig;
import static java.lang.String.format;
import static java.nio.charset.StandardCharsets.UTF_8; import static java.nio.charset.StandardCharsets.UTF_8;
import static java.util.Arrays.stream; import static java.util.Arrays.stream;
import static org.junit.jupiter.api.Assertions.fail; 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.ApiTestCase;
import bisq.apitest.linux.BashCommand;
import bisq.cli.GrpcClient; import bisq.cli.GrpcClient;
public class MethodTest extends ApiTestCase { 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) { 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 // 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 // case we need a bisq.core.payment.PaymentAccount so it can be cast to its
// sub type. // sub-type.
var paymentAccount = grpcClient.createPaymentAccount(jsonString); var paymentAccount = grpcClient.createPaymentAccount(jsonString);
return bisq.core.payment.PaymentAccount.fromProto(paymentAccount, CORE_PROTO_RESOLVER); 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) { protected static String encodeToHex(String s) {
return Utilities.bytesAsHexString(s.getBytes(UTF_8)); 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); 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. // Mkt Price Margin value of offer returned from server is scaled down by 10^-2.
protected final Function<Double, Double> scaledDownMktPriceMargin = (mktPriceMargin) -> 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.apitest.config.ApiTestConfig.BSQ;
import static bisq.core.btc.wallet.Restrictions.getDefaultBuyerSecurityDepositAsPercent; import static bisq.core.btc.wallet.Restrictions.getDefaultBuyerSecurityDepositAsPercent;
import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertEquals;
import static protobuf.OfferPayload.Direction.BUY; import static protobuf.OfferDirection.BUY;
@Disabled @Disabled
@Slf4j @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.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNotEquals; import static org.junit.jupiter.api.Assertions.assertNotEquals;
import static org.junit.jupiter.api.Assertions.assertTrue; import static org.junit.jupiter.api.Assertions.assertTrue;
import static protobuf.OfferPayload.Direction.BUY; import static protobuf.OfferDirection.BUY;
import static protobuf.OfferPayload.Direction.SELL; import static protobuf.OfferDirection.SELL;
@Disabled @Disabled
@Slf4j @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.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNotEquals; import static org.junit.jupiter.api.Assertions.assertNotEquals;
import static org.junit.jupiter.api.Assertions.assertTrue; import static org.junit.jupiter.api.Assertions.assertTrue;
import static protobuf.OfferPayload.Direction.BUY; import static protobuf.OfferDirection.BUY;
import static protobuf.OfferPayload.Direction.SELL; import static protobuf.OfferDirection.SELL;
@Disabled @Disabled
@Slf4j @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.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNotEquals; import static org.junit.jupiter.api.Assertions.assertNotEquals;
import static org.junit.jupiter.api.Assertions.assertTrue; import static org.junit.jupiter.api.Assertions.assertTrue;
import static protobuf.OfferPayload.Direction.BUY; import static protobuf.OfferDirection.BUY;
import static protobuf.OfferPayload.Direction.SELL; import static protobuf.OfferDirection.SELL;
@SuppressWarnings("ConstantConditions") @SuppressWarnings("ConstantConditions")
@Disabled @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.assertFalse;
import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue; import static org.junit.jupiter.api.Assertions.assertTrue;
import static protobuf.OfferPayload.Direction.BUY; import static protobuf.OfferDirection.BUY;
import static protobuf.OfferPayload.Direction.SELL; import static protobuf.OfferDirection.SELL;
@SuppressWarnings("ALL") @SuppressWarnings("ALL")
@Disabled @Disabled

View file

@ -35,7 +35,7 @@ import static bisq.core.btc.wallet.Restrictions.getDefaultBuyerSecurityDepositAs
import static java.lang.String.format; import static java.lang.String.format;
import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertThrows;
import static protobuf.OfferPayload.Direction.BUY; import static protobuf.OfferDirection.BUY;
@Disabled @Disabled
@Slf4j @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; 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. * 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.formatBalancesTbls;
import static bisq.cli.TableFormat.formatOfferTable; import static bisq.cli.TableFormat.formatOfferTable;
import static bisq.core.btc.wallet.Restrictions.getDefaultBuyerSecurityDepositAsPercent; import static bisq.core.btc.wallet.Restrictions.getDefaultBuyerSecurityDepositAsPercent;
import static bisq.core.trade.Trade.Phase.DEPOSIT_CONFIRMED; import static bisq.core.trade.model.bisq_v1.Trade.Phase.DEPOSIT_CONFIRMED;
import static bisq.core.trade.Trade.Phase.FIAT_SENT; import static bisq.core.trade.model.bisq_v1.Trade.Phase.FIAT_SENT;
import static bisq.core.trade.Trade.Phase.PAYOUT_PUBLISHED; import static bisq.core.trade.model.bisq_v1.Trade.Phase.PAYOUT_PUBLISHED;
import static bisq.core.trade.Trade.State.BUYER_RECEIVED_PAYOUT_TX_PUBLISHED_MSG; import static bisq.core.trade.model.bisq_v1.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.model.bisq_v1.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.model.bisq_v1.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.State.SELLER_SAW_ARRIVED_PAYOUT_TX_PUBLISHED_MSG;
import static java.lang.String.format; import static java.lang.String.format;
import static java.util.Collections.singletonList; import static java.util.Collections.singletonList;
import static org.junit.jupiter.api.Assertions.assertEquals; 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.assertNotNull;
import static org.junit.jupiter.api.Assertions.fail; import static org.junit.jupiter.api.Assertions.fail;
import static protobuf.Offer.State.OFFER_FEE_PAID; 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.apitest.config.ApiTestConfig.BSQ;
import static bisq.cli.TableFormat.formatBalancesTbls; import static bisq.cli.TableFormat.formatBalancesTbls;
import static bisq.core.btc.wallet.Restrictions.getDefaultBuyerSecurityDepositAsPercent; import static bisq.core.btc.wallet.Restrictions.getDefaultBuyerSecurityDepositAsPercent;
import static bisq.core.trade.Trade.Phase.DEPOSIT_CONFIRMED; import static bisq.core.trade.model.bisq_v1.Trade.Phase.DEPOSIT_CONFIRMED;
import static bisq.core.trade.Trade.Phase.FIAT_SENT; import static bisq.core.trade.model.bisq_v1.Trade.Phase.FIAT_SENT;
import static bisq.core.trade.Trade.Phase.PAYOUT_PUBLISHED; import static bisq.core.trade.model.bisq_v1.Trade.Phase.PAYOUT_PUBLISHED;
import static bisq.core.trade.Trade.State.*; import static bisq.core.trade.model.bisq_v1.Trade.State.*;
import static java.lang.String.format; import static java.lang.String.format;
import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.fail; import static org.junit.jupiter.api.Assertions.fail;
import static protobuf.Offer.State.OFFER_FEE_PAID; 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; import static protobuf.OpenOffer.State.AVAILABLE;
@Disabled @Disabled

View file

@ -55,14 +55,14 @@ import org.junit.jupiter.api.TestMethodOrder;
import static bisq.apitest.config.ApiTestConfig.BSQ; import static bisq.apitest.config.ApiTestConfig.BSQ;
import static bisq.cli.TableFormat.formatBalancesTbls; import static bisq.cli.TableFormat.formatBalancesTbls;
import static bisq.core.btc.wallet.Restrictions.getDefaultBuyerSecurityDepositAsPercent; import static bisq.core.btc.wallet.Restrictions.getDefaultBuyerSecurityDepositAsPercent;
import static bisq.core.trade.Trade.Phase.DEPOSIT_CONFIRMED; import static bisq.core.trade.model.bisq_v1.Trade.Phase.DEPOSIT_CONFIRMED;
import static bisq.core.trade.Trade.Phase.FIAT_SENT; import static bisq.core.trade.model.bisq_v1.Trade.Phase.FIAT_SENT;
import static bisq.core.trade.Trade.Phase.PAYOUT_PUBLISHED; import static bisq.core.trade.model.bisq_v1.Trade.Phase.PAYOUT_PUBLISHED;
import static bisq.core.trade.Trade.State.*; import static bisq.core.trade.model.bisq_v1.Trade.State.*;
import static java.lang.String.format; import static java.lang.String.format;
import static org.junit.jupiter.api.Assertions.*; import static org.junit.jupiter.api.Assertions.*;
import static protobuf.Offer.State.OFFER_FEE_PAID; 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; 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.formatBalancesTbls;
import static bisq.cli.TableFormat.formatOfferTable; import static bisq.cli.TableFormat.formatOfferTable;
import static bisq.core.btc.wallet.Restrictions.getDefaultBuyerSecurityDepositAsPercent; import static bisq.core.btc.wallet.Restrictions.getDefaultBuyerSecurityDepositAsPercent;
import static bisq.core.trade.Trade.Phase.DEPOSIT_CONFIRMED; import static bisq.core.trade.model.bisq_v1.Trade.Phase.DEPOSIT_CONFIRMED;
import static bisq.core.trade.Trade.Phase.FIAT_SENT; import static bisq.core.trade.model.bisq_v1.Trade.Phase.FIAT_SENT;
import static bisq.core.trade.Trade.Phase.PAYOUT_PUBLISHED; import static bisq.core.trade.model.bisq_v1.Trade.Phase.PAYOUT_PUBLISHED;
import static bisq.core.trade.Trade.Phase.WITHDRAWN; import static bisq.core.trade.model.bisq_v1.Trade.Phase.WITHDRAWN;
import static bisq.core.trade.Trade.State.DEPOSIT_CONFIRMED_IN_BLOCK_CHAIN; import static bisq.core.trade.model.bisq_v1.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.model.bisq_v1.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.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.State.WITHDRAW_COMPLETED;
import static java.lang.String.format; import static java.lang.String.format;
import static java.util.Collections.singletonList; import static java.util.Collections.singletonList;
import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertTrue; import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.junit.jupiter.api.Assertions.fail; 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.apitest.config.ApiTestConfig.BTC;
import static bisq.cli.TableFormat.formatBalancesTbls; import static bisq.cli.TableFormat.formatBalancesTbls;
import static bisq.core.btc.wallet.Restrictions.getDefaultBuyerSecurityDepositAsPercent; import static bisq.core.btc.wallet.Restrictions.getDefaultBuyerSecurityDepositAsPercent;
import static bisq.core.trade.Trade.Phase.DEPOSIT_CONFIRMED; import static bisq.core.trade.model.bisq_v1.Trade.Phase.DEPOSIT_CONFIRMED;
import static bisq.core.trade.Trade.Phase.FIAT_SENT; import static bisq.core.trade.model.bisq_v1.Trade.Phase.FIAT_SENT;
import static bisq.core.trade.Trade.Phase.PAYOUT_PUBLISHED; import static bisq.core.trade.model.bisq_v1.Trade.Phase.PAYOUT_PUBLISHED;
import static bisq.core.trade.Trade.Phase.WITHDRAWN; import static bisq.core.trade.model.bisq_v1.Trade.Phase.WITHDRAWN;
import static bisq.core.trade.Trade.State.*; import static bisq.core.trade.model.bisq_v1.Trade.State.*;
import static java.lang.String.format; import static java.lang.String.format;
import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertTrue; import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.junit.jupiter.api.Assertions.fail; import static org.junit.jupiter.api.Assertions.fail;
import static protobuf.Offer.State.OFFER_FEE_PAID; 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; import static protobuf.OpenOffer.State.AVAILABLE;
@Disabled @Disabled

View file

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

View file

@ -38,13 +38,13 @@ public class WalletTestUtil {
0); 0);
@SuppressWarnings("SameParameterValue") @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 unverifiedBalance,
long unconfirmedChangeBalance, long unconfirmedChangeBalance,
long lockedForVotingBalance, long lockedForVotingBalance,
long lockupBondsBalance, long lockupBondsBalance,
long unlockingBondsBalance) { long unlockingBondsBalance) {
return bisq.core.api.model.BsqBalanceInfo.valueOf(availableConfirmedBalance, return bisq.core.api.model.BsqBalanceInfo.valueOf(availableBalance,
unverifiedBalance, unverifiedBalance,
unconfirmedChangeBalance, unconfirmedChangeBalance,
lockedForVotingBalance, lockedForVotingBalance,
@ -54,7 +54,7 @@ public class WalletTestUtil {
public static void verifyBsqBalances(bisq.core.api.model.BsqBalanceInfo expected, public static void verifyBsqBalances(bisq.core.api.model.BsqBalanceInfo expected,
BsqBalanceInfo actual) { BsqBalanceInfo actual) {
assertEquals(expected.getAvailableConfirmedBalance(), actual.getAvailableConfirmedBalance()); assertEquals(expected.getAvailableBalance(), actual.getAvailableConfirmedBalance());
assertEquals(expected.getUnverifiedBalance(), actual.getUnverifiedBalance()); assertEquals(expected.getUnverifiedBalance(), actual.getUnverifiedBalance());
assertEquals(expected.getUnconfirmedChangeBalance(), actual.getUnconfirmedChangeBalance()); assertEquals(expected.getUnconfirmedChangeBalance(), actual.getUnconfirmedChangeBalance());
assertEquals(expected.getLockedForVotingBalance(), actual.getLockedForVotingBalance()); 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 bisq.core.btc.wallet.Restrictions.getDefaultBuyerSecurityDepositAsPercent;
import static java.lang.System.getenv; import static java.lang.System.getenv;
import static org.junit.jupiter.api.Assertions.fail; import static org.junit.jupiter.api.Assertions.fail;
import static protobuf.OfferPayload.Direction.BUY; import static protobuf.OfferDirection.BUY;
import static protobuf.OfferPayload.Direction.SELL; 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.AbstractOfferTest;
import bisq.apitest.method.offer.BsqSwapOfferTest;
import bisq.apitest.method.offer.CancelOfferTest; import bisq.apitest.method.offer.CancelOfferTest;
import bisq.apitest.method.offer.CreateBSQOffersTest; import bisq.apitest.method.offer.CreateBSQOffersTest;
import bisq.apitest.method.offer.CreateOfferUsingFixedPriceTest; import bisq.apitest.method.offer.CreateOfferUsingFixedPriceTest;
@ -90,6 +91,19 @@ public class OfferTest extends AbstractOfferTest {
@Test @Test
@Order(6) @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() { public void testEditOffer() {
EditOfferTest test = new EditOfferTest(); EditOfferTest test = new EditOfferTest();
// Edit fiat offer tests // 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.AbstractTradeTest;
import bisq.apitest.method.trade.BsqSwapTradeTest;
import bisq.apitest.method.trade.TakeBuyBSQOfferTest; import bisq.apitest.method.trade.TakeBuyBSQOfferTest;
import bisq.apitest.method.trade.TakeBuyBTCOfferTest; import bisq.apitest.method.trade.TakeBuyBTCOfferTest;
import bisq.apitest.method.trade.TakeBuyBTCOfferWithNationalBankAcctTest; import bisq.apitest.method.trade.TakeBuyBTCOfferWithNationalBankAcctTest;
@ -97,4 +98,13 @@ public class TradeTest extends AbstractTradeTest {
test.testBobsConfirmPaymentReceived(testInfo); test.testBobsConfirmPaymentReceived(testInfo);
test.testAlicesBtcWithdrawalToExternalAddress(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; package bisq.apitest.scenario.bot.script;
import bisq.core.util.JsonUtil;
import bisq.common.file.JsonFileManager; import bisq.common.file.JsonFileManager;
import bisq.common.util.Utilities;
import joptsimple.BuiltinHelpFormatter; import joptsimple.BuiltinHelpFormatter;
import joptsimple.OptionParser; import joptsimple.OptionParser;
@ -214,7 +215,7 @@ public class BotScriptGenerator {
} }
private String generateBotScriptTemplate() { private String generateBotScriptTemplate() {
return Utilities.objectToJson(new BotScript( return JsonUtil.objectToJson(new BotScript(
useTestHarness, useTestHarness,
botPaymentMethodId, botPaymentMethodId,
countryCode, countryCode,

View file

@ -32,7 +32,7 @@ configure(subprojects) {
ext { // in alphabetical order ext { // in alphabetical order
bcVersion = '1.63' bcVersion = '1.63'
bitcoinjVersion = '3186b20' bitcoinjVersion = '42bbae9'
codecVersion = '1.13' codecVersion = '1.13'
easybindVersion = '1.0.3' easybindVersion = '1.0.3'
easyVersion = '4.0.1' 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 bisq.cli.ColumnHeaderConstants.COL_HEADER_DIRECTION;
import static java.lang.String.format; import static java.lang.String.format;
import static protobuf.OfferPayload.Direction.BUY; import static protobuf.OfferDirection.BUY;
import static protobuf.OfferPayload.Direction.SELL; import static protobuf.OfferDirection.SELL;
class DirectionFormat { class DirectionFormat {

View file

@ -20,12 +20,17 @@ package bisq.cli;
import bisq.proto.grpc.AddressBalanceInfo; import bisq.proto.grpc.AddressBalanceInfo;
import bisq.proto.grpc.BalancesInfo; import bisq.proto.grpc.BalancesInfo;
import bisq.proto.grpc.BsqBalanceInfo; import bisq.proto.grpc.BsqBalanceInfo;
import bisq.proto.grpc.BsqSwapOfferInfo;
import bisq.proto.grpc.BsqSwapTradeInfo;
import bisq.proto.grpc.BtcBalanceInfo; import bisq.proto.grpc.BtcBalanceInfo;
import bisq.proto.grpc.CreateBsqSwapOfferRequest;
import bisq.proto.grpc.GetMethodHelpRequest; import bisq.proto.grpc.GetMethodHelpRequest;
import bisq.proto.grpc.GetVersionRequest; import bisq.proto.grpc.GetVersionRequest;
import bisq.proto.grpc.OfferInfo; import bisq.proto.grpc.OfferInfo;
import bisq.proto.grpc.RegisterDisputeAgentRequest; import bisq.proto.grpc.RegisterDisputeAgentRequest;
import bisq.proto.grpc.StopRequest; import bisq.proto.grpc.StopRequest;
import bisq.proto.grpc.TakeBsqSwapOfferReply;
import bisq.proto.grpc.TakeBsqSwapOfferRequest;
import bisq.proto.grpc.TakeOfferReply; import bisq.proto.grpc.TakeOfferReply;
import bisq.proto.grpc.TradeInfo; import bisq.proto.grpc.TradeInfo;
import bisq.proto.grpc.TxFeeRateInfo; import bisq.proto.grpc.TxFeeRateInfo;
@ -137,6 +142,21 @@ public final class GrpcClient {
return walletsServiceRequest.getTransaction(txId); 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, public OfferInfo createFixedPricedOffer(String direction,
String currencyCode, String currencyCode,
long amount, long amount,
@ -243,14 +263,26 @@ public final class GrpcClient {
offersServiceRequest.cancelOffer(offerId); offersServiceRequest.cancelOffer(offerId);
} }
public BsqSwapOfferInfo getBsqSwapOffer(String offerId) {
return offersServiceRequest.getBsqSwapOffer(offerId);
}
public OfferInfo getOffer(String offerId) { public OfferInfo getOffer(String offerId) {
return offersServiceRequest.getOffer(offerId); return offersServiceRequest.getOffer(offerId);
} }
public BsqSwapOfferInfo getMyBsqSwapOffer(String offerId) {
return offersServiceRequest.getMyBsqSwapOffer(offerId);
}
public OfferInfo getMyOffer(String offerId) { public OfferInfo getMyOffer(String offerId) {
return offersServiceRequest.getMyOffer(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) { public List<OfferInfo> getOffers(String direction, String currencyCode) {
return offersServiceRequest.getOffers(direction, currencyCode); return offersServiceRequest.getOffers(direction, currencyCode);
} }
@ -271,6 +303,14 @@ public final class GrpcClient {
return offersServiceRequest.getBsqOffersSortedByDate(); 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) { public List<OfferInfo> getMyOffers(String direction, String currencyCode) {
return offersServiceRequest.getMyOffers(direction, currencyCode); return offersServiceRequest.getMyOffers(direction, currencyCode);
} }
@ -291,22 +331,53 @@ public final class GrpcClient {
return offersServiceRequest.getMyBsqOffersSortedByDate(); return offersServiceRequest.getMyBsqOffersSortedByDate();
} }
public List<BsqSwapOfferInfo> getMyBsqSwapBsqOffersSortedByDate() {
return offersServiceRequest.getMyBsqSwapOffersSortedByDate();
}
public OfferInfo getMostRecentOffer(String direction, String currencyCode) { public OfferInfo getMostRecentOffer(String direction, String currencyCode) {
return offersServiceRequest.getMostRecentOffer(direction, currencyCode); return offersServiceRequest.getMostRecentOffer(direction, currencyCode);
} }
public List<BsqSwapOfferInfo> sortBsqSwapOffersByDate(List<BsqSwapOfferInfo> offerInfoList) {
return offersServiceRequest.sortBsqSwapOffersByDate(offerInfoList);
}
public List<OfferInfo> sortOffersByDate(List<OfferInfo> offerInfoList) { public List<OfferInfo> sortOffersByDate(List<OfferInfo> offerInfoList) {
return offersServiceRequest.sortOffersByDate(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) { public TakeOfferReply getTakeOfferReply(String offerId, String paymentAccountId, String takerFeeCurrencyCode) {
return tradesServiceRequest.getTakeOfferReply(offerId, paymentAccountId, 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) { public TradeInfo takeOffer(String offerId, String paymentAccountId, String takerFeeCurrencyCode) {
return tradesServiceRequest.takeOffer(offerId, paymentAccountId, takerFeeCurrencyCode); return tradesServiceRequest.takeOffer(offerId, paymentAccountId, takerFeeCurrencyCode);
} }
public BsqSwapTradeInfo getBsqSwapTrade(String tradeId) {
return tradesServiceRequest.getBsqSwapTrade(tradeId);
}
public TradeInfo getTrade(String tradeId) { public TradeInfo getTrade(String tradeId) {
return tradesServiceRequest.getTrade(tradeId); return tradesServiceRequest.getTrade(tradeId);
} }

View file

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

View file

@ -20,10 +20,7 @@ package bisq.cli.opts;
import joptsimple.OptionSpec; import joptsimple.OptionSpec;
import static bisq.cli.opts.OptLabel.OPT_ACCOUNT_NAME; import static bisq.cli.opts.OptLabel.*;
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;
public class CreateCryptoCurrencyPaymentAcctOptionParser extends AbstractMethodOptionParser implements MethodOpts { public class CreateCryptoCurrencyPaymentAcctOptionParser extends AbstractMethodOptionParser implements MethodOpts {
@ -41,6 +38,11 @@ public class CreateCryptoCurrencyPaymentAcctOptionParser extends AbstractMethodO
.ofType(boolean.class) .ofType(boolean.class)
.defaultsTo(Boolean.FALSE); .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) { public CreateCryptoCurrencyPaymentAcctOptionParser(String[] args) {
super(args); super(args);
} }
@ -82,4 +84,8 @@ public class CreateCryptoCurrencyPaymentAcctOptionParser extends AbstractMethodO
public boolean getIsTradeInstant() { public boolean getIsTradeInstant() {
return options.valueOf(tradeInstantOpt); 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_REGISTRATION_KEY = "registration-key";
public final static String OPT_SECURITY_DEPOSIT = "security-deposit"; public final static String OPT_SECURITY_DEPOSIT = "security-deposit";
public final static String OPT_SHOW_CONTRACT = "show-contract"; 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_ID = "trade-id";
public final static String OPT_TRADE_INSTANT = "trade-instant"; public final static String OPT_TRADE_INSTANT = "trade-instant";
public final static String OPT_TIMEOUT = "timeout"; public final static String OPT_TIMEOUT = "timeout";

View file

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

View file

@ -17,6 +17,7 @@
package bisq.cli.request; package bisq.cli.request;
import bisq.proto.grpc.BsqSwapTradeInfo;
import bisq.proto.grpc.ConfirmPaymentReceivedRequest; import bisq.proto.grpc.ConfirmPaymentReceivedRequest;
import bisq.proto.grpc.ConfirmPaymentStartedRequest; import bisq.proto.grpc.ConfirmPaymentStartedRequest;
import bisq.proto.grpc.GetTradeRequest; import bisq.proto.grpc.GetTradeRequest;
@ -55,6 +56,13 @@ public class TradesServiceRequest {
throw new IllegalStateException(reply.getFailureReason().getDescription()); 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) { public TradeInfo getTrade(String tradeId) {
var request = GetTradeRequest.newBuilder() var request = GetTradeRequest.newBuilder()
.setTradeId(tradeId) .setTradeId(tradeId)

View file

@ -42,5 +42,6 @@ public enum Capability {
REFUND_AGENT, // Supports refund agents 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. 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. 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)) { if (ConfigFileOption.isOption(line)) {
ConfigFileOption option = ConfigFileOption.parse(line); ConfigFileOption option = ConfigFileOption.parse(line);
if (option.name.equals(name)) { if (option.name.equals(name)) {
log.warn("Cleared existing config file option '{}'", option); log.debug("Cleared existing config file option '{}'", option);
continue; 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 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.base.Splitter;
import com.google.common.util.concurrent.FutureCallback; import com.google.common.util.concurrent.FutureCallback;
import com.google.common.util.concurrent.ListeningExecutorService; import com.google.common.util.concurrent.ListeningExecutorService;
@ -87,15 +82,6 @@ import static com.google.common.base.Preconditions.checkNotNull;
@Slf4j @Slf4j
public class Utilities { 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) { public static ExecutorService getSingleThreadExecutor(String name) {
final ThreadFactory threadFactory = new ThreadFactoryBuilder() final ThreadFactory threadFactory = new ThreadFactoryBuilder()
@ -449,18 +435,6 @@ public class Utilities {
return new File(Utilities.class.getProtectionDomain().getCodeSource().getLocation().toURI()).getPath(); 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) { public static String toTruncatedString(Object message) {
return toTruncatedString(message, 200, true); 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.CurrencyUtil;
import bisq.core.locale.Res; import bisq.core.locale.Res;
import bisq.core.offer.Offer; import bisq.core.offer.Offer;
import bisq.core.offer.OfferPayload; import bisq.core.offer.OfferDirection;
import bisq.core.offer.OfferRestrictions; import bisq.core.offer.OfferRestrictions;
import bisq.core.payment.AssetAccount; import bisq.core.payment.AssetAccount;
import bisq.core.payment.ChargeBackRisk; 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.Dispute;
import bisq.core.support.dispute.DisputeResult; import bisq.core.support.dispute.DisputeResult;
import bisq.core.support.dispute.arbitration.TraderDataItem; import bisq.core.support.dispute.arbitration.TraderDataItem;
import bisq.core.trade.Contract; import bisq.core.trade.model.bisq_v1.Contract;
import bisq.core.trade.Trade; import bisq.core.trade.model.bisq_v1.Trade;
import bisq.core.trade.protocol.TradingPeer; import bisq.core.trade.protocol.bisq_v1.model.TradingPeer;
import bisq.core.user.User; import bisq.core.user.User;
import bisq.network.p2p.BootstrapListener; import bisq.network.p2p.BootstrapListener;
@ -308,7 +308,7 @@ public class AccountAgeWitnessService {
} }
private Optional<AccountAgeWitness> findTradePeerWitness(Trade trade) { private Optional<AccountAgeWitness> findTradePeerWitness(Trade trade) {
TradingPeer tradingPeer = trade.getProcessModel().getTradingPeer(); TradingPeer tradingPeer = trade.getProcessModel().getTradePeer();
return (tradingPeer == null || return (tradingPeer == null ||
tradingPeer.getPaymentAccountPayload() == null || tradingPeer.getPaymentAccountPayload() == null ||
tradingPeer.getPubKeyRing() == null) ? tradingPeer.getPubKeyRing() == null) ?
@ -421,11 +421,11 @@ public class AccountAgeWitnessService {
String currencyCode, String currencyCode,
AccountAgeWitness accountAgeWitness, AccountAgeWitness accountAgeWitness,
AccountAge accountAgeCategory, AccountAge accountAgeCategory,
OfferPayload.Direction direction, OfferDirection direction,
PaymentMethod paymentMethod) { PaymentMethod paymentMethod) {
if (CurrencyUtil.isCryptoCurrency(currencyCode) || if (CurrencyUtil.isCryptoCurrency(currencyCode) ||
!PaymentMethod.hasChargebackRisk(paymentMethod, currencyCode) || !PaymentMethod.hasChargebackRisk(paymentMethod, currencyCode) ||
direction == OfferPayload.Direction.SELL) { direction == OfferDirection.SELL) {
return maxTradeLimit.value; return maxTradeLimit.value;
} }
@ -500,7 +500,7 @@ public class AccountAgeWitnessService {
return getAccountAge(getMyWitness(paymentAccountPayload), new Date()); 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) if (paymentAccount == null)
return 0; return 0;
@ -564,7 +564,7 @@ public class AccountAgeWitnessService {
return false; return false;
// Check if the peers trade limit is not less than the trade amount // 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)) { errorMessageHandler)) {
log.error("verifyPeersTradeLimit failed: peersPaymentAccountPayload {}", peersPaymentAccountPayload); log.error("verifyPeersTradeLimit failed: peersPaymentAccountPayload {}", peersPaymentAccountPayload);
return false; return false;
@ -641,13 +641,12 @@ public class AccountAgeWitnessService {
ErrorMessageHandler errorMessageHandler) { ErrorMessageHandler errorMessageHandler) {
checkNotNull(offer); checkNotNull(offer);
final String currencyCode = offer.getCurrencyCode(); final String currencyCode = offer.getCurrencyCode();
final Coin defaultMaxTradeLimit = PaymentMethod.getPaymentMethodById( final Coin defaultMaxTradeLimit = offer.getPaymentMethod().getMaxTradeLimitAsCoin(currencyCode);
offer.getOfferPayload().getPaymentMethodId()).getMaxTradeLimitAsCoin(currencyCode);
long peersCurrentTradeLimit = defaultMaxTradeLimit.value; long peersCurrentTradeLimit = defaultMaxTradeLimit.value;
if (!hasTradeLimitException(peersWitness)) { if (!hasTradeLimitException(peersWitness)) {
final long accountSignAge = getWitnessSignAge(peersWitness, peersCurrentDate); final long accountSignAge = getWitnessSignAge(peersWitness, peersCurrentDate);
AccountAge accountAgeCategory = getPeersAccountAgeCategory(accountSignAge); AccountAge accountAgeCategory = getPeersAccountAgeCategory(accountSignAge);
OfferPayload.Direction direction = offer.isMyOffer(keyRing) ? OfferDirection direction = offer.isMyOffer(keyRing) ?
offer.getMirroredDirection() : offer.getDirection(); offer.getMirroredDirection() : offer.getDirection();
peersCurrentTradeLimit = getTradeLimit(defaultMaxTradeLimit, currencyCode, peersWitness, peersCurrentTradeLimit = getTradeLimit(defaultMaxTradeLimit, currencyCode, peersWitness,
accountAgeCategory, direction, offer.getPaymentMethod()); accountAgeCategory, direction, offer.getPaymentMethod());
@ -731,9 +730,9 @@ public class AccountAgeWitnessService {
public Optional<SignedWitness> traderSignAndPublishPeersAccountAgeWitness(Trade trade) { public Optional<SignedWitness> traderSignAndPublishPeersAccountAgeWitness(Trade trade) {
AccountAgeWitness peersWitness = findTradePeerWitness(trade).orElse(null); AccountAgeWitness peersWitness = findTradePeerWitness(trade).orElse(null);
Coin tradeAmount = trade.getTradeAmount(); Coin tradeAmount = trade.getAmount();
checkNotNull(trade.getProcessModel().getTradingPeer().getPubKeyRing(), "Peer must have a keyring"); checkNotNull(trade.getProcessModel().getTradePeer().getPubKeyRing(), "Peer must have a keyring");
PublicKey peersPubKey = trade.getProcessModel().getTradingPeer().getPubKeyRing().getSignaturePubKey(); PublicKey peersPubKey = trade.getProcessModel().getTradePeer().getPubKeyRing().getSignaturePubKey();
checkNotNull(peersWitness, "Not able to find peers witness, unable to sign for trade {}", checkNotNull(peersWitness, "Not able to find peers witness, unable to sign for trade {}",
trade.toString()); trade.toString());
checkNotNull(tradeAmount, "Trade amount must not be null"); checkNotNull(tradeAmount, "Trade amount must not be null");
@ -926,7 +925,7 @@ public class AccountAgeWitnessService {
return accountIsSigner(myWitness) && return accountIsSigner(myWitness) &&
!peerHasSignedWitness(trade) && !peerHasSignedWitness(trade) &&
tradeAmountIsSufficient(trade.getTradeAmount()); tradeAmountIsSufficient(trade.getAmount());
} }
public String getSignInfoFromAccount(PaymentAccount paymentAccount) { 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.SignedWitness;
import bisq.core.account.sign.SignedWitnessService; import bisq.core.account.sign.SignedWitnessService;
import bisq.core.payment.payload.PaymentAccountPayload; 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; import bisq.network.p2p.storage.P2PDataStorage;
@ -143,7 +143,7 @@ public class AccountAgeWitnessUtils {
} }
boolean isSignWitnessTrade = accountAgeWitnessService.accountIsSigner(witness) && boolean isSignWitnessTrade = accountAgeWitnessService.accountIsSigner(witness) &&
!accountAgeWitnessService.peerHasSignedWitness(trade) && !accountAgeWitnessService.peerHasSignedWitness(trade) &&
accountAgeWitnessService.tradeAmountIsSufficient(trade.getTradeAmount()); accountAgeWitnessService.tradeAmountIsSufficient(trade.getAmount());
log.info("AccountSigning debug log: " + log.info("AccountSigning debug log: " +
"\ntradeId: {}" + "\ntradeId: {}" +
"\nis buyer: {}" + "\nis buyer: {}" +
@ -164,8 +164,8 @@ public class AccountAgeWitnessUtils {
checkingSignTrade, // Following cases added to use same logic as in seller signing check checkingSignTrade, // Following cases added to use same logic as in seller signing check
accountAgeWitnessService.accountIsSigner(witness), accountAgeWitnessService.accountIsSigner(witness),
accountAgeWitnessService.peerHasSignedWitness(trade), accountAgeWitnessService.peerHasSignedWitness(trade),
trade.getTradeAmount(), trade.getAmount(),
accountAgeWitnessService.tradeAmountIsSufficient(trade.getTradeAmount()), accountAgeWitnessService.tradeAmountIsSufficient(trade.getAmount()),
isSignWitnessTrade); isSignWitnessTrade);
} }
} }

View file

@ -25,7 +25,9 @@ import bisq.core.offer.Offer;
import bisq.core.offer.OpenOffer; import bisq.core.offer.OpenOffer;
import bisq.core.payment.PaymentAccount; import bisq.core.payment.PaymentAccount;
import bisq.core.payment.payload.PaymentMethod; 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.TradeStatistics3;
import bisq.core.trade.statistics.TradeStatisticsManager; import bisq.core.trade.statistics.TradeStatisticsManager;
@ -117,6 +119,10 @@ public class CoreApi {
// Offers // Offers
/////////////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////////////
public Offer getBsqSwapOffer(String id) {
return coreOffersService.getBsqSwapOffer(id);
}
public Offer getOffer(String id) { public Offer getOffer(String id) {
return coreOffersService.getOffer(id); return coreOffersService.getOffer(id);
} }
@ -125,6 +131,14 @@ public class CoreApi {
return coreOffersService.getMyOffer(id); 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) { public List<Offer> getOffers(String direction, String currencyCode) {
return coreOffersService.getOffers(direction, currencyCode); return coreOffersService.getOffers(direction, currencyCode);
} }
@ -133,7 +147,27 @@ public class CoreApi {
return coreOffersService.getMyOffers(direction, currencyCode); return coreOffersService.getMyOffers(direction, currencyCode);
} }
public void createAnPlaceOffer(String currencyCode, 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 directionAsString,
String priceAsString, String priceAsString,
boolean useMarketBasedPrice, boolean useMarketBasedPrice,
@ -206,11 +240,13 @@ public class CoreApi {
public PaymentAccount createCryptoCurrencyPaymentAccount(String accountName, public PaymentAccount createCryptoCurrencyPaymentAccount(String accountName,
String currencyCode, String currencyCode,
String address, String address,
boolean tradeInstant) { boolean tradeInstant,
boolean isBsqSwap) {
return paymentAccountsService.createCryptoCurrencyPaymentAccount(accountName, return paymentAccountsService.createCryptoCurrencyPaymentAccount(accountName,
currencyCode, currencyCode,
address, address,
tradeInstant); tradeInstant,
isBsqSwap);
} }
public List<PaymentMethod> getCryptoCurrencyPaymentMethods() { public List<PaymentMethod> getCryptoCurrencyPaymentMethods() {
@ -229,6 +265,19 @@ public class CoreApi {
// Trades // 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, public void takeOffer(String offerId,
String paymentAccountId, String paymentAccountId,
String takerFeeCurrencyCode, String takerFeeCurrencyCode,
@ -258,6 +307,10 @@ public class CoreApi {
coreTradesService.withdrawFunds(tradeId, address, memo); coreTradesService.withdrawFunds(tradeId, address, memo);
} }
public BsqSwapTrade getBsqSwapTrade(String tradeId) {
return coreTradesService.getBsqSwapTrade(tradeId);
}
public Trade getTrade(String tradeId) { public Trade getTrade(String tradeId) {
return coreTradesService.getTrade(tradeId); return coreTradesService.getTrade(tradeId);
} }

View file

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

View file

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

View file

@ -21,14 +21,17 @@ import bisq.core.btc.model.AddressEntry;
import bisq.core.btc.wallet.BtcWalletService; import bisq.core.btc.wallet.BtcWalletService;
import bisq.core.offer.Offer; import bisq.core.offer.Offer;
import bisq.core.offer.OfferUtil; import bisq.core.offer.OfferUtil;
import bisq.core.offer.takeoffer.TakeOfferModel; import bisq.core.offer.bisq_v1.TakeOfferModel;
import bisq.core.trade.Tradable; import bisq.core.offer.bsq_swap.BsqSwapTakeOfferModel;
import bisq.core.trade.Trade; import bisq.core.trade.ClosedTradableManager;
import bisq.core.trade.TradeManager; import bisq.core.trade.TradeManager;
import bisq.core.trade.TradeUtil; import bisq.core.trade.bisq_v1.TradeResultHandler;
import bisq.core.trade.closed.ClosedTradableManager; import bisq.core.trade.bisq_v1.TradeUtil;
import bisq.core.trade.protocol.BuyerProtocol; import bisq.core.trade.model.Tradable;
import bisq.core.trade.protocol.SellerProtocol; 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.user.User;
import bisq.core.util.validation.BtcAddressValidator; import bisq.core.util.validation.BtcAddressValidator;
@ -60,6 +63,7 @@ class CoreTradesService {
private final OfferUtil offerUtil; private final OfferUtil offerUtil;
private final ClosedTradableManager closedTradableManager; private final ClosedTradableManager closedTradableManager;
private final TakeOfferModel takeOfferModel; private final TakeOfferModel takeOfferModel;
private final BsqSwapTakeOfferModel bsqSwapTakeOfferModel;
private final TradeManager tradeManager; private final TradeManager tradeManager;
private final TradeUtil tradeUtil; private final TradeUtil tradeUtil;
private final User user; private final User user;
@ -71,6 +75,7 @@ class CoreTradesService {
OfferUtil offerUtil, OfferUtil offerUtil,
ClosedTradableManager closedTradableManager, ClosedTradableManager closedTradableManager,
TakeOfferModel takeOfferModel, TakeOfferModel takeOfferModel,
BsqSwapTakeOfferModel bsqSwapTakeOfferModel,
TradeManager tradeManager, TradeManager tradeManager,
TradeUtil tradeUtil, TradeUtil tradeUtil,
User user) { User user) {
@ -80,11 +85,33 @@ class CoreTradesService {
this.offerUtil = offerUtil; this.offerUtil = offerUtil;
this.closedTradableManager = closedTradableManager; this.closedTradableManager = closedTradableManager;
this.takeOfferModel = takeOfferModel; this.takeOfferModel = takeOfferModel;
this.bsqSwapTakeOfferModel = bsqSwapTakeOfferModel;
this.tradeManager = tradeManager; this.tradeManager = tradeManager;
this.tradeUtil = tradeUtil; this.tradeUtil = tradeUtil;
this.user = user; 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, void takeOffer(Offer offer,
String paymentAccountId, String paymentAccountId,
String takerFeeCurrencyCode, String takerFeeCurrencyCode,
@ -100,7 +127,7 @@ class CoreTradesService {
throw new IllegalArgumentException(format("payment account with id '%s' not found", paymentAccountId)); throw new IllegalArgumentException(format("payment account with id '%s' not found", paymentAccountId));
var useSavingsWallet = true; var useSavingsWallet = true;
//noinspection ConstantConditions
takeOfferModel.initModel(offer, paymentAccount, useSavingsWallet); takeOfferModel.initModel(offer, paymentAccount, useSavingsWallet);
log.info("Initiating take {} offer, {}", log.info("Initiating take {} offer, {}",
offer.isBuyOffer() ? "buy" : "sell", 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) { String getTradeRole(String tradeId) {
coreWalletsService.verifyWalletsAreAvailable(); coreWalletsService.verifyWalletsAreAvailable();
coreWalletsService.verifyEncryptedWalletIsUnlocked(); coreWalletsService.verifyEncryptedWalletIsUnlocked();

View file

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

View file

@ -17,20 +17,20 @@ public class BsqBalanceInfo implements Payload {
-1); -1);
// All balances are in BSQ satoshis. // All balances are in BSQ satoshis.
private final long availableConfirmedBalance; private final long availableBalance;
private final long unverifiedBalance; private final long unverifiedBalance;
private final long unconfirmedChangeBalance; private final long unconfirmedChangeBalance;
private final long lockedForVotingBalance; private final long lockedForVotingBalance;
private final long lockupBondsBalance; private final long lockupBondsBalance;
private final long unlockingBondsBalance; private final long unlockingBondsBalance;
public BsqBalanceInfo(long availableConfirmedBalance, public BsqBalanceInfo(long availableBalance,
long unverifiedBalance, long unverifiedBalance,
long unconfirmedChangeBalance, long unconfirmedChangeBalance,
long lockedForVotingBalance, long lockedForVotingBalance,
long lockupBondsBalance, long lockupBondsBalance,
long unlockingBondsBalance) { long unlockingBondsBalance) {
this.availableConfirmedBalance = availableConfirmedBalance; this.availableBalance = availableBalance;
this.unverifiedBalance = unverifiedBalance; this.unverifiedBalance = unverifiedBalance;
this.unconfirmedChangeBalance = unconfirmedChangeBalance; this.unconfirmedChangeBalance = unconfirmedChangeBalance;
this.lockedForVotingBalance = lockedForVotingBalance; this.lockedForVotingBalance = lockedForVotingBalance;
@ -39,14 +39,14 @@ public class BsqBalanceInfo implements Payload {
} }
@VisibleForTesting @VisibleForTesting
public static BsqBalanceInfo valueOf(long availableConfirmedBalance, public static BsqBalanceInfo valueOf(long availableBalance,
long unverifiedBalance, long unverifiedBalance,
long unconfirmedChangeBalance, long unconfirmedChangeBalance,
long lockedForVotingBalance, long lockedForVotingBalance,
long lockupBondsBalance, long lockupBondsBalance,
long unlockingBondsBalance) { long unlockingBondsBalance) {
// Convenience for creating a model instance instead of a proto. // Convenience for creating a model instance instead of a proto.
return new BsqBalanceInfo(availableConfirmedBalance, return new BsqBalanceInfo(availableBalance,
unverifiedBalance, unverifiedBalance,
unconfirmedChangeBalance, unconfirmedChangeBalance,
lockedForVotingBalance, lockedForVotingBalance,
@ -58,10 +58,11 @@ public class BsqBalanceInfo implements Payload {
// PROTO BUFFER // PROTO BUFFER
/////////////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////////////
// TODO rename availableConfirmedBalance in proto if possible
@Override @Override
public bisq.proto.grpc.BsqBalanceInfo toProtoMessage() { public bisq.proto.grpc.BsqBalanceInfo toProtoMessage() {
return bisq.proto.grpc.BsqBalanceInfo.newBuilder() return bisq.proto.grpc.BsqBalanceInfo.newBuilder()
.setAvailableConfirmedBalance(availableConfirmedBalance) .setAvailableConfirmedBalance(availableBalance)
.setUnverifiedBalance(unverifiedBalance) .setUnverifiedBalance(unverifiedBalance)
.setUnconfirmedChangeBalance(unconfirmedChangeBalance) .setUnconfirmedChangeBalance(unconfirmedChangeBalance)
.setLockedForVotingBalance(lockedForVotingBalance) .setLockedForVotingBalance(lockedForVotingBalance)
@ -83,7 +84,7 @@ public class BsqBalanceInfo implements Payload {
@Override @Override
public String toString() { public String toString() {
return "BsqBalanceInfo{" + return "BsqBalanceInfo{" +
"availableConfirmedBalance=" + availableConfirmedBalance + "availableBalance=" + availableBalance +
", unverifiedBalance=" + unverifiedBalance + ", unverifiedBalance=" + unverifiedBalance +
", unconfirmedChangeBalance=" + unconfirmedChangeBalance + ", unconfirmedChangeBalance=" + unconfirmedChangeBalance +
", lockedForVotingBalance=" + lockedForVotingBalance + ", 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()) .withPaymentAccountId(offer.getMakerPaymentAccountId())
.withPaymentMethodId(offer.getPaymentMethod().getId()) .withPaymentMethodId(offer.getPaymentMethod().getId())
.withPaymentMethodShortName(offer.getPaymentMethod().getShortName()) .withPaymentMethodShortName(offer.getPaymentMethod().getShortName())
.withBaseCurrencyCode(offer.getOfferPayload().getBaseCurrencyCode()) .withBaseCurrencyCode(offer.getBaseCurrencyCode())
.withCounterCurrencyCode(offer.getOfferPayload().getCounterCurrencyCode()) .withCounterCurrencyCode(offer.getCounterCurrencyCode())
.withDate(offer.getDate().getTime()) .withDate(offer.getDate().getTime())
.withState(offer.getState().name()) .withState(offer.getState().name())
.withIsMyOffer(isMyOffer); .withIsMyOffer(isMyOffer);

View file

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

View file

@ -23,6 +23,7 @@ import bisq.core.btc.wallet.BtcWalletService;
import bisq.core.dao.DaoSetup; import bisq.core.dao.DaoSetup;
import bisq.core.dao.node.full.RpcService; import bisq.core.dao.node.full.RpcService;
import bisq.core.offer.OpenOfferManager; import bisq.core.offer.OpenOfferManager;
import bisq.core.offer.bsq_swap.OpenBsqSwapOfferService;
import bisq.core.provider.price.PriceFeedService; import bisq.core.provider.price.PriceFeedService;
import bisq.core.setup.CorePersistedDataHost; import bisq.core.setup.CorePersistedDataHost;
import bisq.core.setup.CoreSetup; import bisq.core.setup.CoreSetup;
@ -227,6 +228,7 @@ public abstract class BisqExecutable implements GracefulShutDownHandler, BisqSet
} }
try { try {
injector.getInstance(OpenBsqSwapOfferService.class).shutDown();
injector.getInstance(PriceFeedService.class).shutDown(); injector.getInstance(PriceFeedService.class).shutDown();
injector.getInstance(ArbitratorManager.class).shutDown(); injector.getInstance(ArbitratorManager.class).shutDown();
injector.getInstance(TradeStatisticsManager.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.mediation.MediationManager;
import bisq.core.support.dispute.refund.RefundManager; import bisq.core.support.dispute.refund.RefundManager;
import bisq.core.trade.TradeManager; 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.Preferences;
import bisq.core.user.User; import bisq.core.user.User;
import bisq.core.util.FormattingUtils; 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 // miner fee was too low and the transaction got removed from mempool and got out from our wallet after a
// resync. // resync.
openOfferManager.getObservableList().forEach(e -> { openOfferManager.getObservableList().forEach(e -> {
if (e.getOffer().isBsqSwapOffer()) {
return;
}
String offerFeePaymentTxId = e.getOffer().getOfferFeePaymentTxId(); String offerFeePaymentTxId = e.getOffer().getOfferFeePaymentTxId();
if (btcWalletService.getConfidenceForTxId(offerFeePaymentTxId) == null) { if (btcWalletService.getConfidenceForTxId(offerFeePaymentTxId) == null) {
String message = Res.get("popup.warning.openOfferWithInvalidMakerFeeTx", String message = Res.get("popup.warning.openOfferWithInvalidMakerFeeTx",
@ -657,7 +660,10 @@ public class BisqSetup {
} }
private void maybeShowLocalhostRunningInfo() { private void maybeShowLocalhostRunningInfo() {
maybeTriggerDisplayHandler("bitcoinLocalhostNode", displayLocalhostHandler, localBitcoinNode.shouldBeUsed()); if (Config.baseCurrencyNetwork().isMainnet()) {
maybeTriggerDisplayHandler("bitcoinLocalhostNode", displayLocalhostHandler,
localBitcoinNode.shouldBeUsed());
}
} }
private void maybeShowAccountSigningStateInfo() { 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.market.MarketAlerts;
import bisq.core.notifications.alerts.price.PriceAlert; import bisq.core.notifications.alerts.price.PriceAlert;
import bisq.core.offer.OpenOfferManager; 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.AmazonGiftCardAccount;
import bisq.core.payment.RevolutAccount; import bisq.core.payment.RevolutAccount;
import bisq.core.payment.TradeLimits; 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.RefundManager;
import bisq.core.support.dispute.refund.refundagent.RefundAgentManager; import bisq.core.support.dispute.refund.refundagent.RefundAgentManager;
import bisq.core.support.traderchat.TraderChatManager; import bisq.core.support.traderchat.TraderChatManager;
import bisq.core.trade.ClosedTradableManager;
import bisq.core.trade.TradeManager; import bisq.core.trade.TradeManager;
import bisq.core.trade.closed.ClosedTradableManager; import bisq.core.trade.bisq_v1.FailedTradesManager;
import bisq.core.trade.failed.FailedTradesManager; import bisq.core.trade.bsq_swap.BsqSwapTradeManager;
import bisq.core.trade.statistics.TradeStatisticsManager; import bisq.core.trade.statistics.TradeStatisticsManager;
import bisq.core.trade.txproof.xmr.XmrTxProofService; import bisq.core.trade.txproof.xmr.XmrTxProofService;
import bisq.core.user.User; import bisq.core.user.User;
@ -84,6 +86,7 @@ public class DomainInitialisation {
private final TraderChatManager traderChatManager; private final TraderChatManager traderChatManager;
private final TradeManager tradeManager; private final TradeManager tradeManager;
private final ClosedTradableManager closedTradableManager; private final ClosedTradableManager closedTradableManager;
private final BsqSwapTradeManager bsqSwapTradeManager;
private final FailedTradesManager failedTradesManager; private final FailedTradesManager failedTradesManager;
private final XmrTxProofService xmrTxProofService; private final XmrTxProofService xmrTxProofService;
private final OpenOfferManager openOfferManager; private final OpenOfferManager openOfferManager;
@ -112,6 +115,7 @@ public class DomainInitialisation {
private final DaoStateSnapshotService daoStateSnapshotService; private final DaoStateSnapshotService daoStateSnapshotService;
private final TriggerPriceService triggerPriceService; private final TriggerPriceService triggerPriceService;
private final MempoolService mempoolService; private final MempoolService mempoolService;
private final OpenBsqSwapOfferService openBsqSwapOfferService;
@Inject @Inject
public DomainInitialisation(ClockWatcher clockWatcher, public DomainInitialisation(ClockWatcher clockWatcher,
@ -122,6 +126,7 @@ public class DomainInitialisation {
TraderChatManager traderChatManager, TraderChatManager traderChatManager,
TradeManager tradeManager, TradeManager tradeManager,
ClosedTradableManager closedTradableManager, ClosedTradableManager closedTradableManager,
BsqSwapTradeManager bsqSwapTradeManager,
FailedTradesManager failedTradesManager, FailedTradesManager failedTradesManager,
XmrTxProofService xmrTxProofService, XmrTxProofService xmrTxProofService,
OpenOfferManager openOfferManager, OpenOfferManager openOfferManager,
@ -149,7 +154,8 @@ public class DomainInitialisation {
User user, User user,
DaoStateSnapshotService daoStateSnapshotService, DaoStateSnapshotService daoStateSnapshotService,
TriggerPriceService triggerPriceService, TriggerPriceService triggerPriceService,
MempoolService mempoolService) { MempoolService mempoolService,
OpenBsqSwapOfferService openBsqSwapOfferService) {
this.clockWatcher = clockWatcher; this.clockWatcher = clockWatcher;
this.tradeLimits = tradeLimits; this.tradeLimits = tradeLimits;
this.arbitrationManager = arbitrationManager; this.arbitrationManager = arbitrationManager;
@ -158,6 +164,7 @@ public class DomainInitialisation {
this.traderChatManager = traderChatManager; this.traderChatManager = traderChatManager;
this.tradeManager = tradeManager; this.tradeManager = tradeManager;
this.closedTradableManager = closedTradableManager; this.closedTradableManager = closedTradableManager;
this.bsqSwapTradeManager = bsqSwapTradeManager;
this.failedTradesManager = failedTradesManager; this.failedTradesManager = failedTradesManager;
this.xmrTxProofService = xmrTxProofService; this.xmrTxProofService = xmrTxProofService;
this.openOfferManager = openOfferManager; this.openOfferManager = openOfferManager;
@ -186,6 +193,7 @@ public class DomainInitialisation {
this.daoStateSnapshotService = daoStateSnapshotService; this.daoStateSnapshotService = daoStateSnapshotService;
this.triggerPriceService = triggerPriceService; this.triggerPriceService = triggerPriceService;
this.mempoolService = mempoolService; this.mempoolService = mempoolService;
this.openBsqSwapOfferService = openBsqSwapOfferService;
} }
public void initDomainServices(Consumer<String> rejectedTxErrorMessageHandler, public void initDomainServices(Consumer<String> rejectedTxErrorMessageHandler,
@ -210,10 +218,12 @@ public class DomainInitialisation {
traderChatManager.onAllServicesInitialized(); traderChatManager.onAllServicesInitialized();
closedTradableManager.onAllServicesInitialized(); closedTradableManager.onAllServicesInitialized();
bsqSwapTradeManager.onAllServicesInitialized();
failedTradesManager.onAllServicesInitialized(); failedTradesManager.onAllServicesInitialized();
xmrTxProofService.onAllServicesInitialized(); xmrTxProofService.onAllServicesInitialized();
openOfferManager.onAllServicesInitialized(); openOfferManager.onAllServicesInitialized();
openBsqSwapOfferService.onAllServicesInitialized();
balances.onAllServicesInitialized(); balances.onAllServicesInitialized();

View file

@ -18,6 +18,8 @@
package bisq.core.app; package bisq.core.app;
import bisq.core.btc.setup.WalletsSetup; import bisq.core.btc.setup.WalletsSetup;
import bisq.core.filter.Filter;
import bisq.core.filter.FilterManager;
import bisq.core.locale.Res; import bisq.core.locale.Res;
import bisq.core.provider.price.PriceFeedService; import bisq.core.provider.price.PriceFeedService;
import bisq.core.user.Preferences; 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.CloseConnectionReason;
import bisq.network.p2p.network.Connection; import bisq.network.p2p.network.Connection;
import bisq.network.p2p.network.ConnectionListener; import bisq.network.p2p.network.ConnectionListener;
import bisq.network.p2p.storage.payload.ProofOfWorkPayload;
import javax.inject.Inject; import javax.inject.Inject;
import javax.inject.Singleton; import javax.inject.Singleton;
@ -71,25 +74,31 @@ public class P2PNetworkSetup {
final BooleanProperty updatedDataReceived = new SimpleBooleanProperty(); final BooleanProperty updatedDataReceived = new SimpleBooleanProperty();
@Getter @Getter
final BooleanProperty p2pNetworkFailed = new SimpleBooleanProperty(); final BooleanProperty p2pNetworkFailed = new SimpleBooleanProperty();
final FilterManager filterManager;
@Inject @Inject
public P2PNetworkSetup(PriceFeedService priceFeedService, public P2PNetworkSetup(PriceFeedService priceFeedService,
P2PService p2PService, P2PService p2PService,
WalletsSetup walletsSetup, WalletsSetup walletsSetup,
Preferences preferences) { Preferences preferences,
FilterManager filterManager) {
this.priceFeedService = priceFeedService; this.priceFeedService = priceFeedService;
this.p2PService = p2PService; this.p2PService = p2PService;
this.walletsSetup = walletsSetup; this.walletsSetup = walletsSetup;
this.preferences = preferences; 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 bootstrapState = new SimpleStringProperty();
StringProperty bootstrapWarning = new SimpleStringProperty(); StringProperty bootstrapWarning = new SimpleStringProperty();
BooleanProperty hiddenServicePublished = new SimpleBooleanProperty(); BooleanProperty hiddenServicePublished = new SimpleBooleanProperty();
BooleanProperty initialP2PNetworkDataReceived = new SimpleBooleanProperty(); BooleanProperty initialP2PNetworkDataReceived = new SimpleBooleanProperty();
addP2PMessageFilter();
p2PNetworkInfoBinding = EasyBind.combine(bootstrapState, bootstrapWarning, p2PService.getNumConnectedPeers(), p2PNetworkInfoBinding = EasyBind.combine(bootstrapState, bootstrapWarning, p2PService.getNumConnectedPeers(),
walletsSetup.numPeersProperty(), hiddenServicePublished, initialP2PNetworkDataReceived, walletsSetup.numPeersProperty(), hiddenServicePublished, initialP2PNetworkDataReceived,
(state, warning, numP2pPeers, numBtcPeers, hiddenService, dataReceived) -> { (state, warning, numP2pPeers, numBtcPeers, hiddenService, dataReceived) -> {
@ -225,4 +234,13 @@ public class P2PNetworkSetup {
public void setSplashP2PNetworkAnimationVisible(boolean value) { public void setSplashP2PNetworkAnimationVisible(boolean value) {
splashP2PNetworkAnimationVisible.set(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.DaoSetup;
import bisq.core.dao.node.full.RpcService; import bisq.core.dao.node.full.RpcService;
import bisq.core.offer.OpenOfferManager; import bisq.core.offer.OpenOfferManager;
import bisq.core.offer.bsq_swap.OpenBsqSwapOfferService;
import bisq.core.support.dispute.arbitration.arbitrator.ArbitratorManager; import bisq.core.support.dispute.arbitration.arbitrator.ArbitratorManager;
import bisq.network.p2p.NodeAddress; import bisq.network.p2p.NodeAddress;
@ -87,6 +88,7 @@ public abstract class ExecutableForAppWithP2p extends BisqExecutable {
try { try {
if (injector != null) { if (injector != null) {
JsonFileManager.shutDownAllInstances(); JsonFileManager.shutDownAllInstances();
injector.getInstance(OpenBsqSwapOfferService.class).shutDown();
injector.getInstance(RpcService.class).shutDown(); injector.getInstance(RpcService.class).shutDown();
injector.getInstance(DaoSetup.class).shutDown(); injector.getInstance(DaoSetup.class).shutDown();
injector.getInstance(ArbitratorManager.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.offer.OpenOfferManager;
import bisq.core.support.dispute.Dispute; import bisq.core.support.dispute.Dispute;
import bisq.core.support.dispute.refund.RefundManager; 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.TradeManager;
import bisq.core.trade.closed.ClosedTradableManager; import bisq.core.trade.bisq_v1.FailedTradesManager;
import bisq.core.trade.failed.FailedTradesManager; import bisq.core.trade.model.bisq_v1.Trade;
import bisq.common.UserThread; import bisq.common.UserThread;

View file

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

View file

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

View file

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

View file

@ -17,33 +17,82 @@
package bisq.core.btc.model; package bisq.core.btc.model;
import bisq.core.btc.wallet.BtcWalletService;
import bisq.common.proto.network.NetworkPayload; import bisq.common.proto.network.NetworkPayload;
import bisq.common.proto.persistable.PersistablePayload; import bisq.common.proto.persistable.PersistablePayload;
import bisq.common.util.Utilities; import bisq.common.util.Utilities;
import com.google.protobuf.ByteString; 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.EqualsAndHashCode;
import lombok.Getter;
import javax.annotation.concurrent.Immutable; import javax.annotation.concurrent.Immutable;
@EqualsAndHashCode @EqualsAndHashCode
@Immutable @Immutable
@Getter
public final class RawTransactionInput implements NetworkPayload, PersistablePayload { public final class RawTransactionInput implements NetworkPayload, PersistablePayload {
public final long index; // Index of spending txo public final long index; // Index of spending txo
public final byte[] parentTransaction; // Spending tx (fromTx) public final byte[] parentTransaction; // Spending tx (fromTx)
public final long value; 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. * Holds the relevant data for the connected output for a tx input.
* @param index the index of the parentTransaction * @param index the index of the parentTransaction
* @param parentTransaction the spending output tx, not the parent tx of the input * @param parentTransaction the spending output tx, not the parent tx of the input
* @param value the number of satoshis being spent * @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.index = index;
this.parentTransaction = parentTransaction; this.parentTransaction = parentTransaction;
this.value = value; this.value = value;
this.scriptTypeId = scriptTypeId;
} }
@Override @Override
@ -52,11 +101,36 @@ public final class RawTransactionInput implements NetworkPayload, PersistablePay
.setIndex(index) .setIndex(index)
.setParentTransaction(ByteString.copyFrom(parentTransaction)) .setParentTransaction(ByteString.copyFrom(parentTransaction))
.setValue(value) .setValue(value)
.setScriptTypeId(scriptTypeId)
.build(); .build();
} }
public static RawTransactionInput fromProto(protobuf.RawTransactionInput proto) { 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 @Override
@ -65,6 +139,7 @@ public final class RawTransactionInput implements NetworkPayload, PersistablePay
"index=" + index + "index=" + index +
", parentTransaction as HEX " + Utilities.bytesAsHexString(parentTransaction) + ", parentTransaction as HEX " + Utilities.bytesAsHexString(parentTransaction) +
", value=" + value + ", 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("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"), 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 // Devin Bileck
new BtcNode("btc1.bisq.services", "devinbtctu7uctl7hly2juu3thbgeivfnvw3ckj3phy6nyvpnx66yeyd.onion", "172.105.21.216", BtcNode.DEFAULT_PORT, "@devinbileck"), 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"), 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("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("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") 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<>(); new ArrayList<>();
} }

View file

@ -26,6 +26,8 @@ import org.bitcoinj.core.TransactionOutput;
import javax.inject.Inject; import javax.inject.Inject;
import lombok.Getter;
import lombok.Setter;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
/** /**
@ -36,9 +38,13 @@ import lombok.extern.slf4j.Slf4j;
public class BsqCoinSelector extends BisqDefaultCoinSelector { public class BsqCoinSelector extends BisqDefaultCoinSelector {
private final DaoStateService daoStateService; private final DaoStateService daoStateService;
private final UnconfirmedBsqChangeOutputListService unconfirmedBsqChangeOutputListService; private final UnconfirmedBsqChangeOutputListService unconfirmedBsqChangeOutputListService;
@Setter
@Getter
private boolean allowSpendMyOwnUnconfirmedTxOutputs = true;
@Inject @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. // permitForeignPendingTx is not relevant here as we do not support pending foreign utxos anyway.
super(false); super(false);
this.daoStateService = daoStateService; this.daoStateService = daoStateService;
@ -53,17 +59,19 @@ public class BsqCoinSelector extends BisqDefaultCoinSelector {
return false; return false;
// If it is a normal confirmed BSQ output we use the default lookup at the daoState // 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; return true;
// It might be that it is an unconfirmed change output which we allow to be used for spending without requiring a confirmation. // 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). // 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; 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 // 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. // 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. // 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 preparedSendTx = bsqWalletService.getPreparedSendBsqTx(address.toString(), receiverAmount);
Transaction txWithBtcFee = btcWalletService.completePreparedSendBsqTx(preparedSendTx, txFeePerVbyte); Transaction txWithBtcFee = btcWalletService.completePreparedSendBsqTx(preparedSendTx, txFeePerVbyte);
Transaction signedTx = bsqWalletService.signTx(txWithBtcFee); Transaction signedTx = bsqWalletService.signTxAndVerifyNoDustOutputs(txWithBtcFee);
return new BsqTransferModel(address, return new BsqTransferModel(address,
receiverAmount, receiverAmount,

View file

@ -22,6 +22,7 @@ import bisq.core.btc.exceptions.InsufficientBsqException;
import bisq.core.btc.exceptions.TransactionVerificationException; import bisq.core.btc.exceptions.TransactionVerificationException;
import bisq.core.btc.exceptions.WalletException; import bisq.core.btc.exceptions.WalletException;
import bisq.core.btc.listeners.BsqBalanceListener; import bisq.core.btc.listeners.BsqBalanceListener;
import bisq.core.btc.model.RawTransactionInput;
import bisq.core.btc.setup.WalletsSetup; import bisq.core.btc.setup.WalletsSetup;
import bisq.core.dao.DaoKillSwitch; import bisq.core.dao.DaoKillSwitch;
import bisq.core.dao.state.DaoStateListener; 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.dao.state.unconfirmed.UnconfirmedBsqChangeOutputListService;
import bisq.core.provider.fee.FeeService; import bisq.core.provider.fee.FeeService;
import bisq.core.user.Preferences; import bisq.core.user.Preferences;
import bisq.core.util.coin.BsqFormatter;
import bisq.common.UserThread; import bisq.common.UserThread;
import bisq.common.util.Tuple2;
import org.bitcoinj.core.Address; import org.bitcoinj.core.Address;
import org.bitcoinj.core.AddressFormatException; 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 CopyOnWriteArraySet<BsqBalanceListener> bsqBalanceListeners = new CopyOnWriteArraySet<>();
private final List<WalletTransactionsChangeListener> walletTransactionsChangeListeners = new ArrayList<>(); private final List<WalletTransactionsChangeListener> walletTransactionsChangeListeners = new ArrayList<>();
private boolean updateBsqWalletTransactionsPending; private boolean updateBsqWalletTransactionsPending;
@Getter
private final BsqFormatter bsqFormatter;
// balance of non BSQ satoshis // balance of non BSQ satoshis
@Getter @Getter
private Coin availableNonBsqBalance = Coin.ZERO; private Coin availableNonBsqBalance = Coin.ZERO;
@Getter @Getter
private Coin availableConfirmedBalance = Coin.ZERO; private Coin availableBalance = Coin.ZERO;
@Getter @Getter
private Coin unverifiedBalance = Coin.ZERO; private Coin unverifiedBalance = Coin.ZERO;
@Getter @Getter
private Coin verifiedBalance = Coin.ZERO;
@Getter
private Coin unconfirmedChangeBalance = Coin.ZERO; private Coin unconfirmedChangeBalance = Coin.ZERO;
@Getter @Getter
private Coin lockedForVotingBalance = Coin.ZERO; private Coin lockedForVotingBalance = Coin.ZERO;
@ -125,7 +133,8 @@ public class BsqWalletService extends WalletService implements DaoStateListener
UnconfirmedBsqChangeOutputListService unconfirmedBsqChangeOutputListService, UnconfirmedBsqChangeOutputListService unconfirmedBsqChangeOutputListService,
Preferences preferences, Preferences preferences,
FeeService feeService, FeeService feeService,
DaoKillSwitch daoKillSwitch) { DaoKillSwitch daoKillSwitch,
BsqFormatter bsqFormatter) {
super(walletsSetup, super(walletsSetup,
preferences, preferences,
feeService); feeService);
@ -135,6 +144,7 @@ public class BsqWalletService extends WalletService implements DaoStateListener
this.daoStateService = daoStateService; this.daoStateService = daoStateService;
this.unconfirmedBsqChangeOutputListService = unconfirmedBsqChangeOutputListService; this.unconfirmedBsqChangeOutputListService = unconfirmedBsqChangeOutputListService;
this.daoKillSwitch = daoKillSwitch; this.daoKillSwitch = daoKillSwitch;
this.bsqFormatter = bsqFormatter;
nonBsqCoinSelector.setPreferences(preferences); nonBsqCoinSelector.setPreferences(preferences);
@ -284,18 +294,20 @@ public class BsqWalletService extends WalletService implements DaoStateListener
.mapToLong(TxOutput::getValue) .mapToLong(TxOutput::getValue)
.sum()); .sum());
availableConfirmedBalance = bsqCoinSelector.select(NetworkParameters.MAX_MONEY, availableBalance = bsqCoinSelector.select(NetworkParameters.MAX_MONEY,
wallet.calculateAllSpendCandidates()).valueGathered; wallet.calculateAllSpendCandidates()).valueGathered;
if (availableConfirmedBalance.isNegative()) if (availableBalance.isNegative())
availableConfirmedBalance = Coin.ZERO; availableBalance = Coin.ZERO;
unconfirmedChangeBalance = unconfirmedBsqChangeOutputListService.getBalance(); unconfirmedChangeBalance = unconfirmedBsqChangeOutputListService.getBalance();
availableNonBsqBalance = nonBsqCoinSelector.select(NetworkParameters.MAX_MONEY, availableNonBsqBalance = nonBsqCoinSelector.select(NetworkParameters.MAX_MONEY,
wallet.calculateAllSpendCandidates()).valueGathered; 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)); unconfirmedChangeBalance, lockedForVotingBalance, lockupBondsBalance, unlockingBondsBalance));
log.info("updateBsqBalance took {} ms", System.currentTimeMillis() - ts); log.info("updateBsqBalance took {} ms", System.currentTimeMillis() - ts);
} }
@ -481,28 +493,10 @@ public class BsqWalletService extends WalletService implements DaoStateListener
// Sign tx // Sign tx
/////////////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////////////
public Transaction signTx(Transaction tx) throws WalletException, TransactionVerificationException { public Transaction signTxAndVerifyNoDustOutputs(Transaction tx)
for (int i = 0; i < tx.getInputs().size(); i++) { throws WalletException, TransactionVerificationException {
TransactionInput txIn = tx.getInputs().get(i); WalletService.signTx(wallet, aesKey, tx);
TransactionOutput connectedOutput = txIn.getConnectedOutput(); WalletService.verifyNonDustTxo(tx);
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);
return tx; return tx;
} }
@ -540,6 +534,7 @@ public class BsqWalletService extends WalletService implements DaoStateListener
return getPreparedSendTx(receiverAddress, receiverAmount, bsqCoinSelector); return getPreparedSendTx(receiverAddress, receiverAmount, bsqCoinSelector);
} }
/////////////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////////////
// Send BTC (non-BSQ) with BTC fee (e.g. the issuance output from a lost comp. request) // 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 // Blind vote tx
/////////////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////////////
@ -781,6 +816,7 @@ public class BsqWalletService extends WalletService implements DaoStateListener
return tx; return tx;
} }
/////////////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////////////
// Unlock bond 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.exceptions.WalletException;
import bisq.core.btc.model.AddressEntry; import bisq.core.btc.model.AddressEntry;
import bisq.core.btc.model.AddressEntryList; import bisq.core.btc.model.AddressEntryList;
import bisq.core.btc.model.RawTransactionInput;
import bisq.core.btc.setup.WalletsSetup; import bisq.core.btc.setup.WalletsSetup;
import bisq.core.btc.wallet.http.MemPoolSpaceTxBroadcaster; import bisq.core.btc.wallet.http.MemPoolSpaceTxBroadcaster;
import bisq.core.provider.fee.FeeService; import bisq.core.provider.fee.FeeService;
@ -47,6 +48,7 @@ import org.bitcoinj.crypto.KeyCrypterScrypt;
import org.bitcoinj.script.Script; import org.bitcoinj.script.Script;
import org.bitcoinj.script.ScriptBuilder; import org.bitcoinj.script.ScriptBuilder;
import org.bitcoinj.script.ScriptPattern; import org.bitcoinj.script.ScriptPattern;
import org.bitcoinj.wallet.CoinSelection;
import org.bitcoinj.wallet.SendRequest; import org.bitcoinj.wallet.SendRequest;
import org.bitcoinj.wallet.Wallet; import org.bitcoinj.wallet.Wallet;
@ -61,6 +63,7 @@ import org.bouncycastle.crypto.params.KeyParameter;
import java.util.Arrays; import java.util.Arrays;
import java.util.List; import java.util.List;
import java.util.Objects;
import java.util.Optional; import java.util.Optional;
import java.util.Set; import java.util.Set;
import java.util.stream.Collectors; import java.util.stream.Collectors;
@ -1330,4 +1333,30 @@ public class BtcWalletService extends WalletService {
return resultTx; 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.ArrayList;
import java.util.List; import java.util.List;
import java.util.Objects;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import org.jetbrains.annotations.NotNull;
import javax.annotation.Nullable; import javax.annotation.Nullable;
import static com.google.common.base.Preconditions.checkArgument; import static com.google.common.base.Preconditions.checkArgument;
@ -891,8 +890,7 @@ public class TradeWalletService {
input.setScriptSig(inputScript); input.setScriptSig(inputScript);
} else { } else {
input.setScriptSig(ScriptBuilder.createEmpty()); input.setScriptSig(ScriptBuilder.createEmpty());
TransactionWitness witness = TransactionWitness.redeemP2WSH(redeemScript, sellerTxSig, buyerTxSig); input.setWitness(TransactionWitness.redeemP2WSH(redeemScript, sellerTxSig, buyerTxSig));
input.setWitness(witness);
} }
WalletService.printTx("payoutTx", payoutTx); WalletService.printTx("payoutTx", payoutTx);
WalletService.verifyTransaction(payoutTx); WalletService.verifyTransaction(payoutTx);
@ -971,8 +969,7 @@ public class TradeWalletService {
input.setScriptSig(inputScript); input.setScriptSig(inputScript);
} else { } else {
input.setScriptSig(ScriptBuilder.createEmpty()); input.setScriptSig(ScriptBuilder.createEmpty());
TransactionWitness witness = TransactionWitness.redeemP2WSH(redeemScript, sellerTxSig, buyerTxSig); input.setWitness(TransactionWitness.redeemP2WSH(redeemScript, sellerTxSig, buyerTxSig));
input.setWitness(witness);
} }
WalletService.printTx("mediated payoutTx", payoutTx); WalletService.printTx("mediated payoutTx", payoutTx);
WalletService.verifyTransaction(payoutTx); WalletService.verifyTransaction(payoutTx);
@ -1059,8 +1056,7 @@ public class TradeWalletService {
input.setScriptSig(inputScript); input.setScriptSig(inputScript);
} else { } else {
input.setScriptSig(ScriptBuilder.createEmpty()); input.setScriptSig(ScriptBuilder.createEmpty());
TransactionWitness witness = TransactionWitness.redeemP2WSH(redeemScript, arbitratorTxSig, tradersTxSig); input.setWitness(TransactionWitness.redeemP2WSH(redeemScript, arbitratorTxSig, tradersTxSig));
input.setWitness(witness);
} }
WalletService.printTx("disputed payoutTx", payoutTx); WalletService.printTx("disputed payoutTx", payoutTx);
WalletService.verifyTransaction(payoutTx); WalletService.verifyTransaction(payoutTx);
@ -1105,7 +1101,10 @@ public class TradeWalletService {
return new Tuple2<>(redeemScriptHex, unsignedTxHex); 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 { throws IllegalArgumentException {
boolean hashedMultiSigOutputIsLegacy = true; boolean hashedMultiSigOutputIsLegacy = true;
if (rawTxHex.startsWith("010000000001")) if (rawTxHex.startsWith("010000000001"))
@ -1146,8 +1145,7 @@ public class TradeWalletService {
input.setScriptSig(inputScript); input.setScriptSig(inputScript);
} else { } else {
input.setScriptSig(ScriptBuilder.createEmpty()); input.setScriptSig(ScriptBuilder.createEmpty());
TransactionWitness witness = TransactionWitness.redeemP2WSH(redeemScript, sellerTxSig, buyerTxSig); input.setWitness(TransactionWitness.redeemP2WSH(redeemScript, sellerTxSig, buyerTxSig));
input.setWitness(witness);
} }
String txId = payoutTx.getTxId().toString(); String txId = payoutTx.getTxId().toString();
String signedTxHex = Utils.HEX.encode(payoutTx.bitcoinSerialize(!hashedMultiSigOutputIsLegacy)); String signedTxHex = Utils.HEX.encode(payoutTx.bitcoinSerialize(!hashedMultiSigOutputIsLegacy));
@ -1163,6 +1161,96 @@ public class TradeWalletService {
broadcastTx(payoutTx, callback, 20); 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 // Broadcast tx
@ -1207,7 +1295,12 @@ public class TradeWalletService {
// Private methods // 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(), "input.getConnectedOutput() must not be null");
checkNotNull(input.getConnectedOutput().getParentTransaction(), checkNotNull(input.getConnectedOutput().getParentTransaction(),
"input.getConnectedOutput().getParentTransaction() must not be null"); "input.getConnectedOutput().getParentTransaction() must not be null");
@ -1222,10 +1315,13 @@ public class TradeWalletService {
input.getValue().value); input.getValue().value);
} }
private TransactionInput getTransactionInput(Transaction depositTx, private TransactionInput getTransactionInput(Transaction parentTransaction,
byte[] scriptProgram, byte[] scriptProgram,
RawTransactionInput rawTransactionInput) { RawTransactionInput rawTransactionInput) {
return new TransactionInput(params, depositTx, scriptProgram, getConnectedOutPoint(rawTransactionInput), return new TransactionInput(params,
parentTransaction,
scriptProgram,
getConnectedOutPoint(rawTransactionInput),
Coin.valueOf(rawTransactionInput.value)); Coin.valueOf(rawTransactionInput.value));
} }
@ -1239,7 +1335,6 @@ public class TradeWalletService {
checkNotNull(getConnectedOutPoint(rawTransactionInput).getConnectedOutput()).getScriptPubKey()); checkNotNull(getConnectedOutPoint(rawTransactionInput).getConnectedOutput()).getScriptPubKey());
} }
// TODO: Once we have removed legacy arbitrator from dispute domain we can remove that method as well. // 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. // 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 { private void signInput(Transaction transaction, TransactionInput input, int inputIndex) throws SigningException {
checkNotNull(input.getConnectedOutput(), "input.getConnectedOutput() must not be null"); checkNotNull(input.getConnectedOutput(), "input.getConnectedOutput() must not be null");
Script scriptPubKey = input.getConnectedOutput().getScriptPubKey(); Script scriptPubKey = input.getConnectedOutput().getScriptPubKey();
checkNotNull(wallet);
ECKey sigKey = input.getOutpoint().getConnectedKey(wallet); ECKey sigKey = input.getOutpoint().getConnectedKey(wallet);
checkNotNull(sigKey, "signInput: sigKey must not be null. input.getOutpoint()=" + checkNotNull(sigKey, "signInput: sigKey must not be null. input.getOutpoint()=" +
input.getOutpoint().toString()); input.getOutpoint().toString());

View file

@ -102,6 +102,7 @@ import org.jetbrains.annotations.NotNull;
import javax.annotation.Nullable; 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.checkNotNull;
import static com.google.common.base.Preconditions.checkState; import static com.google.common.base.Preconditions.checkState;
@ -278,6 +279,31 @@ public abstract class WalletService {
// Sign tx // 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, public static void signTransactionInput(Wallet wallet,
KeyParameter aesKey, KeyParameter aesKey,
Transaction tx, Transaction tx,
@ -357,12 +383,13 @@ public abstract class WalletService {
txIn.setScriptSig(ScriptBuilder.createEmpty()); txIn.setScriptSig(ScriptBuilder.createEmpty());
txIn.setWitness(TransactionWitness.redeemP2WPKH(txSig, key)); txIn.setWitness(TransactionWitness.redeemP2WPKH(txSig, key));
} catch (ECKey.KeyIsEncryptedException e1) { } catch (ECKey.KeyIsEncryptedException e1) {
log.error(e1.toString());
throw e1; throw e1;
} catch (ECKey.MissingPrivateKeyException e1) { } catch (ECKey.MissingPrivateKeyException e1) {
log.warn("No private key in keypair for input {}", index); log.warn("No private key in keypair for input {}", index);
} }
} else { } else {
// log.error("Unexpected script type."); log.error("Unexpected script type.");
throw new RuntimeException("Unexpected script type."); throw new RuntimeException("Unexpected script type.");
} }
} else { } 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 // Broadcast tx
/////////////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////////////
@ -482,7 +526,7 @@ public abstract class WalletService {
// Balance // Balance
/////////////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////////////
public Coin getAvailableConfirmedBalance() { public Coin getAvailableBalance() {
return wallet != null ? wallet.getBalance(Wallet.BalanceType.AVAILABLE) : Coin.ZERO; return wallet != null ? wallet.getBalance(Wallet.BalanceType.AVAILABLE) : Coin.ZERO;
} }
@ -546,6 +590,10 @@ public abstract class WalletService {
return getNumTxOutputsForAddress(address) == 0; return getNumTxOutputsForAddress(address) == 0;
} }
public boolean isMine(TransactionOutput transactionOutput) {
return transactionOutput.isMine(wallet);
}
// BISQ issue #4039: Prevent dust outputs from being created. // BISQ issue #4039: Prevent dust outputs from being created.
// Check the outputs of a proposed transaction. If any are below the dust threshold, // 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. // 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); return maybeAddTxToWallet(transaction.bitcoinSerialize(), wallet, source);
} }
/////////////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////////////
// bisqWalletEventListener // bisqWalletEventListener
/////////////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////////////
public class BisqWalletListener implements WalletCoinsReceivedEventListener, WalletCoinsSentEventListener, WalletReorganizeEventListener, TransactionConfidenceEventListener { public class BisqWalletListener implements WalletCoinsReceivedEventListener,
WalletCoinsSentEventListener,
WalletReorganizeEventListener,
TransactionConfidenceEventListener {
@Override @Override
public void onCoinsReceived(Wallet wallet, Transaction tx, Coin prevBalance, Coin newBalance) { public void onCoinsReceived(Wallet wallet, Transaction tx, Coin prevBalance, Coin newBalance) {
notifyBalanceListeners(tx); notifyBalanceListeners(tx);
@ -848,7 +900,7 @@ public abstract class WalletService {
.filter(txConfidenceListener -> tx != null && .filter(txConfidenceListener -> tx != null &&
tx.getTxId().toString() != null && tx.getTxId().toString() != null &&
txConfidenceListener != null && txConfidenceListener != null &&
tx.getTxId().toString().equals(txConfidenceListener.getTxID())) tx.getTxId().toString().equals(txConfidenceListener.getTxId()))
.forEach(txConfidenceListener -> .forEach(txConfidenceListener ->
txConfidenceListener.onTransactionConfidenceChanged(tx.getConfidence())); txConfidenceListener.onTransactionConfidenceChanged(tx.getConfidence()));
} }
@ -859,7 +911,7 @@ public abstract class WalletService {
if (balanceListener.getAddress() != null) if (balanceListener.getAddress() != null)
balance = getBalanceForAddress(balanceListener.getAddress()); balance = getBalanceForAddress(balanceListener.getAddress());
else else
balance = getAvailableConfirmedBalance(); balance = getAvailableBalance();
balanceListener.onBalanceChanged(balance, tx); 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.Block;
import bisq.core.dao.state.model.blockchain.Tx; import bisq.core.dao.state.model.blockchain.Tx;
import bisq.core.dao.state.model.blockchain.TxOutput; 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.blockchain.TxType;
import bisq.core.dao.state.model.governance.Ballot; import bisq.core.dao.state.model.governance.Ballot;
import bisq.core.dao.state.model.governance.BondedRoleType; import bisq.core.dao.state.model.governance.BondedRoleType;
@ -649,6 +650,14 @@ public class DaoFacade implements DaoSetupService {
return daoStateService.getUnspentTxOutputs(); return daoStateService.getUnspentTxOutputs();
} }
public boolean isTxOutputSpendable(TxOutputKey txOutputKey) {
return daoStateService.isTxOutputSpendable(txOutputKey);
}
public long getUnspentTxOutputValue(TxOutputKey key) {
return daoStateService.getUnspentTxOutputValue(key);
}
public int getNumTxs() { public int getNumTxs() {
return daoStateService.getNumTxs(); return daoStateService.getNumTxs();
} }
@ -796,4 +805,8 @@ public class DaoFacade implements DaoSetupService {
return allPastParamValues; 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. // We add the BTC inputs for the miner fee.
Transaction txWithBtcFee = btcWalletService.completePreparedBurnBsqTx(preparedBurnFeeTx, opReturnData); Transaction txWithBtcFee = btcWalletService.completePreparedBurnBsqTx(preparedBurnFeeTx, opReturnData);
// We sign the BSQ inputs of the final tx. // 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); log.info("Asset listing fee tx: " + transaction);
return transaction; return transaction;
} catch (WalletException | TransactionVerificationException e) { } catch (WalletException | TransactionVerificationException e) {

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -270,6 +270,6 @@ public class VoteRevealService implements DaoStateListener, DaoSetupService {
throws InsufficientMoneyException, WalletException, TransactionVerificationException { throws InsufficientMoneyException, WalletException, TransactionVerificationException {
Transaction preparedTx = bsqWalletService.getPreparedVoteRevealTx(stakeTxOutput); Transaction preparedTx = bsqWalletService.getPreparedVoteRevealTx(stakeTxOutput);
Transaction txWithBtcFee = btcWalletService.completePreparedVoteRevealTx(preparedTx, opReturnData); 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.Tx;
import bisq.core.dao.state.model.blockchain.TxOutput; import bisq.core.dao.state.model.blockchain.TxOutput;
import bisq.core.dao.state.model.blockchain.TxType; import bisq.core.dao.state.model.blockchain.TxType;
import bisq.core.util.JsonUtil;
import bisq.common.config.Config; import bisq.common.config.Config;
import bisq.common.file.FileUtil; import bisq.common.file.FileUtil;
@ -154,9 +155,9 @@ public class ExportJsonFilesService implements DaoSetupService {
JsonBlocks jsonBlocks = new JsonBlocks(daoState.getChainHeight(), jsonBlockList); JsonBlocks jsonBlocks = new JsonBlocks(daoState.getChainHeight(), jsonBlockList);
ListenableFuture<Void> future = executor.submit(() -> { ListenableFuture<Void> future = executor.submit(() -> {
bsqStateFileManager.writeToDisc(Utilities.objectToJson(jsonBlocks), "blocks"); bsqStateFileManager.writeToDisc(JsonUtil.objectToJson(jsonBlocks), "blocks");
allJsonTxOutputs.forEach(jsonTxOutput -> txOutputFileManager.writeToDisc(Utilities.objectToJson(jsonTxOutput), jsonTxOutput.getId())); allJsonTxOutputs.forEach(jsonTxOutput -> txOutputFileManager.writeToDisc(JsonUtil.objectToJson(jsonTxOutput), jsonTxOutput.getId()));
jsonTxs.forEach(jsonTx -> txFileManager.writeToDisc(Utilities.objectToJson(jsonTx), jsonTx.getId())); jsonTxs.forEach(jsonTx -> txFileManager.writeToDisc(JsonUtil.objectToJson(jsonTx), jsonTx.getId()));
GcUtil.maybeReleaseMemory(); 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.bond.BondConsensus;
import bisq.core.dao.governance.param.Param; import bisq.core.dao.governance.param.Param;
import bisq.core.dao.state.model.DaoState; 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.Block;
import bisq.core.dao.state.model.blockchain.SpentInfo; import bisq.core.dao.state.model.blockchain.SpentInfo;
import bisq.core.dao.state.model.blockchain.Tx; import bisq.core.dao.state.model.blockchain.Tx;
@ -484,6 +485,12 @@ public class DaoStateService implements DaoSetupService {
return Optional.ofNullable(getUnspentTxOutputMap().getOrDefault(key, null)); 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) { public boolean isTxOutputSpendable(TxOutputKey key) {
if (!isUnspent(key)) if (!isUnspent(key))
return false; return false;
@ -492,7 +499,12 @@ public class DaoStateService implements DaoSetupService {
// The above isUnspent call satisfies optionalTxOutput.isPresent() // The above isUnspent call satisfies optionalTxOutput.isPresent()
checkArgument(optionalTxOutput.isPresent(), "optionalTxOutput must be present"); checkArgument(optionalTxOutput.isPresent(), "optionalTxOutput must be present");
TxOutput txOutput = optionalTxOutput.get(); 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()) { switch (txOutput.getTxOutputType()) {
case UNDEFINED_OUTPUT: case UNDEFINED_OUTPUT:
return false; return false;

View file

@ -39,13 +39,15 @@ import java.util.Set;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import lombok.Value; import lombok.EqualsAndHashCode;
import lombok.Getter;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import javax.annotation.Nullable; import javax.annotation.Nullable;
@Slf4j @Slf4j
@Value @Getter
@EqualsAndHashCode
public final class Filter implements ProtectedStoragePayload, ExpirablePayload { public final class Filter implements ProtectedStoragePayload, ExpirablePayload {
public static final long TTL = TimeUnit.DAYS.toMillis(180); 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 // added at v1.6.0
private final boolean disableMempoolValidation; 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 // After we have created the signature from the filter data we clone it and apply the signature
static Filter cloneWithSig(Filter filter, String signatureAsBase64) { static Filter cloneWithSig(Filter filter, String signatureAsBase64) {
return new Filter(filter.getBannedOfferIds(), return new Filter(filter.getBannedOfferIds(),
@ -130,7 +138,9 @@ public final class Filter implements ProtectedStoragePayload, ExpirablePayload {
filter.getBannedAutoConfExplorers(), filter.getBannedAutoConfExplorers(),
filter.getNodeAddressesBannedFromNetwork(), filter.getNodeAddressesBannedFromNetwork(),
filter.isDisableMempoolValidation(), 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 // 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.getBannedAutoConfExplorers(),
filter.getNodeAddressesBannedFromNetwork(), filter.getNodeAddressesBannedFromNetwork(),
filter.isDisableMempoolValidation(), filter.isDisableMempoolValidation(),
filter.isDisableApi()); filter.isDisableApi(),
filter.isDisablePowMessage(),
filter.getPowDifficulty());
} }
public Filter(List<String> bannedOfferIds, public Filter(List<String> bannedOfferIds,
@ -189,7 +201,9 @@ public final class Filter implements ProtectedStoragePayload, ExpirablePayload {
List<String> bannedAutoConfExplorers, List<String> bannedAutoConfExplorers,
Set<String> nodeAddressesBannedFromNetwork, Set<String> nodeAddressesBannedFromNetwork,
boolean disableMempoolValidation, boolean disableMempoolValidation,
boolean disableApi) { boolean disableApi,
boolean disablePowMessage,
int powDifficulty) {
this(bannedOfferIds, this(bannedOfferIds,
nodeAddressesBannedFromTrading, nodeAddressesBannedFromTrading,
bannedPaymentAccounts, bannedPaymentAccounts,
@ -217,7 +231,9 @@ public final class Filter implements ProtectedStoragePayload, ExpirablePayload {
bannedAutoConfExplorers, bannedAutoConfExplorers,
nodeAddressesBannedFromNetwork, nodeAddressesBannedFromNetwork,
disableMempoolValidation, disableMempoolValidation,
disableApi); disableApi,
disablePowMessage,
powDifficulty);
} }
@ -253,7 +269,9 @@ public final class Filter implements ProtectedStoragePayload, ExpirablePayload {
List<String> bannedAutoConfExplorers, List<String> bannedAutoConfExplorers,
Set<String> nodeAddressesBannedFromNetwork, Set<String> nodeAddressesBannedFromNetwork,
boolean disableMempoolValidation, boolean disableMempoolValidation,
boolean disableApi) { boolean disableApi,
boolean disablePowMessage,
int powDifficulty) {
this.bannedOfferIds = bannedOfferIds; this.bannedOfferIds = bannedOfferIds;
this.nodeAddressesBannedFromTrading = nodeAddressesBannedFromTrading; this.nodeAddressesBannedFromTrading = nodeAddressesBannedFromTrading;
this.bannedPaymentAccounts = bannedPaymentAccounts; this.bannedPaymentAccounts = bannedPaymentAccounts;
@ -282,6 +300,8 @@ public final class Filter implements ProtectedStoragePayload, ExpirablePayload {
this.nodeAddressesBannedFromNetwork = nodeAddressesBannedFromNetwork; this.nodeAddressesBannedFromNetwork = nodeAddressesBannedFromNetwork;
this.disableMempoolValidation = disableMempoolValidation; this.disableMempoolValidation = disableMempoolValidation;
this.disableApi = disableApi; this.disableApi = disableApi;
this.disablePowMessage = disablePowMessage;
this.powDifficulty = powDifficulty;
// ownerPubKeyBytes can be null when called from tests // ownerPubKeyBytes can be null when called from tests
if (ownerPubKeyBytes != null) { if (ownerPubKeyBytes != null) {
@ -322,7 +342,9 @@ public final class Filter implements ProtectedStoragePayload, ExpirablePayload {
.addAllBannedAutoConfExplorers(bannedAutoConfExplorers) .addAllBannedAutoConfExplorers(bannedAutoConfExplorers)
.addAllNodeAddressesBannedFromNetwork(nodeAddressesBannedFromNetwork) .addAllNodeAddressesBannedFromNetwork(nodeAddressesBannedFromNetwork)
.setDisableMempoolValidation(disableMempoolValidation) .setDisableMempoolValidation(disableMempoolValidation)
.setDisableApi(disableApi); .setDisableApi(disableApi)
.setDisablePowMessage(disablePowMessage)
.setPowDifficulty(powDifficulty);
Optional.ofNullable(signatureAsBase64).ifPresent(builder::setSignatureAsBase64); Optional.ofNullable(signatureAsBase64).ifPresent(builder::setSignatureAsBase64);
Optional.ofNullable(extraDataMap).ifPresent(builder::putAllExtraData); Optional.ofNullable(extraDataMap).ifPresent(builder::putAllExtraData);
@ -363,7 +385,9 @@ public final class Filter implements ProtectedStoragePayload, ExpirablePayload {
ProtoUtil.protocolStringListToList(proto.getBannedAutoConfExplorersList()), ProtoUtil.protocolStringListToList(proto.getBannedAutoConfExplorersList()),
ProtoUtil.protocolStringListToSet(proto.getNodeAddressesBannedFromNetworkList()), ProtoUtil.protocolStringListToSet(proto.getNodeAddressesBannedFromNetworkList()),
proto.getDisableMempoolValidation(), 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 nodeAddressesBannedFromNetwork=" + nodeAddressesBannedFromNetwork +
",\n disableMempoolValidation=" + disableMempoolValidation + ",\n disableMempoolValidation=" + disableMempoolValidation +
",\n disableApi=" + disableApi + ",\n disableApi=" + disableApi +
",\n disablePowMessage=" + disablePowMessage +
",\n powDifficulty=" + powDifficulty +
"\n}"; "\n}";
} }
} }

View file

@ -19,6 +19,7 @@ package bisq.core.filter;
import bisq.core.btc.nodes.BtcNodes; import bisq.core.btc.nodes.BtcNodes;
import bisq.core.locale.Res; import bisq.core.locale.Res;
import bisq.core.offer.Offer;
import bisq.core.payment.payload.PaymentAccountPayload; import bisq.core.payment.payload.PaymentAccountPayload;
import bisq.core.payment.payload.PaymentMethod; import bisq.core.payment.payload.PaymentMethod;
import bisq.core.provider.ProvidersRepository; import bisq.core.provider.ProvidersRepository;
@ -36,6 +37,7 @@ import bisq.common.app.DevEnv;
import bisq.common.app.Version; import bisq.common.app.Version;
import bisq.common.config.Config; import bisq.common.config.Config;
import bisq.common.config.ConfigFileEditor; import bisq.common.config.ConfigFileEditor;
import bisq.common.crypto.HashCashService;
import bisq.common.crypto.KeyRing; import bisq.common.crypto.KeyRing;
import org.bitcoinj.core.ECKey; import org.bitcoinj.core.ECKey;
@ -55,6 +57,7 @@ import java.nio.charset.StandardCharsets;
import java.math.BigInteger; import java.math.BigInteger;
import java.util.Arrays;
import java.util.Collection; import java.util.Collection;
import java.util.Collections; import java.util.Collections;
import java.util.Date; import java.util.Date;
@ -62,6 +65,7 @@ import java.util.HashSet;
import java.util.List; import java.util.List;
import java.util.Set; import java.util.Set;
import java.util.concurrent.CopyOnWriteArrayList; import java.util.concurrent.CopyOnWriteArrayList;
import java.util.function.BiFunction;
import java.util.function.Consumer; import java.util.function.Consumer;
import java.lang.reflect.Method; import java.lang.reflect.Method;
@ -70,6 +74,7 @@ import lombok.extern.slf4j.Slf4j;
import javax.annotation.Nullable; 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.checkNotNull;
import static org.bitcoinj.core.Utils.HEX; 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_SEED_NODES = "bannedSeedNodes";
private static final String BANNED_BTC_NODES = "bannedBtcNodes"; 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 // Listener
@ -476,6 +486,20 @@ public class FilterManager {
.anyMatch(e -> e.equals(witnessSignerPubKeyAsHex)); .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 // Private
@ -499,13 +523,13 @@ public class FilterManager {
if (currentFilter != null) { if (currentFilter != null) {
if (currentFilter.getCreationDate() > newFilter.getCreationDate()) { 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."); "filter we have already. We ignore the new filter.");
addToInvalidFilters(newFilter); addToInvalidFilters(newFilter);
return; return;
} else { } 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."); "filter we have already. We ignore the old filter.");
addToInvalidFilters(currentFilter); addToInvalidFilters(currentFilter);
} }

View file

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

View file

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

View file

@ -26,7 +26,7 @@ import bisq.core.notifications.MobileMessageType;
import bisq.core.notifications.MobileNotificationService; import bisq.core.notifications.MobileNotificationService;
import bisq.core.offer.Offer; import bisq.core.offer.Offer;
import bisq.core.offer.OfferBookService; 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.MarketPrice;
import bisq.core.provider.price.PriceFeedService; import bisq.core.provider.price.PriceFeedService;
import bisq.core.user.User; 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 // % 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. // altcoins and precision of 4 for fiat.
private String getAlertId(Offer offer) { 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); String priceString = String.valueOf((long) price);
return offer.getId() + "|" + priceString; return offer.getId() + "|" + priceString;
} }
@ -119,7 +119,7 @@ public class MarketAlerts {
MarketPrice marketPrice = priceFeedService.getMarketPrice(currencyCode); MarketPrice marketPrice = priceFeedService.getMarketPrice(currencyCode);
Price offerPrice = offer.getPrice(); Price offerPrice = offer.getPrice();
if (marketPrice != null && offerPrice != null) { if (marketPrice != null && offerPrice != null) {
boolean isSellOffer = offer.getDirection() == OfferPayload.Direction.SELL; boolean isSellOffer = offer.getDirection() == OfferDirection.SELL;
String shortOfferId = offer.getShortId(); String shortOfferId = offer.getShortId();
boolean isFiatCurrency = CurrencyUtil.isFiatCurrency(currencyCode); boolean isFiatCurrency = CurrencyUtil.isFiatCurrency(currencyCode);
String alertId = getAlertId(offer); String alertId = getAlertId(offer);

View file

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

View file

@ -20,6 +20,7 @@ package bisq.core.offer;
import bisq.core.filter.FilterManager; import bisq.core.filter.FilterManager;
import bisq.core.locale.Res; import bisq.core.locale.Res;
import bisq.core.provider.price.PriceFeedService; import bisq.core.provider.price.PriceFeedService;
import bisq.core.util.JsonUtil;
import bisq.network.p2p.BootstrapListener; import bisq.network.p2p.BootstrapListener;
import bisq.network.p2p.P2PService; import bisq.network.p2p.P2PService;
@ -31,7 +32,6 @@ import bisq.common.config.Config;
import bisq.common.file.JsonFileManager; import bisq.common.file.JsonFileManager;
import bisq.common.handlers.ErrorMessageHandler; import bisq.common.handlers.ErrorMessageHandler;
import bisq.common.handlers.ResultHandler; import bisq.common.handlers.ResultHandler;
import bisq.common.util.Utilities;
import javax.inject.Inject; import javax.inject.Inject;
import javax.inject.Named; import javax.inject.Named;
@ -87,9 +87,9 @@ public class OfferBookService {
@Override @Override
public void onAdded(Collection<ProtectedStorageEntry> protectedStorageEntries) { public void onAdded(Collection<ProtectedStorageEntry> protectedStorageEntries) {
protectedStorageEntries.forEach(protectedStorageEntry -> offerBookChangedListeners.forEach(listener -> { protectedStorageEntries.forEach(protectedStorageEntry -> offerBookChangedListeners.forEach(listener -> {
if (protectedStorageEntry.getProtectedStoragePayload() instanceof OfferPayload) { if (protectedStorageEntry.getProtectedStoragePayload() instanceof OfferPayloadBase) {
OfferPayload offerPayload = (OfferPayload) protectedStorageEntry.getProtectedStoragePayload(); OfferPayloadBase offerPayloadBase = (OfferPayloadBase) protectedStorageEntry.getProtectedStoragePayload();
Offer offer = new Offer(offerPayload); Offer offer = new Offer(offerPayloadBase);
offer.setPriceFeedService(priceFeedService); offer.setPriceFeedService(priceFeedService);
listener.onAdded(offer); listener.onAdded(offer);
} }
@ -99,9 +99,9 @@ public class OfferBookService {
@Override @Override
public void onRemoved(Collection<ProtectedStorageEntry> protectedStorageEntries) { public void onRemoved(Collection<ProtectedStorageEntry> protectedStorageEntries) {
protectedStorageEntries.forEach(protectedStorageEntry -> offerBookChangedListeners.forEach(listener -> { protectedStorageEntries.forEach(protectedStorageEntry -> offerBookChangedListeners.forEach(listener -> {
if (protectedStorageEntry.getProtectedStoragePayload() instanceof OfferPayload) { if (protectedStorageEntry.getProtectedStoragePayload() instanceof OfferPayloadBase) {
OfferPayload offerPayload = (OfferPayload) protectedStorageEntry.getProtectedStoragePayload(); OfferPayloadBase offerPayloadBase = (OfferPayloadBase) protectedStorageEntry.getProtectedStoragePayload();
Offer offer = new Offer(offerPayload); Offer offer = new Offer(offerPayloadBase);
offer.setPriceFeedService(priceFeedService); offer.setPriceFeedService(priceFeedService);
listener.onRemoved(offer); listener.onRemoved(offer);
} }
@ -141,7 +141,7 @@ public class OfferBookService {
return; return;
} }
boolean result = p2PService.addProtectedStorageEntry(offer.getOfferPayload()); boolean result = p2PService.addProtectedStorageEntry(offer.getOfferPayloadBase());
if (result) { if (result) {
resultHandler.handleResult(); resultHandler.handleResult();
} else { } else {
@ -149,7 +149,7 @@ public class OfferBookService {
} }
} }
public void refreshTTL(OfferPayload offerPayload, public void refreshTTL(OfferPayloadBase offerPayloadBase,
ResultHandler resultHandler, ResultHandler resultHandler,
ErrorMessageHandler errorMessageHandler) { ErrorMessageHandler errorMessageHandler) {
if (filterManager.requireUpdateToNewVersionForTrading()) { if (filterManager.requireUpdateToNewVersionForTrading()) {
@ -157,7 +157,7 @@ public class OfferBookService {
return; return;
} }
boolean result = p2PService.refreshTTL(offerPayload); boolean result = p2PService.refreshTTL(offerPayloadBase);
if (result) { if (result) {
resultHandler.handleResult(); resultHandler.handleResult();
} else { } else {
@ -171,16 +171,16 @@ public class OfferBookService {
addOffer(offer, resultHandler, errorMessageHandler); addOffer(offer, resultHandler, errorMessageHandler);
} }
public void deactivateOffer(OfferPayload offerPayload, public void deactivateOffer(OfferPayloadBase offerPayloadBase,
@Nullable ResultHandler resultHandler, @Nullable ResultHandler resultHandler,
@Nullable ErrorMessageHandler errorMessageHandler) { @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 ResultHandler resultHandler,
@Nullable ErrorMessageHandler errorMessageHandler) { @Nullable ErrorMessageHandler errorMessageHandler) {
if (p2PService.removeData(offerPayload)) { if (p2PService.removeData(offerPayloadBase)) {
if (resultHandler != null) if (resultHandler != null)
resultHandler.handleResult(); resultHandler.handleResult();
} else { } else {
@ -191,18 +191,18 @@ public class OfferBookService {
public List<Offer> getOffers() { public List<Offer> getOffers() {
return p2PService.getDataMap().values().stream() return p2PService.getDataMap().values().stream()
.filter(data -> data.getProtectedStoragePayload() instanceof OfferPayload) .filter(data -> data.getProtectedStoragePayload() instanceof OfferPayloadBase)
.map(data -> { .map(data -> {
OfferPayload offerPayload = (OfferPayload) data.getProtectedStoragePayload(); OfferPayloadBase offerPayloadBase = (OfferPayloadBase) data.getProtectedStoragePayload();
Offer offer = new Offer(offerPayload); Offer offer = new Offer(offerPayloadBase);
offer.setPriceFeedService(priceFeedService); offer.setPriceFeedService(priceFeedService);
return offer; return offer;
}) })
.collect(Collectors.toList()); .collect(Collectors.toList());
} }
public void removeOfferAtShutDown(OfferPayload offerPayload) { public void removeOfferAtShutDown(OfferPayloadBase offerPayloadBase) {
removeOffer(offerPayload, null, null); removeOffer(offerPayloadBase, null, null);
} }
public boolean isBootstrapped() { public boolean isBootstrapped() {
@ -243,6 +243,6 @@ public class OfferBookService {
}) })
.filter(Objects::nonNull) .filter(Objects::nonNull)
.collect(Collectors.toList()); .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.Preferences;
import bisq.core.user.User; import bisq.core.user.User;
import bisq.common.app.DevEnv;
import bisq.common.app.Version; import bisq.common.app.Version;
import org.bitcoinj.core.Coin; import org.bitcoinj.core.Coin;
@ -43,7 +44,7 @@ import lombok.extern.slf4j.Slf4j;
@Slf4j @Slf4j
@Singleton @Singleton
public class OfferFilter { public class OfferFilterService {
private final User user; private final User user;
private final Preferences preferences; private final Preferences preferences;
private final FilterManager filterManager; private final FilterManager filterManager;
@ -52,7 +53,7 @@ public class OfferFilter {
private final Map<String, Boolean> myInsufficientTradeLimitCache = new HashMap<>(); private final Map<String, Boolean> myInsufficientTradeLimitCache = new HashMap<>();
@Inject @Inject
public OfferFilter(User user, public OfferFilterService(User user,
Preferences preferences, Preferences preferences,
FilterManager filterManager, FilterManager filterManager,
AccountAgeWitnessService accountAgeWitnessService) { AccountAgeWitnessService accountAgeWitnessService) {
@ -80,7 +81,8 @@ public class OfferFilter {
IS_NODE_ADDRESS_BANNED, IS_NODE_ADDRESS_BANNED,
REQUIRE_UPDATE_TO_NEW_VERSION, REQUIRE_UPDATE_TO_NEW_VERSION,
IS_INSUFFICIENT_COUNTERPARTY_TRADE_LIMIT, IS_INSUFFICIENT_COUNTERPARTY_TRADE_LIMIT,
IS_MY_INSUFFICIENT_TRADE_LIMIT; IS_MY_INSUFFICIENT_TRADE_LIMIT,
HIDE_BSQ_SWAPS_DUE_DAO_DEACTIVATED;
@Getter @Getter
private final boolean isValid; private final boolean isValid;
@ -128,6 +130,9 @@ public class OfferFilter {
if (isMyInsufficientTradeLimit(offer)) { if (isMyInsufficientTradeLimit(offer)) {
return Result.IS_MY_INSUFFICIENT_TRADE_LIMIT; return Result.IS_MY_INSUFFICIENT_TRADE_LIMIT;
} }
if (!DevEnv.isDaoActivated() && offer.isBsqSwapOffer()) {
return Result.HIDE_BSQ_SWAPS_DUE_DAO_DEACTIVATED;
}
return Result.VALID; return Result.VALID;
} }

View file

@ -40,7 +40,7 @@ import javax.annotation.Nullable;
public class OfferForJson { public class OfferForJson {
private static final Logger log = LoggerFactory.getLogger(OfferForJson.class); private static final Logger log = LoggerFactory.getLogger(OfferForJson.class);
public final OfferPayload.Direction direction; public final OfferDirection direction;
public final String currencyCode; public final String currencyCode;
public final long minAmount; public final long minAmount;
public final long amount; 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) // 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 String currencyPair;
public OfferPayload.Direction primaryMarketDirection; public OfferDirection primaryMarketDirection;
public String priceDisplayString; public String priceDisplayString;
public String primaryMarketAmountDisplayString; public String primaryMarketAmountDisplayString;
@ -75,7 +75,7 @@ public class OfferForJson {
transient private final MonetaryFormat coinFormat = MonetaryFormat.BTC; transient private final MonetaryFormat coinFormat = MonetaryFormat.BTC;
public OfferForJson(OfferPayload.Direction direction, public OfferForJson(OfferDirection direction,
String currencyCode, String currencyCode,
Coin minAmount, Coin minAmount,
Coin amount, Coin amount,
@ -104,7 +104,7 @@ public class OfferForJson {
try { try {
final Price price = getPrice(); final Price price = getPrice();
if (CurrencyUtil.isCryptoCurrency(currencyCode)) { 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(); currencyPair = currencyCode + "/" + Res.getBaseCurrencyCode();
// int precision = 8; // 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; package bisq.core.offer;
import bisq.core.offer.bisq_v1.OfferPayload;
import bisq.common.app.Capabilities; import bisq.common.app.Capabilities;
import bisq.common.app.Capability; import bisq.common.app.Capability;
import bisq.common.config.Config; import bisq.common.config.Config;
@ -40,7 +42,7 @@ public class OfferRestrictions {
public static Coin TOLERATED_SMALL_TRADE_AMOUNT = Coin.parseCoin("0.01"); public static Coin TOLERATED_SMALL_TRADE_AMOUNT = Coin.parseCoin("0.01");
static boolean hasOfferMandatoryCapability(Offer offer, Capability mandatoryCapability) { 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)) { if (extraDataMap != null && extraDataMap.containsKey(OfferPayload.CAPABILITIES)) {
String commaSeparatedOrdinals = extraDataMap.get(OfferPayload.CAPABILITIES); String commaSeparatedOrdinals = extraDataMap.get(OfferPayload.CAPABILITIES);
Capabilities capabilities = Capabilities.fromStringList(commaSeparatedOrdinals); Capabilities capabilities = Capabilities.fromStringList(commaSeparatedOrdinals);

View file

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

View file

@ -17,7 +17,8 @@
package bisq.core.offer; 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; import bisq.network.p2p.NodeAddress;
@ -35,12 +36,13 @@ import lombok.extern.slf4j.Slf4j;
import javax.annotation.Nullable; import javax.annotation.Nullable;
@EqualsAndHashCode import static com.google.common.base.Preconditions.checkArgument;
@EqualsAndHashCode(exclude = {"bsqSwapOfferHasMissingFunds", "state", "timeoutTimer", "mempoolStatus"})
@Slf4j @Slf4j
public final class OpenOffer implements Tradable { 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. // 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; private static final long TIMEOUT = 60;
transient private Timer timeoutTimer;
public enum State { public enum State {
AVAILABLE, AVAILABLE,
@ -54,10 +56,11 @@ public final class OpenOffer implements Tradable {
private final Offer offer; private final Offer offer;
@Getter @Getter
private State state; private State state;
// TODO Not used. Could be removed?
@Getter @Getter
@Setter
@Nullable @Nullable
private NodeAddress arbitratorNodeAddress; private final NodeAddress arbitratorNodeAddress;
@Getter @Getter
@Setter @Setter
@Nullable @Nullable
@ -76,15 +79,29 @@ public final class OpenOffer implements Tradable {
@Getter @Getter
@Setter @Setter
transient private long mempoolStatus = -1; 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) { public OpenOffer(Offer offer) {
this(offer, 0); 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) { public OpenOffer(Offer offer, long triggerPrice) {
this.offer = offer; this.offer = offer;
this.triggerPrice = triggerPrice; this.triggerPrice = triggerPrice;
state = State.AVAILABLE; state = State.AVAILABLE;
arbitratorNodeAddress = null;
} }
/////////////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////////////
@ -131,9 +148,8 @@ public final class OpenOffer implements Tradable {
proto.getTriggerPrice()); proto.getTriggerPrice());
} }
/////////////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////////////
// Getters // Tradable
/////////////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////////////
@Override @Override
@ -151,6 +167,11 @@ public final class OpenOffer implements Tradable {
return offer.getShortId(); return offer.getShortId();
} }
///////////////////////////////////////////////////////////////////////////////////////////
// Misc
///////////////////////////////////////////////////////////////////////////////////////////
public void setState(State state) { public void setState(State state) {
this.state = state; this.state = state;
@ -166,6 +187,12 @@ public final class OpenOffer implements Tradable {
return state == State.DEACTIVATED; 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() { private void startTimeout() {
stopTimeout(); stopTimeout();
@ -185,7 +212,6 @@ public final class OpenOffer implements Tradable {
} }
} }
@Override @Override
public String toString() { public String toString() {
return "OpenOffer{" + return "OpenOffer{" +
@ -195,6 +221,7 @@ public final class OpenOffer implements Tradable {
",\n mediatorNodeAddress=" + mediatorNodeAddress + ",\n mediatorNodeAddress=" + mediatorNodeAddress +
",\n refundAgentNodeAddress=" + refundAgentNodeAddress + ",\n refundAgentNodeAddress=" + refundAgentNodeAddress +
",\n triggerPrice=" + triggerPrice + ",\n triggerPrice=" + triggerPrice +
",\n bsqSwapOfferHasMissingFunds=" + bsqSwapOfferHasMissingFunds +
"\n}"; "\n}";
} }
} }

View file

@ -25,18 +25,22 @@ import bisq.core.dao.DaoFacade;
import bisq.core.exceptions.TradePriceOutOfToleranceException; import bisq.core.exceptions.TradePriceOutOfToleranceException;
import bisq.core.filter.FilterManager; import bisq.core.filter.FilterManager;
import bisq.core.locale.Res; import bisq.core.locale.Res;
import bisq.core.offer.availability.AvailabilityResult;
import bisq.core.offer.availability.DisputeAgentSelection; import bisq.core.offer.availability.DisputeAgentSelection;
import bisq.core.offer.messages.OfferAvailabilityRequest; import bisq.core.offer.availability.messages.OfferAvailabilityRequest;
import bisq.core.offer.messages.OfferAvailabilityResponse; import bisq.core.offer.availability.messages.OfferAvailabilityResponse;
import bisq.core.offer.placeoffer.PlaceOfferModel; import bisq.core.offer.bisq_v1.CreateOfferService;
import bisq.core.offer.placeoffer.PlaceOfferProtocol; 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.provider.price.PriceFeedService;
import bisq.core.support.dispute.arbitration.arbitrator.ArbitratorManager; import bisq.core.support.dispute.arbitration.arbitrator.ArbitratorManager;
import bisq.core.support.dispute.mediation.mediator.MediatorManager; import bisq.core.support.dispute.mediation.mediator.MediatorManager;
import bisq.core.support.dispute.refund.refundagent.RefundAgentManager; import bisq.core.support.dispute.refund.refundagent.RefundAgentManager;
import bisq.core.trade.TradableList; import bisq.core.trade.ClosedTradableManager;
import bisq.core.trade.closed.ClosedTradableManager; import bisq.core.trade.bisq_v1.TransactionResultHandler;
import bisq.core.trade.handlers.TransactionResultHandler; import bisq.core.trade.model.TradableList;
import bisq.core.trade.statistics.TradeStatisticsManager; import bisq.core.trade.statistics.TradeStatisticsManager;
import bisq.core.user.Preferences; import bisq.core.user.Preferences;
import bisq.core.user.User; import bisq.core.user.User;
@ -84,19 +88,16 @@ import java.util.Set;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import lombok.Getter; import lombok.Getter;
import lombok.extern.slf4j.Slf4j;
import org.jetbrains.annotations.NotNull;
import javax.annotation.Nullable; 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.checkNotNull;
@Slf4j
public class OpenOfferManager implements PeerManager.Listener, DecryptedDirectMessageListener, PersistedDataHost { 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 RETRY_REPUBLISH_DELAY_SEC = 10;
private static final long REPUBLISH_AGAIN_AT_STARTUP_DELAY_SEC = 30; private static final long REPUBLISH_AGAIN_AT_STARTUP_DELAY_SEC = 30;
@ -207,7 +208,8 @@ public class OpenOfferManager implements PeerManager.Listener, DecryptedDirectMe
cleanUpAddressEntries(); cleanUpAddressEntries();
openOffers.stream() openOffers.stream()
.forEach(openOffer -> OfferUtil.getInvalidMakerFeeTxErrorMessage(openOffer.getOffer(), btcWalletService) .forEach(openOffer ->
OfferUtil.getInvalidMakerFeeTxErrorMessage(openOffer.getOffer(), btcWalletService)
.ifPresent(errorMsg -> invalidOffers.add(new Tuple2<>(openOffer, errorMsg)))); .ifPresent(errorMsg -> invalidOffers.add(new Tuple2<>(openOffer, errorMsg))));
} }
@ -238,7 +240,7 @@ public class OpenOfferManager implements PeerManager.Listener, DecryptedDirectMe
log.info("Remove open offers at shutDown. Number of open offers: {}", size); log.info("Remove open offers at shutDown. Number of open offers: {}", size);
if (offerBookService.isBootstrapped() && size > 0) { if (offerBookService.isBootstrapped() && size > 0) {
UserThread.execute(() -> openOffers.forEach( 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 // 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(); int size = openOffers.size();
// Copy list as we remove in the loop // Copy list as we remove in the loop
List<OpenOffer> openOffersList = new ArrayList<>(openOffers); List<OpenOffer> openOffersList = new ArrayList<>(openOffers);
openOffersList.forEach(openOffer -> removeOpenOffer(openOffer, () -> { openOffersList.forEach(this::removeOpenOffer);
}, errorMessage -> {
}));
if (completeHandler != null) 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, TransactionResultHandler resultHandler,
ErrorMessageHandler errorMessageHandler) { ErrorMessageHandler errorMessageHandler) {
checkNotNull(offer.getMakerFee(), "makerFee must not be null"); checkNotNull(offer.getMakerFee(), "makerFee must not be null");
checkArgument(!offer.isBsqSwapOffer());
Coin reservedFundsForOffer = createOfferService.getReservedFundsForOffer(offer.getDirection(), Coin reservedFundsForOffer = createOfferService.getReservedFundsForOffer(offer.getDirection(),
offer.getAmount(), offer.getAmount(),
@ -394,21 +395,30 @@ public class OpenOfferManager implements PeerManager.Listener, DecryptedDirectMe
model, model,
transaction -> { transaction -> {
OpenOffer openOffer = new OpenOffer(offer, triggerPrice); OpenOffer openOffer = new OpenOffer(offer, triggerPrice);
openOffers.add(openOffer); addOpenOfferToList(openOffer);
requestPersistence();
resultHandler.handleResult(transaction);
if (!stopped) { if (!stopped) {
startPeriodicRepublishOffersTimer(); startPeriodicRepublishOffersTimer();
startPeriodicRefreshOffersTimer(); startPeriodicRefreshOffersTimer();
} else { } else {
log.debug("We have stopped already. We ignore that placeOfferProtocol.placeOffer.onResult call."); log.debug("We have stopped already. We ignore that placeOfferProtocol.placeOffer.onResult call.");
} }
resultHandler.handleResult(transaction);
}, },
errorMessageHandler errorMessageHandler
); );
placeOfferProtocol.placeOffer(); 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 // Remove from offerbook
public void removeOffer(Offer offer, ResultHandler resultHandler, ErrorMessageHandler errorMessageHandler) { public void removeOffer(Offer offer, ResultHandler resultHandler, ErrorMessageHandler errorMessageHandler) {
Optional<OpenOffer> openOfferOptional = getOpenOfferById(offer.getId()); 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."); 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. " + errorMessageHandler.handleErrorMessage("Offer was not found in our list of open offers. " +
"We still try to remove it from the offerbook."); "We still try to remove it from the offerbook.");
offerBookService.removeOffer(offer.getOfferPayload(), offerBookService.removeOffer(offer.getOfferPayloadBase(),
() -> offer.setState(Offer.State.REMOVED), () -> offer.setState(Offer.State.REMOVED),
null); null);
} }
@ -427,7 +437,20 @@ public class OpenOfferManager implements PeerManager.Listener, DecryptedDirectMe
public void activateOpenOffer(OpenOffer openOffer, public void activateOpenOffer(OpenOffer openOffer,
ResultHandler resultHandler, ResultHandler resultHandler,
ErrorMessageHandler errorMessageHandler) { ErrorMessageHandler errorMessageHandler) {
if (!offersToBeEdited.containsKey(openOffer.getId())) { 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(); Offer offer = openOffer.getOffer();
offerBookService.activateOffer(offer, offerBookService.activateOffer(offer,
() -> { () -> {
@ -437,16 +460,13 @@ public class OpenOfferManager implements PeerManager.Listener, DecryptedDirectMe
resultHandler.handleResult(); resultHandler.handleResult();
}, },
errorMessageHandler); errorMessageHandler);
} else {
errorMessageHandler.handleErrorMessage("You can't activate an offer that is currently edited.");
}
} }
public void deactivateOpenOffer(OpenOffer openOffer, public void deactivateOpenOffer(OpenOffer openOffer,
ResultHandler resultHandler, ResultHandler resultHandler,
ErrorMessageHandler errorMessageHandler) { ErrorMessageHandler errorMessageHandler) {
Offer offer = openOffer.getOffer(); Offer offer = openOffer.getOffer();
offerBookService.deactivateOffer(offer.getOfferPayload(), offerBookService.deactivateOffer(offer.getOfferPayloadBase(),
() -> { () -> {
openOffer.setState(OpenOffer.State.DEACTIVATED); openOffer.setState(OpenOffer.State.DEACTIVATED);
requestPersistence(); requestPersistence();
@ -456,6 +476,12 @@ public class OpenOfferManager implements PeerManager.Listener, DecryptedDirectMe
errorMessageHandler); errorMessageHandler);
} }
public void removeOpenOffer(OpenOffer openOffer) {
removeOpenOffer(openOffer, () -> {
}, error -> {
});
}
public void removeOpenOffer(OpenOffer openOffer, public void removeOpenOffer(OpenOffer openOffer,
ResultHandler resultHandler, ResultHandler resultHandler,
ErrorMessageHandler errorMessageHandler) { ErrorMessageHandler errorMessageHandler) {
@ -464,7 +490,7 @@ public class OpenOfferManager implements PeerManager.Listener, DecryptedDirectMe
if (openOffer.isDeactivated()) { if (openOffer.isDeactivated()) {
onRemoved(openOffer, resultHandler, offer); onRemoved(openOffer, resultHandler, offer);
} else { } else {
offerBookService.removeOffer(offer.getOfferPayload(), offerBookService.removeOffer(offer.getOfferPayloadBase(),
() -> onRemoved(openOffer, resultHandler, offer), () -> onRemoved(openOffer, resultHandler, offer),
errorMessageHandler); errorMessageHandler);
} }
@ -508,18 +534,17 @@ public class OpenOfferManager implements PeerManager.Listener, DecryptedDirectMe
openOffer.getOffer().setState(Offer.State.REMOVED); openOffer.getOffer().setState(Offer.State.REMOVED);
openOffer.setState(OpenOffer.State.CANCELED); openOffer.setState(OpenOffer.State.CANCELED);
openOffers.remove(openOffer); removeOpenOfferFromList(openOffer);
OpenOffer editedOpenOffer = new OpenOffer(editedOffer, triggerPrice); OpenOffer editedOpenOffer = new OpenOffer(editedOffer, triggerPrice);
editedOpenOffer.setState(originalState); editedOpenOffer.setState(originalState);
openOffers.add(editedOpenOffer); addOpenOfferToList(editedOpenOffer);
if (!editedOpenOffer.isDeactivated()) if (!editedOpenOffer.isDeactivated())
republishOffer(editedOpenOffer); maybeRepublishOffer(editedOpenOffer);
offersToBeEdited.remove(openOffer.getId()); offersToBeEdited.remove(openOffer.getId());
requestPersistence();
resultHandler.handleResult(); resultHandler.handleResult();
} else { } else {
errorMessageHandler.handleErrorMessage("There is no offer with this id existing to be published."); 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); offer.setState(Offer.State.REMOVED);
openOffer.setState(OpenOffer.State.CANCELED); openOffer.setState(OpenOffer.State.CANCELED);
openOffers.remove(openOffer); removeOpenOfferFromList(openOffer);
if (!openOffer.getOffer().isBsqSwapOffer()) {
closedTradableManager.add(openOffer); closedTradableManager.add(openOffer);
log.info("onRemoved offerId={}", offer.getId());
btcWalletService.resetAddressEntriesForOpenOffer(offer.getId()); btcWalletService.resetAddressEntriesForOpenOffer(offer.getId());
requestPersistence(); }
log.info("onRemoved offerId={}", offer.getId());
resultHandler.handleResult(); resultHandler.handleResult();
} }
// Close openOffer after deposit published // Close openOffer after deposit published
public void closeOpenOffer(Offer offer) { public void closeOpenOffer(Offer offer) {
getOpenOfferById(offer.getId()).ifPresent(openOffer -> { getOpenOfferById(offer.getId()).ifPresent(openOffer -> {
openOffers.remove(openOffer); removeOpenOfferFromList(openOffer);
openOffer.setState(OpenOffer.State.CLOSED); openOffer.setState(OpenOffer.State.CLOSED);
offerBookService.removeOffer(openOffer.getOffer().getOfferPayload(), offerBookService.removeOffer(openOffer.getOffer().getOfferPayloadBase(),
() -> log.trace("Successful removed offer"), () -> log.trace("Successful removed offer"),
log::error); log::error);
requestPersistence();
}); });
} }
@ -632,7 +657,7 @@ public class OpenOfferManager implements PeerManager.Listener, DecryptedDirectMe
Validator.nonEmptyStringOf(request.offerId); Validator.nonEmptyStringOf(request.offerId);
checkNotNull(request.getPubKeyRing()); checkNotNull(request.getPubKeyRing());
} catch (Throwable t) { } catch (Throwable t) {
errorMessage = "Message validation failed. Error=" + t.toString() + ", Message=" + request.toString(); errorMessage = "Message validation failed. Error=" + t + ", Message=" + request;
log.warn(errorMessage); log.warn(errorMessage);
sendAckMessage(request, peer, false, errorMessage); sendAckMessage(request, peer, false, errorMessage);
return; return;
@ -787,23 +812,27 @@ public class OpenOfferManager implements PeerManager.Listener, DecryptedDirectMe
ArrayList<OpenOffer> openOffersClone = new ArrayList<>(openOffers.getList()); ArrayList<OpenOffer> openOffersClone = new ArrayList<>(openOffers.getList());
openOffersClone.forEach(originalOpenOffer -> { openOffersClone.forEach(originalOpenOffer -> {
Offer originalOffer = originalOpenOffer.getOffer(); Offer originalOffer = originalOpenOffer.getOffer();
if (originalOffer.isBsqSwapOffer()) {
OfferPayload originalOfferPayload = originalOffer.getOfferPayload(); // 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 // 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 // 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. // 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.MEDIATION) ||
!OfferRestrictions.hasOfferMandatoryCapability(originalOffer, Capability.REFUND_AGENT) || !OfferRestrictions.hasOfferMandatoryCapability(originalOffer, Capability.REFUND_AGENT) ||
!originalOfferPayload.getOwnerNodeAddress().equals(p2PService.getAddress())) { !original.getOwnerNodeAddress().equals(p2PService.getAddress())) {
// - Capabilities changed? // - Capabilities changed?
// We rewrite our offer with the additional capabilities entry // We rewrite our offer with the additional capabilities entry
Map<String, String> updatedExtraDataMap = new HashMap<>(); Map<String, String> updatedExtraDataMap = new HashMap<>();
if (!OfferRestrictions.hasOfferMandatoryCapability(originalOffer, Capability.MEDIATION) || if (!OfferRestrictions.hasOfferMandatoryCapability(originalOffer, Capability.MEDIATION) ||
!OfferRestrictions.hasOfferMandatoryCapability(originalOffer, Capability.REFUND_AGENT)) { !OfferRestrictions.hasOfferMandatoryCapability(originalOffer, Capability.REFUND_AGENT)) {
Map<String, String> originalExtraDataMap = originalOfferPayload.getExtraDataMap(); Map<String, String> originalExtraDataMap = original.getExtraDataMap();
if (originalExtraDataMap != null) { if (originalExtraDataMap != null) {
updatedExtraDataMap.putAll(originalExtraDataMap); 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()); log.info("Converted offer to support new Capability.MEDIATION and Capability.REFUND_AGENT capability. id={}", originalOffer.getId());
} else { } else {
updatedExtraDataMap = originalOfferPayload.getExtraDataMap(); updatedExtraDataMap = original.getExtraDataMap();
} }
// - Protocol version changed? // - Protocol version changed?
int protocolVersion = originalOfferPayload.getProtocolVersion(); int protocolVersion = original.getProtocolVersion();
if (protocolVersion < Version.TRADE_PROTOCOL_VERSION) { if (protocolVersion < Version.TRADE_PROTOCOL_VERSION) {
// We update the trade protocol version // We update the trade protocol version
protocolVersion = Version.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) // - node address changed? (due to a faulty tor dir)
NodeAddress ownerNodeAddress = originalOfferPayload.getOwnerNodeAddress(); NodeAddress ownerNodeAddress = original.getOwnerNodeAddress();
if (!ownerNodeAddress.equals(p2PService.getAddress())) { if (!ownerNodeAddress.equals(p2PService.getAddress())) {
ownerNodeAddress = p2PService.getAddress(); ownerNodeAddress = p2PService.getAddress();
log.info("Updated the owner nodeaddress of offer id={}", originalOffer.getId()); log.info("Updated the owner nodeaddress of offer id={}", originalOffer.getId());
} }
OfferPayload updatedPayload = new OfferPayload(originalOfferPayload.getId(), OfferPayload updatedPayload = new OfferPayload(original.getId(),
originalOfferPayload.getDate(), original.getDate(),
ownerNodeAddress, ownerNodeAddress,
originalOfferPayload.getPubKeyRing(), original.getPubKeyRing(),
originalOfferPayload.getDirection(), original.getDirection(),
originalOfferPayload.getPrice(), original.getPrice(),
originalOfferPayload.getMarketPriceMargin(), original.getMarketPriceMargin(),
originalOfferPayload.isUseMarketBasedPrice(), original.isUseMarketBasedPrice(),
originalOfferPayload.getAmount(), original.getAmount(),
originalOfferPayload.getMinAmount(), original.getMinAmount(),
originalOfferPayload.getBaseCurrencyCode(), original.getBaseCurrencyCode(),
originalOfferPayload.getCounterCurrencyCode(), original.getCounterCurrencyCode(),
originalOfferPayload.getArbitratorNodeAddresses(), original.getArbitratorNodeAddresses(),
originalOfferPayload.getMediatorNodeAddresses(), original.getMediatorNodeAddresses(),
originalOfferPayload.getPaymentMethodId(), original.getPaymentMethodId(),
originalOfferPayload.getMakerPaymentAccountId(), original.getMakerPaymentAccountId(),
originalOfferPayload.getOfferFeePaymentTxId(), original.getOfferFeePaymentTxId(),
originalOfferPayload.getCountryCode(), original.getCountryCode(),
originalOfferPayload.getAcceptedCountryCodes(), original.getAcceptedCountryCodes(),
originalOfferPayload.getBankId(), original.getBankId(),
originalOfferPayload.getAcceptedBankIds(), original.getAcceptedBankIds(),
originalOfferPayload.getVersionNr(), original.getVersionNr(),
originalOfferPayload.getBlockHeightAtOfferCreation(), original.getBlockHeightAtOfferCreation(),
originalOfferPayload.getTxFee(), original.getTxFee(),
originalOfferPayload.getMakerFee(), original.getMakerFee(),
originalOfferPayload.isCurrencyForMakerFeeBtc(), original.isCurrencyForMakerFeeBtc(),
originalOfferPayload.getBuyerSecurityDeposit(), original.getBuyerSecurityDeposit(),
originalOfferPayload.getSellerSecurityDeposit(), original.getSellerSecurityDeposit(),
originalOfferPayload.getMaxTradeLimit(), original.getMaxTradeLimit(),
originalOfferPayload.getMaxTradePeriod(), original.getMaxTradePeriod(),
originalOfferPayload.isUseAutoClose(), original.isUseAutoClose(),
originalOfferPayload.isUseReOpenAfterAutoClose(), original.isUseReOpenAfterAutoClose(),
originalOfferPayload.getLowerClosePrice(), original.getLowerClosePrice(),
originalOfferPayload.getUpperClosePrice(), original.getUpperClosePrice(),
originalOfferPayload.isPrivateOffer(), original.isPrivateOffer(),
originalOfferPayload.getHashOfChallenge(), original.getHashOfChallenge(),
updatedExtraDataMap, updatedExtraDataMap,
protocolVersion); protocolVersion);
@ -878,7 +907,7 @@ public class OpenOfferManager implements PeerManager.Listener, DecryptedDirectMe
// remove old offer // remove old offer
originalOffer.setState(Offer.State.REMOVED); originalOffer.setState(Offer.State.REMOVED);
originalOpenOffer.setState(OpenOffer.State.CANCELED); originalOpenOffer.setState(OpenOffer.State.CANCELED);
openOffers.remove(originalOpenOffer); removeOpenOfferFromList(originalOpenOffer);
// Create new Offer // Create new Offer
Offer updatedOffer = new Offer(updatedPayload); Offer updatedOffer = new Offer(updatedPayload);
@ -887,8 +916,7 @@ public class OpenOfferManager implements PeerManager.Listener, DecryptedDirectMe
OpenOffer updatedOpenOffer = new OpenOffer(updatedOffer, originalOpenOffer.getTriggerPrice()); OpenOffer updatedOpenOffer = new OpenOffer(updatedOffer, originalOpenOffer.getTriggerPrice());
updatedOpenOffer.setState(originalOpenOfferState); updatedOpenOffer.setState(originalOpenOfferState);
openOffers.add(updatedOpenOffer); addOpenOfferToList(updatedOpenOffer);
requestPersistence();
log.info("Updating offer completed. id={}", originalOffer.getId()); log.info("Updating offer completed. id={}", originalOffer.getId());
} }
@ -904,7 +932,6 @@ public class OpenOfferManager implements PeerManager.Listener, DecryptedDirectMe
if (stopped) { if (stopped) {
return; return;
} }
stopPeriodicRefreshOffersTimer(); stopPeriodicRefreshOffersTimer();
List<OpenOffer> openOffersList = new ArrayList<>(openOffers.getList()); List<OpenOffer> openOffersList = new ArrayList<>(openOffers.getList());
@ -917,15 +944,8 @@ public class OpenOfferManager implements PeerManager.Listener, DecryptedDirectMe
} }
OpenOffer openOffer = list.remove(0); OpenOffer openOffer = list.remove(0);
if (openOffers.contains(openOffer) && !openOffer.isDeactivated()) { if (openOffers.contains(openOffer)) {
// TODO It is not clear yet if it is better for the node and the network to send out all add offer maybeRepublishOffer(openOffer, () -> processListForRepublishOffers(list));
// 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));*/
} else { } else {
// If the offer was removed in the meantime or if its deactivated we skip and call // 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. // 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) { public void maybeRepublishOffer(OpenOffer openOffer) {
republishOffer(openOffer, null); maybeRepublishOffer(openOffer, null);
}
private void maybeRepublishOffer(OpenOffer openOffer, @Nullable Runnable completeHandler) {
if (preventedFromPublishing(openOffer)) {
if (completeHandler != null) {
completeHandler.run();
}
return;
} }
private void republishOffer(OpenOffer openOffer, @Nullable Runnable completeHandler) {
offerBookService.addOffer(openOffer.getOffer(), offerBookService.addOffer(openOffer.getOffer(),
() -> { () -> {
if (!stopped) { if (!stopped) {
@ -996,8 +1023,8 @@ public class OpenOfferManager implements PeerManager.Listener, DecryptedDirectMe
final OpenOffer openOffer = openOffersList.get(i); final OpenOffer openOffer = openOffersList.get(i);
UserThread.runAfterRandomDelay(() -> { UserThread.runAfterRandomDelay(() -> {
// we need to check if in the meantime the offer has been removed // we need to check if in the meantime the offer has been removed
if (openOffers.contains(openOffer) && !openOffer.isDeactivated()) if (openOffers.contains(openOffer))
refreshOffer(openOffer); maybeRefreshOffer(openOffer);
}, minDelay, maxDelay, TimeUnit.MILLISECONDS); }, minDelay, maxDelay, TimeUnit.MILLISECONDS);
} }
} else { } else {
@ -1010,8 +1037,11 @@ public class OpenOfferManager implements PeerManager.Listener, DecryptedDirectMe
log.trace("periodicRefreshOffersTimer already stated"); log.trace("periodicRefreshOffersTimer already stated");
} }
private void refreshOffer(OpenOffer openOffer) { private void maybeRefreshOffer(OpenOffer openOffer) {
offerBookService.refreshTTL(openOffer.getOffer().getOfferPayload(), if (preventedFromPublishing(openOffer)) {
return;
}
offerBookService.refreshTTL(openOffer.getOffer().getOfferPayloadBase(),
() -> log.debug("Successful refreshed TTL for offer"), () -> log.debug("Successful refreshed TTL for offer"),
log::warn); log::warn);
} }
@ -1028,7 +1058,7 @@ public class OpenOfferManager implements PeerManager.Listener, DecryptedDirectMe
startPeriodicRepublishOffersTimer(); startPeriodicRepublishOffersTimer();
} }
private void requestPersistence() { public void requestPersistence() {
persistenceManager.requestPersistence(); persistenceManager.requestPersistence();
} }
@ -1057,4 +1087,23 @@ public class OpenOfferManager implements PeerManager.Listener, DecryptedDirectMe
retryRepublishOffersTimer = null; 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/>. * along with Bisq. If not, see <http://www.gnu.org/licenses/>.
*/ */
package bisq.core.offer; package bisq.core.offer.availability;
public enum AvailabilityResult { public enum AvailabilityResult {
UNKNOWN_FAILURE("cannot take offer for unknown reason"), UNKNOWN_FAILURE("cannot take offer for unknown reason"),

View file

@ -18,7 +18,7 @@
package bisq.core.offer.availability; package bisq.core.offer.availability;
import bisq.core.offer.Offer; 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.support.dispute.mediation.mediator.MediatorManager;
import bisq.core.trade.statistics.TradeStatisticsManager; import bisq.core.trade.statistics.TradeStatisticsManager;
import bisq.core.user.User; import bisq.core.user.User;

View file

@ -18,10 +18,10 @@
package bisq.core.offer.availability; package bisq.core.offer.availability;
import bisq.core.offer.Offer; 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.ProcessOfferAvailabilityResponse;
import bisq.core.offer.availability.tasks.SendOfferAvailabilityRequest; 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.core.util.Validator;
import bisq.network.p2p.AckMessage; import bisq.network.p2p.AckMessage;

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