mirror of
https://github.com/bisq-network/bisq.git
synced 2025-02-22 14:42:37 +01:00
Merge pull request #5775 from chimp1984/bsq-swap-impl
Bsq swap: Implementation [3]
This commit is contained in:
commit
da66ffe8f5
522 changed files with 20014 additions and 3714 deletions
|
@ -458,7 +458,7 @@ delayconfirmpaymentreceived() {
|
|||
}
|
||||
|
||||
# This is a large function that should be broken up if it ever makes sense to not treat a trade
|
||||
# execution simulation as an atomic operation. But we are not testing api methods here, just
|
||||
# execution simulation as an bsq swap operation. But we are not testing api methods here, just
|
||||
# demonstrating how to use them to get through the trade protocol. It should work for any trade
|
||||
# between Bob & Alice, as long as Alice is maker, Bob is taker, and the offer to be taken is the
|
||||
# first displayed in Bob's getoffers command output.
|
||||
|
|
|
@ -24,7 +24,7 @@ import java.util.List;
|
|||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
import static bisq.apitest.config.ApiTestConfig.BASH_PATH_VALUE;
|
||||
import static java.lang.management.ManagementFactory.getRuntimeMXBean;
|
||||
|
@ -33,7 +33,9 @@ import static java.lang.management.ManagementFactory.getRuntimeMXBean;
|
|||
public class BashCommand {
|
||||
|
||||
private int exitStatus = -1;
|
||||
@Nullable
|
||||
private String output;
|
||||
@Nullable
|
||||
private String error;
|
||||
|
||||
private final String command;
|
||||
|
@ -92,6 +94,7 @@ public class BashCommand {
|
|||
}
|
||||
|
||||
// TODO return Optional<String>
|
||||
@Nullable
|
||||
public String getOutput() {
|
||||
return this.output;
|
||||
}
|
||||
|
@ -101,7 +104,6 @@ public class BashCommand {
|
|||
return this.error;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private List<String> tokenizeSystemCommand() {
|
||||
return new ArrayList<>() {{
|
||||
add(BASH_PATH_VALUE);
|
||||
|
|
|
@ -57,15 +57,13 @@ class SystemCommandExecutor {
|
|||
private ThreadedStreamHandler errorStreamHandler;
|
||||
|
||||
public SystemCommandExecutor(final List<String> cmdOptions) {
|
||||
if (log.isDebugEnabled())
|
||||
log.debug("cmd options {}", cmdOptions.toString());
|
||||
|
||||
if (cmdOptions.isEmpty())
|
||||
throw new IllegalStateException("No command params specified.");
|
||||
|
||||
if (cmdOptions.contains("sudo"))
|
||||
throw new IllegalStateException("'sudo' commands are prohibited.");
|
||||
|
||||
log.trace("System cmd options {}", cmdOptions);
|
||||
this.cmdOptions = cmdOptions;
|
||||
}
|
||||
|
||||
|
|
|
@ -17,6 +17,8 @@
|
|||
|
||||
package bisq.apitest;
|
||||
|
||||
import java.time.Duration;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import java.util.concurrent.ExecutionException;
|
||||
|
@ -32,9 +34,9 @@ import static bisq.apitest.config.ApiTestRateMeterInterceptorConfig.getTestRateM
|
|||
import static bisq.apitest.config.BisqAppConfig.alicedaemon;
|
||||
import static bisq.apitest.config.BisqAppConfig.arbdaemon;
|
||||
import static bisq.apitest.config.BisqAppConfig.bobdaemon;
|
||||
import static com.google.common.util.concurrent.Uninterruptibles.sleepUninterruptibly;
|
||||
import static java.net.InetAddress.getLoopbackAddress;
|
||||
import static java.util.Arrays.stream;
|
||||
import static java.util.concurrent.TimeUnit.MILLISECONDS;
|
||||
|
||||
|
||||
|
||||
|
@ -131,11 +133,7 @@ public class ApiTestCase {
|
|||
}
|
||||
|
||||
protected static void sleep(long ms) {
|
||||
try {
|
||||
MILLISECONDS.sleep(ms);
|
||||
} catch (InterruptedException ignored) {
|
||||
// empty
|
||||
}
|
||||
sleepUninterruptibly(Duration.ofMillis(ms));
|
||||
}
|
||||
|
||||
protected final String testName(TestInfo testInfo) {
|
||||
|
|
|
@ -31,7 +31,12 @@ import java.io.PrintWriter;
|
|||
import java.util.function.Function;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
import static bisq.apitest.config.ApiTestRateMeterInterceptorConfig.getTestRateMeterInterceptorConfig;
|
||||
import static java.lang.String.format;
|
||||
import static java.nio.charset.StandardCharsets.UTF_8;
|
||||
import static java.util.Arrays.stream;
|
||||
import static org.junit.jupiter.api.Assertions.fail;
|
||||
|
@ -39,6 +44,7 @@ import static org.junit.jupiter.api.Assertions.fail;
|
|||
|
||||
|
||||
import bisq.apitest.ApiTestCase;
|
||||
import bisq.apitest.linux.BashCommand;
|
||||
import bisq.cli.GrpcClient;
|
||||
|
||||
public class MethodTest extends ApiTestCase {
|
||||
|
@ -144,14 +150,42 @@ public class MethodTest extends ApiTestCase {
|
|||
protected final bisq.core.payment.PaymentAccount createPaymentAccount(GrpcClient grpcClient, String jsonString) {
|
||||
// Normally, we do asserts on the protos from the gRPC service, but in this
|
||||
// case we need a bisq.core.payment.PaymentAccount so it can be cast to its
|
||||
// sub type.
|
||||
// sub-type.
|
||||
var paymentAccount = grpcClient.createPaymentAccount(jsonString);
|
||||
return bisq.core.payment.PaymentAccount.fromProto(paymentAccount, CORE_PROTO_RESOLVER);
|
||||
}
|
||||
|
||||
// Static conveniences for test methods and test case fixture setups.
|
||||
|
||||
protected static String encodeToHex(String s) {
|
||||
return Utilities.bytesAsHexString(s.getBytes(UTF_8));
|
||||
}
|
||||
|
||||
protected void verifyNoLoggedNodeExceptions() {
|
||||
var loggedExceptions = getNodeExceptionMessages();
|
||||
if (loggedExceptions != null) {
|
||||
String err = format("Exception(s) found in daemon log(s):%n%s", loggedExceptions);
|
||||
fail(err);
|
||||
}
|
||||
}
|
||||
|
||||
protected void printNodeExceptionMessages(Logger log) {
|
||||
var loggedExceptions = getNodeExceptionMessages();
|
||||
if (loggedExceptions != null)
|
||||
log.error("Exception(s) found in daemon log(s):\n{}", loggedExceptions);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
protected static String getNodeExceptionMessages() {
|
||||
var nodeLogsSpec = config.rootAppDataDir.getAbsolutePath() + "/bisq-BTC_REGTEST_*_dao/bisq.log";
|
||||
var grep = "grep Exception " + nodeLogsSpec;
|
||||
var bashCommand = new BashCommand(grep);
|
||||
try {
|
||||
bashCommand.run();
|
||||
} catch (IOException | InterruptedException ex) {
|
||||
fail("Bash command execution error: " + ex);
|
||||
}
|
||||
if (bashCommand.getError() == null)
|
||||
return bashCommand.getOutput();
|
||||
else
|
||||
throw new IllegalStateException("Bash command execution error: " + bashCommand.getError());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -66,6 +66,16 @@ public abstract class AbstractOfferTest extends MethodTest {
|
|||
bobdaemon);
|
||||
}
|
||||
|
||||
public static void createBsqSwapBsqPaymentAccounts() {
|
||||
alicesBsqAcct = aliceClient.createCryptoCurrencyPaymentAccount("Alice's BsqSwap Account",
|
||||
BSQ,
|
||||
aliceClient.getUnusedBsqAddress(), // TODO refactor, bsq address not needed for atom acct
|
||||
false);
|
||||
bobsBsqAcct = bobClient.createCryptoCurrencyPaymentAccount("Bob's BsqSwap Account",
|
||||
BSQ,
|
||||
bobClient.getUnusedBsqAddress(), // TODO refactor, bsq address not needed for atom acct
|
||||
false);
|
||||
}
|
||||
|
||||
// Mkt Price Margin value of offer returned from server is scaled down by 10^-2.
|
||||
protected final Function<Double, Double> scaledDownMktPriceMargin = (mktPriceMargin) ->
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -35,7 +35,7 @@ import org.junit.jupiter.api.TestMethodOrder;
|
|||
import static bisq.apitest.config.ApiTestConfig.BSQ;
|
||||
import static bisq.core.btc.wallet.Restrictions.getDefaultBuyerSecurityDepositAsPercent;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static protobuf.OfferPayload.Direction.BUY;
|
||||
import static protobuf.OfferDirection.BUY;
|
||||
|
||||
@Disabled
|
||||
@Slf4j
|
||||
|
|
|
@ -40,8 +40,8 @@ import static org.junit.jupiter.api.Assertions.assertEquals;
|
|||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||
import static org.junit.jupiter.api.Assertions.assertNotEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
import static protobuf.OfferPayload.Direction.BUY;
|
||||
import static protobuf.OfferPayload.Direction.SELL;
|
||||
import static protobuf.OfferDirection.BUY;
|
||||
import static protobuf.OfferDirection.SELL;
|
||||
|
||||
@Disabled
|
||||
@Slf4j
|
||||
|
|
|
@ -36,8 +36,8 @@ import static org.junit.jupiter.api.Assertions.assertEquals;
|
|||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||
import static org.junit.jupiter.api.Assertions.assertNotEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
import static protobuf.OfferPayload.Direction.BUY;
|
||||
import static protobuf.OfferPayload.Direction.SELL;
|
||||
import static protobuf.OfferDirection.BUY;
|
||||
import static protobuf.OfferDirection.SELL;
|
||||
|
||||
@Disabled
|
||||
@Slf4j
|
||||
|
|
|
@ -52,8 +52,8 @@ import static org.junit.jupiter.api.Assertions.assertEquals;
|
|||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||
import static org.junit.jupiter.api.Assertions.assertNotEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
import static protobuf.OfferPayload.Direction.BUY;
|
||||
import static protobuf.OfferPayload.Direction.SELL;
|
||||
import static protobuf.OfferDirection.BUY;
|
||||
import static protobuf.OfferDirection.SELL;
|
||||
|
||||
@SuppressWarnings("ConstantConditions")
|
||||
@Disabled
|
||||
|
|
|
@ -47,8 +47,8 @@ import static org.junit.jupiter.api.Assertions.assertEquals;
|
|||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
import static protobuf.OfferPayload.Direction.BUY;
|
||||
import static protobuf.OfferPayload.Direction.SELL;
|
||||
import static protobuf.OfferDirection.BUY;
|
||||
import static protobuf.OfferDirection.SELL;
|
||||
|
||||
@SuppressWarnings("ALL")
|
||||
@Disabled
|
||||
|
|
|
@ -35,7 +35,7 @@ import static bisq.core.btc.wallet.Restrictions.getDefaultBuyerSecurityDepositAs
|
|||
import static java.lang.String.format;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||
import static protobuf.OfferPayload.Direction.BUY;
|
||||
import static protobuf.OfferDirection.BUY;
|
||||
|
||||
@Disabled
|
||||
@Slf4j
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,6 +1,6 @@
|
|||
package bisq.apitest.method.trade;
|
||||
|
||||
import bisq.core.trade.Trade;
|
||||
import bisq.core.trade.model.bisq_v1.Trade;
|
||||
|
||||
/**
|
||||
* A test fixture encapsulating expected trade protocol status.
|
||||
|
|
|
@ -37,13 +37,13 @@ import static bisq.apitest.config.ApiTestConfig.BSQ;
|
|||
import static bisq.cli.TableFormat.formatBalancesTbls;
|
||||
import static bisq.cli.TableFormat.formatOfferTable;
|
||||
import static bisq.core.btc.wallet.Restrictions.getDefaultBuyerSecurityDepositAsPercent;
|
||||
import static bisq.core.trade.Trade.Phase.DEPOSIT_CONFIRMED;
|
||||
import static bisq.core.trade.Trade.Phase.FIAT_SENT;
|
||||
import static bisq.core.trade.Trade.Phase.PAYOUT_PUBLISHED;
|
||||
import static bisq.core.trade.Trade.State.BUYER_RECEIVED_PAYOUT_TX_PUBLISHED_MSG;
|
||||
import static bisq.core.trade.Trade.State.DEPOSIT_CONFIRMED_IN_BLOCK_CHAIN;
|
||||
import static bisq.core.trade.Trade.State.SELLER_RECEIVED_FIAT_PAYMENT_INITIATED_MSG;
|
||||
import static bisq.core.trade.Trade.State.SELLER_SAW_ARRIVED_PAYOUT_TX_PUBLISHED_MSG;
|
||||
import static bisq.core.trade.model.bisq_v1.Trade.Phase.DEPOSIT_CONFIRMED;
|
||||
import static bisq.core.trade.model.bisq_v1.Trade.Phase.FIAT_SENT;
|
||||
import static bisq.core.trade.model.bisq_v1.Trade.Phase.PAYOUT_PUBLISHED;
|
||||
import static bisq.core.trade.model.bisq_v1.Trade.State.BUYER_RECEIVED_PAYOUT_TX_PUBLISHED_MSG;
|
||||
import static bisq.core.trade.model.bisq_v1.Trade.State.DEPOSIT_CONFIRMED_IN_BLOCK_CHAIN;
|
||||
import static bisq.core.trade.model.bisq_v1.Trade.State.SELLER_RECEIVED_FIAT_PAYMENT_INITIATED_MSG;
|
||||
import static bisq.core.trade.model.bisq_v1.Trade.State.SELLER_SAW_ARRIVED_PAYOUT_TX_PUBLISHED_MSG;
|
||||
import static java.lang.String.format;
|
||||
import static java.util.Collections.singletonList;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
|
@ -51,7 +51,7 @@ import static org.junit.jupiter.api.Assertions.assertFalse;
|
|||
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
||||
import static org.junit.jupiter.api.Assertions.fail;
|
||||
import static protobuf.Offer.State.OFFER_FEE_PAID;
|
||||
import static protobuf.OfferPayload.Direction.SELL;
|
||||
import static protobuf.OfferDirection.SELL;
|
||||
|
||||
|
||||
|
||||
|
|
|
@ -37,17 +37,17 @@ import org.junit.jupiter.api.TestMethodOrder;
|
|||
import static bisq.apitest.config.ApiTestConfig.BSQ;
|
||||
import static bisq.cli.TableFormat.formatBalancesTbls;
|
||||
import static bisq.core.btc.wallet.Restrictions.getDefaultBuyerSecurityDepositAsPercent;
|
||||
import static bisq.core.trade.Trade.Phase.DEPOSIT_CONFIRMED;
|
||||
import static bisq.core.trade.Trade.Phase.FIAT_SENT;
|
||||
import static bisq.core.trade.Trade.Phase.PAYOUT_PUBLISHED;
|
||||
import static bisq.core.trade.Trade.State.*;
|
||||
import static bisq.core.trade.model.bisq_v1.Trade.Phase.DEPOSIT_CONFIRMED;
|
||||
import static bisq.core.trade.model.bisq_v1.Trade.Phase.FIAT_SENT;
|
||||
import static bisq.core.trade.model.bisq_v1.Trade.Phase.PAYOUT_PUBLISHED;
|
||||
import static bisq.core.trade.model.bisq_v1.Trade.State.*;
|
||||
import static java.lang.String.format;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
||||
import static org.junit.jupiter.api.Assertions.fail;
|
||||
import static protobuf.Offer.State.OFFER_FEE_PAID;
|
||||
import static protobuf.OfferPayload.Direction.BUY;
|
||||
import static protobuf.OfferDirection.BUY;
|
||||
import static protobuf.OpenOffer.State.AVAILABLE;
|
||||
|
||||
@Disabled
|
||||
|
|
|
@ -55,14 +55,14 @@ import org.junit.jupiter.api.TestMethodOrder;
|
|||
import static bisq.apitest.config.ApiTestConfig.BSQ;
|
||||
import static bisq.cli.TableFormat.formatBalancesTbls;
|
||||
import static bisq.core.btc.wallet.Restrictions.getDefaultBuyerSecurityDepositAsPercent;
|
||||
import static bisq.core.trade.Trade.Phase.DEPOSIT_CONFIRMED;
|
||||
import static bisq.core.trade.Trade.Phase.FIAT_SENT;
|
||||
import static bisq.core.trade.Trade.Phase.PAYOUT_PUBLISHED;
|
||||
import static bisq.core.trade.Trade.State.*;
|
||||
import static bisq.core.trade.model.bisq_v1.Trade.Phase.DEPOSIT_CONFIRMED;
|
||||
import static bisq.core.trade.model.bisq_v1.Trade.Phase.FIAT_SENT;
|
||||
import static bisq.core.trade.model.bisq_v1.Trade.Phase.PAYOUT_PUBLISHED;
|
||||
import static bisq.core.trade.model.bisq_v1.Trade.State.*;
|
||||
import static java.lang.String.format;
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
import static protobuf.Offer.State.OFFER_FEE_PAID;
|
||||
import static protobuf.OfferPayload.Direction.BUY;
|
||||
import static protobuf.OfferDirection.BUY;
|
||||
import static protobuf.OpenOffer.State.AVAILABLE;
|
||||
|
||||
/**
|
||||
|
|
|
@ -38,21 +38,21 @@ import static bisq.apitest.config.ApiTestConfig.BTC;
|
|||
import static bisq.cli.TableFormat.formatBalancesTbls;
|
||||
import static bisq.cli.TableFormat.formatOfferTable;
|
||||
import static bisq.core.btc.wallet.Restrictions.getDefaultBuyerSecurityDepositAsPercent;
|
||||
import static bisq.core.trade.Trade.Phase.DEPOSIT_CONFIRMED;
|
||||
import static bisq.core.trade.Trade.Phase.FIAT_SENT;
|
||||
import static bisq.core.trade.Trade.Phase.PAYOUT_PUBLISHED;
|
||||
import static bisq.core.trade.Trade.Phase.WITHDRAWN;
|
||||
import static bisq.core.trade.Trade.State.DEPOSIT_CONFIRMED_IN_BLOCK_CHAIN;
|
||||
import static bisq.core.trade.Trade.State.SELLER_RECEIVED_FIAT_PAYMENT_INITIATED_MSG;
|
||||
import static bisq.core.trade.Trade.State.SELLER_SAW_ARRIVED_PAYOUT_TX_PUBLISHED_MSG;
|
||||
import static bisq.core.trade.Trade.State.WITHDRAW_COMPLETED;
|
||||
import static bisq.core.trade.model.bisq_v1.Trade.Phase.DEPOSIT_CONFIRMED;
|
||||
import static bisq.core.trade.model.bisq_v1.Trade.Phase.FIAT_SENT;
|
||||
import static bisq.core.trade.model.bisq_v1.Trade.Phase.PAYOUT_PUBLISHED;
|
||||
import static bisq.core.trade.model.bisq_v1.Trade.Phase.WITHDRAWN;
|
||||
import static bisq.core.trade.model.bisq_v1.Trade.State.DEPOSIT_CONFIRMED_IN_BLOCK_CHAIN;
|
||||
import static bisq.core.trade.model.bisq_v1.Trade.State.SELLER_RECEIVED_FIAT_PAYMENT_INITIATED_MSG;
|
||||
import static bisq.core.trade.model.bisq_v1.Trade.State.SELLER_SAW_ARRIVED_PAYOUT_TX_PUBLISHED_MSG;
|
||||
import static bisq.core.trade.model.bisq_v1.Trade.State.WITHDRAW_COMPLETED;
|
||||
import static java.lang.String.format;
|
||||
import static java.util.Collections.singletonList;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
import static org.junit.jupiter.api.Assertions.fail;
|
||||
import static protobuf.OfferPayload.Direction.BUY;
|
||||
import static protobuf.OfferDirection.BUY;
|
||||
|
||||
|
||||
|
||||
|
|
|
@ -37,18 +37,18 @@ import org.junit.jupiter.api.TestMethodOrder;
|
|||
import static bisq.apitest.config.ApiTestConfig.BTC;
|
||||
import static bisq.cli.TableFormat.formatBalancesTbls;
|
||||
import static bisq.core.btc.wallet.Restrictions.getDefaultBuyerSecurityDepositAsPercent;
|
||||
import static bisq.core.trade.Trade.Phase.DEPOSIT_CONFIRMED;
|
||||
import static bisq.core.trade.Trade.Phase.FIAT_SENT;
|
||||
import static bisq.core.trade.Trade.Phase.PAYOUT_PUBLISHED;
|
||||
import static bisq.core.trade.Trade.Phase.WITHDRAWN;
|
||||
import static bisq.core.trade.Trade.State.*;
|
||||
import static bisq.core.trade.model.bisq_v1.Trade.Phase.DEPOSIT_CONFIRMED;
|
||||
import static bisq.core.trade.model.bisq_v1.Trade.Phase.FIAT_SENT;
|
||||
import static bisq.core.trade.model.bisq_v1.Trade.Phase.PAYOUT_PUBLISHED;
|
||||
import static bisq.core.trade.model.bisq_v1.Trade.Phase.WITHDRAWN;
|
||||
import static bisq.core.trade.model.bisq_v1.Trade.State.*;
|
||||
import static java.lang.String.format;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
import static org.junit.jupiter.api.Assertions.fail;
|
||||
import static protobuf.Offer.State.OFFER_FEE_PAID;
|
||||
import static protobuf.OfferPayload.Direction.SELL;
|
||||
import static protobuf.OfferDirection.SELL;
|
||||
import static protobuf.OpenOffer.State.AVAILABLE;
|
||||
|
||||
@Disabled
|
||||
|
|
|
@ -124,7 +124,7 @@ public class BsqWalletTest extends MethodTest {
|
|||
genBtcBlocksThenWait(1, 4000);
|
||||
|
||||
BsqBalanceInfo alicesBsqBalances = aliceClient.getBalances().getBsq();
|
||||
BsqBalanceInfo bobsBsqBalances = waitForBsqNewAvailableConfirmedBalance(bobClient, 150000000);
|
||||
BsqBalanceInfo bobsBsqBalances = waitForBsqNewAvailableBalance(bobClient, 150000000);
|
||||
|
||||
log.debug("See Available Confirmed BSQ Balances...");
|
||||
printBobAndAliceBsqBalances(testInfo,
|
||||
|
@ -166,8 +166,8 @@ public class BsqWalletTest extends MethodTest {
|
|||
return bsqBalance;
|
||||
}
|
||||
|
||||
private BsqBalanceInfo waitForBsqNewAvailableConfirmedBalance(GrpcClient grpcClient,
|
||||
long staleBalance) {
|
||||
private BsqBalanceInfo waitForBsqNewAvailableBalance(GrpcClient grpcClient,
|
||||
long staleBalance) {
|
||||
BsqBalanceInfo bsqBalance = grpcClient.getBsqBalances();
|
||||
for (int numRequests = 1;
|
||||
numRequests <= 15 && bsqBalance.getAvailableConfirmedBalance() == staleBalance;
|
||||
|
|
|
@ -38,13 +38,13 @@ public class WalletTestUtil {
|
|||
0);
|
||||
|
||||
@SuppressWarnings("SameParameterValue")
|
||||
public static bisq.core.api.model.BsqBalanceInfo bsqBalanceModel(long availableConfirmedBalance,
|
||||
public static bisq.core.api.model.BsqBalanceInfo bsqBalanceModel(long availableBalance,
|
||||
long unverifiedBalance,
|
||||
long unconfirmedChangeBalance,
|
||||
long lockedForVotingBalance,
|
||||
long lockupBondsBalance,
|
||||
long unlockingBondsBalance) {
|
||||
return bisq.core.api.model.BsqBalanceInfo.valueOf(availableConfirmedBalance,
|
||||
return bisq.core.api.model.BsqBalanceInfo.valueOf(availableBalance,
|
||||
unverifiedBalance,
|
||||
unconfirmedChangeBalance,
|
||||
lockedForVotingBalance,
|
||||
|
@ -54,7 +54,7 @@ public class WalletTestUtil {
|
|||
|
||||
public static void verifyBsqBalances(bisq.core.api.model.BsqBalanceInfo expected,
|
||||
BsqBalanceInfo actual) {
|
||||
assertEquals(expected.getAvailableConfirmedBalance(), actual.getAvailableConfirmedBalance());
|
||||
assertEquals(expected.getAvailableBalance(), actual.getAvailableConfirmedBalance());
|
||||
assertEquals(expected.getUnverifiedBalance(), actual.getUnverifiedBalance());
|
||||
assertEquals(expected.getUnconfirmedChangeBalance(), actual.getUnconfirmedChangeBalance());
|
||||
assertEquals(expected.getLockedForVotingBalance(), actual.getLockedForVotingBalance());
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -35,8 +35,8 @@ import static bisq.cli.CurrencyFormat.formatPrice;
|
|||
import static bisq.core.btc.wallet.Restrictions.getDefaultBuyerSecurityDepositAsPercent;
|
||||
import static java.lang.System.getenv;
|
||||
import static org.junit.jupiter.api.Assertions.fail;
|
||||
import static protobuf.OfferPayload.Direction.BUY;
|
||||
import static protobuf.OfferPayload.Direction.SELL;
|
||||
import static protobuf.OfferDirection.BUY;
|
||||
import static protobuf.OfferDirection.SELL;
|
||||
|
||||
|
||||
|
||||
|
|
|
@ -28,6 +28,7 @@ import org.junit.jupiter.api.TestMethodOrder;
|
|||
|
||||
|
||||
import bisq.apitest.method.offer.AbstractOfferTest;
|
||||
import bisq.apitest.method.offer.BsqSwapOfferTest;
|
||||
import bisq.apitest.method.offer.CancelOfferTest;
|
||||
import bisq.apitest.method.offer.CreateBSQOffersTest;
|
||||
import bisq.apitest.method.offer.CreateOfferUsingFixedPriceTest;
|
||||
|
@ -90,6 +91,19 @@ public class OfferTest extends AbstractOfferTest {
|
|||
|
||||
@Test
|
||||
@Order(6)
|
||||
public void testCreateBSQSwapOffers() {
|
||||
BsqSwapOfferTest test = new BsqSwapOfferTest();
|
||||
BsqSwapOfferTest.createBsqSwapBsqPaymentAccounts();
|
||||
test.testAliceCreateBsqSwapBuyOffer1();
|
||||
test.testAliceCreateBsqSwapBuyOffer2();
|
||||
test.testAliceCreateBsqSwapBuyOffer3();
|
||||
test.testAliceCreateBsqSwapBuyOffer4();
|
||||
test.testGetMyBsqSwapOffers();
|
||||
test.testGetAvailableBsqSwapOffers();
|
||||
}
|
||||
|
||||
@Test
|
||||
@Order(7)
|
||||
public void testEditOffer() {
|
||||
EditOfferTest test = new EditOfferTest();
|
||||
// Edit fiat offer tests
|
||||
|
|
|
@ -29,6 +29,7 @@ import org.junit.jupiter.api.TestMethodOrder;
|
|||
|
||||
|
||||
import bisq.apitest.method.trade.AbstractTradeTest;
|
||||
import bisq.apitest.method.trade.BsqSwapTradeTest;
|
||||
import bisq.apitest.method.trade.TakeBuyBSQOfferTest;
|
||||
import bisq.apitest.method.trade.TakeBuyBTCOfferTest;
|
||||
import bisq.apitest.method.trade.TakeBuyBTCOfferWithNationalBankAcctTest;
|
||||
|
@ -97,4 +98,13 @@ public class TradeTest extends AbstractTradeTest {
|
|||
test.testBobsConfirmPaymentReceived(testInfo);
|
||||
test.testAlicesBtcWithdrawalToExternalAddress(testInfo);
|
||||
}
|
||||
|
||||
@Test
|
||||
@Order(6)
|
||||
public void testBsqSwapTradeTest(final TestInfo testInfo) {
|
||||
BsqSwapTradeTest test = new BsqSwapTradeTest();
|
||||
test.createBsqSwapBsqPaymentAccounts();
|
||||
test.testAliceCreateBsqSwapBuyOffer();
|
||||
test.testBobTakesBsqSwapOffer();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -17,8 +17,9 @@
|
|||
|
||||
package bisq.apitest.scenario.bot.script;
|
||||
|
||||
import bisq.core.util.JsonUtil;
|
||||
|
||||
import bisq.common.file.JsonFileManager;
|
||||
import bisq.common.util.Utilities;
|
||||
|
||||
import joptsimple.BuiltinHelpFormatter;
|
||||
import joptsimple.OptionParser;
|
||||
|
@ -214,7 +215,7 @@ public class BotScriptGenerator {
|
|||
}
|
||||
|
||||
private String generateBotScriptTemplate() {
|
||||
return Utilities.objectToJson(new BotScript(
|
||||
return JsonUtil.objectToJson(new BotScript(
|
||||
useTestHarness,
|
||||
botPaymentMethodId,
|
||||
countryCode,
|
||||
|
|
|
@ -32,7 +32,7 @@ configure(subprojects) {
|
|||
|
||||
ext { // in alphabetical order
|
||||
bcVersion = '1.63'
|
||||
bitcoinjVersion = '3186b20'
|
||||
bitcoinjVersion = '42bbae9'
|
||||
codecVersion = '1.13'
|
||||
easybindVersion = '1.0.3'
|
||||
easyVersion = '4.0.1'
|
||||
|
|
|
@ -24,8 +24,8 @@ import java.util.function.Function;
|
|||
|
||||
import static bisq.cli.ColumnHeaderConstants.COL_HEADER_DIRECTION;
|
||||
import static java.lang.String.format;
|
||||
import static protobuf.OfferPayload.Direction.BUY;
|
||||
import static protobuf.OfferPayload.Direction.SELL;
|
||||
import static protobuf.OfferDirection.BUY;
|
||||
import static protobuf.OfferDirection.SELL;
|
||||
|
||||
class DirectionFormat {
|
||||
|
||||
|
|
|
@ -20,12 +20,17 @@ package bisq.cli;
|
|||
import bisq.proto.grpc.AddressBalanceInfo;
|
||||
import bisq.proto.grpc.BalancesInfo;
|
||||
import bisq.proto.grpc.BsqBalanceInfo;
|
||||
import bisq.proto.grpc.BsqSwapOfferInfo;
|
||||
import bisq.proto.grpc.BsqSwapTradeInfo;
|
||||
import bisq.proto.grpc.BtcBalanceInfo;
|
||||
import bisq.proto.grpc.CreateBsqSwapOfferRequest;
|
||||
import bisq.proto.grpc.GetMethodHelpRequest;
|
||||
import bisq.proto.grpc.GetVersionRequest;
|
||||
import bisq.proto.grpc.OfferInfo;
|
||||
import bisq.proto.grpc.RegisterDisputeAgentRequest;
|
||||
import bisq.proto.grpc.StopRequest;
|
||||
import bisq.proto.grpc.TakeBsqSwapOfferReply;
|
||||
import bisq.proto.grpc.TakeBsqSwapOfferRequest;
|
||||
import bisq.proto.grpc.TakeOfferReply;
|
||||
import bisq.proto.grpc.TradeInfo;
|
||||
import bisq.proto.grpc.TxFeeRateInfo;
|
||||
|
@ -137,6 +142,21 @@ public final class GrpcClient {
|
|||
return walletsServiceRequest.getTransaction(txId);
|
||||
}
|
||||
|
||||
public BsqSwapOfferInfo createBsqSwapOffer(String direction,
|
||||
long amount,
|
||||
long minAmount,
|
||||
String fixedPrice,
|
||||
String paymentAcctId) {
|
||||
var request = CreateBsqSwapOfferRequest.newBuilder()
|
||||
.setDirection(direction)
|
||||
.setAmount(amount)
|
||||
.setMinAmount(minAmount)
|
||||
.setPrice(fixedPrice)
|
||||
.setPaymentAccountId(paymentAcctId)
|
||||
.build();
|
||||
return grpcStubs.offersService.createBsqSwapOffer(request).getBsqSwapOffer();
|
||||
}
|
||||
|
||||
public OfferInfo createFixedPricedOffer(String direction,
|
||||
String currencyCode,
|
||||
long amount,
|
||||
|
@ -243,14 +263,26 @@ public final class GrpcClient {
|
|||
offersServiceRequest.cancelOffer(offerId);
|
||||
}
|
||||
|
||||
public BsqSwapOfferInfo getBsqSwapOffer(String offerId) {
|
||||
return offersServiceRequest.getBsqSwapOffer(offerId);
|
||||
}
|
||||
|
||||
public OfferInfo getOffer(String offerId) {
|
||||
return offersServiceRequest.getOffer(offerId);
|
||||
}
|
||||
|
||||
public BsqSwapOfferInfo getMyBsqSwapOffer(String offerId) {
|
||||
return offersServiceRequest.getMyBsqSwapOffer(offerId);
|
||||
}
|
||||
|
||||
public OfferInfo getMyOffer(String offerId) {
|
||||
return offersServiceRequest.getMyOffer(offerId);
|
||||
}
|
||||
|
||||
public List<BsqSwapOfferInfo> getBsqSwapOffers(String direction, String currencyCode) {
|
||||
return offersServiceRequest.getBsqSwapOffers(direction, currencyCode);
|
||||
}
|
||||
|
||||
public List<OfferInfo> getOffers(String direction, String currencyCode) {
|
||||
return offersServiceRequest.getOffers(direction, currencyCode);
|
||||
}
|
||||
|
@ -271,6 +303,14 @@ public final class GrpcClient {
|
|||
return offersServiceRequest.getBsqOffersSortedByDate();
|
||||
}
|
||||
|
||||
public List<BsqSwapOfferInfo> getBsqSwapOffersSortedByDate() {
|
||||
return offersServiceRequest.getBsqSwapOffersSortedByDate();
|
||||
}
|
||||
|
||||
public List<BsqSwapOfferInfo> getMyBsqSwapOffers(String direction, String currencyCode) {
|
||||
return offersServiceRequest.getMyBsqSwapOffers(direction, currencyCode);
|
||||
}
|
||||
|
||||
public List<OfferInfo> getMyOffers(String direction, String currencyCode) {
|
||||
return offersServiceRequest.getMyOffers(direction, currencyCode);
|
||||
}
|
||||
|
@ -291,22 +331,53 @@ public final class GrpcClient {
|
|||
return offersServiceRequest.getMyBsqOffersSortedByDate();
|
||||
}
|
||||
|
||||
public List<BsqSwapOfferInfo> getMyBsqSwapBsqOffersSortedByDate() {
|
||||
return offersServiceRequest.getMyBsqSwapOffersSortedByDate();
|
||||
}
|
||||
|
||||
public OfferInfo getMostRecentOffer(String direction, String currencyCode) {
|
||||
return offersServiceRequest.getMostRecentOffer(direction, currencyCode);
|
||||
}
|
||||
|
||||
public List<BsqSwapOfferInfo> sortBsqSwapOffersByDate(List<BsqSwapOfferInfo> offerInfoList) {
|
||||
return offersServiceRequest.sortBsqSwapOffersByDate(offerInfoList);
|
||||
}
|
||||
|
||||
public List<OfferInfo> sortOffersByDate(List<OfferInfo> offerInfoList) {
|
||||
return offersServiceRequest.sortOffersByDate(offerInfoList);
|
||||
}
|
||||
|
||||
public TakeBsqSwapOfferReply getTakeBsqSwapOfferReply(String offerId,
|
||||
String paymentAccountId,
|
||||
String takerFeeCurrencyCode) {
|
||||
var request = TakeBsqSwapOfferRequest.newBuilder()
|
||||
.setOfferId(offerId)
|
||||
.setPaymentAccountId(paymentAccountId)
|
||||
.setTakerFeeCurrencyCode(takerFeeCurrencyCode)
|
||||
.build();
|
||||
return grpcStubs.tradesService.takeBsqSwapOffer(request);
|
||||
}
|
||||
|
||||
public TakeOfferReply getTakeOfferReply(String offerId, String paymentAccountId, String takerFeeCurrencyCode) {
|
||||
return tradesServiceRequest.getTakeOfferReply(offerId, paymentAccountId, takerFeeCurrencyCode);
|
||||
}
|
||||
|
||||
public BsqSwapTradeInfo takeBsqSwapOffer(String offerId, String paymentAccountId, String takerFeeCurrencyCode) {
|
||||
var reply = getTakeBsqSwapOfferReply(offerId, paymentAccountId, takerFeeCurrencyCode);
|
||||
if (reply.hasBsqSwapTrade())
|
||||
return reply.getBsqSwapTrade();
|
||||
else
|
||||
throw new IllegalStateException(reply.getFailureReason().getDescription());
|
||||
}
|
||||
|
||||
public TradeInfo takeOffer(String offerId, String paymentAccountId, String takerFeeCurrencyCode) {
|
||||
return tradesServiceRequest.takeOffer(offerId, paymentAccountId, takerFeeCurrencyCode);
|
||||
}
|
||||
|
||||
public BsqSwapTradeInfo getBsqSwapTrade(String tradeId) {
|
||||
return tradesServiceRequest.getBsqSwapTrade(tradeId);
|
||||
}
|
||||
|
||||
public TradeInfo getTrade(String tradeId) {
|
||||
return tradesServiceRequest.getTrade(tradeId);
|
||||
}
|
||||
|
|
|
@ -30,6 +30,7 @@ import lombok.Getter;
|
|||
|
||||
import static bisq.cli.opts.OptLabel.OPT_HELP;
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
abstract class AbstractMethodOptionParser implements MethodOpts {
|
||||
|
||||
// The full command line args passed to CliMain.main(String[] args).
|
||||
|
@ -53,7 +54,6 @@ abstract class AbstractMethodOptionParser implements MethodOpts {
|
|||
public AbstractMethodOptionParser parse() {
|
||||
try {
|
||||
options = parser.parse(new ArgumentList(args).getMethodArguments());
|
||||
//noinspection unchecked
|
||||
nonOptionArguments = (List<String>) options.nonOptionArguments();
|
||||
return this;
|
||||
} catch (OptionException ex) {
|
||||
|
|
|
@ -20,10 +20,7 @@ package bisq.cli.opts;
|
|||
|
||||
import joptsimple.OptionSpec;
|
||||
|
||||
import static bisq.cli.opts.OptLabel.OPT_ACCOUNT_NAME;
|
||||
import static bisq.cli.opts.OptLabel.OPT_ADDRESS;
|
||||
import static bisq.cli.opts.OptLabel.OPT_CURRENCY_CODE;
|
||||
import static bisq.cli.opts.OptLabel.OPT_TRADE_INSTANT;
|
||||
import static bisq.cli.opts.OptLabel.*;
|
||||
|
||||
public class CreateCryptoCurrencyPaymentAcctOptionParser extends AbstractMethodOptionParser implements MethodOpts {
|
||||
|
||||
|
@ -41,6 +38,11 @@ public class CreateCryptoCurrencyPaymentAcctOptionParser extends AbstractMethodO
|
|||
.ofType(boolean.class)
|
||||
.defaultsTo(Boolean.FALSE);
|
||||
|
||||
final OptionSpec<Boolean> tradeBsqSwapOpt = parser.accepts(OPT_TRADE_BSQ_SWAP, "create trade bsq swap account")
|
||||
.withOptionalArg()
|
||||
.ofType(boolean.class)
|
||||
.defaultsTo(Boolean.FALSE);
|
||||
|
||||
public CreateCryptoCurrencyPaymentAcctOptionParser(String[] args) {
|
||||
super(args);
|
||||
}
|
||||
|
@ -82,4 +84,8 @@ public class CreateCryptoCurrencyPaymentAcctOptionParser extends AbstractMethodO
|
|||
public boolean getIsTradeInstant() {
|
||||
return options.valueOf(tradeInstantOpt);
|
||||
}
|
||||
|
||||
public boolean getIsTradeBsqSwap() {
|
||||
return options.valueOf(tradeBsqSwapOpt);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -44,6 +44,7 @@ public class OptLabel {
|
|||
public final static String OPT_REGISTRATION_KEY = "registration-key";
|
||||
public final static String OPT_SECURITY_DEPOSIT = "security-deposit";
|
||||
public final static String OPT_SHOW_CONTRACT = "show-contract";
|
||||
public final static String OPT_TRADE_BSQ_SWAP = "trade-bsq-swap";
|
||||
public final static String OPT_TRADE_ID = "trade-id";
|
||||
public final static String OPT_TRADE_INSTANT = "trade-instant";
|
||||
public final static String OPT_TIMEOUT = "timeout";
|
||||
|
|
|
@ -17,6 +17,7 @@
|
|||
|
||||
package bisq.cli.request;
|
||||
|
||||
import bisq.proto.grpc.BsqSwapOfferInfo;
|
||||
import bisq.proto.grpc.CancelOfferRequest;
|
||||
import bisq.proto.grpc.CreateOfferRequest;
|
||||
import bisq.proto.grpc.EditOfferRequest;
|
||||
|
@ -38,8 +39,8 @@ import static bisq.proto.grpc.EditOfferRequest.EditType.MKT_PRICE_MARGIN_ONLY;
|
|||
import static bisq.proto.grpc.EditOfferRequest.EditType.TRIGGER_PRICE_ONLY;
|
||||
import static java.util.Comparator.comparing;
|
||||
import static java.util.stream.Collectors.toList;
|
||||
import static protobuf.OfferPayload.Direction.BUY;
|
||||
import static protobuf.OfferPayload.Direction.SELL;
|
||||
import static protobuf.OfferDirection.BUY;
|
||||
import static protobuf.OfferDirection.SELL;
|
||||
|
||||
|
||||
|
||||
|
@ -207,6 +208,13 @@ public class OffersServiceRequest {
|
|||
grpcStubs.offersService.cancelOffer(request);
|
||||
}
|
||||
|
||||
public BsqSwapOfferInfo getBsqSwapOffer(String offerId) {
|
||||
var request = GetOfferRequest.newBuilder()
|
||||
.setId(offerId)
|
||||
.build();
|
||||
return grpcStubs.offersService.getBsqSwapOffer(request).getBsqSwapOffer();
|
||||
}
|
||||
|
||||
public OfferInfo getOffer(String offerId) {
|
||||
var request = GetOfferRequest.newBuilder()
|
||||
.setId(offerId)
|
||||
|
@ -214,6 +222,14 @@ public class OffersServiceRequest {
|
|||
return grpcStubs.offersService.getOffer(request).getOffer();
|
||||
}
|
||||
|
||||
public BsqSwapOfferInfo getMyBsqSwapOffer(String offerId) {
|
||||
var request = GetMyOfferRequest.newBuilder()
|
||||
.setId(offerId)
|
||||
.build();
|
||||
return grpcStubs.offersService.getMyBsqSwapOffer(request).getBsqSwapOffer();
|
||||
|
||||
}
|
||||
|
||||
public OfferInfo getMyOffer(String offerId) {
|
||||
var request = GetMyOfferRequest.newBuilder()
|
||||
.setId(offerId)
|
||||
|
@ -221,6 +237,15 @@ public class OffersServiceRequest {
|
|||
return grpcStubs.offersService.getMyOffer(request).getOffer();
|
||||
}
|
||||
|
||||
public List<BsqSwapOfferInfo> getBsqSwapOffers(String direction, String currencyCode) {
|
||||
var request = GetOffersRequest.newBuilder()
|
||||
.setDirection(direction)
|
||||
.setCurrencyCode(currencyCode)
|
||||
.build();
|
||||
|
||||
return grpcStubs.offersService.getBsqSwapOffers(request).getBsqSwapOffersList();
|
||||
}
|
||||
|
||||
public List<OfferInfo> getOffers(String direction, String currencyCode) {
|
||||
if (isSupportedCryptoCurrency(currencyCode)) {
|
||||
return getCryptoCurrencyOffers(direction, currencyCode);
|
||||
|
@ -251,6 +276,13 @@ public class OffersServiceRequest {
|
|||
return offers.isEmpty() ? offers : sortOffersByDate(offers);
|
||||
}
|
||||
|
||||
public List<BsqSwapOfferInfo> getBsqSwapOffersSortedByDate() {
|
||||
ArrayList<BsqSwapOfferInfo> offers = new ArrayList<>();
|
||||
offers.addAll(getBsqSwapOffers(BUY.name(), "BSQ"));
|
||||
offers.addAll(getBsqSwapOffers(SELL.name(), "BSQ"));
|
||||
return sortBsqSwapOffersByDate(offers);
|
||||
}
|
||||
|
||||
public List<OfferInfo> getBsqOffersSortedByDate() {
|
||||
ArrayList<OfferInfo> offers = new ArrayList<>();
|
||||
offers.addAll(getCryptoCurrencyOffers(BUY.name(), "BSQ"));
|
||||
|
@ -258,6 +290,14 @@ public class OffersServiceRequest {
|
|||
return sortOffersByDate(offers);
|
||||
}
|
||||
|
||||
public List<BsqSwapOfferInfo> getMyBsqSwapOffers(String direction, String currencyCode) {
|
||||
var request = GetMyOffersRequest.newBuilder()
|
||||
.setDirection(direction)
|
||||
.setCurrencyCode(currencyCode)
|
||||
.build();
|
||||
return grpcStubs.offersService.getMyBsqSwapOffers(request).getBsqSwapOffersList();
|
||||
}
|
||||
|
||||
public List<OfferInfo> getMyOffers(String direction, String currencyCode) {
|
||||
if (isSupportedCryptoCurrency(currencyCode)) {
|
||||
return getMyCryptoCurrencyOffers(direction, currencyCode);
|
||||
|
@ -295,11 +335,25 @@ public class OffersServiceRequest {
|
|||
return sortOffersByDate(offers);
|
||||
}
|
||||
|
||||
public List<BsqSwapOfferInfo> getMyBsqSwapOffersSortedByDate() {
|
||||
ArrayList<BsqSwapOfferInfo> offers = new ArrayList<>();
|
||||
offers.addAll(getMyBsqSwapOffers(BUY.name(), "BSQ"));
|
||||
offers.addAll(getMyBsqSwapOffers(SELL.name(), "BSQ"));
|
||||
return sortBsqSwapOffersByDate(offers);
|
||||
}
|
||||
|
||||
public OfferInfo getMostRecentOffer(String direction, String currencyCode) {
|
||||
List<OfferInfo> offers = getOffersSortedByDate(direction, currencyCode);
|
||||
return offers.isEmpty() ? null : offers.get(offers.size() - 1);
|
||||
}
|
||||
|
||||
public List<BsqSwapOfferInfo> sortBsqSwapOffersByDate(List<BsqSwapOfferInfo> offerInfoList) {
|
||||
return offerInfoList.stream()
|
||||
.sorted(comparing(BsqSwapOfferInfo::getDate))
|
||||
.collect(toList());
|
||||
|
||||
}
|
||||
|
||||
public List<OfferInfo> sortOffersByDate(List<OfferInfo> offerInfoList) {
|
||||
return offerInfoList.stream()
|
||||
.sorted(comparing(OfferInfo::getDate))
|
||||
|
|
|
@ -17,6 +17,7 @@
|
|||
|
||||
package bisq.cli.request;
|
||||
|
||||
import bisq.proto.grpc.BsqSwapTradeInfo;
|
||||
import bisq.proto.grpc.ConfirmPaymentReceivedRequest;
|
||||
import bisq.proto.grpc.ConfirmPaymentStartedRequest;
|
||||
import bisq.proto.grpc.GetTradeRequest;
|
||||
|
@ -55,6 +56,13 @@ public class TradesServiceRequest {
|
|||
throw new IllegalStateException(reply.getFailureReason().getDescription());
|
||||
}
|
||||
|
||||
public BsqSwapTradeInfo getBsqSwapTrade(String tradeId) {
|
||||
var request = GetTradeRequest.newBuilder()
|
||||
.setTradeId(tradeId)
|
||||
.build();
|
||||
return grpcStubs.tradesService.getBsqSwapTrade(request).getBsqSwapTrade();
|
||||
}
|
||||
|
||||
public TradeInfo getTrade(String tradeId) {
|
||||
var request = GetTradeRequest.newBuilder()
|
||||
.setTradeId(tradeId)
|
||||
|
|
|
@ -42,5 +42,6 @@ public enum Capability {
|
|||
REFUND_AGENT, // Supports refund agents
|
||||
TRADE_STATISTICS_HASH_UPDATE, // We changed the hash method in 1.2.0 and that requires update to 1.2.2 for handling it correctly, otherwise the seed nodes have to process too much data.
|
||||
NO_ADDRESS_PRE_FIX, // At 1.4.0 we removed the prefix filter for mailbox messages. If a peer has that capability we do not sent the prefix.
|
||||
TRADE_STATISTICS_3 // We used a new reduced trade statistics model from v1.4.0 on
|
||||
TRADE_STATISTICS_3, // We used a new reduced trade statistics model from v1.4.0 on
|
||||
BSQ_SWAP_OFFER // Supports new message type BsqSwapOffer
|
||||
}
|
||||
|
|
|
@ -65,7 +65,7 @@ public class ConfigFileEditor {
|
|||
if (ConfigFileOption.isOption(line)) {
|
||||
ConfigFileOption option = ConfigFileOption.parse(line);
|
||||
if (option.name.equals(name)) {
|
||||
log.warn("Cleared existing config file option '{}'", option);
|
||||
log.debug("Cleared existing config file option '{}'", option);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
|
181
common/src/main/java/bisq/common/crypto/HashCashService.java
Normal file
181
common/src/main/java/bisq/common/crypto/HashCashService.java
Normal 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);
|
||||
}
|
||||
}
|
122
common/src/main/java/bisq/common/crypto/ProofOfWork.java
Normal file
122
common/src/main/java/bisq/common/crypto/ProofOfWork.java
Normal 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}";
|
||||
}
|
||||
}
|
156
common/src/main/java/bisq/common/crypto/ProofOfWorkService.java
Normal file
156
common/src/main/java/bisq/common/crypto/ProofOfWorkService.java
Normal 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);
|
||||
}
|
||||
}
|
|
@ -19,11 +19,6 @@ package bisq.common.util;
|
|||
|
||||
import org.bitcoinj.core.Utils;
|
||||
|
||||
import com.google.gson.ExclusionStrategy;
|
||||
import com.google.gson.FieldAttributes;
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.GsonBuilder;
|
||||
|
||||
import com.google.common.base.Splitter;
|
||||
import com.google.common.util.concurrent.FutureCallback;
|
||||
import com.google.common.util.concurrent.ListeningExecutorService;
|
||||
|
@ -87,15 +82,6 @@ import static com.google.common.base.Preconditions.checkNotNull;
|
|||
|
||||
@Slf4j
|
||||
public class Utilities {
|
||||
public static String objectToJson(Object object) {
|
||||
Gson gson = new GsonBuilder()
|
||||
.setExclusionStrategies(new AnnotationExclusionStrategy())
|
||||
/*.excludeFieldsWithModifiers(Modifier.TRANSIENT)*/
|
||||
/* .setFieldNamingPolicy(FieldNamingPolicy.UPPER_CAMEL_CASE)*/
|
||||
.setPrettyPrinting()
|
||||
.create();
|
||||
return gson.toJson(object);
|
||||
}
|
||||
|
||||
public static ExecutorService getSingleThreadExecutor(String name) {
|
||||
final ThreadFactory threadFactory = new ThreadFactoryBuilder()
|
||||
|
@ -449,18 +435,6 @@ public class Utilities {
|
|||
return new File(Utilities.class.getProtectionDomain().getCodeSource().getLocation().toURI()).getPath();
|
||||
}
|
||||
|
||||
private static class AnnotationExclusionStrategy implements ExclusionStrategy {
|
||||
@Override
|
||||
public boolean shouldSkipField(FieldAttributes f) {
|
||||
return f.getAnnotation(JsonExclude.class) != null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean shouldSkipClass(Class<?> clazz) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public static String toTruncatedString(Object message) {
|
||||
return toTruncatedString(message, 200, true);
|
||||
}
|
||||
|
|
|
@ -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.");
|
||||
}
|
||||
}
|
|
@ -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.");
|
||||
}
|
||||
}
|
|
@ -23,7 +23,7 @@ import bisq.core.filter.FilterManager;
|
|||
import bisq.core.locale.CurrencyUtil;
|
||||
import bisq.core.locale.Res;
|
||||
import bisq.core.offer.Offer;
|
||||
import bisq.core.offer.OfferPayload;
|
||||
import bisq.core.offer.OfferDirection;
|
||||
import bisq.core.offer.OfferRestrictions;
|
||||
import bisq.core.payment.AssetAccount;
|
||||
import bisq.core.payment.ChargeBackRisk;
|
||||
|
@ -33,9 +33,9 @@ import bisq.core.payment.payload.PaymentMethod;
|
|||
import bisq.core.support.dispute.Dispute;
|
||||
import bisq.core.support.dispute.DisputeResult;
|
||||
import bisq.core.support.dispute.arbitration.TraderDataItem;
|
||||
import bisq.core.trade.Contract;
|
||||
import bisq.core.trade.Trade;
|
||||
import bisq.core.trade.protocol.TradingPeer;
|
||||
import bisq.core.trade.model.bisq_v1.Contract;
|
||||
import bisq.core.trade.model.bisq_v1.Trade;
|
||||
import bisq.core.trade.protocol.bisq_v1.model.TradingPeer;
|
||||
import bisq.core.user.User;
|
||||
|
||||
import bisq.network.p2p.BootstrapListener;
|
||||
|
@ -308,7 +308,7 @@ public class AccountAgeWitnessService {
|
|||
}
|
||||
|
||||
private Optional<AccountAgeWitness> findTradePeerWitness(Trade trade) {
|
||||
TradingPeer tradingPeer = trade.getProcessModel().getTradingPeer();
|
||||
TradingPeer tradingPeer = trade.getProcessModel().getTradePeer();
|
||||
return (tradingPeer == null ||
|
||||
tradingPeer.getPaymentAccountPayload() == null ||
|
||||
tradingPeer.getPubKeyRing() == null) ?
|
||||
|
@ -421,11 +421,11 @@ public class AccountAgeWitnessService {
|
|||
String currencyCode,
|
||||
AccountAgeWitness accountAgeWitness,
|
||||
AccountAge accountAgeCategory,
|
||||
OfferPayload.Direction direction,
|
||||
OfferDirection direction,
|
||||
PaymentMethod paymentMethod) {
|
||||
if (CurrencyUtil.isCryptoCurrency(currencyCode) ||
|
||||
!PaymentMethod.hasChargebackRisk(paymentMethod, currencyCode) ||
|
||||
direction == OfferPayload.Direction.SELL) {
|
||||
direction == OfferDirection.SELL) {
|
||||
return maxTradeLimit.value;
|
||||
}
|
||||
|
||||
|
@ -500,7 +500,7 @@ public class AccountAgeWitnessService {
|
|||
return getAccountAge(getMyWitness(paymentAccountPayload), new Date());
|
||||
}
|
||||
|
||||
public long getMyTradeLimit(PaymentAccount paymentAccount, String currencyCode, OfferPayload.Direction direction) {
|
||||
public long getMyTradeLimit(PaymentAccount paymentAccount, String currencyCode, OfferDirection direction) {
|
||||
if (paymentAccount == null)
|
||||
return 0;
|
||||
|
||||
|
@ -564,7 +564,7 @@ public class AccountAgeWitnessService {
|
|||
return false;
|
||||
|
||||
// Check if the peers trade limit is not less than the trade amount
|
||||
if (!verifyPeersTradeLimit(trade.getOffer(), trade.getTradeAmount(), peersWitness, peersCurrentDate,
|
||||
if (!verifyPeersTradeLimit(trade.getOffer(), trade.getAmount(), peersWitness, peersCurrentDate,
|
||||
errorMessageHandler)) {
|
||||
log.error("verifyPeersTradeLimit failed: peersPaymentAccountPayload {}", peersPaymentAccountPayload);
|
||||
return false;
|
||||
|
@ -641,13 +641,12 @@ public class AccountAgeWitnessService {
|
|||
ErrorMessageHandler errorMessageHandler) {
|
||||
checkNotNull(offer);
|
||||
final String currencyCode = offer.getCurrencyCode();
|
||||
final Coin defaultMaxTradeLimit = PaymentMethod.getPaymentMethodById(
|
||||
offer.getOfferPayload().getPaymentMethodId()).getMaxTradeLimitAsCoin(currencyCode);
|
||||
final Coin defaultMaxTradeLimit = offer.getPaymentMethod().getMaxTradeLimitAsCoin(currencyCode);
|
||||
long peersCurrentTradeLimit = defaultMaxTradeLimit.value;
|
||||
if (!hasTradeLimitException(peersWitness)) {
|
||||
final long accountSignAge = getWitnessSignAge(peersWitness, peersCurrentDate);
|
||||
AccountAge accountAgeCategory = getPeersAccountAgeCategory(accountSignAge);
|
||||
OfferPayload.Direction direction = offer.isMyOffer(keyRing) ?
|
||||
OfferDirection direction = offer.isMyOffer(keyRing) ?
|
||||
offer.getMirroredDirection() : offer.getDirection();
|
||||
peersCurrentTradeLimit = getTradeLimit(defaultMaxTradeLimit, currencyCode, peersWitness,
|
||||
accountAgeCategory, direction, offer.getPaymentMethod());
|
||||
|
@ -731,9 +730,9 @@ public class AccountAgeWitnessService {
|
|||
|
||||
public Optional<SignedWitness> traderSignAndPublishPeersAccountAgeWitness(Trade trade) {
|
||||
AccountAgeWitness peersWitness = findTradePeerWitness(trade).orElse(null);
|
||||
Coin tradeAmount = trade.getTradeAmount();
|
||||
checkNotNull(trade.getProcessModel().getTradingPeer().getPubKeyRing(), "Peer must have a keyring");
|
||||
PublicKey peersPubKey = trade.getProcessModel().getTradingPeer().getPubKeyRing().getSignaturePubKey();
|
||||
Coin tradeAmount = trade.getAmount();
|
||||
checkNotNull(trade.getProcessModel().getTradePeer().getPubKeyRing(), "Peer must have a keyring");
|
||||
PublicKey peersPubKey = trade.getProcessModel().getTradePeer().getPubKeyRing().getSignaturePubKey();
|
||||
checkNotNull(peersWitness, "Not able to find peers witness, unable to sign for trade {}",
|
||||
trade.toString());
|
||||
checkNotNull(tradeAmount, "Trade amount must not be null");
|
||||
|
@ -926,7 +925,7 @@ public class AccountAgeWitnessService {
|
|||
|
||||
return accountIsSigner(myWitness) &&
|
||||
!peerHasSignedWitness(trade) &&
|
||||
tradeAmountIsSufficient(trade.getTradeAmount());
|
||||
tradeAmountIsSufficient(trade.getAmount());
|
||||
}
|
||||
|
||||
public String getSignInfoFromAccount(PaymentAccount paymentAccount) {
|
||||
|
|
|
@ -20,7 +20,7 @@ package bisq.core.account.witness;
|
|||
import bisq.core.account.sign.SignedWitness;
|
||||
import bisq.core.account.sign.SignedWitnessService;
|
||||
import bisq.core.payment.payload.PaymentAccountPayload;
|
||||
import bisq.core.trade.Trade;
|
||||
import bisq.core.trade.model.bisq_v1.Trade;
|
||||
|
||||
import bisq.network.p2p.storage.P2PDataStorage;
|
||||
|
||||
|
@ -143,7 +143,7 @@ public class AccountAgeWitnessUtils {
|
|||
}
|
||||
boolean isSignWitnessTrade = accountAgeWitnessService.accountIsSigner(witness) &&
|
||||
!accountAgeWitnessService.peerHasSignedWitness(trade) &&
|
||||
accountAgeWitnessService.tradeAmountIsSufficient(trade.getTradeAmount());
|
||||
accountAgeWitnessService.tradeAmountIsSufficient(trade.getAmount());
|
||||
log.info("AccountSigning debug log: " +
|
||||
"\ntradeId: {}" +
|
||||
"\nis buyer: {}" +
|
||||
|
@ -164,8 +164,8 @@ public class AccountAgeWitnessUtils {
|
|||
checkingSignTrade, // Following cases added to use same logic as in seller signing check
|
||||
accountAgeWitnessService.accountIsSigner(witness),
|
||||
accountAgeWitnessService.peerHasSignedWitness(trade),
|
||||
trade.getTradeAmount(),
|
||||
accountAgeWitnessService.tradeAmountIsSufficient(trade.getTradeAmount()),
|
||||
trade.getAmount(),
|
||||
accountAgeWitnessService.tradeAmountIsSufficient(trade.getAmount()),
|
||||
isSignWitnessTrade);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -25,7 +25,9 @@ import bisq.core.offer.Offer;
|
|||
import bisq.core.offer.OpenOffer;
|
||||
import bisq.core.payment.PaymentAccount;
|
||||
import bisq.core.payment.payload.PaymentMethod;
|
||||
import bisq.core.trade.Trade;
|
||||
import bisq.core.trade.bisq_v1.TradeResultHandler;
|
||||
import bisq.core.trade.model.bisq_v1.Trade;
|
||||
import bisq.core.trade.model.bsq_swap.BsqSwapTrade;
|
||||
import bisq.core.trade.statistics.TradeStatistics3;
|
||||
import bisq.core.trade.statistics.TradeStatisticsManager;
|
||||
|
||||
|
@ -117,6 +119,10 @@ public class CoreApi {
|
|||
// Offers
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
public Offer getBsqSwapOffer(String id) {
|
||||
return coreOffersService.getBsqSwapOffer(id);
|
||||
}
|
||||
|
||||
public Offer getOffer(String id) {
|
||||
return coreOffersService.getOffer(id);
|
||||
}
|
||||
|
@ -125,6 +131,14 @@ public class CoreApi {
|
|||
return coreOffersService.getMyOffer(id);
|
||||
}
|
||||
|
||||
public Offer getMyBsqSwapOffer(String id) {
|
||||
return coreOffersService.getMyBsqSwapOffer(id);
|
||||
}
|
||||
|
||||
public List<Offer> getBsqSwapOffers(String direction) {
|
||||
return coreOffersService.getBsqSwapOffers(direction);
|
||||
}
|
||||
|
||||
public List<Offer> getOffers(String direction, String currencyCode) {
|
||||
return coreOffersService.getOffers(direction, currencyCode);
|
||||
}
|
||||
|
@ -133,18 +147,38 @@ public class CoreApi {
|
|||
return coreOffersService.getMyOffers(direction, currencyCode);
|
||||
}
|
||||
|
||||
public void createAnPlaceOffer(String currencyCode,
|
||||
String directionAsString,
|
||||
String priceAsString,
|
||||
boolean useMarketBasedPrice,
|
||||
double marketPriceMargin,
|
||||
long amountAsLong,
|
||||
long minAmountAsLong,
|
||||
double buyerSecurityDeposit,
|
||||
long triggerPrice,
|
||||
String paymentAccountId,
|
||||
String makerFeeCurrencyCode,
|
||||
Consumer<Offer> resultHandler) {
|
||||
public List<Offer> getMyBsqSwapOffers(String direction) {
|
||||
return coreOffersService.getMyBsqSwapOffers(direction);
|
||||
}
|
||||
|
||||
public OpenOffer getMyOpenBsqSwapOffer(String id) {
|
||||
return coreOffersService.getMyOpenBsqSwapOffer(id);
|
||||
}
|
||||
|
||||
public void createAndPlaceBsqSwapOffer(String directionAsString,
|
||||
long amountAsLong,
|
||||
long minAmountAsLong,
|
||||
String priceAsString,
|
||||
Consumer<Offer> resultHandler) {
|
||||
coreOffersService.createAndPlaceBsqSwapOffer(directionAsString,
|
||||
amountAsLong,
|
||||
minAmountAsLong,
|
||||
priceAsString,
|
||||
resultHandler);
|
||||
}
|
||||
|
||||
public void createAndPlaceOffer(String currencyCode,
|
||||
String directionAsString,
|
||||
String priceAsString,
|
||||
boolean useMarketBasedPrice,
|
||||
double marketPriceMargin,
|
||||
long amountAsLong,
|
||||
long minAmountAsLong,
|
||||
double buyerSecurityDeposit,
|
||||
long triggerPrice,
|
||||
String paymentAccountId,
|
||||
String makerFeeCurrencyCode,
|
||||
Consumer<Offer> resultHandler) {
|
||||
coreOffersService.createAndPlaceOffer(currencyCode,
|
||||
directionAsString,
|
||||
priceAsString,
|
||||
|
@ -206,11 +240,13 @@ public class CoreApi {
|
|||
public PaymentAccount createCryptoCurrencyPaymentAccount(String accountName,
|
||||
String currencyCode,
|
||||
String address,
|
||||
boolean tradeInstant) {
|
||||
boolean tradeInstant,
|
||||
boolean isBsqSwap) {
|
||||
return paymentAccountsService.createCryptoCurrencyPaymentAccount(accountName,
|
||||
currencyCode,
|
||||
address,
|
||||
tradeInstant);
|
||||
tradeInstant,
|
||||
isBsqSwap);
|
||||
}
|
||||
|
||||
public List<PaymentMethod> getCryptoCurrencyPaymentMethods() {
|
||||
|
@ -229,6 +265,19 @@ public class CoreApi {
|
|||
// Trades
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
public void takeBsqSwapOffer(String offerId,
|
||||
String paymentAccountId,
|
||||
String takerFeeCurrencyCode,
|
||||
TradeResultHandler<BsqSwapTrade> tradeResultHandler,
|
||||
ErrorMessageHandler errorMessageHandler) {
|
||||
Offer bsqSwapOffer = coreOffersService.getBsqSwapOffer(offerId);
|
||||
coreTradesService.takeBsqSwapOffer(bsqSwapOffer,
|
||||
paymentAccountId,
|
||||
takerFeeCurrencyCode,
|
||||
tradeResultHandler,
|
||||
errorMessageHandler);
|
||||
}
|
||||
|
||||
public void takeOffer(String offerId,
|
||||
String paymentAccountId,
|
||||
String takerFeeCurrencyCode,
|
||||
|
@ -258,6 +307,10 @@ public class CoreApi {
|
|||
coreTradesService.withdrawFunds(tradeId, address, memo);
|
||||
}
|
||||
|
||||
public BsqSwapTrade getBsqSwapTrade(String tradeId) {
|
||||
return coreTradesService.getBsqSwapTrade(tradeId);
|
||||
}
|
||||
|
||||
public Trade getTrade(String tradeId) {
|
||||
return coreTradesService.getTrade(tradeId);
|
||||
}
|
||||
|
|
|
@ -19,15 +19,17 @@ package bisq.core.api;
|
|||
|
||||
import bisq.core.monetary.Altcoin;
|
||||
import bisq.core.monetary.Price;
|
||||
import bisq.core.offer.CreateOfferService;
|
||||
import bisq.core.offer.MutableOfferPayloadFields;
|
||||
import bisq.core.offer.Offer;
|
||||
import bisq.core.offer.OfferBookService;
|
||||
import bisq.core.offer.OfferFilter;
|
||||
import bisq.core.offer.OfferPayload;
|
||||
import bisq.core.offer.OfferDirection;
|
||||
import bisq.core.offer.OfferFilterService;
|
||||
import bisq.core.offer.OfferUtil;
|
||||
import bisq.core.offer.OpenOffer;
|
||||
import bisq.core.offer.OpenOfferManager;
|
||||
import bisq.core.offer.bisq_v1.CreateOfferService;
|
||||
import bisq.core.offer.bisq_v1.MutableOfferPayloadFields;
|
||||
import bisq.core.offer.bisq_v1.OfferPayload;
|
||||
import bisq.core.offer.bsq_swap.OpenBsqSwapOfferService;
|
||||
import bisq.core.payment.PaymentAccount;
|
||||
import bisq.core.provider.price.PriceFeedService;
|
||||
import bisq.core.user.User;
|
||||
|
@ -57,8 +59,7 @@ import static bisq.common.util.MathUtils.roundDoubleToLong;
|
|||
import static bisq.common.util.MathUtils.scaleUpByPowerOf10;
|
||||
import static bisq.core.locale.CurrencyUtil.isCryptoCurrency;
|
||||
import static bisq.core.offer.Offer.State;
|
||||
import static bisq.core.offer.OfferPayload.Direction;
|
||||
import static bisq.core.offer.OfferPayload.Direction.BUY;
|
||||
import static bisq.core.offer.OfferDirection.BUY;
|
||||
import static bisq.core.offer.OpenOffer.State.AVAILABLE;
|
||||
import static bisq.core.offer.OpenOffer.State.DEACTIVATED;
|
||||
import static bisq.core.payment.PaymentAccountUtil.isPaymentAccountValidForOffer;
|
||||
|
@ -85,8 +86,9 @@ class CoreOffersService {
|
|||
private final CoreWalletsService coreWalletsService;
|
||||
private final CreateOfferService createOfferService;
|
||||
private final OfferBookService offerBookService;
|
||||
private final OfferFilter offerFilter;
|
||||
private final OfferFilterService offerFilterService;
|
||||
private final OpenOfferManager openOfferManager;
|
||||
private final OpenBsqSwapOfferService openBsqSwapOfferService;
|
||||
private final OfferUtil offerUtil;
|
||||
private final PriceFeedService priceFeedService;
|
||||
private final User user;
|
||||
|
@ -97,8 +99,9 @@ class CoreOffersService {
|
|||
CoreWalletsService coreWalletsService,
|
||||
CreateOfferService createOfferService,
|
||||
OfferBookService offerBookService,
|
||||
OfferFilter offerFilter,
|
||||
OfferFilterService offerFilterService,
|
||||
OpenOfferManager openOfferManager,
|
||||
OpenBsqSwapOfferService openBsqSwapOfferService,
|
||||
OfferUtil offerUtil,
|
||||
PriceFeedService priceFeedService,
|
||||
User user) {
|
||||
|
@ -107,18 +110,29 @@ class CoreOffersService {
|
|||
this.coreWalletsService = coreWalletsService;
|
||||
this.createOfferService = createOfferService;
|
||||
this.offerBookService = offerBookService;
|
||||
this.offerFilter = offerFilter;
|
||||
this.offerFilterService = offerFilterService;
|
||||
this.openOfferManager = openOfferManager;
|
||||
this.openBsqSwapOfferService = openBsqSwapOfferService;
|
||||
this.offerUtil = offerUtil;
|
||||
this.priceFeedService = priceFeedService;
|
||||
this.user = user;
|
||||
}
|
||||
|
||||
Offer getBsqSwapOffer(String id) {
|
||||
return offerBookService.getOffers().stream()
|
||||
.filter(o -> o.getId().equals(id))
|
||||
.filter(o -> !o.isMyOffer(keyRing))
|
||||
.filter(o -> offerFilterService.canTakeOffer(o, coreContext.isApiUser()).isValid())
|
||||
.filter(o -> o.isBsqSwapOffer())
|
||||
.findAny().orElseThrow(() ->
|
||||
new IllegalStateException(format("offer with id '%s' not found", id)));
|
||||
}
|
||||
|
||||
Offer getOffer(String id) {
|
||||
return offerBookService.getOffers().stream()
|
||||
.filter(o -> o.getId().equals(id))
|
||||
.filter(o -> !o.isMyOffer(keyRing))
|
||||
.filter(o -> offerFilter.canTakeOffer(o, coreContext.isApiUser()).isValid())
|
||||
.filter(o -> offerFilterService.canTakeOffer(o, coreContext.isApiUser()).isValid())
|
||||
.findAny().orElseThrow(() ->
|
||||
new IllegalStateException(format("offer with id '%s' not found", id)));
|
||||
}
|
||||
|
@ -131,11 +145,31 @@ class CoreOffersService {
|
|||
new IllegalStateException(format("offer with id '%s' not found", id)));
|
||||
}
|
||||
|
||||
Offer getMyBsqSwapOffer(String id) {
|
||||
return offerBookService.getOffers().stream()
|
||||
.filter(o -> o.getId().equals(id))
|
||||
.filter(o -> o.isMyOffer(keyRing))
|
||||
.filter(o -> o.isBsqSwapOffer())
|
||||
.findAny().orElseThrow(() ->
|
||||
new IllegalStateException(format("offer with id '%s' not found", id)));
|
||||
}
|
||||
|
||||
|
||||
List<Offer> getBsqSwapOffers(String direction) {
|
||||
var offers = offerBookService.getOffers().stream()
|
||||
.filter(o -> !o.isMyOffer(keyRing))
|
||||
.filter(o -> o.getDirection().name().equalsIgnoreCase(direction))
|
||||
.filter(o -> o.isBsqSwapOffer())
|
||||
.sorted(priceComparator(direction))
|
||||
.collect(Collectors.toList());
|
||||
return offers;
|
||||
}
|
||||
|
||||
List<Offer> getOffers(String direction, String currencyCode) {
|
||||
return offerBookService.getOffers().stream()
|
||||
.filter(o -> !o.isMyOffer(keyRing))
|
||||
.filter(o -> offerMatchesDirectionAndCurrency(o, direction, currencyCode))
|
||||
.filter(o -> offerFilter.canTakeOffer(o, coreContext.isApiUser()).isValid())
|
||||
.filter(o -> offerFilterService.canTakeOffer(o, coreContext.isApiUser()).isValid())
|
||||
.sorted(priceComparator(direction))
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
@ -148,6 +182,24 @@ class CoreOffersService {
|
|||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
List<Offer> getMyBsqSwapOffers(String direction) {
|
||||
var offers = offerBookService.getOffers().stream()
|
||||
.filter(o -> o.isMyOffer(keyRing))
|
||||
.filter(o -> o.getDirection().name().equalsIgnoreCase(direction))
|
||||
.filter(Offer::isBsqSwapOffer)
|
||||
.sorted(priceComparator(direction))
|
||||
.collect(Collectors.toList());
|
||||
return offers;
|
||||
}
|
||||
|
||||
OpenOffer getMyOpenBsqSwapOffer(String id) {
|
||||
return openOfferManager.getOpenOfferById(id)
|
||||
.filter(open -> open.getOffer().isMyOffer(keyRing))
|
||||
.filter(open -> open.getOffer().isBsqSwapOffer())
|
||||
.orElseThrow(() ->
|
||||
new IllegalStateException(format("openoffer with id '%s' not found", id)));
|
||||
}
|
||||
|
||||
OpenOffer getMyOpenOffer(String id) {
|
||||
return openOfferManager.getOpenOfferById(id)
|
||||
.filter(open -> open.getOffer().isMyOffer(keyRing))
|
||||
|
@ -161,7 +213,28 @@ class CoreOffersService {
|
|||
.isPresent();
|
||||
}
|
||||
|
||||
// Create and place new offer.
|
||||
void createAndPlaceBsqSwapOffer(String directionAsString,
|
||||
long amountAsLong,
|
||||
long minAmountAsLong,
|
||||
String priceAsString,
|
||||
Consumer<Offer> resultHandler) {
|
||||
coreWalletsService.verifyWalletsAreAvailable();
|
||||
coreWalletsService.verifyEncryptedWalletIsUnlocked();
|
||||
|
||||
String currencyCode = "BSQ";
|
||||
String offerId = OfferUtil.getRandomOfferId();
|
||||
OfferDirection direction = OfferDirection.valueOf(directionAsString.toUpperCase());
|
||||
Coin amount = Coin.valueOf(amountAsLong);
|
||||
Coin minAmount = Coin.valueOf(minAmountAsLong);
|
||||
Price price = Price.valueOf(currencyCode, priceStringToLong(priceAsString, currencyCode));
|
||||
openBsqSwapOfferService.requestNewOffer(offerId,
|
||||
direction,
|
||||
amount,
|
||||
minAmount,
|
||||
price,
|
||||
offer -> placeBsqSwapOffer(offer, () -> resultHandler.accept(offer)));
|
||||
}
|
||||
|
||||
void createAndPlaceOffer(String currencyCode,
|
||||
String directionAsString,
|
||||
String priceAsString,
|
||||
|
@ -183,8 +256,8 @@ class CoreOffersService {
|
|||
throw new IllegalArgumentException(format("payment account with id %s not found", paymentAccountId));
|
||||
|
||||
String upperCaseCurrencyCode = currencyCode.toUpperCase();
|
||||
String offerId = createOfferService.getRandomOfferId();
|
||||
Direction direction = Direction.valueOf(directionAsString.toUpperCase());
|
||||
String offerId = OfferUtil.getRandomOfferId();
|
||||
OfferDirection direction = OfferDirection.valueOf(directionAsString.toUpperCase());
|
||||
Price price = Price.valueOf(upperCaseCurrencyCode, priceStringToLong(priceAsString, upperCaseCurrencyCode));
|
||||
Coin amount = Coin.valueOf(amountAsLong);
|
||||
Coin minAmount = Coin.valueOf(minAmountAsLong);
|
||||
|
@ -256,7 +329,7 @@ class CoreOffersService {
|
|||
editedMarketPriceMargin,
|
||||
editType);
|
||||
Offer editedOffer = new Offer(editedPayload);
|
||||
priceFeedService.setCurrencyCode(openOffer.getOffer().getOfferPayload().getCurrencyCode());
|
||||
priceFeedService.setCurrencyCode(openOffer.getOffer().getCurrencyCode());
|
||||
editedOffer.setPriceFeedService(priceFeedService);
|
||||
editedOffer.setState(State.AVAILABLE);
|
||||
openOfferManager.editOpenOfferStart(openOffer,
|
||||
|
@ -277,6 +350,15 @@ class CoreOffersService {
|
|||
log::error);
|
||||
}
|
||||
|
||||
private void placeBsqSwapOffer(Offer offer, Runnable resultHandler) {
|
||||
openBsqSwapOfferService.placeBsqSwapOffer(offer,
|
||||
resultHandler,
|
||||
log::error);
|
||||
|
||||
if (offer.getErrorMessage() != null)
|
||||
throw new IllegalStateException(offer.getErrorMessage());
|
||||
}
|
||||
|
||||
private void placeOffer(Offer offer,
|
||||
double buyerSecurityDeposit,
|
||||
long triggerPrice,
|
||||
|
@ -302,7 +384,7 @@ class CoreOffersService {
|
|||
// code fields. Note: triggerPrice isDeactivated fields are in OpenOffer, not
|
||||
// in OfferPayload.
|
||||
Offer offer = openOffer.getOffer();
|
||||
String currencyCode = offer.getOfferPayload().getCurrencyCode();
|
||||
String currencyCode = offer.getCurrencyCode();
|
||||
boolean isEditingPrice = editType.equals(FIXED_PRICE_ONLY) || editType.equals(FIXED_PRICE_AND_ACTIVATION_STATE);
|
||||
Price editedPrice;
|
||||
if (isEditingPrice) {
|
||||
|
@ -320,15 +402,15 @@ class CoreOffersService {
|
|||
Objects.requireNonNull(editedPrice).getValue(),
|
||||
isUsingMktPriceMargin ? exactMultiply(editedMarketPriceMargin, 0.01) : 0.00,
|
||||
isUsingMktPriceMargin,
|
||||
offer.getOfferPayload().getBaseCurrencyCode(),
|
||||
offer.getOfferPayload().getCounterCurrencyCode(),
|
||||
offer.getBaseCurrencyCode(),
|
||||
offer.getCounterCurrencyCode(),
|
||||
offer.getPaymentMethod().getId(),
|
||||
offer.getMakerPaymentAccountId(),
|
||||
offer.getOfferPayload().getCountryCode(),
|
||||
offer.getOfferPayload().getAcceptedCountryCodes(),
|
||||
offer.getOfferPayload().getBankId(),
|
||||
offer.getOfferPayload().getAcceptedBankIds(),
|
||||
offer.getOfferPayload().getExtraDataMap());
|
||||
offer.getCountryCode(),
|
||||
offer.getAcceptedCountryCodes(),
|
||||
offer.getBankId(),
|
||||
offer.getAcceptedBankIds(),
|
||||
offer.getExtraDataMap());
|
||||
log.info("Merging OfferPayload with {}", mutableOfferPayloadFields);
|
||||
return offerUtil.getMergedOfferPayload(openOffer, mutableOfferPayloadFields);
|
||||
}
|
||||
|
@ -336,7 +418,7 @@ class CoreOffersService {
|
|||
private void verifyPaymentAccountIsValidForNewOffer(Offer offer, PaymentAccount paymentAccount) {
|
||||
if (!isPaymentAccountValidForOffer(offer, paymentAccount)) {
|
||||
String error = format("cannot create %s offer with payment account %s",
|
||||
offer.getOfferPayload().getCounterCurrencyCode(),
|
||||
offer.getCounterCurrencyCode(),
|
||||
paymentAccount.getId());
|
||||
throw new IllegalStateException(error);
|
||||
}
|
||||
|
@ -346,7 +428,7 @@ class CoreOffersService {
|
|||
String direction,
|
||||
String currencyCode) {
|
||||
var offerOfWantedDirection = offer.getDirection().name().equalsIgnoreCase(direction);
|
||||
var offerInWantedCurrency = offer.getOfferPayload().getCounterCurrencyCode()
|
||||
var offerInWantedCurrency = offer.getCounterCurrencyCode()
|
||||
.equalsIgnoreCase(currencyCode);
|
||||
return offerOfWantedDirection && offerInWantedCurrency;
|
||||
}
|
||||
|
|
|
@ -20,6 +20,7 @@ package bisq.core.api;
|
|||
import bisq.core.account.witness.AccountAgeWitnessService;
|
||||
import bisq.core.api.model.PaymentAccountForm;
|
||||
import bisq.core.locale.CryptoCurrency;
|
||||
import bisq.core.payment.AssetAccount;
|
||||
import bisq.core.payment.CryptoCurrencyAccount;
|
||||
import bisq.core.payment.InstantCryptoCurrencyAccount;
|
||||
import bisq.core.payment.PaymentAccount;
|
||||
|
@ -82,7 +83,7 @@ class CorePaymentAccountsService {
|
|||
|
||||
List<PaymentMethod> getFiatPaymentMethods() {
|
||||
return PaymentMethod.getPaymentMethods().stream()
|
||||
.filter(paymentMethod -> !paymentMethod.isAsset())
|
||||
.filter(PaymentMethod::isFiat)
|
||||
.sorted(Comparator.comparing(PaymentMethod::getId))
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
@ -102,7 +103,8 @@ class CorePaymentAccountsService {
|
|||
PaymentAccount createCryptoCurrencyPaymentAccount(String accountName,
|
||||
String currencyCode,
|
||||
String address,
|
||||
boolean tradeInstant) {
|
||||
boolean tradeInstant,
|
||||
boolean isBsqSwap) {
|
||||
String bsqCode = currencyCode.toUpperCase();
|
||||
if (!bsqCode.equals("BSQ"))
|
||||
throw new IllegalArgumentException("api does not currently support " + currencyCode + " accounts");
|
||||
|
@ -110,12 +112,21 @@ class CorePaymentAccountsService {
|
|||
// Validate the BSQ address string but ignore the return value.
|
||||
coreWalletsService.getValidBsqAddress(address);
|
||||
|
||||
var cryptoCurrencyAccount = tradeInstant
|
||||
? (InstantCryptoCurrencyAccount) PaymentAccountFactory.getPaymentAccount(PaymentMethod.BLOCK_CHAINS_INSTANT)
|
||||
: (CryptoCurrencyAccount) PaymentAccountFactory.getPaymentAccount(PaymentMethod.BLOCK_CHAINS);
|
||||
// TODO Split into 2 methods: createAtomicPaymentAccount(), createCryptoCurrencyPaymentAccount().
|
||||
PaymentAccount cryptoCurrencyAccount;
|
||||
if (isBsqSwap) {
|
||||
cryptoCurrencyAccount = PaymentAccountFactory.getPaymentAccount(PaymentMethod.BSQ_SWAP);
|
||||
} else {
|
||||
cryptoCurrencyAccount = tradeInstant
|
||||
? (InstantCryptoCurrencyAccount) PaymentAccountFactory.getPaymentAccount(PaymentMethod.BLOCK_CHAINS_INSTANT)
|
||||
: (CryptoCurrencyAccount) PaymentAccountFactory.getPaymentAccount(PaymentMethod.BLOCK_CHAINS);
|
||||
}
|
||||
cryptoCurrencyAccount.init();
|
||||
cryptoCurrencyAccount.setAccountName(accountName);
|
||||
cryptoCurrencyAccount.setAddress(address);
|
||||
if (!isBsqSwap) {
|
||||
((AssetAccount) cryptoCurrencyAccount).setAddress(address);
|
||||
}
|
||||
|
||||
Optional<CryptoCurrency> cryptoCurrency = getCryptoCurrency(bsqCode);
|
||||
cryptoCurrency.ifPresent(cryptoCurrencyAccount::setSingleTradeCurrency);
|
||||
user.addPaymentAccount(cryptoCurrencyAccount);
|
||||
|
@ -132,7 +143,7 @@ class CorePaymentAccountsService {
|
|||
|
||||
List<PaymentMethod> getCryptoCurrencyPaymentMethods() {
|
||||
return PaymentMethod.getPaymentMethods().stream()
|
||||
.filter(PaymentMethod::isAsset)
|
||||
.filter(PaymentMethod::isAltcoin)
|
||||
.sorted(Comparator.comparing(PaymentMethod::getId))
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
|
|
@ -21,14 +21,17 @@ import bisq.core.btc.model.AddressEntry;
|
|||
import bisq.core.btc.wallet.BtcWalletService;
|
||||
import bisq.core.offer.Offer;
|
||||
import bisq.core.offer.OfferUtil;
|
||||
import bisq.core.offer.takeoffer.TakeOfferModel;
|
||||
import bisq.core.trade.Tradable;
|
||||
import bisq.core.trade.Trade;
|
||||
import bisq.core.offer.bisq_v1.TakeOfferModel;
|
||||
import bisq.core.offer.bsq_swap.BsqSwapTakeOfferModel;
|
||||
import bisq.core.trade.ClosedTradableManager;
|
||||
import bisq.core.trade.TradeManager;
|
||||
import bisq.core.trade.TradeUtil;
|
||||
import bisq.core.trade.closed.ClosedTradableManager;
|
||||
import bisq.core.trade.protocol.BuyerProtocol;
|
||||
import bisq.core.trade.protocol.SellerProtocol;
|
||||
import bisq.core.trade.bisq_v1.TradeResultHandler;
|
||||
import bisq.core.trade.bisq_v1.TradeUtil;
|
||||
import bisq.core.trade.model.Tradable;
|
||||
import bisq.core.trade.model.bisq_v1.Trade;
|
||||
import bisq.core.trade.model.bsq_swap.BsqSwapTrade;
|
||||
import bisq.core.trade.protocol.bisq_v1.BuyerProtocol;
|
||||
import bisq.core.trade.protocol.bisq_v1.SellerProtocol;
|
||||
import bisq.core.user.User;
|
||||
import bisq.core.util.validation.BtcAddressValidator;
|
||||
|
||||
|
@ -60,6 +63,7 @@ class CoreTradesService {
|
|||
private final OfferUtil offerUtil;
|
||||
private final ClosedTradableManager closedTradableManager;
|
||||
private final TakeOfferModel takeOfferModel;
|
||||
private final BsqSwapTakeOfferModel bsqSwapTakeOfferModel;
|
||||
private final TradeManager tradeManager;
|
||||
private final TradeUtil tradeUtil;
|
||||
private final User user;
|
||||
|
@ -71,6 +75,7 @@ class CoreTradesService {
|
|||
OfferUtil offerUtil,
|
||||
ClosedTradableManager closedTradableManager,
|
||||
TakeOfferModel takeOfferModel,
|
||||
BsqSwapTakeOfferModel bsqSwapTakeOfferModel,
|
||||
TradeManager tradeManager,
|
||||
TradeUtil tradeUtil,
|
||||
User user) {
|
||||
|
@ -80,11 +85,33 @@ class CoreTradesService {
|
|||
this.offerUtil = offerUtil;
|
||||
this.closedTradableManager = closedTradableManager;
|
||||
this.takeOfferModel = takeOfferModel;
|
||||
this.bsqSwapTakeOfferModel = bsqSwapTakeOfferModel;
|
||||
this.tradeManager = tradeManager;
|
||||
this.tradeUtil = tradeUtil;
|
||||
this.user = user;
|
||||
}
|
||||
|
||||
// todo we need to pass the intended trade amount
|
||||
void takeBsqSwapOffer(Offer offer,
|
||||
String paymentAccountId,
|
||||
String takerFeeCurrencyCode,
|
||||
TradeResultHandler<BsqSwapTrade> tradeResultHandler,
|
||||
ErrorMessageHandler errorMessageHandler) {
|
||||
coreWalletsService.verifyWalletsAreAvailable();
|
||||
coreWalletsService.verifyEncryptedWalletIsUnlocked();
|
||||
|
||||
bsqSwapTakeOfferModel.initWithData(offer);
|
||||
|
||||
//todo use the intended trade amount
|
||||
bsqSwapTakeOfferModel.applyAmount(offer.getAmount());
|
||||
|
||||
log.info("Initiating take {} offer, {}",
|
||||
offer.isBuyOffer() ? "buy" : "sell",
|
||||
bsqSwapTakeOfferModel);
|
||||
|
||||
bsqSwapTakeOfferModel.onTakeOffer(tradeResultHandler, log::warn, errorMessageHandler, coreContext.isApiUser());
|
||||
}
|
||||
|
||||
void takeOffer(Offer offer,
|
||||
String paymentAccountId,
|
||||
String takerFeeCurrencyCode,
|
||||
|
@ -100,7 +127,7 @@ class CoreTradesService {
|
|||
throw new IllegalArgumentException(format("payment account with id '%s' not found", paymentAccountId));
|
||||
|
||||
var useSavingsWallet = true;
|
||||
//noinspection ConstantConditions
|
||||
|
||||
takeOfferModel.initModel(offer, paymentAccount, useSavingsWallet);
|
||||
log.info("Initiating take {} offer, {}",
|
||||
offer.isBuyOffer() ? "buy" : "sell",
|
||||
|
@ -205,6 +232,13 @@ class CoreTradesService {
|
|||
});
|
||||
}
|
||||
|
||||
BsqSwapTrade getBsqSwapTrade(String tradeId) {
|
||||
coreWalletsService.verifyWalletsAreAvailable();
|
||||
coreWalletsService.verifyEncryptedWalletIsUnlocked();
|
||||
return tradeManager.findBsqSwapTradeById(tradeId).orElseThrow(() ->
|
||||
new IllegalArgumentException(format("trade with id '%s' not found", tradeId)));
|
||||
}
|
||||
|
||||
String getTradeRole(String tradeId) {
|
||||
coreWalletsService.verifyWalletsAreAvailable();
|
||||
coreWalletsService.verifyEncryptedWalletIsUnlocked();
|
||||
|
|
|
@ -82,7 +82,6 @@ import lombok.extern.slf4j.Slf4j;
|
|||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
import static bisq.common.config.BaseCurrencyNetwork.BTC_DAO_REGTEST;
|
||||
import static bisq.core.btc.wallet.Restrictions.getMinNonDustOutput;
|
||||
import static bisq.core.util.ParsingUtils.parseToCoin;
|
||||
import static java.lang.String.format;
|
||||
|
@ -583,14 +582,14 @@ class CoreWalletsService {
|
|||
verifyWalletsAreAvailable();
|
||||
verifyEncryptedWalletIsUnlocked();
|
||||
|
||||
var availableConfirmedBalance = bsqWalletService.getAvailableConfirmedBalance();
|
||||
var availableBalance = bsqWalletService.getAvailableBalance();
|
||||
var unverifiedBalance = bsqWalletService.getUnverifiedBalance();
|
||||
var unconfirmedChangeBalance = bsqWalletService.getUnconfirmedChangeBalance();
|
||||
var lockedForVotingBalance = bsqWalletService.getLockedForVotingBalance();
|
||||
var lockupBondsBalance = bsqWalletService.getLockupBondsBalance();
|
||||
var unlockingBondsBalance = bsqWalletService.getUnlockingBondsBalance();
|
||||
|
||||
return new BsqBalanceInfo(availableConfirmedBalance.value,
|
||||
return new BsqBalanceInfo(availableBalance.value,
|
||||
unverifiedBalance.value,
|
||||
unconfirmedChangeBalance.value,
|
||||
lockedForVotingBalance.value,
|
||||
|
|
|
@ -17,20 +17,20 @@ public class BsqBalanceInfo implements Payload {
|
|||
-1);
|
||||
|
||||
// All balances are in BSQ satoshis.
|
||||
private final long availableConfirmedBalance;
|
||||
private final long availableBalance;
|
||||
private final long unverifiedBalance;
|
||||
private final long unconfirmedChangeBalance;
|
||||
private final long lockedForVotingBalance;
|
||||
private final long lockupBondsBalance;
|
||||
private final long unlockingBondsBalance;
|
||||
|
||||
public BsqBalanceInfo(long availableConfirmedBalance,
|
||||
public BsqBalanceInfo(long availableBalance,
|
||||
long unverifiedBalance,
|
||||
long unconfirmedChangeBalance,
|
||||
long lockedForVotingBalance,
|
||||
long lockupBondsBalance,
|
||||
long unlockingBondsBalance) {
|
||||
this.availableConfirmedBalance = availableConfirmedBalance;
|
||||
this.availableBalance = availableBalance;
|
||||
this.unverifiedBalance = unverifiedBalance;
|
||||
this.unconfirmedChangeBalance = unconfirmedChangeBalance;
|
||||
this.lockedForVotingBalance = lockedForVotingBalance;
|
||||
|
@ -39,14 +39,14 @@ public class BsqBalanceInfo implements Payload {
|
|||
}
|
||||
|
||||
@VisibleForTesting
|
||||
public static BsqBalanceInfo valueOf(long availableConfirmedBalance,
|
||||
public static BsqBalanceInfo valueOf(long availableBalance,
|
||||
long unverifiedBalance,
|
||||
long unconfirmedChangeBalance,
|
||||
long lockedForVotingBalance,
|
||||
long lockupBondsBalance,
|
||||
long unlockingBondsBalance) {
|
||||
// Convenience for creating a model instance instead of a proto.
|
||||
return new BsqBalanceInfo(availableConfirmedBalance,
|
||||
return new BsqBalanceInfo(availableBalance,
|
||||
unverifiedBalance,
|
||||
unconfirmedChangeBalance,
|
||||
lockedForVotingBalance,
|
||||
|
@ -58,10 +58,11 @@ public class BsqBalanceInfo implements Payload {
|
|||
// PROTO BUFFER
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
// TODO rename availableConfirmedBalance in proto if possible
|
||||
@Override
|
||||
public bisq.proto.grpc.BsqBalanceInfo toProtoMessage() {
|
||||
return bisq.proto.grpc.BsqBalanceInfo.newBuilder()
|
||||
.setAvailableConfirmedBalance(availableConfirmedBalance)
|
||||
.setAvailableConfirmedBalance(availableBalance)
|
||||
.setUnverifiedBalance(unverifiedBalance)
|
||||
.setUnconfirmedChangeBalance(unconfirmedChangeBalance)
|
||||
.setLockedForVotingBalance(lockedForVotingBalance)
|
||||
|
@ -83,7 +84,7 @@ public class BsqBalanceInfo implements Payload {
|
|||
@Override
|
||||
public String toString() {
|
||||
return "BsqBalanceInfo{" +
|
||||
"availableConfirmedBalance=" + availableConfirmedBalance +
|
||||
"availableBalance=" + availableBalance +
|
||||
", unverifiedBalance=" + unverifiedBalance +
|
||||
", unconfirmedChangeBalance=" + unconfirmedChangeBalance +
|
||||
", lockedForVotingBalance=" + lockedForVotingBalance +
|
||||
|
|
227
core/src/main/java/bisq/core/api/model/BsqSwapOfferInfo.java
Normal file
227
core/src/main/java/bisq/core/api/model/BsqSwapOfferInfo.java
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
323
core/src/main/java/bisq/core/api/model/BsqSwapTradeInfo.java
Normal file
323
core/src/main/java/bisq/core/api/model/BsqSwapTradeInfo.java
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -144,8 +144,8 @@ public class OfferInfo implements Payload {
|
|||
.withPaymentAccountId(offer.getMakerPaymentAccountId())
|
||||
.withPaymentMethodId(offer.getPaymentMethod().getId())
|
||||
.withPaymentMethodShortName(offer.getPaymentMethod().getShortName())
|
||||
.withBaseCurrencyCode(offer.getOfferPayload().getBaseCurrencyCode())
|
||||
.withCounterCurrencyCode(offer.getOfferPayload().getCounterCurrencyCode())
|
||||
.withBaseCurrencyCode(offer.getBaseCurrencyCode())
|
||||
.withCounterCurrencyCode(offer.getCounterCurrencyCode())
|
||||
.withDate(offer.getDate().getTime())
|
||||
.withState(offer.getState().name())
|
||||
.withIsMyOffer(isMyOffer);
|
||||
|
|
|
@ -17,8 +17,8 @@
|
|||
|
||||
package bisq.core.api.model;
|
||||
|
||||
import bisq.core.trade.Contract;
|
||||
import bisq.core.trade.Trade;
|
||||
import bisq.core.trade.model.bisq_v1.Contract;
|
||||
import bisq.core.trade.model.bisq_v1.Trade;
|
||||
|
||||
import bisq.common.Payload;
|
||||
|
||||
|
@ -34,7 +34,7 @@ import static bisq.core.api.model.PaymentAccountPayloadInfo.toPaymentAccountPayl
|
|||
@Getter
|
||||
public class TradeInfo implements Payload {
|
||||
|
||||
// The client cannot see bisq.core.trade.Trade or its fromProto method. We use the
|
||||
// The client cannot see Trade or its fromProto method. We use the
|
||||
// lighter weight TradeInfo proto wrapper instead, containing just enough fields to
|
||||
// view and interact with trades.
|
||||
|
||||
|
@ -127,19 +127,19 @@ public class TradeInfo implements Payload {
|
|||
.withDate(trade.getDate().getTime())
|
||||
.withRole(role == null ? "" : role)
|
||||
.withIsCurrencyForTakerFeeBtc(trade.isCurrencyForTakerFeeBtc())
|
||||
.withTxFeeAsLong(trade.getTxFeeAsLong())
|
||||
.withTxFeeAsLong(trade.getTradeTxFeeAsLong())
|
||||
.withTakerFeeAsLong(trade.getTakerFeeAsLong())
|
||||
.withTakerFeeAsLong(trade.getTakerFeeAsLong())
|
||||
.withTakerFeeTxId(trade.getTakerFeeTxId())
|
||||
.withDepositTxId(trade.getDepositTxId())
|
||||
.withPayoutTxId(trade.getPayoutTxId())
|
||||
.withTradeAmountAsLong(trade.getTradeAmountAsLong())
|
||||
.withTradePrice(trade.getTradePrice().getValue())
|
||||
.withTradeVolume(trade.getTradeVolume() == null ? 0 : trade.getTradeVolume().getValue())
|
||||
.withTradeAmountAsLong(trade.getAmountAsLong())
|
||||
.withTradePrice(trade.getPrice().getValue())
|
||||
.withTradeVolume(trade.getVolume() == null ? 0 : trade.getVolume().getValue())
|
||||
.withTradingPeerNodeAddress(Objects.requireNonNull(
|
||||
trade.getTradingPeerNodeAddress()).getHostNameWithoutPostFix())
|
||||
.withState(trade.getState().name())
|
||||
.withPhase(trade.getPhase().name())
|
||||
.withState(trade.getTradeState().name())
|
||||
.withPhase(trade.getTradePhase().name())
|
||||
.withTradePeriodState(trade.getTradePeriodState().name())
|
||||
.withIsDepositPublished(trade.isDepositPublished())
|
||||
.withIsDepositConfirmed(trade.isDepositConfirmed())
|
||||
|
|
|
@ -23,6 +23,7 @@ import bisq.core.btc.wallet.BtcWalletService;
|
|||
import bisq.core.dao.DaoSetup;
|
||||
import bisq.core.dao.node.full.RpcService;
|
||||
import bisq.core.offer.OpenOfferManager;
|
||||
import bisq.core.offer.bsq_swap.OpenBsqSwapOfferService;
|
||||
import bisq.core.provider.price.PriceFeedService;
|
||||
import bisq.core.setup.CorePersistedDataHost;
|
||||
import bisq.core.setup.CoreSetup;
|
||||
|
@ -227,6 +228,7 @@ public abstract class BisqExecutable implements GracefulShutDownHandler, BisqSet
|
|||
}
|
||||
|
||||
try {
|
||||
injector.getInstance(OpenBsqSwapOfferService.class).shutDown();
|
||||
injector.getInstance(PriceFeedService.class).shutDown();
|
||||
injector.getInstance(ArbitratorManager.class).shutDown();
|
||||
injector.getInstance(TradeStatisticsManager.class).shutDown();
|
||||
|
|
|
@ -42,7 +42,7 @@ import bisq.core.support.dispute.arbitration.ArbitrationManager;
|
|||
import bisq.core.support.dispute.mediation.MediationManager;
|
||||
import bisq.core.support.dispute.refund.RefundManager;
|
||||
import bisq.core.trade.TradeManager;
|
||||
import bisq.core.trade.TradeTxException;
|
||||
import bisq.core.trade.bisq_v1.TradeTxException;
|
||||
import bisq.core.user.Preferences;
|
||||
import bisq.core.user.User;
|
||||
import bisq.core.util.FormattingUtils;
|
||||
|
@ -525,6 +525,9 @@ public class BisqSetup {
|
|||
// miner fee was too low and the transaction got removed from mempool and got out from our wallet after a
|
||||
// resync.
|
||||
openOfferManager.getObservableList().forEach(e -> {
|
||||
if (e.getOffer().isBsqSwapOffer()) {
|
||||
return;
|
||||
}
|
||||
String offerFeePaymentTxId = e.getOffer().getOfferFeePaymentTxId();
|
||||
if (btcWalletService.getConfidenceForTxId(offerFeePaymentTxId) == null) {
|
||||
String message = Res.get("popup.warning.openOfferWithInvalidMakerFeeTx",
|
||||
|
@ -657,7 +660,10 @@ public class BisqSetup {
|
|||
}
|
||||
|
||||
private void maybeShowLocalhostRunningInfo() {
|
||||
maybeTriggerDisplayHandler("bitcoinLocalhostNode", displayLocalhostHandler, localBitcoinNode.shouldBeUsed());
|
||||
if (Config.baseCurrencyNetwork().isMainnet()) {
|
||||
maybeTriggerDisplayHandler("bitcoinLocalhostNode", displayLocalhostHandler,
|
||||
localBitcoinNode.shouldBeUsed());
|
||||
}
|
||||
}
|
||||
|
||||
private void maybeShowAccountSigningStateInfo() {
|
||||
|
|
|
@ -34,7 +34,8 @@ import bisq.core.notifications.alerts.TradeEvents;
|
|||
import bisq.core.notifications.alerts.market.MarketAlerts;
|
||||
import bisq.core.notifications.alerts.price.PriceAlert;
|
||||
import bisq.core.offer.OpenOfferManager;
|
||||
import bisq.core.offer.TriggerPriceService;
|
||||
import bisq.core.offer.bisq_v1.TriggerPriceService;
|
||||
import bisq.core.offer.bsq_swap.OpenBsqSwapOfferService;
|
||||
import bisq.core.payment.AmazonGiftCardAccount;
|
||||
import bisq.core.payment.RevolutAccount;
|
||||
import bisq.core.payment.TradeLimits;
|
||||
|
@ -48,9 +49,10 @@ import bisq.core.support.dispute.mediation.mediator.MediatorManager;
|
|||
import bisq.core.support.dispute.refund.RefundManager;
|
||||
import bisq.core.support.dispute.refund.refundagent.RefundAgentManager;
|
||||
import bisq.core.support.traderchat.TraderChatManager;
|
||||
import bisq.core.trade.ClosedTradableManager;
|
||||
import bisq.core.trade.TradeManager;
|
||||
import bisq.core.trade.closed.ClosedTradableManager;
|
||||
import bisq.core.trade.failed.FailedTradesManager;
|
||||
import bisq.core.trade.bisq_v1.FailedTradesManager;
|
||||
import bisq.core.trade.bsq_swap.BsqSwapTradeManager;
|
||||
import bisq.core.trade.statistics.TradeStatisticsManager;
|
||||
import bisq.core.trade.txproof.xmr.XmrTxProofService;
|
||||
import bisq.core.user.User;
|
||||
|
@ -84,6 +86,7 @@ public class DomainInitialisation {
|
|||
private final TraderChatManager traderChatManager;
|
||||
private final TradeManager tradeManager;
|
||||
private final ClosedTradableManager closedTradableManager;
|
||||
private final BsqSwapTradeManager bsqSwapTradeManager;
|
||||
private final FailedTradesManager failedTradesManager;
|
||||
private final XmrTxProofService xmrTxProofService;
|
||||
private final OpenOfferManager openOfferManager;
|
||||
|
@ -112,6 +115,7 @@ public class DomainInitialisation {
|
|||
private final DaoStateSnapshotService daoStateSnapshotService;
|
||||
private final TriggerPriceService triggerPriceService;
|
||||
private final MempoolService mempoolService;
|
||||
private final OpenBsqSwapOfferService openBsqSwapOfferService;
|
||||
|
||||
@Inject
|
||||
public DomainInitialisation(ClockWatcher clockWatcher,
|
||||
|
@ -122,6 +126,7 @@ public class DomainInitialisation {
|
|||
TraderChatManager traderChatManager,
|
||||
TradeManager tradeManager,
|
||||
ClosedTradableManager closedTradableManager,
|
||||
BsqSwapTradeManager bsqSwapTradeManager,
|
||||
FailedTradesManager failedTradesManager,
|
||||
XmrTxProofService xmrTxProofService,
|
||||
OpenOfferManager openOfferManager,
|
||||
|
@ -149,7 +154,8 @@ public class DomainInitialisation {
|
|||
User user,
|
||||
DaoStateSnapshotService daoStateSnapshotService,
|
||||
TriggerPriceService triggerPriceService,
|
||||
MempoolService mempoolService) {
|
||||
MempoolService mempoolService,
|
||||
OpenBsqSwapOfferService openBsqSwapOfferService) {
|
||||
this.clockWatcher = clockWatcher;
|
||||
this.tradeLimits = tradeLimits;
|
||||
this.arbitrationManager = arbitrationManager;
|
||||
|
@ -158,6 +164,7 @@ public class DomainInitialisation {
|
|||
this.traderChatManager = traderChatManager;
|
||||
this.tradeManager = tradeManager;
|
||||
this.closedTradableManager = closedTradableManager;
|
||||
this.bsqSwapTradeManager = bsqSwapTradeManager;
|
||||
this.failedTradesManager = failedTradesManager;
|
||||
this.xmrTxProofService = xmrTxProofService;
|
||||
this.openOfferManager = openOfferManager;
|
||||
|
@ -186,6 +193,7 @@ public class DomainInitialisation {
|
|||
this.daoStateSnapshotService = daoStateSnapshotService;
|
||||
this.triggerPriceService = triggerPriceService;
|
||||
this.mempoolService = mempoolService;
|
||||
this.openBsqSwapOfferService = openBsqSwapOfferService;
|
||||
}
|
||||
|
||||
public void initDomainServices(Consumer<String> rejectedTxErrorMessageHandler,
|
||||
|
@ -210,10 +218,12 @@ public class DomainInitialisation {
|
|||
traderChatManager.onAllServicesInitialized();
|
||||
|
||||
closedTradableManager.onAllServicesInitialized();
|
||||
bsqSwapTradeManager.onAllServicesInitialized();
|
||||
failedTradesManager.onAllServicesInitialized();
|
||||
xmrTxProofService.onAllServicesInitialized();
|
||||
|
||||
openOfferManager.onAllServicesInitialized();
|
||||
openBsqSwapOfferService.onAllServicesInitialized();
|
||||
|
||||
balances.onAllServicesInitialized();
|
||||
|
||||
|
|
|
@ -18,6 +18,8 @@
|
|||
package bisq.core.app;
|
||||
|
||||
import bisq.core.btc.setup.WalletsSetup;
|
||||
import bisq.core.filter.Filter;
|
||||
import bisq.core.filter.FilterManager;
|
||||
import bisq.core.locale.Res;
|
||||
import bisq.core.provider.price.PriceFeedService;
|
||||
import bisq.core.user.Preferences;
|
||||
|
@ -27,6 +29,7 @@ import bisq.network.p2p.P2PServiceListener;
|
|||
import bisq.network.p2p.network.CloseConnectionReason;
|
||||
import bisq.network.p2p.network.Connection;
|
||||
import bisq.network.p2p.network.ConnectionListener;
|
||||
import bisq.network.p2p.storage.payload.ProofOfWorkPayload;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import javax.inject.Singleton;
|
||||
|
@ -71,25 +74,31 @@ public class P2PNetworkSetup {
|
|||
final BooleanProperty updatedDataReceived = new SimpleBooleanProperty();
|
||||
@Getter
|
||||
final BooleanProperty p2pNetworkFailed = new SimpleBooleanProperty();
|
||||
final FilterManager filterManager;
|
||||
|
||||
@Inject
|
||||
public P2PNetworkSetup(PriceFeedService priceFeedService,
|
||||
P2PService p2PService,
|
||||
WalletsSetup walletsSetup,
|
||||
Preferences preferences) {
|
||||
Preferences preferences,
|
||||
FilterManager filterManager) {
|
||||
|
||||
this.priceFeedService = priceFeedService;
|
||||
this.p2PService = p2PService;
|
||||
this.walletsSetup = walletsSetup;
|
||||
this.preferences = preferences;
|
||||
this.filterManager = filterManager;
|
||||
}
|
||||
|
||||
BooleanProperty init(Runnable initWalletServiceHandler, @Nullable Consumer<Boolean> displayTorNetworkSettingsHandler) {
|
||||
BooleanProperty init(Runnable initWalletServiceHandler,
|
||||
@Nullable Consumer<Boolean> displayTorNetworkSettingsHandler) {
|
||||
StringProperty bootstrapState = new SimpleStringProperty();
|
||||
StringProperty bootstrapWarning = new SimpleStringProperty();
|
||||
BooleanProperty hiddenServicePublished = new SimpleBooleanProperty();
|
||||
BooleanProperty initialP2PNetworkDataReceived = new SimpleBooleanProperty();
|
||||
|
||||
addP2PMessageFilter();
|
||||
|
||||
p2PNetworkInfoBinding = EasyBind.combine(bootstrapState, bootstrapWarning, p2PService.getNumConnectedPeers(),
|
||||
walletsSetup.numPeersProperty(), hiddenServicePublished, initialP2PNetworkDataReceived,
|
||||
(state, warning, numP2pPeers, numBtcPeers, hiddenService, dataReceived) -> {
|
||||
|
@ -225,4 +234,13 @@ public class P2PNetworkSetup {
|
|||
public void setSplashP2PNetworkAnimationVisible(boolean value) {
|
||||
splashP2PNetworkAnimationVisible.set(value);
|
||||
}
|
||||
|
||||
private void addP2PMessageFilter() {
|
||||
p2PService.getP2PDataStorage().setFilterPredicate(payload -> {
|
||||
Filter filter = filterManager.getFilter();
|
||||
return filter == null ||
|
||||
!filter.isDisablePowMessage() ||
|
||||
!(payload instanceof ProofOfWorkPayload);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -24,6 +24,7 @@ import bisq.core.btc.wallet.BtcWalletService;
|
|||
import bisq.core.dao.DaoSetup;
|
||||
import bisq.core.dao.node.full.RpcService;
|
||||
import bisq.core.offer.OpenOfferManager;
|
||||
import bisq.core.offer.bsq_swap.OpenBsqSwapOfferService;
|
||||
import bisq.core.support.dispute.arbitration.arbitrator.ArbitratorManager;
|
||||
|
||||
import bisq.network.p2p.NodeAddress;
|
||||
|
@ -87,6 +88,7 @@ public abstract class ExecutableForAppWithP2p extends BisqExecutable {
|
|||
try {
|
||||
if (injector != null) {
|
||||
JsonFileManager.shutDownAllInstances();
|
||||
injector.getInstance(OpenBsqSwapOfferService.class).shutDown();
|
||||
injector.getInstance(RpcService.class).shutDown();
|
||||
injector.getInstance(DaoSetup.class).shutDown();
|
||||
injector.getInstance(ArbitratorManager.class).shutDown();
|
||||
|
|
|
@ -24,10 +24,10 @@ import bisq.core.offer.OpenOffer;
|
|||
import bisq.core.offer.OpenOfferManager;
|
||||
import bisq.core.support.dispute.Dispute;
|
||||
import bisq.core.support.dispute.refund.RefundManager;
|
||||
import bisq.core.trade.Trade;
|
||||
import bisq.core.trade.ClosedTradableManager;
|
||||
import bisq.core.trade.TradeManager;
|
||||
import bisq.core.trade.closed.ClosedTradableManager;
|
||||
import bisq.core.trade.failed.FailedTradesManager;
|
||||
import bisq.core.trade.bisq_v1.FailedTradesManager;
|
||||
import bisq.core.trade.model.bisq_v1.Trade;
|
||||
|
||||
import bisq.common.UserThread;
|
||||
|
||||
|
|
|
@ -55,11 +55,11 @@ public class TxFeeEstimationService {
|
|||
// segwit deposit tx with change vsize = 263
|
||||
// segwit payout tx vsize = 169
|
||||
// segwit delayed payout tx vsize = 139
|
||||
public static int TYPICAL_TX_WITH_1_INPUT_VSIZE = 175;
|
||||
private static int DEPOSIT_TX_VSIZE = 233;
|
||||
public static final int TYPICAL_TX_WITH_1_INPUT_VSIZE = 175;
|
||||
private static final int DEPOSIT_TX_VSIZE = 233;
|
||||
|
||||
private static int BSQ_INPUT_INCREASE = 70;
|
||||
private static int MAX_ITERATIONS = 10;
|
||||
private static final int BSQ_INPUT_INCREASE = 70;
|
||||
private static final int MAX_ITERATIONS = 10;
|
||||
|
||||
private final FeeService feeService;
|
||||
private final BtcWalletService btcWalletService;
|
||||
|
|
|
@ -20,7 +20,7 @@ package bisq.core.btc.listeners;
|
|||
import org.bitcoinj.core.Coin;
|
||||
|
||||
public interface BsqBalanceListener {
|
||||
void onUpdateBalances(Coin availableConfirmedBalance,
|
||||
void onUpdateBalances(Coin availableBalance,
|
||||
Coin availableNonBsqBalance,
|
||||
Coin unverifiedBalance,
|
||||
Coin unconfirmedChangeBalance,
|
||||
|
|
|
@ -19,18 +19,15 @@ package bisq.core.btc.listeners;
|
|||
|
||||
import org.bitcoinj.core.TransactionConfidence;
|
||||
|
||||
public class TxConfidenceListener {
|
||||
private final String txID;
|
||||
import lombok.Getter;
|
||||
|
||||
public TxConfidenceListener(String txID) {
|
||||
this.txID = txID;
|
||||
public abstract class TxConfidenceListener {
|
||||
@Getter
|
||||
private final String txId;
|
||||
|
||||
public TxConfidenceListener(String txId) {
|
||||
this.txId = txId;
|
||||
}
|
||||
|
||||
public String getTxID() {
|
||||
return txID;
|
||||
}
|
||||
|
||||
@SuppressWarnings("UnusedParameters")
|
||||
public void onTransactionConfidenceChanged(TransactionConfidence confidence) {
|
||||
}
|
||||
abstract public void onTransactionConfidenceChanged(TransactionConfidence confidence);
|
||||
}
|
||||
|
|
|
@ -17,33 +17,82 @@
|
|||
|
||||
package bisq.core.btc.model;
|
||||
|
||||
import bisq.core.btc.wallet.BtcWalletService;
|
||||
|
||||
import bisq.common.proto.network.NetworkPayload;
|
||||
import bisq.common.proto.persistable.PersistablePayload;
|
||||
import bisq.common.util.Utilities;
|
||||
|
||||
import com.google.protobuf.ByteString;
|
||||
|
||||
import org.bitcoinj.core.Transaction;
|
||||
import org.bitcoinj.core.TransactionInput;
|
||||
import org.bitcoinj.script.Script;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.Getter;
|
||||
|
||||
import javax.annotation.concurrent.Immutable;
|
||||
|
||||
@EqualsAndHashCode
|
||||
@Immutable
|
||||
@Getter
|
||||
public final class RawTransactionInput implements NetworkPayload, PersistablePayload {
|
||||
public final long index; // Index of spending txo
|
||||
public final byte[] parentTransaction; // Spending tx (fromTx)
|
||||
public final long value;
|
||||
|
||||
// Added at Bsq swap release
|
||||
// id of the org.bitcoinj.script.Script.ScriptType. Useful to know if input is segwit.
|
||||
// Lowest Script.ScriptType.id value is 1, so we use 0 as value for not defined
|
||||
public final int scriptTypeId;
|
||||
|
||||
public RawTransactionInput(TransactionInput input) {
|
||||
this(input.getOutpoint().getIndex(),
|
||||
Objects.requireNonNull(Objects.requireNonNull(input.getConnectedOutput()).getParentTransaction()),
|
||||
Objects.requireNonNull(input.getValue()).value,
|
||||
input.getConnectedOutput() != null &&
|
||||
input.getConnectedOutput().getScriptPubKey() != null &&
|
||||
input.getConnectedOutput().getScriptPubKey().getScriptType() != null ?
|
||||
input.getConnectedOutput().getScriptPubKey().getScriptType().id : -1);
|
||||
}
|
||||
|
||||
// Does not set the scriptTypeId. Use RawTransactionInput(TransactionInput input) for any new code.
|
||||
@Deprecated
|
||||
public RawTransactionInput(long index, byte[] parentTransaction, long value) {
|
||||
this(index, parentTransaction, value, 0);
|
||||
}
|
||||
|
||||
private RawTransactionInput(long index, Transaction parentTransaction, long value, int scriptTypeId) {
|
||||
this(index,
|
||||
parentTransaction.bitcoinSerialize(scriptTypeId == Script.ScriptType.P2WPKH.id ||
|
||||
scriptTypeId == Script.ScriptType.P2WSH.id),
|
||||
value, scriptTypeId);
|
||||
}
|
||||
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
// PROTO BUFFER
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
/**
|
||||
* Holds the relevant data for the connected output for a tx input.
|
||||
* @param index the index of the parentTransaction
|
||||
* @param parentTransaction the spending output tx, not the parent tx of the input
|
||||
* @param value the number of satoshis being spent
|
||||
* @param scriptTypeId The id of the org.bitcoinj.script.Script.ScriptType of the spending output
|
||||
* If not set it is 0.
|
||||
*/
|
||||
public RawTransactionInput(long index, byte[] parentTransaction, long value) {
|
||||
private RawTransactionInput(long index,
|
||||
byte[] parentTransaction,
|
||||
long value,
|
||||
int scriptTypeId) {
|
||||
this.index = index;
|
||||
this.parentTransaction = parentTransaction;
|
||||
this.value = value;
|
||||
this.scriptTypeId = scriptTypeId;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -52,11 +101,36 @@ public final class RawTransactionInput implements NetworkPayload, PersistablePay
|
|||
.setIndex(index)
|
||||
.setParentTransaction(ByteString.copyFrom(parentTransaction))
|
||||
.setValue(value)
|
||||
.setScriptTypeId(scriptTypeId)
|
||||
.build();
|
||||
}
|
||||
|
||||
public static RawTransactionInput fromProto(protobuf.RawTransactionInput proto) {
|
||||
return new RawTransactionInput(proto.getIndex(), proto.getParentTransaction().toByteArray(), proto.getValue());
|
||||
return new RawTransactionInput(proto.getIndex(),
|
||||
proto.getParentTransaction().toByteArray(),
|
||||
proto.getValue(),
|
||||
proto.getScriptTypeId());
|
||||
}
|
||||
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
// API
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
public boolean isSegwit() {
|
||||
return isP2WPKH() || isP2WSH();
|
||||
}
|
||||
|
||||
public boolean isP2WPKH() {
|
||||
return scriptTypeId == Script.ScriptType.P2WPKH.id;
|
||||
}
|
||||
|
||||
public boolean isP2WSH() {
|
||||
return scriptTypeId == Script.ScriptType.P2WSH.id;
|
||||
}
|
||||
|
||||
public String getParentTxId(BtcWalletService btcWalletService) {
|
||||
return btcWalletService.getTxFromSerializedTx(parentTransaction).getTxId().toString();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -65,6 +139,7 @@ public final class RawTransactionInput implements NetworkPayload, PersistablePay
|
|||
"index=" + index +
|
||||
", parentTransaction as HEX " + Utilities.bytesAsHexString(parentTransaction) +
|
||||
", value=" + value +
|
||||
", scriptTypeId=" + scriptTypeId +
|
||||
'}';
|
||||
}
|
||||
}
|
||||
|
|
|
@ -64,9 +64,6 @@ public class BtcNodes {
|
|||
new BtcNode("btc1.sqrrm.net", "jygcc54etaubgdpcvzgbihjaqbc37cstpvum5sjzvka4bibkp4wrgnqd.onion", "185.25.48.184", BtcNode.DEFAULT_PORT, "@sqrrm"),
|
||||
new BtcNode("btc2.sqrrm.net", "h32haomoe52ljz6qopedsocvotvoj5lm2zmecfhdhawb3flbsf64l2qd.onion", "81.171.22.143", BtcNode.DEFAULT_PORT, "@sqrrm"),
|
||||
|
||||
// KanoczTomas
|
||||
// new BtcNode("btc.ispol.sk", "mbm6ffx6j5ygi2ck.onion", "193.58.196.212", BtcNode.DEFAULT_PORT, "@KanoczTomas"),
|
||||
|
||||
// Devin Bileck
|
||||
new BtcNode("btc1.bisq.services", "devinbtctu7uctl7hly2juu3thbgeivfnvw3ckj3phy6nyvpnx66yeyd.onion", "172.105.21.216", BtcNode.DEFAULT_PORT, "@devinbileck"),
|
||||
new BtcNode("btc2.bisq.services", "devinbtcyk643iruzfpaxw3on2jket7rbjmwygm42dmdyub3ietrbmid.onion", "173.255.240.205", BtcNode.DEFAULT_PORT, "@devinbileck"),
|
||||
|
@ -77,9 +74,6 @@ public class BtcNodes {
|
|||
new BtcNode("node140.hnl.wiz.biz", "jto2jfbsxhb6yvhcrrjddrgbakte6tgsy3c3z3prss64gndgvovvosyd.onion", "103.99.168.140", BtcNode.DEFAULT_PORT, "@wiz"),
|
||||
new BtcNode("node210.fmt.wiz.biz", "rfqmn3qe36uaptkxhdvi74p4hyrzhir6vhmzb2hqryxodig4gue2zbyd.onion", "103.99.170.210", BtcNode.DEFAULT_PORT, "@wiz"),
|
||||
new BtcNode("node220.fmt.wiz.biz", "azbpsh4arqlm6442wfimy7qr65bmha2zhgjg7wbaji6vvaug53hur2qd.onion", "103.99.170.220", BtcNode.DEFAULT_PORT, "@wiz")
|
||||
|
||||
// Rob Kaandorp
|
||||
// new BtcNode(null, "2pj2o2mrawj7yotg.onion", null, BtcNode.DEFAULT_PORT, "@robkaandorp") // cannot provide IP because no static IP
|
||||
) :
|
||||
new ArrayList<>();
|
||||
}
|
||||
|
|
|
@ -26,6 +26,8 @@ import org.bitcoinj.core.TransactionOutput;
|
|||
|
||||
import javax.inject.Inject;
|
||||
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
/**
|
||||
|
@ -36,9 +38,13 @@ import lombok.extern.slf4j.Slf4j;
|
|||
public class BsqCoinSelector extends BisqDefaultCoinSelector {
|
||||
private final DaoStateService daoStateService;
|
||||
private final UnconfirmedBsqChangeOutputListService unconfirmedBsqChangeOutputListService;
|
||||
@Setter
|
||||
@Getter
|
||||
private boolean allowSpendMyOwnUnconfirmedTxOutputs = true;
|
||||
|
||||
@Inject
|
||||
public BsqCoinSelector(DaoStateService daoStateService, UnconfirmedBsqChangeOutputListService unconfirmedBsqChangeOutputListService) {
|
||||
public BsqCoinSelector(DaoStateService daoStateService,
|
||||
UnconfirmedBsqChangeOutputListService unconfirmedBsqChangeOutputListService) {
|
||||
// permitForeignPendingTx is not relevant here as we do not support pending foreign utxos anyway.
|
||||
super(false);
|
||||
this.daoStateService = daoStateService;
|
||||
|
@ -53,17 +59,19 @@ public class BsqCoinSelector extends BisqDefaultCoinSelector {
|
|||
return false;
|
||||
|
||||
// If it is a normal confirmed BSQ output we use the default lookup at the daoState
|
||||
if (daoStateService.isTxOutputSpendable(new TxOutputKey(parentTransaction.getTxId().toString(), output.getIndex())))
|
||||
TxOutputKey txOutputKey = new TxOutputKey(parentTransaction.getTxId().toString(), output.getIndex());
|
||||
if (daoStateService.isTxOutputSpendable(txOutputKey))
|
||||
return true;
|
||||
|
||||
// It might be that it is an unconfirmed change output which we allow to be used for spending without requiring a confirmation.
|
||||
// We check if we have the output in the dao state, if so we have a confirmed but unspendable output (e.g. confiscated).
|
||||
if (daoStateService.getTxOutput(new TxOutputKey(parentTransaction.getTxId().toString(), output.getIndex())).isPresent())
|
||||
if (daoStateService.getTxOutput(txOutputKey).isPresent())
|
||||
return false;
|
||||
|
||||
// If we have set the isUnconfirmedSpendable flag to true (default) we check for unconfirmed own change outputs.
|
||||
// Only if it's not existing yet in the dao state (unconfirmed) we use our unconfirmedBsqChangeOutputList to
|
||||
// check if it is an own change output.
|
||||
return unconfirmedBsqChangeOutputListService.hasTransactionOutput(output);
|
||||
return allowSpendMyOwnUnconfirmedTxOutputs && unconfirmedBsqChangeOutputListService.hasTransactionOutput(output);
|
||||
}
|
||||
|
||||
// For BSQ we do not check for dust attack utxos as they are 5.46 BSQ and a considerable value.
|
||||
|
|
|
@ -42,7 +42,7 @@ public class BsqTransferService {
|
|||
|
||||
Transaction preparedSendTx = bsqWalletService.getPreparedSendBsqTx(address.toString(), receiverAmount);
|
||||
Transaction txWithBtcFee = btcWalletService.completePreparedSendBsqTx(preparedSendTx, txFeePerVbyte);
|
||||
Transaction signedTx = bsqWalletService.signTx(txWithBtcFee);
|
||||
Transaction signedTx = bsqWalletService.signTxAndVerifyNoDustOutputs(txWithBtcFee);
|
||||
|
||||
return new BsqTransferModel(address,
|
||||
receiverAmount,
|
||||
|
|
|
@ -22,6 +22,7 @@ import bisq.core.btc.exceptions.InsufficientBsqException;
|
|||
import bisq.core.btc.exceptions.TransactionVerificationException;
|
||||
import bisq.core.btc.exceptions.WalletException;
|
||||
import bisq.core.btc.listeners.BsqBalanceListener;
|
||||
import bisq.core.btc.model.RawTransactionInput;
|
||||
import bisq.core.btc.setup.WalletsSetup;
|
||||
import bisq.core.dao.DaoKillSwitch;
|
||||
import bisq.core.dao.state.DaoStateListener;
|
||||
|
@ -34,8 +35,10 @@ import bisq.core.dao.state.model.blockchain.TxType;
|
|||
import bisq.core.dao.state.unconfirmed.UnconfirmedBsqChangeOutputListService;
|
||||
import bisq.core.provider.fee.FeeService;
|
||||
import bisq.core.user.Preferences;
|
||||
import bisq.core.util.coin.BsqFormatter;
|
||||
|
||||
import bisq.common.UserThread;
|
||||
import bisq.common.util.Tuple2;
|
||||
|
||||
import org.bitcoinj.core.Address;
|
||||
import org.bitcoinj.core.AddressFormatException;
|
||||
|
@ -95,15 +98,20 @@ public class BsqWalletService extends WalletService implements DaoStateListener
|
|||
private final CopyOnWriteArraySet<BsqBalanceListener> bsqBalanceListeners = new CopyOnWriteArraySet<>();
|
||||
private final List<WalletTransactionsChangeListener> walletTransactionsChangeListeners = new ArrayList<>();
|
||||
private boolean updateBsqWalletTransactionsPending;
|
||||
@Getter
|
||||
private final BsqFormatter bsqFormatter;
|
||||
|
||||
|
||||
// balance of non BSQ satoshis
|
||||
@Getter
|
||||
private Coin availableNonBsqBalance = Coin.ZERO;
|
||||
@Getter
|
||||
private Coin availableConfirmedBalance = Coin.ZERO;
|
||||
private Coin availableBalance = Coin.ZERO;
|
||||
@Getter
|
||||
private Coin unverifiedBalance = Coin.ZERO;
|
||||
@Getter
|
||||
private Coin verifiedBalance = Coin.ZERO;
|
||||
@Getter
|
||||
private Coin unconfirmedChangeBalance = Coin.ZERO;
|
||||
@Getter
|
||||
private Coin lockedForVotingBalance = Coin.ZERO;
|
||||
|
@ -125,7 +133,8 @@ public class BsqWalletService extends WalletService implements DaoStateListener
|
|||
UnconfirmedBsqChangeOutputListService unconfirmedBsqChangeOutputListService,
|
||||
Preferences preferences,
|
||||
FeeService feeService,
|
||||
DaoKillSwitch daoKillSwitch) {
|
||||
DaoKillSwitch daoKillSwitch,
|
||||
BsqFormatter bsqFormatter) {
|
||||
super(walletsSetup,
|
||||
preferences,
|
||||
feeService);
|
||||
|
@ -135,6 +144,7 @@ public class BsqWalletService extends WalletService implements DaoStateListener
|
|||
this.daoStateService = daoStateService;
|
||||
this.unconfirmedBsqChangeOutputListService = unconfirmedBsqChangeOutputListService;
|
||||
this.daoKillSwitch = daoKillSwitch;
|
||||
this.bsqFormatter = bsqFormatter;
|
||||
|
||||
nonBsqCoinSelector.setPreferences(preferences);
|
||||
|
||||
|
@ -284,18 +294,20 @@ public class BsqWalletService extends WalletService implements DaoStateListener
|
|||
.mapToLong(TxOutput::getValue)
|
||||
.sum());
|
||||
|
||||
availableConfirmedBalance = bsqCoinSelector.select(NetworkParameters.MAX_MONEY,
|
||||
availableBalance = bsqCoinSelector.select(NetworkParameters.MAX_MONEY,
|
||||
wallet.calculateAllSpendCandidates()).valueGathered;
|
||||
|
||||
if (availableConfirmedBalance.isNegative())
|
||||
availableConfirmedBalance = Coin.ZERO;
|
||||
if (availableBalance.isNegative())
|
||||
availableBalance = Coin.ZERO;
|
||||
|
||||
unconfirmedChangeBalance = unconfirmedBsqChangeOutputListService.getBalance();
|
||||
|
||||
availableNonBsqBalance = nonBsqCoinSelector.select(NetworkParameters.MAX_MONEY,
|
||||
wallet.calculateAllSpendCandidates()).valueGathered;
|
||||
|
||||
bsqBalanceListeners.forEach(e -> e.onUpdateBalances(availableConfirmedBalance, availableNonBsqBalance, unverifiedBalance,
|
||||
verifiedBalance = availableBalance.subtract(unconfirmedChangeBalance);
|
||||
|
||||
bsqBalanceListeners.forEach(e -> e.onUpdateBalances(availableBalance, availableNonBsqBalance, unverifiedBalance,
|
||||
unconfirmedChangeBalance, lockedForVotingBalance, lockupBondsBalance, unlockingBondsBalance));
|
||||
log.info("updateBsqBalance took {} ms", System.currentTimeMillis() - ts);
|
||||
}
|
||||
|
@ -481,28 +493,10 @@ public class BsqWalletService extends WalletService implements DaoStateListener
|
|||
// Sign tx
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
public Transaction signTx(Transaction tx) throws WalletException, TransactionVerificationException {
|
||||
for (int i = 0; i < tx.getInputs().size(); i++) {
|
||||
TransactionInput txIn = tx.getInputs().get(i);
|
||||
TransactionOutput connectedOutput = txIn.getConnectedOutput();
|
||||
if (connectedOutput != null && connectedOutput.isMine(wallet)) {
|
||||
signTransactionInput(wallet, aesKey, tx, txIn, i);
|
||||
checkScriptSig(tx, txIn, i);
|
||||
}
|
||||
}
|
||||
|
||||
for (TransactionOutput txo : tx.getOutputs()) {
|
||||
Coin value = txo.getValue();
|
||||
// OpReturn outputs have value 0
|
||||
if (value.isPositive()) {
|
||||
checkArgument(Restrictions.isAboveDust(txo.getValue()),
|
||||
"An output value is below dust limit. Transaction=" + tx);
|
||||
}
|
||||
}
|
||||
|
||||
checkWalletConsistency(wallet);
|
||||
verifyTransaction(tx);
|
||||
printTx("BSQ wallet: Signed Tx", tx);
|
||||
public Transaction signTxAndVerifyNoDustOutputs(Transaction tx)
|
||||
throws WalletException, TransactionVerificationException {
|
||||
WalletService.signTx(wallet, aesKey, tx);
|
||||
WalletService.verifyNonDustTxo(tx);
|
||||
return tx;
|
||||
}
|
||||
|
||||
|
@ -540,6 +534,7 @@ public class BsqWalletService extends WalletService implements DaoStateListener
|
|||
return getPreparedSendTx(receiverAddress, receiverAmount, bsqCoinSelector);
|
||||
}
|
||||
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
// Send BTC (non-BSQ) with BTC fee (e.g. the issuance output from a lost comp. request)
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
@ -732,6 +727,46 @@ public class BsqWalletService extends WalletService implements DaoStateListener
|
|||
}
|
||||
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
// BsqSwap tx
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
public Tuple2<List<RawTransactionInput>, Coin> getBuyersBsqInputsForBsqSwapTx(Coin required)
|
||||
throws InsufficientBsqException {
|
||||
daoKillSwitch.assertDaoIsNotDisabled();
|
||||
// As unconfirmed BSQ inputs cannot be verified by the peer we can only use confirmed BSQ.
|
||||
boolean prev = bsqCoinSelector.isAllowSpendMyOwnUnconfirmedTxOutputs();
|
||||
bsqCoinSelector.setAllowSpendMyOwnUnconfirmedTxOutputs(false);
|
||||
CoinSelection coinSelection = bsqCoinSelector.select(required, wallet.calculateAllSpendCandidates());
|
||||
Coin change;
|
||||
try {
|
||||
change = bsqCoinSelector.getChange(required, coinSelection);
|
||||
} catch (InsufficientMoneyException e) {
|
||||
throw new InsufficientBsqException(e.missing);
|
||||
} finally {
|
||||
bsqCoinSelector.setAllowSpendMyOwnUnconfirmedTxOutputs(prev);
|
||||
}
|
||||
|
||||
Transaction dummyTx = new Transaction(params);
|
||||
coinSelection.gathered.forEach(dummyTx::addInput);
|
||||
List<RawTransactionInput> inputs = dummyTx.getInputs().stream()
|
||||
.map(RawTransactionInput::new)
|
||||
.collect(Collectors.toList());
|
||||
return new Tuple2<>(inputs, change);
|
||||
}
|
||||
|
||||
public void signBsqSwapTransaction(Transaction transaction, List<TransactionInput> myInputs)
|
||||
throws TransactionVerificationException {
|
||||
for (TransactionInput input : myInputs) {
|
||||
TransactionOutput connectedOutput = input.getConnectedOutput();
|
||||
checkNotNull(connectedOutput, "connectedOutput must not be null");
|
||||
checkArgument(connectedOutput.isMine(wallet), "connectedOutput is not mine");
|
||||
signTransactionInput(wallet, aesKey, transaction, input, input.getIndex());
|
||||
checkScriptSig(transaction, input, input.getIndex());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
// Blind vote tx
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
@ -781,6 +816,7 @@ public class BsqWalletService extends WalletService implements DaoStateListener
|
|||
return tx;
|
||||
}
|
||||
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
// Unlock bond tx
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
|
|
@ -23,6 +23,7 @@ import bisq.core.btc.exceptions.TransactionVerificationException;
|
|||
import bisq.core.btc.exceptions.WalletException;
|
||||
import bisq.core.btc.model.AddressEntry;
|
||||
import bisq.core.btc.model.AddressEntryList;
|
||||
import bisq.core.btc.model.RawTransactionInput;
|
||||
import bisq.core.btc.setup.WalletsSetup;
|
||||
import bisq.core.btc.wallet.http.MemPoolSpaceTxBroadcaster;
|
||||
import bisq.core.provider.fee.FeeService;
|
||||
|
@ -47,6 +48,7 @@ import org.bitcoinj.crypto.KeyCrypterScrypt;
|
|||
import org.bitcoinj.script.Script;
|
||||
import org.bitcoinj.script.ScriptBuilder;
|
||||
import org.bitcoinj.script.ScriptPattern;
|
||||
import org.bitcoinj.wallet.CoinSelection;
|
||||
import org.bitcoinj.wallet.SendRequest;
|
||||
import org.bitcoinj.wallet.Wallet;
|
||||
|
||||
|
@ -61,6 +63,7 @@ import org.bouncycastle.crypto.params.KeyParameter;
|
|||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
|
@ -1330,4 +1333,30 @@ public class BtcWalletService extends WalletService {
|
|||
|
||||
return resultTx;
|
||||
}
|
||||
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
// Find inputs and change
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
public Tuple2<List<RawTransactionInput>, Coin> getInputsAndChange(Coin required) throws InsufficientMoneyException {
|
||||
BtcCoinSelector coinSelector = new BtcCoinSelector(walletsSetup.getAddressesByContext(AddressEntry.Context.AVAILABLE),
|
||||
preferences.getIgnoreDustThreshold());
|
||||
CoinSelection coinSelection = coinSelector.select(required, Objects.requireNonNull(wallet).calculateAllSpendCandidates());
|
||||
|
||||
Coin change;
|
||||
try {
|
||||
change = coinSelector.getChange(required, coinSelection);
|
||||
} catch (InsufficientMoneyException e) {
|
||||
log.error("Missing funds in getSellersBtcInputsForBsqSwapTx. missing={}", e.missing);
|
||||
throw new InsufficientMoneyException(e.missing);
|
||||
}
|
||||
|
||||
Transaction dummyTx = new Transaction(params);
|
||||
coinSelection.gathered.forEach(dummyTx::addInput);
|
||||
List<RawTransactionInput> inputs = dummyTx.getInputs().stream()
|
||||
.map(RawTransactionInput::new)
|
||||
.collect(Collectors.toList());
|
||||
return new Tuple2<>(inputs, change);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -63,13 +63,12 @@ import org.bouncycastle.crypto.params.KeyParameter;
|
|||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
import static com.google.common.base.Preconditions.checkArgument;
|
||||
|
@ -891,8 +890,7 @@ public class TradeWalletService {
|
|||
input.setScriptSig(inputScript);
|
||||
} else {
|
||||
input.setScriptSig(ScriptBuilder.createEmpty());
|
||||
TransactionWitness witness = TransactionWitness.redeemP2WSH(redeemScript, sellerTxSig, buyerTxSig);
|
||||
input.setWitness(witness);
|
||||
input.setWitness(TransactionWitness.redeemP2WSH(redeemScript, sellerTxSig, buyerTxSig));
|
||||
}
|
||||
WalletService.printTx("payoutTx", payoutTx);
|
||||
WalletService.verifyTransaction(payoutTx);
|
||||
|
@ -971,8 +969,7 @@ public class TradeWalletService {
|
|||
input.setScriptSig(inputScript);
|
||||
} else {
|
||||
input.setScriptSig(ScriptBuilder.createEmpty());
|
||||
TransactionWitness witness = TransactionWitness.redeemP2WSH(redeemScript, sellerTxSig, buyerTxSig);
|
||||
input.setWitness(witness);
|
||||
input.setWitness(TransactionWitness.redeemP2WSH(redeemScript, sellerTxSig, buyerTxSig));
|
||||
}
|
||||
WalletService.printTx("mediated payoutTx", payoutTx);
|
||||
WalletService.verifyTransaction(payoutTx);
|
||||
|
@ -1059,8 +1056,7 @@ public class TradeWalletService {
|
|||
input.setScriptSig(inputScript);
|
||||
} else {
|
||||
input.setScriptSig(ScriptBuilder.createEmpty());
|
||||
TransactionWitness witness = TransactionWitness.redeemP2WSH(redeemScript, arbitratorTxSig, tradersTxSig);
|
||||
input.setWitness(witness);
|
||||
input.setWitness(TransactionWitness.redeemP2WSH(redeemScript, arbitratorTxSig, tradersTxSig));
|
||||
}
|
||||
WalletService.printTx("disputed payoutTx", payoutTx);
|
||||
WalletService.verifyTransaction(payoutTx);
|
||||
|
@ -1077,14 +1073,14 @@ public class TradeWalletService {
|
|||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
public Tuple2<String, String> emergencyBuildPayoutTxFrom2of2MultiSig(String depositTxHex,
|
||||
Coin buyerPayoutAmount,
|
||||
Coin sellerPayoutAmount,
|
||||
Coin txFee,
|
||||
String buyerAddressString,
|
||||
String sellerAddressString,
|
||||
String buyerPubKeyAsHex,
|
||||
String sellerPubKeyAsHex,
|
||||
boolean hashedMultiSigOutputIsLegacy) {
|
||||
Coin buyerPayoutAmount,
|
||||
Coin sellerPayoutAmount,
|
||||
Coin txFee,
|
||||
String buyerAddressString,
|
||||
String sellerAddressString,
|
||||
String buyerPubKeyAsHex,
|
||||
String sellerPubKeyAsHex,
|
||||
boolean hashedMultiSigOutputIsLegacy) {
|
||||
byte[] buyerPubKey = ECKey.fromPublicOnly(Utils.HEX.decode(buyerPubKeyAsHex)).getPubKey();
|
||||
byte[] sellerPubKey = ECKey.fromPublicOnly(Utils.HEX.decode(sellerPubKeyAsHex)).getPubKey();
|
||||
Script redeemScript = get2of2MultiSigRedeemScript(buyerPubKey, sellerPubKey);
|
||||
|
@ -1105,7 +1101,10 @@ public class TradeWalletService {
|
|||
return new Tuple2<>(redeemScriptHex, unsignedTxHex);
|
||||
}
|
||||
|
||||
public String emergencyGenerateSignature(String rawTxHex, String redeemScriptHex, Coin inputValue, String myPrivKeyAsHex)
|
||||
public String emergencyGenerateSignature(String rawTxHex,
|
||||
String redeemScriptHex,
|
||||
Coin inputValue,
|
||||
String myPrivKeyAsHex)
|
||||
throws IllegalArgumentException {
|
||||
boolean hashedMultiSigOutputIsLegacy = true;
|
||||
if (rawTxHex.startsWith("010000000001"))
|
||||
|
@ -1129,10 +1128,10 @@ public class TradeWalletService {
|
|||
}
|
||||
|
||||
public Tuple2<String, String> emergencyApplySignatureToPayoutTxFrom2of2MultiSig(String unsignedTxHex,
|
||||
String redeemScriptHex,
|
||||
String buyerSignatureAsHex,
|
||||
String sellerSignatureAsHex,
|
||||
boolean hashedMultiSigOutputIsLegacy)
|
||||
String redeemScriptHex,
|
||||
String buyerSignatureAsHex,
|
||||
String sellerSignatureAsHex,
|
||||
boolean hashedMultiSigOutputIsLegacy)
|
||||
throws AddressFormatException, SignatureDecodeException {
|
||||
Transaction payoutTx = new Transaction(params, Utils.HEX.decode(unsignedTxHex));
|
||||
TransactionSignature buyerTxSig = TransactionSignature.decodeFromBitcoin(Utils.HEX.decode(buyerSignatureAsHex), true, true);
|
||||
|
@ -1146,8 +1145,7 @@ public class TradeWalletService {
|
|||
input.setScriptSig(inputScript);
|
||||
} else {
|
||||
input.setScriptSig(ScriptBuilder.createEmpty());
|
||||
TransactionWitness witness = TransactionWitness.redeemP2WSH(redeemScript, sellerTxSig, buyerTxSig);
|
||||
input.setWitness(witness);
|
||||
input.setWitness(TransactionWitness.redeemP2WSH(redeemScript, sellerTxSig, buyerTxSig));
|
||||
}
|
||||
String txId = payoutTx.getTxId().toString();
|
||||
String signedTxHex = Utils.HEX.encode(payoutTx.bitcoinSerialize(!hashedMultiSigOutputIsLegacy));
|
||||
|
@ -1163,6 +1161,96 @@ public class TradeWalletService {
|
|||
broadcastTx(payoutTx, callback, 20);
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
// BsqSwap tx
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
public Transaction sellerBuildBsqSwapTx(List<RawTransactionInput> buyersBsqInputs,
|
||||
List<RawTransactionInput> sellersBtcInputs,
|
||||
Coin sellersBsqPayoutAmount,
|
||||
String sellersBsqPayoutAddress,
|
||||
@Nullable Coin buyersBsqChangeAmount,
|
||||
@Nullable String buyersBsqChangeAddress,
|
||||
Coin buyersBtcPayoutAmount,
|
||||
String buyersBtcPayoutAddress,
|
||||
@Nullable Coin sellersBtcChangeAmount,
|
||||
@Nullable String sellersBtcChangeAddress) throws AddressFormatException {
|
||||
|
||||
Transaction transaction = new Transaction(params);
|
||||
List<TransactionInput> sellersBtcTransactionInput = sellersBtcInputs.stream()
|
||||
.map(rawInput -> getTransactionInput(transaction, new byte[]{}, rawInput))
|
||||
.collect(Collectors.toList());
|
||||
return buildBsqSwapTx(buyersBsqInputs,
|
||||
sellersBtcTransactionInput,
|
||||
sellersBsqPayoutAmount,
|
||||
sellersBsqPayoutAddress,
|
||||
buyersBsqChangeAmount,
|
||||
buyersBsqChangeAddress,
|
||||
buyersBtcPayoutAmount,
|
||||
buyersBtcPayoutAddress,
|
||||
sellersBtcChangeAmount,
|
||||
sellersBtcChangeAddress,
|
||||
transaction);
|
||||
}
|
||||
|
||||
public Transaction buyerBuildBsqSwapTx(List<RawTransactionInput> buyersBsqInputs,
|
||||
List<TransactionInput> sellersBtcInputs,
|
||||
Coin sellersBsqPayoutAmount,
|
||||
String sellersBsqPayoutAddress,
|
||||
@Nullable Coin buyersBsqChangeAmount,
|
||||
@Nullable String buyersBsqChangeAddress,
|
||||
Coin buyersBtcPayoutAmount,
|
||||
String buyersBtcPayoutAddress,
|
||||
@Nullable Coin sellersBtcChangeAmount,
|
||||
@Nullable String sellersBtcChangeAddress) throws AddressFormatException {
|
||||
Transaction transaction = new Transaction(params);
|
||||
return buildBsqSwapTx(buyersBsqInputs,
|
||||
sellersBtcInputs,
|
||||
sellersBsqPayoutAmount,
|
||||
sellersBsqPayoutAddress,
|
||||
buyersBsqChangeAmount,
|
||||
buyersBsqChangeAddress,
|
||||
buyersBtcPayoutAmount,
|
||||
buyersBtcPayoutAddress,
|
||||
sellersBtcChangeAmount,
|
||||
sellersBtcChangeAddress,
|
||||
transaction);
|
||||
}
|
||||
|
||||
private Transaction buildBsqSwapTx(List<RawTransactionInput> buyersBsqInputs,
|
||||
List<TransactionInput> sellersBtcInputs,
|
||||
Coin sellersBsqPayoutAmount,
|
||||
String sellersBsqPayoutAddress,
|
||||
@Nullable Coin buyersBsqChangeAmount,
|
||||
@Nullable String buyersBsqChangeAddress,
|
||||
Coin buyersBtcPayoutAmount,
|
||||
String buyersBtcPayoutAddress,
|
||||
@Nullable Coin sellersBtcChangeAmount,
|
||||
@Nullable String sellersBtcChangeAddress,
|
||||
Transaction transaction) throws AddressFormatException {
|
||||
|
||||
buyersBsqInputs.forEach(rawInput -> transaction.addInput(getTransactionInput(transaction, new byte[]{}, rawInput)));
|
||||
sellersBtcInputs.forEach(transaction::addInput);
|
||||
|
||||
transaction.addOutput(sellersBsqPayoutAmount, Address.fromString(params, sellersBsqPayoutAddress));
|
||||
|
||||
if (buyersBsqChangeAmount != null && buyersBsqChangeAmount.isPositive())
|
||||
transaction.addOutput(buyersBsqChangeAmount, Address.fromString(params, Objects.requireNonNull(buyersBsqChangeAddress)));
|
||||
|
||||
transaction.addOutput(buyersBtcPayoutAmount, Address.fromString(params, buyersBtcPayoutAddress));
|
||||
|
||||
if (sellersBtcChangeAmount != null && sellersBtcChangeAmount.isPositive())
|
||||
transaction.addOutput(sellersBtcChangeAmount, Address.fromString(params, Objects.requireNonNull(sellersBtcChangeAddress)));
|
||||
|
||||
return transaction;
|
||||
}
|
||||
|
||||
public void signBsqSwapTransaction(Transaction transaction, List<TransactionInput> myInputs)
|
||||
throws SigningException {
|
||||
for (TransactionInput input : myInputs) {
|
||||
signInput(transaction, input, input.getIndex());
|
||||
}
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
// Broadcast tx
|
||||
|
@ -1207,7 +1295,12 @@ public class TradeWalletService {
|
|||
// Private methods
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
private RawTransactionInput getRawInputFromTransactionInput(@NotNull TransactionInput input) {
|
||||
// This method might be replace by RawTransactionInput constructor taking the TransactionInput as param.
|
||||
// As we used segwit=false for the bitcoinSerialize method here we still keep it to not risk to break anything,
|
||||
// though it very likely should be fine to replace it with the RawTransactionInput constructor call.
|
||||
@Deprecated
|
||||
private RawTransactionInput getRawInputFromTransactionInput(TransactionInput input) {
|
||||
checkNotNull(input, "input must not be null");
|
||||
checkNotNull(input.getConnectedOutput(), "input.getConnectedOutput() must not be null");
|
||||
checkNotNull(input.getConnectedOutput().getParentTransaction(),
|
||||
"input.getConnectedOutput().getParentTransaction() must not be null");
|
||||
|
@ -1222,10 +1315,13 @@ public class TradeWalletService {
|
|||
input.getValue().value);
|
||||
}
|
||||
|
||||
private TransactionInput getTransactionInput(Transaction depositTx,
|
||||
private TransactionInput getTransactionInput(Transaction parentTransaction,
|
||||
byte[] scriptProgram,
|
||||
RawTransactionInput rawTransactionInput) {
|
||||
return new TransactionInput(params, depositTx, scriptProgram, getConnectedOutPoint(rawTransactionInput),
|
||||
return new TransactionInput(params,
|
||||
parentTransaction,
|
||||
scriptProgram,
|
||||
getConnectedOutPoint(rawTransactionInput),
|
||||
Coin.valueOf(rawTransactionInput.value));
|
||||
}
|
||||
|
||||
|
@ -1239,7 +1335,6 @@ public class TradeWalletService {
|
|||
checkNotNull(getConnectedOutPoint(rawTransactionInput).getConnectedOutput()).getScriptPubKey());
|
||||
}
|
||||
|
||||
|
||||
// TODO: Once we have removed legacy arbitrator from dispute domain we can remove that method as well.
|
||||
// Atm it is still used by traderSignAndFinalizeDisputedPayoutTx which is used by ArbitrationManager.
|
||||
|
||||
|
@ -1299,7 +1394,6 @@ public class TradeWalletService {
|
|||
private void signInput(Transaction transaction, TransactionInput input, int inputIndex) throws SigningException {
|
||||
checkNotNull(input.getConnectedOutput(), "input.getConnectedOutput() must not be null");
|
||||
Script scriptPubKey = input.getConnectedOutput().getScriptPubKey();
|
||||
checkNotNull(wallet);
|
||||
ECKey sigKey = input.getOutpoint().getConnectedKey(wallet);
|
||||
checkNotNull(sigKey, "signInput: sigKey must not be null. input.getOutpoint()=" +
|
||||
input.getOutpoint().toString());
|
||||
|
|
|
@ -102,6 +102,7 @@ import org.jetbrains.annotations.NotNull;
|
|||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
import static com.google.common.base.Preconditions.checkArgument;
|
||||
import static com.google.common.base.Preconditions.checkNotNull;
|
||||
import static com.google.common.base.Preconditions.checkState;
|
||||
|
||||
|
@ -278,6 +279,31 @@ public abstract class WalletService {
|
|||
// Sign tx
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
public static void signTx(Wallet wallet,
|
||||
KeyParameter aesKey,
|
||||
Transaction tx)
|
||||
throws WalletException, TransactionVerificationException {
|
||||
for (int i = 0; i < tx.getInputs().size(); i++) {
|
||||
TransactionInput input = tx.getInput(i);
|
||||
TransactionOutput connectedOutput = input.getConnectedOutput();
|
||||
if (connectedOutput == null) {
|
||||
log.error("connectedOutput is null");
|
||||
continue;
|
||||
}
|
||||
if (!connectedOutput.isMine(wallet)) {
|
||||
log.error("connectedOutput is not mine");
|
||||
continue;
|
||||
}
|
||||
|
||||
signTransactionInput(wallet, aesKey, tx, input, i);
|
||||
checkScriptSig(tx, input, i);
|
||||
}
|
||||
|
||||
checkWalletConsistency(wallet);
|
||||
verifyTransaction(tx);
|
||||
printTx("Signed Tx", tx);
|
||||
}
|
||||
|
||||
public static void signTransactionInput(Wallet wallet,
|
||||
KeyParameter aesKey,
|
||||
Transaction tx,
|
||||
|
@ -357,12 +383,13 @@ public abstract class WalletService {
|
|||
txIn.setScriptSig(ScriptBuilder.createEmpty());
|
||||
txIn.setWitness(TransactionWitness.redeemP2WPKH(txSig, key));
|
||||
} catch (ECKey.KeyIsEncryptedException e1) {
|
||||
log.error(e1.toString());
|
||||
throw e1;
|
||||
} catch (ECKey.MissingPrivateKeyException e1) {
|
||||
log.warn("No private key in keypair for input {}", index);
|
||||
}
|
||||
} else {
|
||||
// log.error("Unexpected script type.");
|
||||
log.error("Unexpected script type.");
|
||||
throw new RuntimeException("Unexpected script type.");
|
||||
}
|
||||
} else {
|
||||
|
@ -374,6 +401,23 @@ public abstract class WalletService {
|
|||
}
|
||||
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
// Dust
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
public static void verifyNonDustTxo(Transaction tx) {
|
||||
for (TransactionOutput txo : tx.getOutputs()) {
|
||||
Coin value = txo.getValue();
|
||||
// OpReturn outputs have value 0
|
||||
if (value.isPositive()) {
|
||||
checkArgument(Restrictions.isAboveDust(txo.getValue()),
|
||||
"An output value is below dust limit. Transaction=" + tx);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
// Broadcast tx
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
@ -482,7 +526,7 @@ public abstract class WalletService {
|
|||
// Balance
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
public Coin getAvailableConfirmedBalance() {
|
||||
public Coin getAvailableBalance() {
|
||||
return wallet != null ? wallet.getBalance(Wallet.BalanceType.AVAILABLE) : Coin.ZERO;
|
||||
}
|
||||
|
||||
|
@ -546,6 +590,10 @@ public abstract class WalletService {
|
|||
return getNumTxOutputsForAddress(address) == 0;
|
||||
}
|
||||
|
||||
public boolean isMine(TransactionOutput transactionOutput) {
|
||||
return transactionOutput.isMine(wallet);
|
||||
}
|
||||
|
||||
// BISQ issue #4039: Prevent dust outputs from being created.
|
||||
// Check the outputs of a proposed transaction. If any are below the dust threshold,
|
||||
// add up the dust, log the details, and return the cumulative dust amount.
|
||||
|
@ -818,11 +866,15 @@ public abstract class WalletService {
|
|||
return maybeAddTxToWallet(transaction.bitcoinSerialize(), wallet, source);
|
||||
}
|
||||
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
// bisqWalletEventListener
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
public class BisqWalletListener implements WalletCoinsReceivedEventListener, WalletCoinsSentEventListener, WalletReorganizeEventListener, TransactionConfidenceEventListener {
|
||||
public class BisqWalletListener implements WalletCoinsReceivedEventListener,
|
||||
WalletCoinsSentEventListener,
|
||||
WalletReorganizeEventListener,
|
||||
TransactionConfidenceEventListener {
|
||||
@Override
|
||||
public void onCoinsReceived(Wallet wallet, Transaction tx, Coin prevBalance, Coin newBalance) {
|
||||
notifyBalanceListeners(tx);
|
||||
|
@ -848,7 +900,7 @@ public abstract class WalletService {
|
|||
.filter(txConfidenceListener -> tx != null &&
|
||||
tx.getTxId().toString() != null &&
|
||||
txConfidenceListener != null &&
|
||||
tx.getTxId().toString().equals(txConfidenceListener.getTxID()))
|
||||
tx.getTxId().toString().equals(txConfidenceListener.getTxId()))
|
||||
.forEach(txConfidenceListener ->
|
||||
txConfidenceListener.onTransactionConfidenceChanged(tx.getConfidence()));
|
||||
}
|
||||
|
@ -859,7 +911,7 @@ public abstract class WalletService {
|
|||
if (balanceListener.getAddress() != null)
|
||||
balance = getBalanceForAddress(balanceListener.getAddress());
|
||||
else
|
||||
balance = getAvailableConfirmedBalance();
|
||||
balance = getAvailableBalance();
|
||||
|
||||
balanceListener.onBalanceChanged(balance, tx);
|
||||
}
|
||||
|
|
|
@ -60,6 +60,7 @@ import bisq.core.dao.state.model.blockchain.BaseTxOutput;
|
|||
import bisq.core.dao.state.model.blockchain.Block;
|
||||
import bisq.core.dao.state.model.blockchain.Tx;
|
||||
import bisq.core.dao.state.model.blockchain.TxOutput;
|
||||
import bisq.core.dao.state.model.blockchain.TxOutputKey;
|
||||
import bisq.core.dao.state.model.blockchain.TxType;
|
||||
import bisq.core.dao.state.model.governance.Ballot;
|
||||
import bisq.core.dao.state.model.governance.BondedRoleType;
|
||||
|
@ -649,6 +650,14 @@ public class DaoFacade implements DaoSetupService {
|
|||
return daoStateService.getUnspentTxOutputs();
|
||||
}
|
||||
|
||||
public boolean isTxOutputSpendable(TxOutputKey txOutputKey) {
|
||||
return daoStateService.isTxOutputSpendable(txOutputKey);
|
||||
}
|
||||
|
||||
public long getUnspentTxOutputValue(TxOutputKey key) {
|
||||
return daoStateService.getUnspentTxOutputValue(key);
|
||||
}
|
||||
|
||||
public int getNumTxs() {
|
||||
return daoStateService.getNumTxs();
|
||||
}
|
||||
|
@ -796,4 +805,8 @@ public class DaoFacade implements DaoSetupService {
|
|||
|
||||
return allPastParamValues;
|
||||
}
|
||||
|
||||
public boolean isParseBlockChainComplete() {
|
||||
return daoStateService.isParseBlockChainComplete();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -205,7 +205,7 @@ public class AssetService implements DaoSetupService, DaoStateListener {
|
|||
// We add the BTC inputs for the miner fee.
|
||||
Transaction txWithBtcFee = btcWalletService.completePreparedBurnBsqTx(preparedBurnFeeTx, opReturnData);
|
||||
// We sign the BSQ inputs of the final tx.
|
||||
Transaction transaction = bsqWalletService.signTx(txWithBtcFee);
|
||||
Transaction transaction = bsqWalletService.signTxAndVerifyNoDustOutputs(txWithBtcFee);
|
||||
log.info("Asset listing fee tx: " + transaction);
|
||||
return transaction;
|
||||
} catch (WalletException | TransactionVerificationException e) {
|
||||
|
|
|
@ -356,7 +356,7 @@ public class MyBlindVoteListService implements PersistedDataHost, DaoStateListen
|
|||
throws InsufficientMoneyException, WalletException, TransactionVerificationException {
|
||||
Transaction preparedTx = bsqWalletService.getPreparedBlindVoteTx(fee, stake);
|
||||
Transaction txWithBtcFee = btcWalletService.completePreparedBlindVoteTx(preparedTx, opReturnData);
|
||||
return bsqWalletService.signTx(txWithBtcFee);
|
||||
return bsqWalletService.signTxAndVerifyNoDustOutputs(txWithBtcFee);
|
||||
}
|
||||
|
||||
private void maybeRePublishMyBlindVote() {
|
||||
|
|
|
@ -104,7 +104,7 @@ public class LockupTxService {
|
|||
byte[] opReturnData = BondConsensus.getLockupOpReturnData(lockTime, lockupReason, hash);
|
||||
Transaction preparedTx = bsqWalletService.getPreparedLockupTx(lockupAmount);
|
||||
Transaction txWithBtcFee = btcWalletService.completePreparedBsqTx(preparedTx, opReturnData);
|
||||
Transaction transaction = bsqWalletService.signTx(txWithBtcFee);
|
||||
Transaction transaction = bsqWalletService.signTxAndVerifyNoDustOutputs(txWithBtcFee);
|
||||
log.info("Lockup tx: " + transaction);
|
||||
return transaction;
|
||||
}
|
||||
|
|
|
@ -104,7 +104,7 @@ public class UnlockTxService {
|
|||
TxOutput lockupTxOutput = optionalLockupTxOutput.get();
|
||||
Transaction preparedTx = bsqWalletService.getPreparedUnlockTx(lockupTxOutput);
|
||||
Transaction txWithBtcFee = btcWalletService.completePreparedBsqTx(preparedTx, null);
|
||||
Transaction transaction = bsqWalletService.signTx(txWithBtcFee);
|
||||
Transaction transaction = bsqWalletService.signTxAndVerifyNoDustOutputs(txWithBtcFee);
|
||||
log.info("Unlock tx: " + transaction);
|
||||
return transaction;
|
||||
}
|
||||
|
|
|
@ -140,7 +140,7 @@ public class ProofOfBurnService implements DaoSetupService, DaoStateListener {
|
|||
// We add the BTC inputs for the miner fee.
|
||||
Transaction txWithBtcFee = btcWalletService.completePreparedBurnBsqTx(preparedBurnFeeTx, opReturnData);
|
||||
// We sign the BSQ inputs of the final tx.
|
||||
Transaction transaction = bsqWalletService.signTx(txWithBtcFee);
|
||||
Transaction transaction = bsqWalletService.signTxAndVerifyNoDustOutputs(txWithBtcFee);
|
||||
log.info("Proof of burn tx: " + transaction);
|
||||
return transaction;
|
||||
} catch (WalletException | TransactionVerificationException e) {
|
||||
|
|
|
@ -99,7 +99,7 @@ public abstract class BaseProposalFactory<R extends Proposal> {
|
|||
Transaction txWithBtcFee = completeTx(preparedBurnFeeTx, opReturnData, proposal);
|
||||
|
||||
// We sign the BSQ inputs of the final tx.
|
||||
Transaction transaction = bsqWalletService.signTx(txWithBtcFee);
|
||||
Transaction transaction = bsqWalletService.signTxAndVerifyNoDustOutputs(txWithBtcFee);
|
||||
log.info("Proposal tx: " + transaction);
|
||||
return transaction;
|
||||
} catch (WalletException | TransactionVerificationException e) {
|
||||
|
|
|
@ -270,6 +270,6 @@ public class VoteRevealService implements DaoStateListener, DaoSetupService {
|
|||
throws InsufficientMoneyException, WalletException, TransactionVerificationException {
|
||||
Transaction preparedTx = bsqWalletService.getPreparedVoteRevealTx(stakeTxOutput);
|
||||
Transaction txWithBtcFee = btcWalletService.completePreparedVoteRevealTx(preparedTx, opReturnData);
|
||||
return bsqWalletService.signTx(txWithBtcFee);
|
||||
return bsqWalletService.signTxAndVerifyNoDustOutputs(txWithBtcFee);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -25,6 +25,7 @@ import bisq.core.dao.state.model.blockchain.PubKeyScript;
|
|||
import bisq.core.dao.state.model.blockchain.Tx;
|
||||
import bisq.core.dao.state.model.blockchain.TxOutput;
|
||||
import bisq.core.dao.state.model.blockchain.TxType;
|
||||
import bisq.core.util.JsonUtil;
|
||||
|
||||
import bisq.common.config.Config;
|
||||
import bisq.common.file.FileUtil;
|
||||
|
@ -154,9 +155,9 @@ public class ExportJsonFilesService implements DaoSetupService {
|
|||
JsonBlocks jsonBlocks = new JsonBlocks(daoState.getChainHeight(), jsonBlockList);
|
||||
|
||||
ListenableFuture<Void> future = executor.submit(() -> {
|
||||
bsqStateFileManager.writeToDisc(Utilities.objectToJson(jsonBlocks), "blocks");
|
||||
allJsonTxOutputs.forEach(jsonTxOutput -> txOutputFileManager.writeToDisc(Utilities.objectToJson(jsonTxOutput), jsonTxOutput.getId()));
|
||||
jsonTxs.forEach(jsonTx -> txFileManager.writeToDisc(Utilities.objectToJson(jsonTx), jsonTx.getId()));
|
||||
bsqStateFileManager.writeToDisc(JsonUtil.objectToJson(jsonBlocks), "blocks");
|
||||
allJsonTxOutputs.forEach(jsonTxOutput -> txOutputFileManager.writeToDisc(JsonUtil.objectToJson(jsonTxOutput), jsonTxOutput.getId()));
|
||||
jsonTxs.forEach(jsonTx -> txFileManager.writeToDisc(JsonUtil.objectToJson(jsonTx), jsonTx.getId()));
|
||||
|
||||
GcUtil.maybeReleaseMemory();
|
||||
|
||||
|
|
|
@ -21,6 +21,7 @@ import bisq.core.dao.DaoSetupService;
|
|||
import bisq.core.dao.governance.bond.BondConsensus;
|
||||
import bisq.core.dao.governance.param.Param;
|
||||
import bisq.core.dao.state.model.DaoState;
|
||||
import bisq.core.dao.state.model.blockchain.BaseTxOutput;
|
||||
import bisq.core.dao.state.model.blockchain.Block;
|
||||
import bisq.core.dao.state.model.blockchain.SpentInfo;
|
||||
import bisq.core.dao.state.model.blockchain.Tx;
|
||||
|
@ -484,6 +485,12 @@ public class DaoStateService implements DaoSetupService {
|
|||
return Optional.ofNullable(getUnspentTxOutputMap().getOrDefault(key, null));
|
||||
}
|
||||
|
||||
public long getUnspentTxOutputValue(TxOutputKey key) {
|
||||
return getUnspentTxOutput(key)
|
||||
.map(BaseTxOutput::getValue)
|
||||
.orElse(0L);
|
||||
}
|
||||
|
||||
public boolean isTxOutputSpendable(TxOutputKey key) {
|
||||
if (!isUnspent(key))
|
||||
return false;
|
||||
|
@ -492,7 +499,12 @@ public class DaoStateService implements DaoSetupService {
|
|||
// The above isUnspent call satisfies optionalTxOutput.isPresent()
|
||||
checkArgument(optionalTxOutput.isPresent(), "optionalTxOutput must be present");
|
||||
TxOutput txOutput = optionalTxOutput.get();
|
||||
return isTxOutputSpendable(txOutput);
|
||||
}
|
||||
|
||||
public boolean isTxOutputSpendable(TxOutput txOutput) {
|
||||
// OP_RETURN_OUTPUTs are actually not spendable but as we have no value on them
|
||||
// they would not be used anyway.
|
||||
switch (txOutput.getTxOutputType()) {
|
||||
case UNDEFINED_OUTPUT:
|
||||
return false;
|
||||
|
|
|
@ -39,13 +39,15 @@ import java.util.Set;
|
|||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import lombok.Value;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.Getter;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
@Slf4j
|
||||
@Value
|
||||
@Getter
|
||||
@EqualsAndHashCode
|
||||
public final class Filter implements ProtectedStoragePayload, ExpirablePayload {
|
||||
public static final long TTL = TimeUnit.DAYS.toMillis(180);
|
||||
|
||||
|
@ -101,6 +103,12 @@ public final class Filter implements ProtectedStoragePayload, ExpirablePayload {
|
|||
// added at v1.6.0
|
||||
private final boolean disableMempoolValidation;
|
||||
|
||||
// added at BsqSwap release
|
||||
private final boolean disablePowMessage;
|
||||
// Number of leading zeros for pow for BSQ swap offers. Difficulty of 8 requires 0.856 ms in average, 15 about 100 ms.
|
||||
// See ProofOfWorkTest for more info.
|
||||
private final int powDifficulty;
|
||||
|
||||
// After we have created the signature from the filter data we clone it and apply the signature
|
||||
static Filter cloneWithSig(Filter filter, String signatureAsBase64) {
|
||||
return new Filter(filter.getBannedOfferIds(),
|
||||
|
@ -130,7 +138,9 @@ public final class Filter implements ProtectedStoragePayload, ExpirablePayload {
|
|||
filter.getBannedAutoConfExplorers(),
|
||||
filter.getNodeAddressesBannedFromNetwork(),
|
||||
filter.isDisableMempoolValidation(),
|
||||
filter.isDisableApi());
|
||||
filter.isDisableApi(),
|
||||
filter.isDisablePowMessage(),
|
||||
filter.getPowDifficulty());
|
||||
}
|
||||
|
||||
// Used for signature verification as we created the sig without the signatureAsBase64 field we set it to null again
|
||||
|
@ -162,7 +172,9 @@ public final class Filter implements ProtectedStoragePayload, ExpirablePayload {
|
|||
filter.getBannedAutoConfExplorers(),
|
||||
filter.getNodeAddressesBannedFromNetwork(),
|
||||
filter.isDisableMempoolValidation(),
|
||||
filter.isDisableApi());
|
||||
filter.isDisableApi(),
|
||||
filter.isDisablePowMessage(),
|
||||
filter.getPowDifficulty());
|
||||
}
|
||||
|
||||
public Filter(List<String> bannedOfferIds,
|
||||
|
@ -189,7 +201,9 @@ public final class Filter implements ProtectedStoragePayload, ExpirablePayload {
|
|||
List<String> bannedAutoConfExplorers,
|
||||
Set<String> nodeAddressesBannedFromNetwork,
|
||||
boolean disableMempoolValidation,
|
||||
boolean disableApi) {
|
||||
boolean disableApi,
|
||||
boolean disablePowMessage,
|
||||
int powDifficulty) {
|
||||
this(bannedOfferIds,
|
||||
nodeAddressesBannedFromTrading,
|
||||
bannedPaymentAccounts,
|
||||
|
@ -217,7 +231,9 @@ public final class Filter implements ProtectedStoragePayload, ExpirablePayload {
|
|||
bannedAutoConfExplorers,
|
||||
nodeAddressesBannedFromNetwork,
|
||||
disableMempoolValidation,
|
||||
disableApi);
|
||||
disableApi,
|
||||
disablePowMessage,
|
||||
powDifficulty);
|
||||
}
|
||||
|
||||
|
||||
|
@ -253,7 +269,9 @@ public final class Filter implements ProtectedStoragePayload, ExpirablePayload {
|
|||
List<String> bannedAutoConfExplorers,
|
||||
Set<String> nodeAddressesBannedFromNetwork,
|
||||
boolean disableMempoolValidation,
|
||||
boolean disableApi) {
|
||||
boolean disableApi,
|
||||
boolean disablePowMessage,
|
||||
int powDifficulty) {
|
||||
this.bannedOfferIds = bannedOfferIds;
|
||||
this.nodeAddressesBannedFromTrading = nodeAddressesBannedFromTrading;
|
||||
this.bannedPaymentAccounts = bannedPaymentAccounts;
|
||||
|
@ -282,6 +300,8 @@ public final class Filter implements ProtectedStoragePayload, ExpirablePayload {
|
|||
this.nodeAddressesBannedFromNetwork = nodeAddressesBannedFromNetwork;
|
||||
this.disableMempoolValidation = disableMempoolValidation;
|
||||
this.disableApi = disableApi;
|
||||
this.disablePowMessage = disablePowMessage;
|
||||
this.powDifficulty = powDifficulty;
|
||||
|
||||
// ownerPubKeyBytes can be null when called from tests
|
||||
if (ownerPubKeyBytes != null) {
|
||||
|
@ -322,7 +342,9 @@ public final class Filter implements ProtectedStoragePayload, ExpirablePayload {
|
|||
.addAllBannedAutoConfExplorers(bannedAutoConfExplorers)
|
||||
.addAllNodeAddressesBannedFromNetwork(nodeAddressesBannedFromNetwork)
|
||||
.setDisableMempoolValidation(disableMempoolValidation)
|
||||
.setDisableApi(disableApi);
|
||||
.setDisableApi(disableApi)
|
||||
.setDisablePowMessage(disablePowMessage)
|
||||
.setPowDifficulty(powDifficulty);
|
||||
|
||||
Optional.ofNullable(signatureAsBase64).ifPresent(builder::setSignatureAsBase64);
|
||||
Optional.ofNullable(extraDataMap).ifPresent(builder::putAllExtraData);
|
||||
|
@ -363,7 +385,9 @@ public final class Filter implements ProtectedStoragePayload, ExpirablePayload {
|
|||
ProtoUtil.protocolStringListToList(proto.getBannedAutoConfExplorersList()),
|
||||
ProtoUtil.protocolStringListToSet(proto.getNodeAddressesBannedFromNetworkList()),
|
||||
proto.getDisableMempoolValidation(),
|
||||
proto.getDisableApi()
|
||||
proto.getDisableApi(),
|
||||
proto.getDisablePowMessage(),
|
||||
proto.getPowDifficulty()
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -409,6 +433,8 @@ public final class Filter implements ProtectedStoragePayload, ExpirablePayload {
|
|||
",\n nodeAddressesBannedFromNetwork=" + nodeAddressesBannedFromNetwork +
|
||||
",\n disableMempoolValidation=" + disableMempoolValidation +
|
||||
",\n disableApi=" + disableApi +
|
||||
",\n disablePowMessage=" + disablePowMessage +
|
||||
",\n powDifficulty=" + powDifficulty +
|
||||
"\n}";
|
||||
}
|
||||
}
|
||||
|
|
|
@ -19,6 +19,7 @@ package bisq.core.filter;
|
|||
|
||||
import bisq.core.btc.nodes.BtcNodes;
|
||||
import bisq.core.locale.Res;
|
||||
import bisq.core.offer.Offer;
|
||||
import bisq.core.payment.payload.PaymentAccountPayload;
|
||||
import bisq.core.payment.payload.PaymentMethod;
|
||||
import bisq.core.provider.ProvidersRepository;
|
||||
|
@ -36,6 +37,7 @@ import bisq.common.app.DevEnv;
|
|||
import bisq.common.app.Version;
|
||||
import bisq.common.config.Config;
|
||||
import bisq.common.config.ConfigFileEditor;
|
||||
import bisq.common.crypto.HashCashService;
|
||||
import bisq.common.crypto.KeyRing;
|
||||
|
||||
import org.bitcoinj.core.ECKey;
|
||||
|
@ -55,6 +57,7 @@ import java.nio.charset.StandardCharsets;
|
|||
|
||||
import java.math.BigInteger;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.Date;
|
||||
|
@ -62,6 +65,7 @@ import java.util.HashSet;
|
|||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.CopyOnWriteArrayList;
|
||||
import java.util.function.BiFunction;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
|
@ -70,6 +74,7 @@ import lombok.extern.slf4j.Slf4j;
|
|||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
import static com.google.common.base.Preconditions.checkArgument;
|
||||
import static com.google.common.base.Preconditions.checkNotNull;
|
||||
import static org.bitcoinj.core.Utils.HEX;
|
||||
|
||||
|
@ -82,6 +87,11 @@ public class FilterManager {
|
|||
private static final String BANNED_SEED_NODES = "bannedSeedNodes";
|
||||
private static final String BANNED_BTC_NODES = "bannedBtcNodes";
|
||||
|
||||
private final BiFunction<byte[], byte[], Boolean> challengeValidation = Arrays::equals;
|
||||
// We only require a new pow if difficulty has increased
|
||||
private final BiFunction<Integer, Integer, Boolean> difficultyValidation =
|
||||
(value, controlValue) -> value - controlValue >= 0;
|
||||
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
// Listener
|
||||
|
@ -476,6 +486,20 @@ public class FilterManager {
|
|||
.anyMatch(e -> e.equals(witnessSignerPubKeyAsHex));
|
||||
}
|
||||
|
||||
public boolean isProofOfWorkValid(Offer offer) {
|
||||
Filter filter = getFilter();
|
||||
if (filter == null) {
|
||||
return true;
|
||||
}
|
||||
checkArgument(offer.getBsqSwapOfferPayload().isPresent(),
|
||||
"Offer payload must be BsqSwapOfferPayload");
|
||||
return HashCashService.verify(offer.getBsqSwapOfferPayload().get().getProofOfWork(),
|
||||
HashCashService.getBytes(offer.getId() + offer.getOwnerNodeAddress().toString()),
|
||||
filter.getPowDifficulty(),
|
||||
challengeValidation,
|
||||
difficultyValidation);
|
||||
}
|
||||
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
// Private
|
||||
|
@ -499,13 +523,13 @@ public class FilterManager {
|
|||
|
||||
if (currentFilter != null) {
|
||||
if (currentFilter.getCreationDate() > newFilter.getCreationDate()) {
|
||||
log.warn("We received a new filter from the network but the creation date is older than the " +
|
||||
log.debug("We received a new filter from the network but the creation date is older than the " +
|
||||
"filter we have already. We ignore the new filter.");
|
||||
|
||||
addToInvalidFilters(newFilter);
|
||||
return;
|
||||
} else {
|
||||
log.warn("We received a new filter from the network and the creation date is newer than the " +
|
||||
log.debug("We received a new filter from the network and the creation date is newer than the " +
|
||||
"filter we have already. We ignore the old filter.");
|
||||
addToInvalidFilters(currentFilter);
|
||||
}
|
||||
|
|
|
@ -120,13 +120,13 @@ public class Res {
|
|||
.replace("bitcoin", baseCurrencyNameLowerCase);
|
||||
} catch (MissingResourceException e) {
|
||||
log.warn("Missing resource for key: {}", key);
|
||||
e.printStackTrace();
|
||||
if (DevEnv.isDevMode())
|
||||
if (DevEnv.isDevMode()) {
|
||||
e.printStackTrace();
|
||||
UserThread.runAfter(() -> {
|
||||
// We delay a bit to not throw while UI is not ready
|
||||
throw new RuntimeException("Missing resource for key: " + key);
|
||||
}, 1);
|
||||
|
||||
}
|
||||
return key;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -21,8 +21,8 @@ import bisq.core.locale.Res;
|
|||
import bisq.core.notifications.MobileMessage;
|
||||
import bisq.core.notifications.MobileMessageType;
|
||||
import bisq.core.notifications.MobileNotificationService;
|
||||
import bisq.core.trade.Trade;
|
||||
import bisq.core.trade.TradeManager;
|
||||
import bisq.core.trade.model.bisq_v1.Trade;
|
||||
|
||||
import bisq.common.crypto.KeyRing;
|
||||
import bisq.common.crypto.PubKeyRing;
|
||||
|
|
|
@ -26,7 +26,7 @@ import bisq.core.notifications.MobileMessageType;
|
|||
import bisq.core.notifications.MobileNotificationService;
|
||||
import bisq.core.offer.Offer;
|
||||
import bisq.core.offer.OfferBookService;
|
||||
import bisq.core.offer.OfferPayload;
|
||||
import bisq.core.offer.OfferDirection;
|
||||
import bisq.core.provider.price.MarketPrice;
|
||||
import bisq.core.provider.price.PriceFeedService;
|
||||
import bisq.core.user.User;
|
||||
|
@ -109,7 +109,7 @@ public class MarketAlerts {
|
|||
// % price get multiplied by 10000 to have 0.12% be converted to 12. For fixed price we have precision of 8 for
|
||||
// altcoins and precision of 4 for fiat.
|
||||
private String getAlertId(Offer offer) {
|
||||
double price = offer.isUseMarketBasedPrice() ? offer.getMarketPriceMargin() * 10000 : offer.getOfferPayload().getPrice();
|
||||
double price = offer.isUseMarketBasedPrice() ? offer.getMarketPriceMargin() * 10000 : offer.getFixedPrice();
|
||||
String priceString = String.valueOf((long) price);
|
||||
return offer.getId() + "|" + priceString;
|
||||
}
|
||||
|
@ -119,7 +119,7 @@ public class MarketAlerts {
|
|||
MarketPrice marketPrice = priceFeedService.getMarketPrice(currencyCode);
|
||||
Price offerPrice = offer.getPrice();
|
||||
if (marketPrice != null && offerPrice != null) {
|
||||
boolean isSellOffer = offer.getDirection() == OfferPayload.Direction.SELL;
|
||||
boolean isSellOffer = offer.getDirection() == OfferDirection.SELL;
|
||||
String shortOfferId = offer.getShortId();
|
||||
boolean isFiatCurrency = CurrencyUtil.isFiatCurrency(currencyCode);
|
||||
String alertId = getAlertId(offer);
|
||||
|
|
|
@ -24,6 +24,9 @@ import bisq.core.monetary.Price;
|
|||
import bisq.core.monetary.Volume;
|
||||
import bisq.core.offer.availability.OfferAvailabilityModel;
|
||||
import bisq.core.offer.availability.OfferAvailabilityProtocol;
|
||||
import bisq.core.offer.bisq_v1.MarketPriceNotAvailableException;
|
||||
import bisq.core.offer.bisq_v1.OfferPayload;
|
||||
import bisq.core.offer.bsq_swap.BsqSwapOfferPayload;
|
||||
import bisq.core.payment.payload.PaymentMethod;
|
||||
import bisq.core.provider.price.MarketPrice;
|
||||
import bisq.core.provider.price.PriceFeedService;
|
||||
|
@ -75,7 +78,6 @@ public class Offer implements NetworkPayload, PersistablePayload {
|
|||
// from one provider.
|
||||
private final static double PRICE_TOLERANCE = 0.01;
|
||||
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
// Enums
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
@ -94,7 +96,7 @@ public class Offer implements NetworkPayload, PersistablePayload {
|
|||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
@Getter
|
||||
private final OfferPayload offerPayload;
|
||||
private final OfferPayloadBase offerPayloadBase;
|
||||
@JsonExclude
|
||||
@Getter
|
||||
final transient private ObjectProperty<Offer.State> stateProperty = new SimpleObjectProperty<>(Offer.State.UNKNOWN);
|
||||
|
@ -119,8 +121,8 @@ public class Offer implements NetworkPayload, PersistablePayload {
|
|||
// Constructor
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
public Offer(OfferPayload offerPayload) {
|
||||
this.offerPayload = offerPayload;
|
||||
public Offer(OfferPayloadBase offerPayloadBase) {
|
||||
this.offerPayloadBase = offerPayloadBase;
|
||||
}
|
||||
|
||||
|
||||
|
@ -130,11 +132,21 @@ public class Offer implements NetworkPayload, PersistablePayload {
|
|||
|
||||
@Override
|
||||
public protobuf.Offer toProtoMessage() {
|
||||
return protobuf.Offer.newBuilder().setOfferPayload(offerPayload.toProtoMessage().getOfferPayload()).build();
|
||||
if (isBsqSwapOffer()) {
|
||||
return protobuf.Offer.newBuilder().setBsqSwapOfferPayload(((BsqSwapOfferPayload) offerPayloadBase)
|
||||
.toProtoMessage().getBsqSwapOfferPayload()).build();
|
||||
} else {
|
||||
return protobuf.Offer.newBuilder().setOfferPayload(((OfferPayload) offerPayloadBase)
|
||||
.toProtoMessage().getOfferPayload()).build();
|
||||
}
|
||||
}
|
||||
|
||||
public static Offer fromProto(protobuf.Offer proto) {
|
||||
return new Offer(OfferPayload.fromProto(proto.getOfferPayload()));
|
||||
if (proto.hasOfferPayload()) {
|
||||
return new Offer(OfferPayload.fromProto(proto.getOfferPayload()));
|
||||
} else {
|
||||
return new Offer(BsqSwapOfferPayload.fromProto(proto.getBsqSwapOfferPayload()));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
@ -166,43 +178,53 @@ public class Offer implements NetworkPayload, PersistablePayload {
|
|||
@Nullable
|
||||
public Price getPrice() {
|
||||
String currencyCode = getCurrencyCode();
|
||||
if (offerPayload.isUseMarketBasedPrice()) {
|
||||
checkNotNull(priceFeedService, "priceFeed must not be null");
|
||||
MarketPrice marketPrice = priceFeedService.getMarketPrice(currencyCode);
|
||||
if (marketPrice != null && marketPrice.isRecentExternalPriceAvailable()) {
|
||||
double factor;
|
||||
double marketPriceMargin = offerPayload.getMarketPriceMargin();
|
||||
if (CurrencyUtil.isCryptoCurrency(currencyCode)) {
|
||||
factor = getDirection() == OfferPayload.Direction.SELL ?
|
||||
1 - marketPriceMargin : 1 + marketPriceMargin;
|
||||
} else {
|
||||
factor = getDirection() == OfferPayload.Direction.BUY ?
|
||||
1 - marketPriceMargin : 1 + marketPriceMargin;
|
||||
}
|
||||
double marketPriceAsDouble = marketPrice.getPrice();
|
||||
double targetPriceAsDouble = marketPriceAsDouble * factor;
|
||||
try {
|
||||
int precision = CurrencyUtil.isCryptoCurrency(currencyCode) ?
|
||||
Altcoin.SMALLEST_UNIT_EXPONENT :
|
||||
Fiat.SMALLEST_UNIT_EXPONENT;
|
||||
double scaled = MathUtils.scaleUpByPowerOf10(targetPriceAsDouble, precision);
|
||||
final long roundedToLong = MathUtils.roundDoubleToLong(scaled);
|
||||
return Price.valueOf(currencyCode, roundedToLong);
|
||||
} catch (Exception e) {
|
||||
log.error("Exception at getPrice / parseToFiat: " + e.toString() + "\n" +
|
||||
"That case should never happen.");
|
||||
return null;
|
||||
}
|
||||
Optional<OfferPayload> optionalOfferPayload = getOfferPayload();
|
||||
if (!optionalOfferPayload.isPresent()) {
|
||||
return Price.valueOf(currencyCode, offerPayloadBase.getPrice());
|
||||
}
|
||||
|
||||
OfferPayload offerPayload = optionalOfferPayload.get();
|
||||
if (!offerPayload.isUseMarketBasedPrice()) {
|
||||
return Price.valueOf(currencyCode, offerPayloadBase.getPrice());
|
||||
}
|
||||
|
||||
checkNotNull(priceFeedService, "priceFeed must not be null");
|
||||
MarketPrice marketPrice = priceFeedService.getMarketPrice(currencyCode);
|
||||
if (marketPrice != null && marketPrice.isRecentExternalPriceAvailable()) {
|
||||
double factor;
|
||||
double marketPriceMargin = offerPayload.getMarketPriceMargin();
|
||||
if (CurrencyUtil.isCryptoCurrency(currencyCode)) {
|
||||
factor = getDirection() == OfferDirection.SELL ?
|
||||
1 - marketPriceMargin : 1 + marketPriceMargin;
|
||||
} else {
|
||||
log.trace("We don't have a market price. " +
|
||||
"That case could only happen if you don't have a price feed.");
|
||||
factor = getDirection() == OfferDirection.BUY ?
|
||||
1 - marketPriceMargin : 1 + marketPriceMargin;
|
||||
}
|
||||
double marketPriceAsDouble = marketPrice.getPrice();
|
||||
double targetPriceAsDouble = marketPriceAsDouble * factor;
|
||||
try {
|
||||
int precision = CurrencyUtil.isCryptoCurrency(currencyCode) ?
|
||||
Altcoin.SMALLEST_UNIT_EXPONENT :
|
||||
Fiat.SMALLEST_UNIT_EXPONENT;
|
||||
double scaled = MathUtils.scaleUpByPowerOf10(targetPriceAsDouble, precision);
|
||||
final long roundedToLong = MathUtils.roundDoubleToLong(scaled);
|
||||
return Price.valueOf(currencyCode, roundedToLong);
|
||||
} catch (Exception e) {
|
||||
log.error("Exception at getPrice / parseToFiat: " + e + "\n" +
|
||||
"That case should never happen.");
|
||||
return null;
|
||||
}
|
||||
} else {
|
||||
return Price.valueOf(currencyCode, offerPayload.getPrice());
|
||||
log.trace("We don't have a market price. " +
|
||||
"That case could only happen if you don't have a price feed.");
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public long getFixedPrice() {
|
||||
return offerPayloadBase.getPrice();
|
||||
}
|
||||
|
||||
public void checkTradePriceTolerance(long takersTradePrice) throws TradePriceOutOfToleranceException,
|
||||
MarketPriceNotAvailableException, IllegalArgumentException {
|
||||
Price tradePrice = Price.valueOf(getCurrencyCode(), takersTradePrice);
|
||||
|
@ -234,17 +256,16 @@ public class Offer implements NetworkPayload, PersistablePayload {
|
|||
@Nullable
|
||||
public Volume getVolumeByAmount(Coin amount) {
|
||||
Price price = getPrice();
|
||||
if (price != null && amount != null) {
|
||||
Volume volumeByAmount = price.getVolumeByAmount(amount);
|
||||
if (offerPayload.getPaymentMethodId().equals(PaymentMethod.HAL_CASH_ID))
|
||||
volumeByAmount = VolumeUtil.getAdjustedVolumeForHalCash(volumeByAmount);
|
||||
else if (CurrencyUtil.isFiatCurrency(offerPayload.getCurrencyCode()))
|
||||
volumeByAmount = VolumeUtil.getRoundedFiatVolume(volumeByAmount);
|
||||
|
||||
return volumeByAmount;
|
||||
} else {
|
||||
if (price == null || amount == null) {
|
||||
return null;
|
||||
}
|
||||
Volume volumeByAmount = price.getVolumeByAmount(amount);
|
||||
if (offerPayloadBase.getPaymentMethodId().equals(PaymentMethod.HAL_CASH_ID))
|
||||
volumeByAmount = VolumeUtil.getAdjustedVolumeForHalCash(volumeByAmount);
|
||||
else if (CurrencyUtil.isFiatCurrency(offerPayloadBase.getCurrencyCode()))
|
||||
volumeByAmount = VolumeUtil.getRoundedFiatVolume(volumeByAmount);
|
||||
|
||||
return volumeByAmount;
|
||||
}
|
||||
|
||||
public void resetState() {
|
||||
|
@ -265,7 +286,7 @@ public class Offer implements NetworkPayload, PersistablePayload {
|
|||
}
|
||||
|
||||
public void setOfferFeePaymentTxId(String offerFeePaymentTxID) {
|
||||
offerPayload.setOfferFeePaymentTxId(offerFeePaymentTxID);
|
||||
getOfferPayload().ifPresent(p -> p.setOfferFeePaymentTxId(offerFeePaymentTxID));
|
||||
}
|
||||
|
||||
public void setErrorMessage(String errorMessage) {
|
||||
|
@ -279,52 +300,52 @@ public class Offer implements NetworkPayload, PersistablePayload {
|
|||
|
||||
// converted payload properties
|
||||
public Coin getTxFee() {
|
||||
return Coin.valueOf(offerPayload.getTxFee());
|
||||
return Coin.valueOf(getOfferPayload().map(OfferPayload::getTxFee).orElse(0L));
|
||||
}
|
||||
|
||||
public Coin getMakerFee() {
|
||||
return Coin.valueOf(offerPayload.getMakerFee());
|
||||
return getOfferPayload().map(OfferPayload::getMakerFee).map(Coin::valueOf).orElse(Coin.ZERO);
|
||||
}
|
||||
|
||||
public boolean isCurrencyForMakerFeeBtc() {
|
||||
return offerPayload.isCurrencyForMakerFeeBtc();
|
||||
return getOfferPayload().map(OfferPayload::isCurrencyForMakerFeeBtc).orElse(false);
|
||||
}
|
||||
|
||||
public Coin getBuyerSecurityDeposit() {
|
||||
return Coin.valueOf(offerPayload.getBuyerSecurityDeposit());
|
||||
return Coin.valueOf(getOfferPayload().map(OfferPayload::getBuyerSecurityDeposit).orElse(0L));
|
||||
}
|
||||
|
||||
public Coin getSellerSecurityDeposit() {
|
||||
return Coin.valueOf(offerPayload.getSellerSecurityDeposit());
|
||||
return Coin.valueOf(getOfferPayload().map(OfferPayload::getSellerSecurityDeposit).orElse(0L));
|
||||
}
|
||||
|
||||
public Coin getMaxTradeLimit() {
|
||||
return Coin.valueOf(offerPayload.getMaxTradeLimit());
|
||||
return getOfferPayload().map(OfferPayload::getMaxTradeLimit).map(Coin::valueOf).orElse(Coin.ZERO);
|
||||
}
|
||||
|
||||
public Coin getAmount() {
|
||||
return Coin.valueOf(offerPayload.getAmount());
|
||||
return Coin.valueOf(offerPayloadBase.getAmount());
|
||||
}
|
||||
|
||||
public Coin getMinAmount() {
|
||||
return Coin.valueOf(offerPayload.getMinAmount());
|
||||
return Coin.valueOf(offerPayloadBase.getMinAmount());
|
||||
}
|
||||
|
||||
public boolean isRange() {
|
||||
return offerPayload.getAmount() != offerPayload.getMinAmount();
|
||||
return offerPayloadBase.getAmount() != offerPayloadBase.getMinAmount();
|
||||
}
|
||||
|
||||
public Date getDate() {
|
||||
return new Date(offerPayload.getDate());
|
||||
return new Date(offerPayloadBase.getDate());
|
||||
}
|
||||
|
||||
public PaymentMethod getPaymentMethod() {
|
||||
return PaymentMethod.getPaymentMethodById(offerPayload.getPaymentMethodId());
|
||||
return PaymentMethod.getPaymentMethodById(offerPayloadBase.getPaymentMethodId());
|
||||
}
|
||||
|
||||
// utils
|
||||
public String getShortId() {
|
||||
return Utilities.getShortId(offerPayload.getId());
|
||||
return Utilities.getShortId(offerPayloadBase.getId());
|
||||
}
|
||||
|
||||
@Nullable
|
||||
|
@ -338,18 +359,17 @@ public class Offer implements NetworkPayload, PersistablePayload {
|
|||
}
|
||||
|
||||
public boolean isBuyOffer() {
|
||||
return getDirection() == OfferPayload.Direction.BUY;
|
||||
return getDirection() == OfferDirection.BUY;
|
||||
}
|
||||
|
||||
public OfferPayload.Direction getMirroredDirection() {
|
||||
return getDirection() == OfferPayload.Direction.BUY ? OfferPayload.Direction.SELL : OfferPayload.Direction.BUY;
|
||||
public OfferDirection getMirroredDirection() {
|
||||
return getDirection() == OfferDirection.BUY ? OfferDirection.SELL : OfferDirection.BUY;
|
||||
}
|
||||
|
||||
public boolean isMyOffer(KeyRing keyRing) {
|
||||
return getPubKeyRing().equals(keyRing.getPubKeyRing());
|
||||
}
|
||||
|
||||
|
||||
public Optional<String> getAccountAgeWitnessHashAsHex() {
|
||||
Map<String, String> extraDataMap = getExtraDataMap();
|
||||
if (extraDataMap != null && extraDataMap.containsKey(OfferPayload.ACCOUNT_AGE_WITNESS_HASH))
|
||||
|
@ -400,32 +420,32 @@ public class Offer implements NetworkPayload, PersistablePayload {
|
|||
// Delegate Getter (boilerplate code generated via IntelliJ generate delegate feature)
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
public OfferPayload.Direction getDirection() {
|
||||
return offerPayload.getDirection();
|
||||
public OfferDirection getDirection() {
|
||||
return offerPayloadBase.getDirection();
|
||||
}
|
||||
|
||||
public String getId() {
|
||||
return offerPayload.getId();
|
||||
return offerPayloadBase.getId();
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public List<String> getAcceptedBankIds() {
|
||||
return offerPayload.getAcceptedBankIds();
|
||||
return getOfferPayload().map(OfferPayload::getAcceptedBankIds).orElse(null);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public String getBankId() {
|
||||
return offerPayload.getBankId();
|
||||
return getOfferPayload().map(OfferPayload::getBankId).orElse(null);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public List<String> getAcceptedCountryCodes() {
|
||||
return offerPayload.getAcceptedCountryCodes();
|
||||
return getOfferPayload().map(OfferPayload::getAcceptedCountryCodes).orElse(null);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public String getCountryCode() {
|
||||
return offerPayload.getCountryCode();
|
||||
return getOfferPayload().map(OfferPayload::getCountryCode).orElse(null);
|
||||
}
|
||||
|
||||
public String getCurrencyCode() {
|
||||
|
@ -433,89 +453,84 @@ public class Offer implements NetworkPayload, PersistablePayload {
|
|||
return currencyCode;
|
||||
}
|
||||
|
||||
currencyCode = offerPayload.getBaseCurrencyCode().equals("BTC") ?
|
||||
offerPayload.getCounterCurrencyCode() :
|
||||
offerPayload.getBaseCurrencyCode();
|
||||
currencyCode = getBaseCurrencyCode().equals("BTC") ?
|
||||
getCounterCurrencyCode() :
|
||||
getBaseCurrencyCode();
|
||||
return currencyCode;
|
||||
}
|
||||
|
||||
public String getCounterCurrencyCode() {
|
||||
return offerPayloadBase.getCounterCurrencyCode();
|
||||
}
|
||||
|
||||
public String getBaseCurrencyCode() {
|
||||
return offerPayloadBase.getBaseCurrencyCode();
|
||||
}
|
||||
|
||||
public String getPaymentMethodId() {
|
||||
return offerPayloadBase.getPaymentMethodId();
|
||||
}
|
||||
|
||||
public long getProtocolVersion() {
|
||||
return offerPayload.getProtocolVersion();
|
||||
return offerPayloadBase.getProtocolVersion();
|
||||
}
|
||||
|
||||
public boolean isUseMarketBasedPrice() {
|
||||
return offerPayload.isUseMarketBasedPrice();
|
||||
return getOfferPayload().map(OfferPayload::isUseMarketBasedPrice).orElse(false);
|
||||
}
|
||||
|
||||
public double getMarketPriceMargin() {
|
||||
return offerPayload.getMarketPriceMargin();
|
||||
return getOfferPayload().map(OfferPayload::getMarketPriceMargin).orElse(0D);
|
||||
}
|
||||
|
||||
public NodeAddress getMakerNodeAddress() {
|
||||
return offerPayload.getOwnerNodeAddress();
|
||||
return offerPayloadBase.getOwnerNodeAddress();
|
||||
}
|
||||
|
||||
public PubKeyRing getPubKeyRing() {
|
||||
return offerPayload.getPubKeyRing();
|
||||
return offerPayloadBase.getPubKeyRing();
|
||||
}
|
||||
|
||||
public String getMakerPaymentAccountId() {
|
||||
return offerPayload.getMakerPaymentAccountId();
|
||||
return offerPayloadBase.getMakerPaymentAccountId();
|
||||
}
|
||||
|
||||
public String getOfferFeePaymentTxId() {
|
||||
return offerPayload.getOfferFeePaymentTxId();
|
||||
return getOfferPayload().map(OfferPayload::getOfferFeePaymentTxId).orElse(null);
|
||||
}
|
||||
|
||||
public String getVersionNr() {
|
||||
return offerPayload.getVersionNr();
|
||||
return offerPayloadBase.getVersionNr();
|
||||
}
|
||||
|
||||
public long getMaxTradePeriod() {
|
||||
return offerPayload.getMaxTradePeriod();
|
||||
return getOfferPayload().map(OfferPayload::getMaxTradePeriod).orElse(0L);
|
||||
}
|
||||
|
||||
public NodeAddress getOwnerNodeAddress() {
|
||||
return offerPayload.getOwnerNodeAddress();
|
||||
return offerPayloadBase.getOwnerNodeAddress();
|
||||
}
|
||||
|
||||
// Yet unused
|
||||
public PublicKey getOwnerPubKey() {
|
||||
return offerPayload.getOwnerPubKey();
|
||||
return offerPayloadBase.getOwnerPubKey();
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public Map<String, String> getExtraDataMap() {
|
||||
return offerPayload.getExtraDataMap();
|
||||
return offerPayloadBase.getExtraDataMap();
|
||||
}
|
||||
|
||||
public boolean isUseAutoClose() {
|
||||
return offerPayload.isUseAutoClose();
|
||||
}
|
||||
|
||||
public long getBlockHeightAtOfferCreation() {
|
||||
return offerPayload.getBlockHeightAtOfferCreation();
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public String getHashOfChallenge() {
|
||||
return offerPayload.getHashOfChallenge();
|
||||
}
|
||||
|
||||
public boolean isPrivateOffer() {
|
||||
return offerPayload.isPrivateOffer();
|
||||
}
|
||||
|
||||
public long getUpperClosePrice() {
|
||||
return offerPayload.getUpperClosePrice();
|
||||
}
|
||||
|
||||
public long getLowerClosePrice() {
|
||||
return offerPayload.getLowerClosePrice();
|
||||
return getOfferPayload().map(OfferPayload::isUseAutoClose).orElse(false);
|
||||
}
|
||||
|
||||
public boolean isUseReOpenAfterAutoClose() {
|
||||
return offerPayload.isUseReOpenAfterAutoClose();
|
||||
return getOfferPayload().map(OfferPayload::isUseReOpenAfterAutoClose).orElse(false);
|
||||
}
|
||||
|
||||
public boolean isBsqSwapOffer() {
|
||||
return getOfferPayloadBase() instanceof BsqSwapOfferPayload;
|
||||
}
|
||||
|
||||
public boolean isXmrAutoConf() {
|
||||
|
@ -533,6 +548,24 @@ public class Offer implements NetworkPayload, PersistablePayload {
|
|||
return getCurrencyCode().equals("XMR");
|
||||
}
|
||||
|
||||
public Optional<OfferPayload> getOfferPayload() {
|
||||
if (offerPayloadBase instanceof OfferPayload) {
|
||||
return Optional.of((OfferPayload) offerPayloadBase);
|
||||
}
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
public Optional<BsqSwapOfferPayload> getBsqSwapOfferPayload() {
|
||||
if (offerPayloadBase instanceof BsqSwapOfferPayload) {
|
||||
return Optional.of((BsqSwapOfferPayload) offerPayloadBase);
|
||||
}
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
public byte[] getOfferPayloadHash() {
|
||||
return offerPayloadBase.getHash();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
|
@ -540,7 +573,8 @@ public class Offer implements NetworkPayload, PersistablePayload {
|
|||
|
||||
Offer offer = (Offer) o;
|
||||
|
||||
if (offerPayload != null ? !offerPayload.equals(offer.offerPayload) : offer.offerPayload != null) return false;
|
||||
if (offerPayloadBase != null ? !offerPayloadBase.equals(offer.offerPayloadBase) : offer.offerPayloadBase != null)
|
||||
return false;
|
||||
//noinspection SimplifiableIfStatement
|
||||
if (getState() != offer.getState()) return false;
|
||||
return !(getErrorMessage() != null ? !getErrorMessage().equals(offer.getErrorMessage()) : offer.getErrorMessage() != null);
|
||||
|
@ -549,7 +583,7 @@ public class Offer implements NetworkPayload, PersistablePayload {
|
|||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
int result = offerPayload != null ? offerPayload.hashCode() : 0;
|
||||
int result = offerPayloadBase != null ? offerPayloadBase.hashCode() : 0;
|
||||
result = 31 * result + (getState() != null ? getState().hashCode() : 0);
|
||||
result = 31 * result + (getErrorMessage() != null ? getErrorMessage().hashCode() : 0);
|
||||
return result;
|
||||
|
@ -560,7 +594,7 @@ public class Offer implements NetworkPayload, PersistablePayload {
|
|||
return "Offer{" +
|
||||
"getErrorMessage()='" + getErrorMessage() + '\'' +
|
||||
", state=" + getState() +
|
||||
", offerPayload=" + offerPayload +
|
||||
", offerPayloadBase=" + offerPayloadBase +
|
||||
'}';
|
||||
}
|
||||
}
|
||||
|
|
|
@ -20,6 +20,7 @@ package bisq.core.offer;
|
|||
import bisq.core.filter.FilterManager;
|
||||
import bisq.core.locale.Res;
|
||||
import bisq.core.provider.price.PriceFeedService;
|
||||
import bisq.core.util.JsonUtil;
|
||||
|
||||
import bisq.network.p2p.BootstrapListener;
|
||||
import bisq.network.p2p.P2PService;
|
||||
|
@ -31,7 +32,6 @@ import bisq.common.config.Config;
|
|||
import bisq.common.file.JsonFileManager;
|
||||
import bisq.common.handlers.ErrorMessageHandler;
|
||||
import bisq.common.handlers.ResultHandler;
|
||||
import bisq.common.util.Utilities;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import javax.inject.Named;
|
||||
|
@ -87,9 +87,9 @@ public class OfferBookService {
|
|||
@Override
|
||||
public void onAdded(Collection<ProtectedStorageEntry> protectedStorageEntries) {
|
||||
protectedStorageEntries.forEach(protectedStorageEntry -> offerBookChangedListeners.forEach(listener -> {
|
||||
if (protectedStorageEntry.getProtectedStoragePayload() instanceof OfferPayload) {
|
||||
OfferPayload offerPayload = (OfferPayload) protectedStorageEntry.getProtectedStoragePayload();
|
||||
Offer offer = new Offer(offerPayload);
|
||||
if (protectedStorageEntry.getProtectedStoragePayload() instanceof OfferPayloadBase) {
|
||||
OfferPayloadBase offerPayloadBase = (OfferPayloadBase) protectedStorageEntry.getProtectedStoragePayload();
|
||||
Offer offer = new Offer(offerPayloadBase);
|
||||
offer.setPriceFeedService(priceFeedService);
|
||||
listener.onAdded(offer);
|
||||
}
|
||||
|
@ -99,9 +99,9 @@ public class OfferBookService {
|
|||
@Override
|
||||
public void onRemoved(Collection<ProtectedStorageEntry> protectedStorageEntries) {
|
||||
protectedStorageEntries.forEach(protectedStorageEntry -> offerBookChangedListeners.forEach(listener -> {
|
||||
if (protectedStorageEntry.getProtectedStoragePayload() instanceof OfferPayload) {
|
||||
OfferPayload offerPayload = (OfferPayload) protectedStorageEntry.getProtectedStoragePayload();
|
||||
Offer offer = new Offer(offerPayload);
|
||||
if (protectedStorageEntry.getProtectedStoragePayload() instanceof OfferPayloadBase) {
|
||||
OfferPayloadBase offerPayloadBase = (OfferPayloadBase) protectedStorageEntry.getProtectedStoragePayload();
|
||||
Offer offer = new Offer(offerPayloadBase);
|
||||
offer.setPriceFeedService(priceFeedService);
|
||||
listener.onRemoved(offer);
|
||||
}
|
||||
|
@ -141,7 +141,7 @@ public class OfferBookService {
|
|||
return;
|
||||
}
|
||||
|
||||
boolean result = p2PService.addProtectedStorageEntry(offer.getOfferPayload());
|
||||
boolean result = p2PService.addProtectedStorageEntry(offer.getOfferPayloadBase());
|
||||
if (result) {
|
||||
resultHandler.handleResult();
|
||||
} else {
|
||||
|
@ -149,7 +149,7 @@ public class OfferBookService {
|
|||
}
|
||||
}
|
||||
|
||||
public void refreshTTL(OfferPayload offerPayload,
|
||||
public void refreshTTL(OfferPayloadBase offerPayloadBase,
|
||||
ResultHandler resultHandler,
|
||||
ErrorMessageHandler errorMessageHandler) {
|
||||
if (filterManager.requireUpdateToNewVersionForTrading()) {
|
||||
|
@ -157,7 +157,7 @@ public class OfferBookService {
|
|||
return;
|
||||
}
|
||||
|
||||
boolean result = p2PService.refreshTTL(offerPayload);
|
||||
boolean result = p2PService.refreshTTL(offerPayloadBase);
|
||||
if (result) {
|
||||
resultHandler.handleResult();
|
||||
} else {
|
||||
|
@ -171,16 +171,16 @@ public class OfferBookService {
|
|||
addOffer(offer, resultHandler, errorMessageHandler);
|
||||
}
|
||||
|
||||
public void deactivateOffer(OfferPayload offerPayload,
|
||||
public void deactivateOffer(OfferPayloadBase offerPayloadBase,
|
||||
@Nullable ResultHandler resultHandler,
|
||||
@Nullable ErrorMessageHandler errorMessageHandler) {
|
||||
removeOffer(offerPayload, resultHandler, errorMessageHandler);
|
||||
removeOffer(offerPayloadBase, resultHandler, errorMessageHandler);
|
||||
}
|
||||
|
||||
public void removeOffer(OfferPayload offerPayload,
|
||||
public void removeOffer(OfferPayloadBase offerPayloadBase,
|
||||
@Nullable ResultHandler resultHandler,
|
||||
@Nullable ErrorMessageHandler errorMessageHandler) {
|
||||
if (p2PService.removeData(offerPayload)) {
|
||||
if (p2PService.removeData(offerPayloadBase)) {
|
||||
if (resultHandler != null)
|
||||
resultHandler.handleResult();
|
||||
} else {
|
||||
|
@ -191,18 +191,18 @@ public class OfferBookService {
|
|||
|
||||
public List<Offer> getOffers() {
|
||||
return p2PService.getDataMap().values().stream()
|
||||
.filter(data -> data.getProtectedStoragePayload() instanceof OfferPayload)
|
||||
.filter(data -> data.getProtectedStoragePayload() instanceof OfferPayloadBase)
|
||||
.map(data -> {
|
||||
OfferPayload offerPayload = (OfferPayload) data.getProtectedStoragePayload();
|
||||
Offer offer = new Offer(offerPayload);
|
||||
OfferPayloadBase offerPayloadBase = (OfferPayloadBase) data.getProtectedStoragePayload();
|
||||
Offer offer = new Offer(offerPayloadBase);
|
||||
offer.setPriceFeedService(priceFeedService);
|
||||
return offer;
|
||||
})
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
public void removeOfferAtShutDown(OfferPayload offerPayload) {
|
||||
removeOffer(offerPayload, null, null);
|
||||
public void removeOfferAtShutDown(OfferPayloadBase offerPayloadBase) {
|
||||
removeOffer(offerPayloadBase, null, null);
|
||||
}
|
||||
|
||||
public boolean isBootstrapped() {
|
||||
|
@ -243,6 +243,6 @@ public class OfferBookService {
|
|||
})
|
||||
.filter(Objects::nonNull)
|
||||
.collect(Collectors.toList());
|
||||
jsonFileManager.writeToDiscThreaded(Utilities.objectToJson(offerForJsonList), "offers_statistics");
|
||||
jsonFileManager.writeToDiscThreaded(JsonUtil.objectToJson(offerForJsonList), "offers_statistics");
|
||||
}
|
||||
}
|
||||
|
|
33
core/src/main/java/bisq/core/offer/OfferDirection.java
Normal file
33
core/src/main/java/bisq/core/offer/OfferDirection.java
Normal 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());
|
||||
}
|
||||
}
|
|
@ -25,6 +25,7 @@ import bisq.core.payment.PaymentAccountUtil;
|
|||
import bisq.core.user.Preferences;
|
||||
import bisq.core.user.User;
|
||||
|
||||
import bisq.common.app.DevEnv;
|
||||
import bisq.common.app.Version;
|
||||
|
||||
import org.bitcoinj.core.Coin;
|
||||
|
@ -43,7 +44,7 @@ import lombok.extern.slf4j.Slf4j;
|
|||
|
||||
@Slf4j
|
||||
@Singleton
|
||||
public class OfferFilter {
|
||||
public class OfferFilterService {
|
||||
private final User user;
|
||||
private final Preferences preferences;
|
||||
private final FilterManager filterManager;
|
||||
|
@ -52,10 +53,10 @@ public class OfferFilter {
|
|||
private final Map<String, Boolean> myInsufficientTradeLimitCache = new HashMap<>();
|
||||
|
||||
@Inject
|
||||
public OfferFilter(User user,
|
||||
Preferences preferences,
|
||||
FilterManager filterManager,
|
||||
AccountAgeWitnessService accountAgeWitnessService) {
|
||||
public OfferFilterService(User user,
|
||||
Preferences preferences,
|
||||
FilterManager filterManager,
|
||||
AccountAgeWitnessService accountAgeWitnessService) {
|
||||
this.user = user;
|
||||
this.preferences = preferences;
|
||||
this.filterManager = filterManager;
|
||||
|
@ -80,7 +81,8 @@ public class OfferFilter {
|
|||
IS_NODE_ADDRESS_BANNED,
|
||||
REQUIRE_UPDATE_TO_NEW_VERSION,
|
||||
IS_INSUFFICIENT_COUNTERPARTY_TRADE_LIMIT,
|
||||
IS_MY_INSUFFICIENT_TRADE_LIMIT;
|
||||
IS_MY_INSUFFICIENT_TRADE_LIMIT,
|
||||
HIDE_BSQ_SWAPS_DUE_DAO_DEACTIVATED;
|
||||
|
||||
@Getter
|
||||
private final boolean isValid;
|
||||
|
@ -128,6 +130,9 @@ public class OfferFilter {
|
|||
if (isMyInsufficientTradeLimit(offer)) {
|
||||
return Result.IS_MY_INSUFFICIENT_TRADE_LIMIT;
|
||||
}
|
||||
if (!DevEnv.isDaoActivated() && offer.isBsqSwapOffer()) {
|
||||
return Result.HIDE_BSQ_SWAPS_DUE_DAO_DEACTIVATED;
|
||||
}
|
||||
|
||||
return Result.VALID;
|
||||
}
|
|
@ -40,7 +40,7 @@ import javax.annotation.Nullable;
|
|||
public class OfferForJson {
|
||||
private static final Logger log = LoggerFactory.getLogger(OfferForJson.class);
|
||||
|
||||
public final OfferPayload.Direction direction;
|
||||
public final OfferDirection direction;
|
||||
public final String currencyCode;
|
||||
public final long minAmount;
|
||||
public final long amount;
|
||||
|
@ -53,7 +53,7 @@ public class OfferForJson {
|
|||
|
||||
// primaryMarket fields are based on industry standard where primaryMarket is always in the focus (in the app BTC is always in the focus - will be changed in a larger refactoring once)
|
||||
public String currencyPair;
|
||||
public OfferPayload.Direction primaryMarketDirection;
|
||||
public OfferDirection primaryMarketDirection;
|
||||
|
||||
public String priceDisplayString;
|
||||
public String primaryMarketAmountDisplayString;
|
||||
|
@ -75,7 +75,7 @@ public class OfferForJson {
|
|||
transient private final MonetaryFormat coinFormat = MonetaryFormat.BTC;
|
||||
|
||||
|
||||
public OfferForJson(OfferPayload.Direction direction,
|
||||
public OfferForJson(OfferDirection direction,
|
||||
String currencyCode,
|
||||
Coin minAmount,
|
||||
Coin amount,
|
||||
|
@ -104,7 +104,7 @@ public class OfferForJson {
|
|||
try {
|
||||
final Price price = getPrice();
|
||||
if (CurrencyUtil.isCryptoCurrency(currencyCode)) {
|
||||
primaryMarketDirection = direction == OfferPayload.Direction.BUY ? OfferPayload.Direction.SELL : OfferPayload.Direction.BUY;
|
||||
primaryMarketDirection = direction == OfferDirection.BUY ? OfferDirection.SELL : OfferDirection.BUY;
|
||||
currencyPair = currencyCode + "/" + Res.getBaseCurrencyCode();
|
||||
|
||||
// int precision = 8;
|
||||
|
|
147
core/src/main/java/bisq/core/offer/OfferPayloadBase.java
Normal file
147
core/src/main/java/bisq/core/offer/OfferPayloadBase.java
Normal 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}";
|
||||
}
|
||||
}
|
|
@ -17,6 +17,8 @@
|
|||
|
||||
package bisq.core.offer;
|
||||
|
||||
import bisq.core.offer.bisq_v1.OfferPayload;
|
||||
|
||||
import bisq.common.app.Capabilities;
|
||||
import bisq.common.app.Capability;
|
||||
import bisq.common.config.Config;
|
||||
|
@ -40,7 +42,7 @@ public class OfferRestrictions {
|
|||
public static Coin TOLERATED_SMALL_TRADE_AMOUNT = Coin.parseCoin("0.01");
|
||||
|
||||
static boolean hasOfferMandatoryCapability(Offer offer, Capability mandatoryCapability) {
|
||||
Map<String, String> extraDataMap = offer.getOfferPayload().getExtraDataMap();
|
||||
Map<String, String> extraDataMap = offer.getExtraDataMap();
|
||||
if (extraDataMap != null && extraDataMap.containsKey(OfferPayload.CAPABILITIES)) {
|
||||
String commaSeparatedOrdinals = extraDataMap.get(OfferPayload.CAPABILITIES);
|
||||
Capabilities capabilities = Capabilities.fromStringList(commaSeparatedOrdinals);
|
||||
|
|
|
@ -25,9 +25,12 @@ import bisq.core.locale.CurrencyUtil;
|
|||
import bisq.core.locale.Res;
|
||||
import bisq.core.monetary.Price;
|
||||
import bisq.core.monetary.Volume;
|
||||
import bisq.core.offer.bisq_v1.MutableOfferPayloadFields;
|
||||
import bisq.core.offer.bisq_v1.OfferPayload;
|
||||
import bisq.core.payment.CashByMailAccount;
|
||||
import bisq.core.payment.F2FAccount;
|
||||
import bisq.core.payment.PaymentAccount;
|
||||
import bisq.core.payment.payload.PaymentMethod;
|
||||
import bisq.core.provider.fee.FeeService;
|
||||
import bisq.core.provider.price.MarketPrice;
|
||||
import bisq.core.provider.price.PriceFeedService;
|
||||
|
@ -42,8 +45,10 @@ import bisq.core.util.coin.CoinUtil;
|
|||
import bisq.network.p2p.P2PService;
|
||||
|
||||
import bisq.common.app.Capabilities;
|
||||
import bisq.common.app.Version;
|
||||
import bisq.common.util.MathUtils;
|
||||
import bisq.common.util.Tuple2;
|
||||
import bisq.common.util.Utilities;
|
||||
|
||||
import org.bitcoinj.core.Coin;
|
||||
import org.bitcoinj.core.Transaction;
|
||||
|
@ -57,6 +62,7 @@ import javax.inject.Singleton;
|
|||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.UUID;
|
||||
import java.util.function.Predicate;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
@ -69,7 +75,7 @@ import static bisq.core.btc.wallet.Restrictions.getMaxBuyerSecurityDepositAsPerc
|
|||
import static bisq.core.btc.wallet.Restrictions.getMinBuyerSecurityDepositAsPercent;
|
||||
import static bisq.core.btc.wallet.Restrictions.getMinNonDustOutput;
|
||||
import static bisq.core.btc.wallet.Restrictions.isDust;
|
||||
import static bisq.core.offer.OfferPayload.*;
|
||||
import static bisq.core.offer.bisq_v1.OfferPayload.*;
|
||||
import static com.google.common.base.Preconditions.checkArgument;
|
||||
import static com.google.common.base.Preconditions.checkNotNull;
|
||||
import static java.lang.String.format;
|
||||
|
@ -112,6 +118,40 @@ public class OfferUtil {
|
|||
this.tradeStatisticsManager = tradeStatisticsManager;
|
||||
}
|
||||
|
||||
public static String getRandomOfferId() {
|
||||
return Utilities.getRandomPrefix(5, 8) + "-" +
|
||||
UUID.randomUUID() + "-" +
|
||||
getStrippedVersion();
|
||||
}
|
||||
|
||||
public static String getStrippedVersion() {
|
||||
return Version.VERSION.replace(".", "");
|
||||
}
|
||||
|
||||
// We add a counter at the end of the offer id signalling the number of times that offer has
|
||||
// been mutated ether due edit or due pow adjustments.
|
||||
public static String getOfferIdWithMutationCounter(String id) {
|
||||
String[] split = id.split("-");
|
||||
String base = id;
|
||||
int counter = 0;
|
||||
if (split.length > 7) {
|
||||
String counterString = split[7];
|
||||
int endIndex = id.length() - counterString.length() - 1;
|
||||
base = id.substring(0, endIndex);
|
||||
try {
|
||||
counter = Integer.parseInt(counterString);
|
||||
} catch (Exception ignore) {
|
||||
}
|
||||
}
|
||||
counter++;
|
||||
return base + "-" + counter;
|
||||
}
|
||||
|
||||
public static String getVersionFromId(String id) {
|
||||
String[] split = id.split("-");
|
||||
return split[6];
|
||||
}
|
||||
|
||||
public void maybeSetFeePaymentCurrencyPreference(String feeCurrencyCode) {
|
||||
if (!feeCurrencyCode.isEmpty()) {
|
||||
if (!isValidFeePaymentCurrencyCode.test(feeCurrencyCode))
|
||||
|
@ -132,13 +172,13 @@ public class OfferUtil {
|
|||
* @return {@code true} for an offer to buy BTC from the taker, {@code false} for an
|
||||
* offer to sell BTC to the taker
|
||||
*/
|
||||
public boolean isBuyOffer(Direction direction) {
|
||||
return direction == Direction.BUY;
|
||||
public boolean isBuyOffer(OfferDirection direction) {
|
||||
return direction == OfferDirection.BUY;
|
||||
}
|
||||
|
||||
public long getMaxTradeLimit(PaymentAccount paymentAccount,
|
||||
String currencyCode,
|
||||
Direction direction) {
|
||||
OfferDirection direction) {
|
||||
return paymentAccount != null
|
||||
? accountAgeWitnessService.getMyTradeLimit(paymentAccount, currencyCode, direction)
|
||||
: 0;
|
||||
|
@ -181,7 +221,7 @@ public class OfferUtil {
|
|||
// We have to keep a minimum amount of BSQ == bitcoin dust limit, otherwise there
|
||||
// would be dust violations for change UTXOs; essentially means the minimum usable
|
||||
// balance of BSQ is 5.46.
|
||||
Coin usableBsqBalance = bsqWalletService.getAvailableConfirmedBalance().subtract(getMinNonDustOutput());
|
||||
Coin usableBsqBalance = bsqWalletService.getAvailableBalance().subtract(getMinNonDustOutput());
|
||||
return usableBsqBalance.isNegative() ? Coin.ZERO : usableBsqBalance;
|
||||
}
|
||||
|
||||
|
@ -240,7 +280,7 @@ public class OfferUtil {
|
|||
* @return {@code true} if the balance is sufficient, {@code false} otherwise
|
||||
*/
|
||||
public boolean isBsqForMakerFeeAvailable(@Nullable Coin amount) {
|
||||
Coin availableBalance = bsqWalletService.getAvailableConfirmedBalance();
|
||||
Coin availableBalance = bsqWalletService.getAvailableBalance();
|
||||
Coin makerFee = CoinUtil.getMakerFee(false, amount);
|
||||
|
||||
// If we don't know yet the maker fee (amount is not set) we return true,
|
||||
|
@ -274,7 +314,7 @@ public class OfferUtil {
|
|||
}
|
||||
|
||||
public boolean isBsqForTakerFeeAvailable(@Nullable Coin amount) {
|
||||
Coin availableBalance = bsqWalletService.getAvailableConfirmedBalance();
|
||||
Coin availableBalance = bsqWalletService.getAvailableBalance();
|
||||
Coin takerFee = getTakerFee(false, amount);
|
||||
|
||||
// If we don't know yet the maker fee (amount is not set) we return true,
|
||||
|
@ -291,7 +331,7 @@ public class OfferUtil {
|
|||
}
|
||||
|
||||
public boolean isBlockChainPaymentMethod(Offer offer) {
|
||||
return offer != null && offer.getPaymentMethod().isAsset();
|
||||
return offer != null && offer.getPaymentMethod().isBlockchain();
|
||||
}
|
||||
|
||||
public Optional<Volume> getFeeInUserFiatCurrency(Coin makerFee,
|
||||
|
@ -313,7 +353,7 @@ public class OfferUtil {
|
|||
|
||||
public Map<String, String> getExtraDataMap(PaymentAccount paymentAccount,
|
||||
String currencyCode,
|
||||
Direction direction) {
|
||||
OfferDirection direction) {
|
||||
Map<String, String> extraDataMap = new HashMap<>();
|
||||
if (CurrencyUtil.isFiatCurrency(currencyCode)) {
|
||||
String myWitnessHashAsHex = accountAgeWitnessService
|
||||
|
@ -336,7 +376,7 @@ public class OfferUtil {
|
|||
|
||||
extraDataMap.put(CAPABILITIES, Capabilities.app.toStringList());
|
||||
|
||||
if (currencyCode.equals("XMR") && direction == Direction.SELL) {
|
||||
if (currencyCode.equals("XMR") && direction == OfferDirection.SELL) {
|
||||
preferences.getAutoConfirmSettingsList().stream()
|
||||
.filter(e -> e.getCurrencyCode().equals("XMR"))
|
||||
.filter(AutoConfirmSettings::isEnabled)
|
||||
|
@ -350,17 +390,21 @@ public class OfferUtil {
|
|||
PaymentAccount paymentAccount,
|
||||
String currencyCode,
|
||||
Coin makerFeeAsCoin) {
|
||||
validateBasicOfferData(paymentAccount.getPaymentMethod(), currencyCode);
|
||||
checkNotNull(makerFeeAsCoin, "makerFee must not be null");
|
||||
checkNotNull(p2PService.getAddress(), "Address must not be null");
|
||||
checkArgument(buyerSecurityDeposit <= getMaxBuyerSecurityDepositAsPercent(),
|
||||
"securityDeposit must not exceed " +
|
||||
getMaxBuyerSecurityDepositAsPercent());
|
||||
checkArgument(buyerSecurityDeposit >= getMinBuyerSecurityDepositAsPercent(),
|
||||
"securityDeposit must not be less than " +
|
||||
getMinBuyerSecurityDepositAsPercent());
|
||||
}
|
||||
|
||||
public void validateBasicOfferData(PaymentMethod paymentMethod, String currencyCode) {
|
||||
checkNotNull(p2PService.getAddress(), "Address must not be null");
|
||||
checkArgument(!filterManager.isCurrencyBanned(currencyCode),
|
||||
Res.get("offerbook.warning.currencyBanned"));
|
||||
checkArgument(!filterManager.isPaymentMethodBanned(paymentAccount.getPaymentMethod()),
|
||||
checkArgument(!filterManager.isPaymentMethodBanned(paymentMethod),
|
||||
Res.get("offerbook.warning.paymentMethodBanned"));
|
||||
}
|
||||
|
||||
|
@ -370,45 +414,45 @@ public class OfferUtil {
|
|||
// Immutable fields are sourced from the original openOffer param.
|
||||
public OfferPayload getMergedOfferPayload(OpenOffer openOffer,
|
||||
MutableOfferPayloadFields mutableOfferPayloadFields) {
|
||||
OfferPayload originalOfferPayload = openOffer.getOffer().getOfferPayload();
|
||||
return new OfferPayload(originalOfferPayload.getId(),
|
||||
originalOfferPayload.getDate(),
|
||||
originalOfferPayload.getOwnerNodeAddress(),
|
||||
originalOfferPayload.getPubKeyRing(),
|
||||
originalOfferPayload.getDirection(),
|
||||
OfferPayload original = openOffer.getOffer().getOfferPayload().orElseThrow();
|
||||
return new OfferPayload(original.getId(),
|
||||
original.getDate(),
|
||||
original.getOwnerNodeAddress(),
|
||||
original.getPubKeyRing(),
|
||||
original.getDirection(),
|
||||
mutableOfferPayloadFields.getPrice(),
|
||||
mutableOfferPayloadFields.getMarketPriceMargin(),
|
||||
mutableOfferPayloadFields.isUseMarketBasedPrice(),
|
||||
originalOfferPayload.getAmount(),
|
||||
originalOfferPayload.getMinAmount(),
|
||||
original.getAmount(),
|
||||
original.getMinAmount(),
|
||||
mutableOfferPayloadFields.getBaseCurrencyCode(),
|
||||
mutableOfferPayloadFields.getCounterCurrencyCode(),
|
||||
originalOfferPayload.getArbitratorNodeAddresses(),
|
||||
originalOfferPayload.getMediatorNodeAddresses(),
|
||||
original.getArbitratorNodeAddresses(),
|
||||
original.getMediatorNodeAddresses(),
|
||||
mutableOfferPayloadFields.getPaymentMethodId(),
|
||||
mutableOfferPayloadFields.getMakerPaymentAccountId(),
|
||||
originalOfferPayload.getOfferFeePaymentTxId(),
|
||||
original.getOfferFeePaymentTxId(),
|
||||
mutableOfferPayloadFields.getCountryCode(),
|
||||
mutableOfferPayloadFields.getAcceptedCountryCodes(),
|
||||
mutableOfferPayloadFields.getBankId(),
|
||||
mutableOfferPayloadFields.getAcceptedBankIds(),
|
||||
originalOfferPayload.getVersionNr(),
|
||||
originalOfferPayload.getBlockHeightAtOfferCreation(),
|
||||
originalOfferPayload.getTxFee(),
|
||||
originalOfferPayload.getMakerFee(),
|
||||
originalOfferPayload.isCurrencyForMakerFeeBtc(),
|
||||
originalOfferPayload.getBuyerSecurityDeposit(),
|
||||
originalOfferPayload.getSellerSecurityDeposit(),
|
||||
originalOfferPayload.getMaxTradeLimit(),
|
||||
originalOfferPayload.getMaxTradePeriod(),
|
||||
originalOfferPayload.isUseAutoClose(),
|
||||
originalOfferPayload.isUseReOpenAfterAutoClose(),
|
||||
originalOfferPayload.getLowerClosePrice(),
|
||||
originalOfferPayload.getUpperClosePrice(),
|
||||
originalOfferPayload.isPrivateOffer(),
|
||||
originalOfferPayload.getHashOfChallenge(),
|
||||
original.getVersionNr(),
|
||||
original.getBlockHeightAtOfferCreation(),
|
||||
original.getTxFee(),
|
||||
original.getMakerFee(),
|
||||
original.isCurrencyForMakerFeeBtc(),
|
||||
original.getBuyerSecurityDeposit(),
|
||||
original.getSellerSecurityDeposit(),
|
||||
original.getMaxTradeLimit(),
|
||||
original.getMaxTradePeriod(),
|
||||
original.isUseAutoClose(),
|
||||
original.isUseReOpenAfterAutoClose(),
|
||||
original.getLowerClosePrice(),
|
||||
original.getUpperClosePrice(),
|
||||
original.isPrivateOffer(),
|
||||
original.getHashOfChallenge(),
|
||||
mutableOfferPayloadFields.getExtraDataMap(),
|
||||
originalOfferPayload.getProtocolVersion());
|
||||
original.getProtocolVersion());
|
||||
}
|
||||
|
||||
private Optional<Volume> getFeeInUserFiatCurrency(Coin makerFee,
|
||||
|
@ -482,7 +526,7 @@ public class OfferUtil {
|
|||
}
|
||||
} else {
|
||||
errorMsg = "The maker fee tx is invalid as it does not has at least 2 outputs." + extraString +
|
||||
"\nMakerFeeTx=" + makerFeeTx.toString();
|
||||
"\nMakerFeeTx=" + makerFeeTx;
|
||||
}
|
||||
|
||||
if (errorMsg == null) {
|
||||
|
|
|
@ -17,7 +17,8 @@
|
|||
|
||||
package bisq.core.offer;
|
||||
|
||||
import bisq.core.trade.Tradable;
|
||||
import bisq.core.offer.bsq_swap.BsqSwapOfferPayload;
|
||||
import bisq.core.trade.model.Tradable;
|
||||
|
||||
import bisq.network.p2p.NodeAddress;
|
||||
|
||||
|
@ -35,12 +36,13 @@ import lombok.extern.slf4j.Slf4j;
|
|||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
@EqualsAndHashCode
|
||||
import static com.google.common.base.Preconditions.checkArgument;
|
||||
|
||||
@EqualsAndHashCode(exclude = {"bsqSwapOfferHasMissingFunds", "state", "timeoutTimer", "mempoolStatus"})
|
||||
@Slf4j
|
||||
public final class OpenOffer implements Tradable {
|
||||
// Timeout for offer reservation during takeoffer process. If deposit tx is not completed in that time we reset the offer to AVAILABLE state.
|
||||
private static final long TIMEOUT = 60;
|
||||
transient private Timer timeoutTimer;
|
||||
|
||||
public enum State {
|
||||
AVAILABLE,
|
||||
|
@ -54,10 +56,11 @@ public final class OpenOffer implements Tradable {
|
|||
private final Offer offer;
|
||||
@Getter
|
||||
private State state;
|
||||
|
||||
// TODO Not used. Could be removed?
|
||||
@Getter
|
||||
@Setter
|
||||
@Nullable
|
||||
private NodeAddress arbitratorNodeAddress;
|
||||
private final NodeAddress arbitratorNodeAddress;
|
||||
@Getter
|
||||
@Setter
|
||||
@Nullable
|
||||
|
@ -76,15 +79,29 @@ public final class OpenOffer implements Tradable {
|
|||
@Getter
|
||||
@Setter
|
||||
transient private long mempoolStatus = -1;
|
||||
transient private Timer timeoutTimer;
|
||||
|
||||
// Added at BsqSwap release. We do not persist that field
|
||||
@Getter
|
||||
@Setter
|
||||
transient private boolean bsqSwapOfferHasMissingFunds;
|
||||
|
||||
public OpenOffer(Offer offer) {
|
||||
this(offer, 0);
|
||||
}
|
||||
|
||||
public OpenOffer(Offer offer, State state) {
|
||||
this.offer = offer;
|
||||
this.triggerPrice = 0;
|
||||
this.state = state;
|
||||
arbitratorNodeAddress = null;
|
||||
}
|
||||
|
||||
public OpenOffer(Offer offer, long triggerPrice) {
|
||||
this.offer = offer;
|
||||
this.triggerPrice = triggerPrice;
|
||||
state = State.AVAILABLE;
|
||||
arbitratorNodeAddress = null;
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
@ -131,9 +148,8 @@ public final class OpenOffer implements Tradable {
|
|||
proto.getTriggerPrice());
|
||||
}
|
||||
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
// Getters
|
||||
// Tradable
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
@Override
|
||||
|
@ -151,6 +167,11 @@ public final class OpenOffer implements Tradable {
|
|||
return offer.getShortId();
|
||||
}
|
||||
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
// Misc
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
public void setState(State state) {
|
||||
this.state = state;
|
||||
|
||||
|
@ -166,6 +187,12 @@ public final class OpenOffer implements Tradable {
|
|||
return state == State.DEACTIVATED;
|
||||
}
|
||||
|
||||
public BsqSwapOfferPayload getBsqSwapOfferPayload() {
|
||||
checkArgument(getOffer().getBsqSwapOfferPayload().isPresent(),
|
||||
"getBsqSwapOfferPayload must be called only when BsqSwapOfferPayload is the expected payload");
|
||||
return getOffer().getBsqSwapOfferPayload().get();
|
||||
}
|
||||
|
||||
private void startTimeout() {
|
||||
stopTimeout();
|
||||
|
||||
|
@ -185,7 +212,6 @@ public final class OpenOffer implements Tradable {
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "OpenOffer{" +
|
||||
|
@ -195,6 +221,7 @@ public final class OpenOffer implements Tradable {
|
|||
",\n mediatorNodeAddress=" + mediatorNodeAddress +
|
||||
",\n refundAgentNodeAddress=" + refundAgentNodeAddress +
|
||||
",\n triggerPrice=" + triggerPrice +
|
||||
",\n bsqSwapOfferHasMissingFunds=" + bsqSwapOfferHasMissingFunds +
|
||||
"\n}";
|
||||
}
|
||||
}
|
||||
|
|
|
@ -25,18 +25,22 @@ import bisq.core.dao.DaoFacade;
|
|||
import bisq.core.exceptions.TradePriceOutOfToleranceException;
|
||||
import bisq.core.filter.FilterManager;
|
||||
import bisq.core.locale.Res;
|
||||
import bisq.core.offer.availability.AvailabilityResult;
|
||||
import bisq.core.offer.availability.DisputeAgentSelection;
|
||||
import bisq.core.offer.messages.OfferAvailabilityRequest;
|
||||
import bisq.core.offer.messages.OfferAvailabilityResponse;
|
||||
import bisq.core.offer.placeoffer.PlaceOfferModel;
|
||||
import bisq.core.offer.placeoffer.PlaceOfferProtocol;
|
||||
import bisq.core.offer.availability.messages.OfferAvailabilityRequest;
|
||||
import bisq.core.offer.availability.messages.OfferAvailabilityResponse;
|
||||
import bisq.core.offer.bisq_v1.CreateOfferService;
|
||||
import bisq.core.offer.bisq_v1.MarketPriceNotAvailableException;
|
||||
import bisq.core.offer.bisq_v1.OfferPayload;
|
||||
import bisq.core.offer.placeoffer.bisq_v1.PlaceOfferModel;
|
||||
import bisq.core.offer.placeoffer.bisq_v1.PlaceOfferProtocol;
|
||||
import bisq.core.provider.price.PriceFeedService;
|
||||
import bisq.core.support.dispute.arbitration.arbitrator.ArbitratorManager;
|
||||
import bisq.core.support.dispute.mediation.mediator.MediatorManager;
|
||||
import bisq.core.support.dispute.refund.refundagent.RefundAgentManager;
|
||||
import bisq.core.trade.TradableList;
|
||||
import bisq.core.trade.closed.ClosedTradableManager;
|
||||
import bisq.core.trade.handlers.TransactionResultHandler;
|
||||
import bisq.core.trade.ClosedTradableManager;
|
||||
import bisq.core.trade.bisq_v1.TransactionResultHandler;
|
||||
import bisq.core.trade.model.TradableList;
|
||||
import bisq.core.trade.statistics.TradeStatisticsManager;
|
||||
import bisq.core.user.Preferences;
|
||||
import bisq.core.user.User;
|
||||
|
@ -84,19 +88,16 @@ import java.util.Set;
|
|||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import lombok.Getter;
|
||||
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
import static com.google.common.base.Preconditions.checkArgument;
|
||||
import static com.google.common.base.Preconditions.checkNotNull;
|
||||
|
||||
@Slf4j
|
||||
public class OpenOfferManager implements PeerManager.Listener, DecryptedDirectMessageListener, PersistedDataHost {
|
||||
private static final Logger log = LoggerFactory.getLogger(OpenOfferManager.class);
|
||||
|
||||
private static final long RETRY_REPUBLISH_DELAY_SEC = 10;
|
||||
private static final long REPUBLISH_AGAIN_AT_STARTUP_DELAY_SEC = 30;
|
||||
|
@ -207,8 +208,9 @@ public class OpenOfferManager implements PeerManager.Listener, DecryptedDirectMe
|
|||
cleanUpAddressEntries();
|
||||
|
||||
openOffers.stream()
|
||||
.forEach(openOffer -> OfferUtil.getInvalidMakerFeeTxErrorMessage(openOffer.getOffer(), btcWalletService)
|
||||
.ifPresent(errorMsg -> invalidOffers.add(new Tuple2<>(openOffer, errorMsg))));
|
||||
.forEach(openOffer ->
|
||||
OfferUtil.getInvalidMakerFeeTxErrorMessage(openOffer.getOffer(), btcWalletService)
|
||||
.ifPresent(errorMsg -> invalidOffers.add(new Tuple2<>(openOffer, errorMsg))));
|
||||
}
|
||||
|
||||
private void cleanUpAddressEntries() {
|
||||
|
@ -238,7 +240,7 @@ public class OpenOfferManager implements PeerManager.Listener, DecryptedDirectMe
|
|||
log.info("Remove open offers at shutDown. Number of open offers: {}", size);
|
||||
if (offerBookService.isBootstrapped() && size > 0) {
|
||||
UserThread.execute(() -> openOffers.forEach(
|
||||
openOffer -> offerBookService.removeOfferAtShutDown(openOffer.getOffer().getOfferPayload())
|
||||
openOffer -> offerBookService.removeOfferAtShutDown(openOffer.getOffer().getOfferPayloadBase())
|
||||
));
|
||||
|
||||
// Force broadcaster to send out immediately, otherwise we could have a 2 sec delay until the
|
||||
|
@ -265,11 +267,9 @@ public class OpenOfferManager implements PeerManager.Listener, DecryptedDirectMe
|
|||
int size = openOffers.size();
|
||||
// Copy list as we remove in the loop
|
||||
List<OpenOffer> openOffersList = new ArrayList<>(openOffers);
|
||||
openOffersList.forEach(openOffer -> removeOpenOffer(openOffer, () -> {
|
||||
}, errorMessage -> {
|
||||
}));
|
||||
openOffersList.forEach(this::removeOpenOffer);
|
||||
if (completeHandler != null)
|
||||
UserThread.runAfter(completeHandler, size * 200 + 500, TimeUnit.MILLISECONDS);
|
||||
UserThread.runAfter(completeHandler, size * 200L + 500, TimeUnit.MILLISECONDS);
|
||||
}
|
||||
|
||||
|
||||
|
@ -372,6 +372,7 @@ public class OpenOfferManager implements PeerManager.Listener, DecryptedDirectMe
|
|||
TransactionResultHandler resultHandler,
|
||||
ErrorMessageHandler errorMessageHandler) {
|
||||
checkNotNull(offer.getMakerFee(), "makerFee must not be null");
|
||||
checkArgument(!offer.isBsqSwapOffer());
|
||||
|
||||
Coin reservedFundsForOffer = createOfferService.getReservedFundsForOffer(offer.getDirection(),
|
||||
offer.getAmount(),
|
||||
|
@ -394,21 +395,30 @@ public class OpenOfferManager implements PeerManager.Listener, DecryptedDirectMe
|
|||
model,
|
||||
transaction -> {
|
||||
OpenOffer openOffer = new OpenOffer(offer, triggerPrice);
|
||||
openOffers.add(openOffer);
|
||||
requestPersistence();
|
||||
resultHandler.handleResult(transaction);
|
||||
addOpenOfferToList(openOffer);
|
||||
if (!stopped) {
|
||||
startPeriodicRepublishOffersTimer();
|
||||
startPeriodicRefreshOffersTimer();
|
||||
} else {
|
||||
log.debug("We have stopped already. We ignore that placeOfferProtocol.placeOffer.onResult call.");
|
||||
}
|
||||
resultHandler.handleResult(transaction);
|
||||
},
|
||||
errorMessageHandler
|
||||
);
|
||||
placeOfferProtocol.placeOffer();
|
||||
}
|
||||
|
||||
public void addOpenBsqSwapOffer(OpenOffer openOffer) {
|
||||
addOpenOfferToList(openOffer);
|
||||
if (!stopped) {
|
||||
startPeriodicRepublishOffersTimer();
|
||||
startPeriodicRefreshOffersTimer();
|
||||
} else {
|
||||
log.debug("We have stopped already. We ignore that placeOfferProtocol.placeOffer.onResult call.");
|
||||
}
|
||||
}
|
||||
|
||||
// Remove from offerbook
|
||||
public void removeOffer(Offer offer, ResultHandler resultHandler, ErrorMessageHandler errorMessageHandler) {
|
||||
Optional<OpenOffer> openOfferOptional = getOpenOfferById(offer.getId());
|
||||
|
@ -418,7 +428,7 @@ public class OpenOfferManager implements PeerManager.Listener, DecryptedDirectMe
|
|||
log.warn("Offer was not found in our list of open offers. We still try to remove it from the offerbook.");
|
||||
errorMessageHandler.handleErrorMessage("Offer was not found in our list of open offers. " +
|
||||
"We still try to remove it from the offerbook.");
|
||||
offerBookService.removeOffer(offer.getOfferPayload(),
|
||||
offerBookService.removeOffer(offer.getOfferPayloadBase(),
|
||||
() -> offer.setState(Offer.State.REMOVED),
|
||||
null);
|
||||
}
|
||||
|
@ -427,26 +437,36 @@ public class OpenOfferManager implements PeerManager.Listener, DecryptedDirectMe
|
|||
public void activateOpenOffer(OpenOffer openOffer,
|
||||
ResultHandler resultHandler,
|
||||
ErrorMessageHandler errorMessageHandler) {
|
||||
if (!offersToBeEdited.containsKey(openOffer.getId())) {
|
||||
Offer offer = openOffer.getOffer();
|
||||
offerBookService.activateOffer(offer,
|
||||
() -> {
|
||||
openOffer.setState(OpenOffer.State.AVAILABLE);
|
||||
requestPersistence();
|
||||
log.debug("activateOpenOffer, offerId={}", offer.getId());
|
||||
resultHandler.handleResult();
|
||||
},
|
||||
errorMessageHandler);
|
||||
} else {
|
||||
if (offersToBeEdited.containsKey(openOffer.getId())) {
|
||||
errorMessageHandler.handleErrorMessage("You can't activate an offer that is currently edited.");
|
||||
return;
|
||||
}
|
||||
|
||||
// If there is not enough funds for a BsqSwapOffer we do not publish the offer, but still apply the state change.
|
||||
// Once the wallet gets funded the offer gets published automatically.
|
||||
if (isBsqSwapOfferLackingFunds(openOffer)) {
|
||||
openOffer.setState(OpenOffer.State.AVAILABLE);
|
||||
requestPersistence();
|
||||
resultHandler.handleResult();
|
||||
return;
|
||||
}
|
||||
|
||||
Offer offer = openOffer.getOffer();
|
||||
offerBookService.activateOffer(offer,
|
||||
() -> {
|
||||
openOffer.setState(OpenOffer.State.AVAILABLE);
|
||||
requestPersistence();
|
||||
log.debug("activateOpenOffer, offerId={}", offer.getId());
|
||||
resultHandler.handleResult();
|
||||
},
|
||||
errorMessageHandler);
|
||||
}
|
||||
|
||||
public void deactivateOpenOffer(OpenOffer openOffer,
|
||||
ResultHandler resultHandler,
|
||||
ErrorMessageHandler errorMessageHandler) {
|
||||
Offer offer = openOffer.getOffer();
|
||||
offerBookService.deactivateOffer(offer.getOfferPayload(),
|
||||
offerBookService.deactivateOffer(offer.getOfferPayloadBase(),
|
||||
() -> {
|
||||
openOffer.setState(OpenOffer.State.DEACTIVATED);
|
||||
requestPersistence();
|
||||
|
@ -456,6 +476,12 @@ public class OpenOfferManager implements PeerManager.Listener, DecryptedDirectMe
|
|||
errorMessageHandler);
|
||||
}
|
||||
|
||||
public void removeOpenOffer(OpenOffer openOffer) {
|
||||
removeOpenOffer(openOffer, () -> {
|
||||
}, error -> {
|
||||
});
|
||||
}
|
||||
|
||||
public void removeOpenOffer(OpenOffer openOffer,
|
||||
ResultHandler resultHandler,
|
||||
ErrorMessageHandler errorMessageHandler) {
|
||||
|
@ -464,7 +490,7 @@ public class OpenOfferManager implements PeerManager.Listener, DecryptedDirectMe
|
|||
if (openOffer.isDeactivated()) {
|
||||
onRemoved(openOffer, resultHandler, offer);
|
||||
} else {
|
||||
offerBookService.removeOffer(offer.getOfferPayload(),
|
||||
offerBookService.removeOffer(offer.getOfferPayloadBase(),
|
||||
() -> onRemoved(openOffer, resultHandler, offer),
|
||||
errorMessageHandler);
|
||||
}
|
||||
|
@ -508,18 +534,17 @@ public class OpenOfferManager implements PeerManager.Listener, DecryptedDirectMe
|
|||
|
||||
openOffer.getOffer().setState(Offer.State.REMOVED);
|
||||
openOffer.setState(OpenOffer.State.CANCELED);
|
||||
openOffers.remove(openOffer);
|
||||
removeOpenOfferFromList(openOffer);
|
||||
|
||||
OpenOffer editedOpenOffer = new OpenOffer(editedOffer, triggerPrice);
|
||||
editedOpenOffer.setState(originalState);
|
||||
|
||||
openOffers.add(editedOpenOffer);
|
||||
addOpenOfferToList(editedOpenOffer);
|
||||
|
||||
if (!editedOpenOffer.isDeactivated())
|
||||
republishOffer(editedOpenOffer);
|
||||
maybeRepublishOffer(editedOpenOffer);
|
||||
|
||||
offersToBeEdited.remove(openOffer.getId());
|
||||
requestPersistence();
|
||||
resultHandler.handleResult();
|
||||
} else {
|
||||
errorMessageHandler.handleErrorMessage("There is no offer with this id existing to be published.");
|
||||
|
@ -542,26 +567,26 @@ public class OpenOfferManager implements PeerManager.Listener, DecryptedDirectMe
|
|||
}
|
||||
}
|
||||
|
||||
private void onRemoved(@NotNull OpenOffer openOffer, ResultHandler resultHandler, Offer offer) {
|
||||
private void onRemoved(OpenOffer openOffer, ResultHandler resultHandler, Offer offer) {
|
||||
offer.setState(Offer.State.REMOVED);
|
||||
openOffer.setState(OpenOffer.State.CANCELED);
|
||||
openOffers.remove(openOffer);
|
||||
closedTradableManager.add(openOffer);
|
||||
removeOpenOfferFromList(openOffer);
|
||||
if (!openOffer.getOffer().isBsqSwapOffer()) {
|
||||
closedTradableManager.add(openOffer);
|
||||
btcWalletService.resetAddressEntriesForOpenOffer(offer.getId());
|
||||
}
|
||||
log.info("onRemoved offerId={}", offer.getId());
|
||||
btcWalletService.resetAddressEntriesForOpenOffer(offer.getId());
|
||||
requestPersistence();
|
||||
resultHandler.handleResult();
|
||||
}
|
||||
|
||||
// Close openOffer after deposit published
|
||||
public void closeOpenOffer(Offer offer) {
|
||||
getOpenOfferById(offer.getId()).ifPresent(openOffer -> {
|
||||
openOffers.remove(openOffer);
|
||||
removeOpenOfferFromList(openOffer);
|
||||
openOffer.setState(OpenOffer.State.CLOSED);
|
||||
offerBookService.removeOffer(openOffer.getOffer().getOfferPayload(),
|
||||
offerBookService.removeOffer(openOffer.getOffer().getOfferPayloadBase(),
|
||||
() -> log.trace("Successful removed offer"),
|
||||
log::error);
|
||||
requestPersistence();
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -632,7 +657,7 @@ public class OpenOfferManager implements PeerManager.Listener, DecryptedDirectMe
|
|||
Validator.nonEmptyStringOf(request.offerId);
|
||||
checkNotNull(request.getPubKeyRing());
|
||||
} catch (Throwable t) {
|
||||
errorMessage = "Message validation failed. Error=" + t.toString() + ", Message=" + request.toString();
|
||||
errorMessage = "Message validation failed. Error=" + t + ", Message=" + request;
|
||||
log.warn(errorMessage);
|
||||
sendAckMessage(request, peer, false, errorMessage);
|
||||
return;
|
||||
|
@ -787,23 +812,27 @@ public class OpenOfferManager implements PeerManager.Listener, DecryptedDirectMe
|
|||
ArrayList<OpenOffer> openOffersClone = new ArrayList<>(openOffers.getList());
|
||||
openOffersClone.forEach(originalOpenOffer -> {
|
||||
Offer originalOffer = originalOpenOffer.getOffer();
|
||||
|
||||
OfferPayload originalOfferPayload = originalOffer.getOfferPayload();
|
||||
if (originalOffer.isBsqSwapOffer()) {
|
||||
// Offer without a fee transaction don't need to be updated, they can be removed and a new
|
||||
// offer created without incurring any extra costs
|
||||
return;
|
||||
}
|
||||
OfferPayload original = originalOffer.getOfferPayload().orElseThrow();
|
||||
// We added CAPABILITIES with entry for Capability.MEDIATION in v1.1.6 and
|
||||
// Capability.REFUND_AGENT in v1.2.0 and want to rewrite a
|
||||
// persisted offer after the user has updated to 1.2.0 so their offer will be accepted by the network.
|
||||
|
||||
if (originalOfferPayload.getProtocolVersion() < Version.TRADE_PROTOCOL_VERSION ||
|
||||
if (original.getProtocolVersion() < Version.TRADE_PROTOCOL_VERSION ||
|
||||
!OfferRestrictions.hasOfferMandatoryCapability(originalOffer, Capability.MEDIATION) ||
|
||||
!OfferRestrictions.hasOfferMandatoryCapability(originalOffer, Capability.REFUND_AGENT) ||
|
||||
!originalOfferPayload.getOwnerNodeAddress().equals(p2PService.getAddress())) {
|
||||
!original.getOwnerNodeAddress().equals(p2PService.getAddress())) {
|
||||
|
||||
// - Capabilities changed?
|
||||
// We rewrite our offer with the additional capabilities entry
|
||||
Map<String, String> updatedExtraDataMap = new HashMap<>();
|
||||
if (!OfferRestrictions.hasOfferMandatoryCapability(originalOffer, Capability.MEDIATION) ||
|
||||
!OfferRestrictions.hasOfferMandatoryCapability(originalOffer, Capability.REFUND_AGENT)) {
|
||||
Map<String, String> originalExtraDataMap = originalOfferPayload.getExtraDataMap();
|
||||
Map<String, String> originalExtraDataMap = original.getExtraDataMap();
|
||||
|
||||
if (originalExtraDataMap != null) {
|
||||
updatedExtraDataMap.putAll(originalExtraDataMap);
|
||||
|
@ -814,11 +843,11 @@ public class OpenOfferManager implements PeerManager.Listener, DecryptedDirectMe
|
|||
|
||||
log.info("Converted offer to support new Capability.MEDIATION and Capability.REFUND_AGENT capability. id={}", originalOffer.getId());
|
||||
} else {
|
||||
updatedExtraDataMap = originalOfferPayload.getExtraDataMap();
|
||||
updatedExtraDataMap = original.getExtraDataMap();
|
||||
}
|
||||
|
||||
// - Protocol version changed?
|
||||
int protocolVersion = originalOfferPayload.getProtocolVersion();
|
||||
int protocolVersion = original.getProtocolVersion();
|
||||
if (protocolVersion < Version.TRADE_PROTOCOL_VERSION) {
|
||||
// We update the trade protocol version
|
||||
protocolVersion = Version.TRADE_PROTOCOL_VERSION;
|
||||
|
@ -826,48 +855,48 @@ public class OpenOfferManager implements PeerManager.Listener, DecryptedDirectMe
|
|||
}
|
||||
|
||||
// - node address changed? (due to a faulty tor dir)
|
||||
NodeAddress ownerNodeAddress = originalOfferPayload.getOwnerNodeAddress();
|
||||
NodeAddress ownerNodeAddress = original.getOwnerNodeAddress();
|
||||
if (!ownerNodeAddress.equals(p2PService.getAddress())) {
|
||||
ownerNodeAddress = p2PService.getAddress();
|
||||
log.info("Updated the owner nodeaddress of offer id={}", originalOffer.getId());
|
||||
}
|
||||
|
||||
OfferPayload updatedPayload = new OfferPayload(originalOfferPayload.getId(),
|
||||
originalOfferPayload.getDate(),
|
||||
OfferPayload updatedPayload = new OfferPayload(original.getId(),
|
||||
original.getDate(),
|
||||
ownerNodeAddress,
|
||||
originalOfferPayload.getPubKeyRing(),
|
||||
originalOfferPayload.getDirection(),
|
||||
originalOfferPayload.getPrice(),
|
||||
originalOfferPayload.getMarketPriceMargin(),
|
||||
originalOfferPayload.isUseMarketBasedPrice(),
|
||||
originalOfferPayload.getAmount(),
|
||||
originalOfferPayload.getMinAmount(),
|
||||
originalOfferPayload.getBaseCurrencyCode(),
|
||||
originalOfferPayload.getCounterCurrencyCode(),
|
||||
originalOfferPayload.getArbitratorNodeAddresses(),
|
||||
originalOfferPayload.getMediatorNodeAddresses(),
|
||||
originalOfferPayload.getPaymentMethodId(),
|
||||
originalOfferPayload.getMakerPaymentAccountId(),
|
||||
originalOfferPayload.getOfferFeePaymentTxId(),
|
||||
originalOfferPayload.getCountryCode(),
|
||||
originalOfferPayload.getAcceptedCountryCodes(),
|
||||
originalOfferPayload.getBankId(),
|
||||
originalOfferPayload.getAcceptedBankIds(),
|
||||
originalOfferPayload.getVersionNr(),
|
||||
originalOfferPayload.getBlockHeightAtOfferCreation(),
|
||||
originalOfferPayload.getTxFee(),
|
||||
originalOfferPayload.getMakerFee(),
|
||||
originalOfferPayload.isCurrencyForMakerFeeBtc(),
|
||||
originalOfferPayload.getBuyerSecurityDeposit(),
|
||||
originalOfferPayload.getSellerSecurityDeposit(),
|
||||
originalOfferPayload.getMaxTradeLimit(),
|
||||
originalOfferPayload.getMaxTradePeriod(),
|
||||
originalOfferPayload.isUseAutoClose(),
|
||||
originalOfferPayload.isUseReOpenAfterAutoClose(),
|
||||
originalOfferPayload.getLowerClosePrice(),
|
||||
originalOfferPayload.getUpperClosePrice(),
|
||||
originalOfferPayload.isPrivateOffer(),
|
||||
originalOfferPayload.getHashOfChallenge(),
|
||||
original.getPubKeyRing(),
|
||||
original.getDirection(),
|
||||
original.getPrice(),
|
||||
original.getMarketPriceMargin(),
|
||||
original.isUseMarketBasedPrice(),
|
||||
original.getAmount(),
|
||||
original.getMinAmount(),
|
||||
original.getBaseCurrencyCode(),
|
||||
original.getCounterCurrencyCode(),
|
||||
original.getArbitratorNodeAddresses(),
|
||||
original.getMediatorNodeAddresses(),
|
||||
original.getPaymentMethodId(),
|
||||
original.getMakerPaymentAccountId(),
|
||||
original.getOfferFeePaymentTxId(),
|
||||
original.getCountryCode(),
|
||||
original.getAcceptedCountryCodes(),
|
||||
original.getBankId(),
|
||||
original.getAcceptedBankIds(),
|
||||
original.getVersionNr(),
|
||||
original.getBlockHeightAtOfferCreation(),
|
||||
original.getTxFee(),
|
||||
original.getMakerFee(),
|
||||
original.isCurrencyForMakerFeeBtc(),
|
||||
original.getBuyerSecurityDeposit(),
|
||||
original.getSellerSecurityDeposit(),
|
||||
original.getMaxTradeLimit(),
|
||||
original.getMaxTradePeriod(),
|
||||
original.isUseAutoClose(),
|
||||
original.isUseReOpenAfterAutoClose(),
|
||||
original.getLowerClosePrice(),
|
||||
original.getUpperClosePrice(),
|
||||
original.isPrivateOffer(),
|
||||
original.getHashOfChallenge(),
|
||||
updatedExtraDataMap,
|
||||
protocolVersion);
|
||||
|
||||
|
@ -878,7 +907,7 @@ public class OpenOfferManager implements PeerManager.Listener, DecryptedDirectMe
|
|||
// remove old offer
|
||||
originalOffer.setState(Offer.State.REMOVED);
|
||||
originalOpenOffer.setState(OpenOffer.State.CANCELED);
|
||||
openOffers.remove(originalOpenOffer);
|
||||
removeOpenOfferFromList(originalOpenOffer);
|
||||
|
||||
// Create new Offer
|
||||
Offer updatedOffer = new Offer(updatedPayload);
|
||||
|
@ -887,8 +916,7 @@ public class OpenOfferManager implements PeerManager.Listener, DecryptedDirectMe
|
|||
|
||||
OpenOffer updatedOpenOffer = new OpenOffer(updatedOffer, originalOpenOffer.getTriggerPrice());
|
||||
updatedOpenOffer.setState(originalOpenOfferState);
|
||||
openOffers.add(updatedOpenOffer);
|
||||
requestPersistence();
|
||||
addOpenOfferToList(updatedOpenOffer);
|
||||
|
||||
log.info("Updating offer completed. id={}", originalOffer.getId());
|
||||
}
|
||||
|
@ -904,7 +932,6 @@ public class OpenOfferManager implements PeerManager.Listener, DecryptedDirectMe
|
|||
if (stopped) {
|
||||
return;
|
||||
}
|
||||
|
||||
stopPeriodicRefreshOffersTimer();
|
||||
|
||||
List<OpenOffer> openOffersList = new ArrayList<>(openOffers.getList());
|
||||
|
@ -917,15 +944,8 @@ public class OpenOfferManager implements PeerManager.Listener, DecryptedDirectMe
|
|||
}
|
||||
|
||||
OpenOffer openOffer = list.remove(0);
|
||||
if (openOffers.contains(openOffer) && !openOffer.isDeactivated()) {
|
||||
// TODO It is not clear yet if it is better for the node and the network to send out all add offer
|
||||
// messages in one go or to spread it over a delay. With power users who have 100-200 offers that can have
|
||||
// some significant impact to user experience and the network
|
||||
republishOffer(openOffer, () -> processListForRepublishOffers(list));
|
||||
|
||||
/* republishOffer(openOffer,
|
||||
() -> UserThread.runAfter(() -> processListForRepublishOffers(list),
|
||||
30, TimeUnit.MILLISECONDS));*/
|
||||
if (openOffers.contains(openOffer)) {
|
||||
maybeRepublishOffer(openOffer, () -> processListForRepublishOffers(list));
|
||||
} else {
|
||||
// If the offer was removed in the meantime or if its deactivated we skip and call
|
||||
// processListForRepublishOffers again with the list where we removed the offer already.
|
||||
|
@ -933,11 +953,18 @@ public class OpenOfferManager implements PeerManager.Listener, DecryptedDirectMe
|
|||
}
|
||||
}
|
||||
|
||||
private void republishOffer(OpenOffer openOffer) {
|
||||
republishOffer(openOffer, null);
|
||||
public void maybeRepublishOffer(OpenOffer openOffer) {
|
||||
maybeRepublishOffer(openOffer, null);
|
||||
}
|
||||
|
||||
private void republishOffer(OpenOffer openOffer, @Nullable Runnable completeHandler) {
|
||||
private void maybeRepublishOffer(OpenOffer openOffer, @Nullable Runnable completeHandler) {
|
||||
if (preventedFromPublishing(openOffer)) {
|
||||
if (completeHandler != null) {
|
||||
completeHandler.run();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
offerBookService.addOffer(openOffer.getOffer(),
|
||||
() -> {
|
||||
if (!stopped) {
|
||||
|
@ -996,8 +1023,8 @@ public class OpenOfferManager implements PeerManager.Listener, DecryptedDirectMe
|
|||
final OpenOffer openOffer = openOffersList.get(i);
|
||||
UserThread.runAfterRandomDelay(() -> {
|
||||
// we need to check if in the meantime the offer has been removed
|
||||
if (openOffers.contains(openOffer) && !openOffer.isDeactivated())
|
||||
refreshOffer(openOffer);
|
||||
if (openOffers.contains(openOffer))
|
||||
maybeRefreshOffer(openOffer);
|
||||
}, minDelay, maxDelay, TimeUnit.MILLISECONDS);
|
||||
}
|
||||
} else {
|
||||
|
@ -1010,8 +1037,11 @@ public class OpenOfferManager implements PeerManager.Listener, DecryptedDirectMe
|
|||
log.trace("periodicRefreshOffersTimer already stated");
|
||||
}
|
||||
|
||||
private void refreshOffer(OpenOffer openOffer) {
|
||||
offerBookService.refreshTTL(openOffer.getOffer().getOfferPayload(),
|
||||
private void maybeRefreshOffer(OpenOffer openOffer) {
|
||||
if (preventedFromPublishing(openOffer)) {
|
||||
return;
|
||||
}
|
||||
offerBookService.refreshTTL(openOffer.getOffer().getOfferPayloadBase(),
|
||||
() -> log.debug("Successful refreshed TTL for offer"),
|
||||
log::warn);
|
||||
}
|
||||
|
@ -1028,7 +1058,7 @@ public class OpenOfferManager implements PeerManager.Listener, DecryptedDirectMe
|
|||
startPeriodicRepublishOffersTimer();
|
||||
}
|
||||
|
||||
private void requestPersistence() {
|
||||
public void requestPersistence() {
|
||||
persistenceManager.requestPersistence();
|
||||
}
|
||||
|
||||
|
@ -1057,4 +1087,23 @@ public class OpenOfferManager implements PeerManager.Listener, DecryptedDirectMe
|
|||
retryRepublishOffersTimer = null;
|
||||
}
|
||||
}
|
||||
|
||||
private void addOpenOfferToList(OpenOffer openOffer) {
|
||||
openOffers.add(openOffer);
|
||||
requestPersistence();
|
||||
}
|
||||
|
||||
private void removeOpenOfferFromList(OpenOffer openOffer) {
|
||||
openOffers.remove(openOffer);
|
||||
requestPersistence();
|
||||
}
|
||||
|
||||
private boolean isBsqSwapOfferLackingFunds(OpenOffer openOffer) {
|
||||
return openOffer.getOffer().isBsqSwapOffer() &&
|
||||
openOffer.isBsqSwapOfferHasMissingFunds();
|
||||
}
|
||||
|
||||
private boolean preventedFromPublishing(OpenOffer openOffer) {
|
||||
return openOffer.isDeactivated() || openOffer.isBsqSwapOfferHasMissingFunds();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -15,7 +15,7 @@
|
|||
* along with Bisq. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package bisq.core.offer;
|
||||
package bisq.core.offer.availability;
|
||||
|
||||
public enum AvailabilityResult {
|
||||
UNKNOWN_FAILURE("cannot take offer for unknown reason"),
|
|
@ -18,7 +18,7 @@
|
|||
package bisq.core.offer.availability;
|
||||
|
||||
import bisq.core.offer.Offer;
|
||||
import bisq.core.offer.messages.OfferAvailabilityResponse;
|
||||
import bisq.core.offer.availability.messages.OfferAvailabilityResponse;
|
||||
import bisq.core.support.dispute.mediation.mediator.MediatorManager;
|
||||
import bisq.core.trade.statistics.TradeStatisticsManager;
|
||||
import bisq.core.user.User;
|
||||
|
|
|
@ -18,10 +18,10 @@
|
|||
package bisq.core.offer.availability;
|
||||
|
||||
import bisq.core.offer.Offer;
|
||||
import bisq.core.offer.availability.messages.OfferAvailabilityResponse;
|
||||
import bisq.core.offer.availability.messages.OfferMessage;
|
||||
import bisq.core.offer.availability.tasks.ProcessOfferAvailabilityResponse;
|
||||
import bisq.core.offer.availability.tasks.SendOfferAvailabilityRequest;
|
||||
import bisq.core.offer.messages.OfferAvailabilityResponse;
|
||||
import bisq.core.offer.messages.OfferMessage;
|
||||
import bisq.core.util.Validator;
|
||||
|
||||
import bisq.network.p2p.AckMessage;
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Reference in a new issue