support scheduling offers with locked funds

This commit is contained in:
woodser 2022-05-15 13:58:27 -04:00
parent 2da77de41b
commit fa15612586
25 changed files with 386 additions and 201 deletions

View file

@ -44,7 +44,7 @@ import static java.lang.String.format;
import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.fail; import static org.junit.jupiter.api.Assertions.fail;
import static protobuf.Offer.State.OFFER_FEE_PAID; import static protobuf.Offer.State.OFFER_FEE_RESERVED;
import static protobuf.OfferPayload.Direction.BUY; import static protobuf.OfferPayload.Direction.BUY;
import static protobuf.OpenOffer.State.AVAILABLE; import static protobuf.OpenOffer.State.AVAILABLE;
@ -168,7 +168,7 @@ public class TakeBuyBTCOfferTest extends AbstractTradeTest {
sleep(5000); sleep(5000);
continue; continue;
} else { } else {
assertEquals(OFFER_FEE_PAID.name(), trade.getOffer().getState()); assertEquals(OFFER_FEE_RESERVED.name(), trade.getOffer().getState());
EXPECTED_PROTOCOL_STATUS.setState(BUYER_SAW_ARRIVED_PAYMENT_SENT_MSG) EXPECTED_PROTOCOL_STATUS.setState(BUYER_SAW_ARRIVED_PAYMENT_SENT_MSG)
.setPhase(PAYMENT_SENT) .setPhase(PAYMENT_SENT)
.setFiatSent(true); .setFiatSent(true);

View file

@ -46,7 +46,7 @@ import static java.lang.String.format;
import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.fail; import static org.junit.jupiter.api.Assertions.fail;
import static protobuf.Offer.State.OFFER_FEE_PAID; import static protobuf.Offer.State.OFFER_FEE_RESERVED;
import static protobuf.OfferPayload.Direction.SELL; import static protobuf.OfferPayload.Direction.SELL;
import static protobuf.OpenOffer.State.AVAILABLE; import static protobuf.OpenOffer.State.AVAILABLE;
@ -220,7 +220,7 @@ public class TakeSellBTCOfferTest extends AbstractTradeTest {
sleep(3000); sleep(3000);
trade = aliceClient.getTrade(tradeId); trade = aliceClient.getTrade(tradeId);
assertEquals(OFFER_FEE_PAID.name(), trade.getOffer().getState()); assertEquals(OFFER_FEE_RESERVED.name(), trade.getOffer().getState());
EXPECTED_PROTOCOL_STATUS.setState(SELLER_SAW_ARRIVED_PAYOUT_TX_PUBLISHED_MSG) EXPECTED_PROTOCOL_STATUS.setState(SELLER_SAW_ARRIVED_PAYOUT_TX_PUBLISHED_MSG)
.setPhase(PAYOUT_PUBLISHED) .setPhase(PAYOUT_PUBLISHED)
.setPayoutPublished(true) .setPayoutPublished(true)

View file

@ -119,13 +119,12 @@ class CoreOffersService {
} }
Offer getMyOffer(String id) { Offer getMyOffer(String id) {
Offer offer = offerBookService.getOffers().stream() return openOfferManager.getObservableList().stream()
.map(OpenOffer::getOffer)
.filter(o -> o.getId().equals(id)) .filter(o -> o.getId().equals(id))
.filter(o -> o.isMyOffer(keyRing)) .filter(o -> o.isMyOffer(keyRing))
.findAny().orElseThrow(() -> .findAny().orElseThrow(() ->
new IllegalStateException(format("offer with id '%s' not found", id))); new IllegalStateException(format("offer with id '%s' not found", id)));
setOpenOfferState(offer);
return offer;
} }
List<Offer> getOffers(String direction, String currencyCode) { List<Offer> getOffers(String direction, String currencyCode) {
@ -143,9 +142,10 @@ class CoreOffersService {
} }
List<Offer> getMyOffers(String direction, String currencyCode) { List<Offer> getMyOffers(String direction, String currencyCode) {
// get my offers posted to books // get my open offers
List<Offer> offers = offerBookService.getOffers().stream() List<Offer> offers = openOfferManager.getObservableList().stream()
.map(OpenOffer::getOffer)
.filter(o -> o.isMyOffer(keyRing)) .filter(o -> o.isMyOffer(keyRing))
.filter(o -> offerMatchesDirectionAndCurrency(o, direction, currencyCode)) .filter(o -> offerMatchesDirectionAndCurrency(o, direction, currencyCode))
.sorted(priceComparator(direction)) .sorted(priceComparator(direction))
@ -162,9 +162,6 @@ class CoreOffersService {
} }
openOfferManager.removeOpenOffers(unreservedOpenOffers, null); openOfferManager.removeOpenOffers(unreservedOpenOffers, null);
// set offer states
for (Offer offer : offers) setOpenOfferState(offer);
return offers; return offers;
} }
@ -174,6 +171,7 @@ class CoreOffersService {
// collect reserved key images and check for duplicate funds // collect reserved key images and check for duplicate funds
List<String> allKeyImages = new ArrayList<String>(); List<String> allKeyImages = new ArrayList<String>();
for (Offer offer : offers) { for (Offer offer : offers) {
if (offer.getOfferPayload().getReserveTxKeyImages() == null) continue;
for (String keyImage : offer.getOfferPayload().getReserveTxKeyImages()) { for (String keyImage : offer.getOfferPayload().getReserveTxKeyImages()) {
if (!allKeyImages.add(keyImage)) { if (!allKeyImages.add(keyImage)) {
log.warn("Key image {} belongs to another offer, removing offer {}", keyImage, offer.getId()); // TODO (woodser): this is list, not set, so not checking for duplicates log.warn("Key image {} belongs to another offer, removing offer {}", keyImage, offer.getId()); // TODO (woodser): this is list, not set, so not checking for duplicates
@ -192,6 +190,7 @@ class CoreOffersService {
// check for offers with spent key images // check for offers with spent key images
for (Offer offer : offers) { for (Offer offer : offers) {
if (offer.getOfferPayload().getReserveTxKeyImages() == null) continue;
if (unreservedOffers.contains(offer)) continue; if (unreservedOffers.contains(offer)) continue;
for (String keyImage : offer.getOfferPayload().getReserveTxKeyImages()) { for (String keyImage : offer.getOfferPayload().getReserveTxKeyImages()) {
if (spentKeyImages.contains(keyImage)) { if (spentKeyImages.contains(keyImage)) {
@ -256,7 +255,6 @@ class CoreOffersService {
boolean useSavingsWallet = true; boolean useSavingsWallet = true;
//noinspection ConstantConditions //noinspection ConstantConditions
placeOffer(offer, placeOffer(offer,
buyerSecurityDeposit,
triggerPriceAsString, triggerPriceAsString,
useSavingsWallet, useSavingsWallet,
transaction -> resultHandler.accept(offer), transaction -> resultHandler.accept(offer),
@ -308,14 +306,12 @@ class CoreOffersService {
} }
private void placeOffer(Offer offer, private void placeOffer(Offer offer,
double buyerSecurityDeposit,
String triggerPriceAsString, String triggerPriceAsString,
boolean useSavingsWallet, boolean useSavingsWallet,
Consumer<Transaction> resultHandler, Consumer<Transaction> resultHandler,
ErrorMessageHandler errorMessageHandler) { ErrorMessageHandler errorMessageHandler) {
long triggerPriceAsLong = PriceUtil.getMarketPriceAsLong(triggerPriceAsString, offer.getCurrencyCode()); long triggerPriceAsLong = PriceUtil.getMarketPriceAsLong(triggerPriceAsString, offer.getCurrencyCode());
openOfferManager.placeOffer(offer, openOfferManager.placeOffer(offer,
buyerSecurityDeposit,
useSavingsWallet, useSavingsWallet,
triggerPriceAsLong, triggerPriceAsLong,
resultHandler::accept, resultHandler::accept,
@ -331,11 +327,6 @@ class CoreOffersService {
return offerOfWantedDirection && offerInWantedCurrency; return offerOfWantedDirection && offerInWantedCurrency;
} }
private void setOpenOfferState(Offer offer) {
Optional<OpenOffer> openOffer = openOfferManager.getOpenOfferById(offer.getId());
if (openOffer.isPresent()) offer.setState(openOffer.get().getState() == OpenOffer.State.AVAILABLE ? Offer.State.AVAILABLE : Offer.State.NOT_AVAILABLE);
}
private Comparator<Offer> priceComparator(String direction) { private Comparator<Offer> priceComparator(String direction) {
// A buyer probably wants to see sell orders in price ascending order. // A buyer probably wants to see sell orders in price ascending order.
// A seller probably wants to see buy orders in price descending order. // A seller probably wants to see buy orders in price descending order.

View file

@ -20,9 +20,10 @@ package bisq.core.api.model;
import bisq.core.offer.Offer; import bisq.core.offer.Offer;
import bisq.common.Payload; import bisq.common.Payload;
import bisq.common.proto.ProtoUtil;
import java.util.Objects; import java.util.Objects;
import java.util.Optional;
import javax.annotation.Nullable;
import lombok.EqualsAndHashCode; import lombok.EqualsAndHashCode;
import lombok.Getter; import lombok.Getter;
import lombok.ToString; import lombok.ToString;
@ -47,6 +48,7 @@ public class OfferInfo implements Payload {
private final long minVolume; private final long minVolume;
private final long txFee; private final long txFee;
private final long makerFee; private final long makerFee;
@Nullable
private final String offerFeePaymentTxId; private final String offerFeePaymentTxId;
private final long buyerSecurityDeposit; private final long buyerSecurityDeposit;
private final long sellerSecurityDeposit; private final long sellerSecurityDeposit;
@ -129,7 +131,7 @@ public class OfferInfo implements Payload {
@Override @Override
public bisq.proto.grpc.OfferInfo toProtoMessage() { public bisq.proto.grpc.OfferInfo toProtoMessage() {
return bisq.proto.grpc.OfferInfo.newBuilder() bisq.proto.grpc.OfferInfo.Builder builder = bisq.proto.grpc.OfferInfo.newBuilder()
.setId(id) .setId(id)
.setDirection(direction) .setDirection(direction)
.setPrice(price) .setPrice(price)
@ -141,7 +143,6 @@ public class OfferInfo implements Payload {
.setMinVolume(minVolume) .setMinVolume(minVolume)
.setMakerFee(makerFee) .setMakerFee(makerFee)
.setTxFee(txFee) .setTxFee(txFee)
.setOfferFeePaymentTxId(offerFeePaymentTxId)
.setBuyerSecurityDeposit(buyerSecurityDeposit) .setBuyerSecurityDeposit(buyerSecurityDeposit)
.setSellerSecurityDeposit(sellerSecurityDeposit) .setSellerSecurityDeposit(sellerSecurityDeposit)
.setTriggerPrice(triggerPrice) .setTriggerPrice(triggerPrice)
@ -151,8 +152,9 @@ public class OfferInfo implements Payload {
.setBaseCurrencyCode(baseCurrencyCode) .setBaseCurrencyCode(baseCurrencyCode)
.setCounterCurrencyCode(counterCurrencyCode) .setCounterCurrencyCode(counterCurrencyCode)
.setDate(date) .setDate(date)
.setState(state) .setState(state);
.build(); Optional.ofNullable(offerFeePaymentTxId).ifPresent(builder::setOfferFeePaymentTxId);
return builder.build();
} }
@SuppressWarnings("unused") @SuppressWarnings("unused")
@ -169,7 +171,7 @@ public class OfferInfo implements Payload {
.withMinVolume(proto.getMinVolume()) .withMinVolume(proto.getMinVolume())
.withMakerFee(proto.getMakerFee()) .withMakerFee(proto.getMakerFee())
.withTxFee(proto.getTxFee()) .withTxFee(proto.getTxFee())
.withOfferFeePaymentTxId(proto.getOfferFeePaymentTxId()) .withOfferFeePaymentTxId(ProtoUtil.stringOrNullFromProto(proto.getOfferFeePaymentTxId()))
.withBuyerSecurityDeposit(proto.getBuyerSecurityDeposit()) .withBuyerSecurityDeposit(proto.getBuyerSecurityDeposit())
.withSellerSecurityDeposit(proto.getSellerSecurityDeposit()) .withSellerSecurityDeposit(proto.getSellerSecurityDeposit())
.withTriggerPrice(proto.getTriggerPrice()) .withTriggerPrice(proto.getTriggerPrice())

View file

@ -413,13 +413,8 @@ public class XmrWalletService {
System.out.println("Monero wallet balance: " + wallet.getBalance(0)); System.out.println("Monero wallet balance: " + wallet.getBalance(0));
System.out.println("Monero wallet unlocked balance: " + wallet.getUnlockedBalance(0)); System.out.println("Monero wallet unlocked balance: " + wallet.getUnlockedBalance(0));
// notify on balance changes // register internal listener to notify external listeners
wallet.addListener(new MoneroWalletListener() { wallet.addListener(new XmrWalletListener());
@Override
public void onBalancesChanged(BigInteger newBalance, BigInteger newUnlockedBalance) {
notifyBalanceListeners();
}
});
} }
} }
@ -758,6 +753,15 @@ public class XmrWalletService {
available = Stream.concat(available, getAddressEntries(XmrAddressEntry.Context.OFFER_FUNDING).stream()); available = Stream.concat(available, getAddressEntries(XmrAddressEntry.Context.OFFER_FUNDING).stream());
return available.filter(addressEntry -> getBalanceForSubaddress(addressEntry.getSubaddressIndex()).isPositive()); return available.filter(addressEntry -> getBalanceForSubaddress(addressEntry.getSubaddressIndex()).isPositive());
} }
public void addWalletListener(MoneroWalletListenerI listener) {
walletListeners.add(listener);
}
public void removeWalletListener(MoneroWalletListenerI listener) {
if (!walletListeners.contains(listener)) throw new RuntimeException("Listener is not registered with wallet");
walletListeners.remove(listener);
}
// TODO (woodser): update balance and other listening // TODO (woodser): update balance and other listening
public void addBalanceListener(XmrBalanceListener listener) { public void addBalanceListener(XmrBalanceListener listener) {
@ -786,26 +790,22 @@ public class XmrWalletService {
for (MoneroTxWallet tx : txs) sb.append('\n' + tx.toString()); for (MoneroTxWallet tx : txs) sb.append('\n' + tx.toString());
log.info("\n" + tracePrefix + ":" + sb.toString()); log.info("\n" + tracePrefix + ":" + sb.toString());
} }
// -------------------------------- HELPERS -------------------------------
/** /**
* Wraps a MoneroWalletListener to notify the Haveno application. * Processes internally before notifying external listeners.
* *
* TODO (woodser): this is no longer necessary since not syncing to thread? * TODO: no longer neccessary to execute on user thread?
*/ */
public class HavenoWalletListener extends MoneroWalletListener { private class XmrWalletListener extends MoneroWalletListener {
private MoneroWalletListener listener;
public HavenoWalletListener(MoneroWalletListener listener) {
this.listener = listener;
}
@Override @Override
public void onSyncProgress(long height, long startHeight, long endHeight, double percentDone, String message) { public void onSyncProgress(long height, long startHeight, long endHeight, double percentDone, String message) {
UserThread.execute(new Runnable() { UserThread.execute(new Runnable() {
@Override @Override
public void run() { public void run() {
listener.onSyncProgress(height, startHeight, endHeight, percentDone, message); for (MoneroWalletListenerI listener : walletListeners) listener.onSyncProgress(height, startHeight, endHeight, percentDone, message);
} }
}); });
} }
@ -815,7 +815,7 @@ public class XmrWalletService {
UserThread.execute(new Runnable() { UserThread.execute(new Runnable() {
@Override @Override
public void run() { public void run() {
listener.onNewBlock(height); for (MoneroWalletListenerI listener : walletListeners) listener.onNewBlock(height);
} }
}); });
} }
@ -825,7 +825,8 @@ public class XmrWalletService {
UserThread.execute(new Runnable() { UserThread.execute(new Runnable() {
@Override @Override
public void run() { public void run() {
listener.onBalancesChanged(newBalance, newUnlockedBalance); for (MoneroWalletListenerI listener : walletListeners) listener.onBalancesChanged(newBalance, newUnlockedBalance);
notifyBalanceListeners();
} }
}); });
} }
@ -835,7 +836,7 @@ public class XmrWalletService {
UserThread.execute(new Runnable() { UserThread.execute(new Runnable() {
@Override @Override
public void run() { public void run() {
listener.onOutputReceived(output); for (MoneroWalletListenerI listener : walletListeners) listener.onOutputReceived(output);
} }
}); });
} }
@ -845,7 +846,7 @@ public class XmrWalletService {
UserThread.execute(new Runnable() { UserThread.execute(new Runnable() {
@Override @Override
public void run() { public void run() {
listener.onOutputSpent(output); for (MoneroWalletListenerI listener : walletListeners) listener.onOutputSpent(output);
} }
}); });
} }

View file

@ -22,13 +22,13 @@ import bisq.core.locale.CurrencyUtil;
import bisq.core.monetary.Altcoin; import bisq.core.monetary.Altcoin;
import bisq.core.monetary.Price; import bisq.core.monetary.Price;
import bisq.core.monetary.Volume; import bisq.core.monetary.Volume;
import bisq.core.offer.OfferPayload.Direction;
import bisq.core.offer.availability.OfferAvailabilityModel; import bisq.core.offer.availability.OfferAvailabilityModel;
import bisq.core.offer.availability.OfferAvailabilityProtocol; import bisq.core.offer.availability.OfferAvailabilityProtocol;
import bisq.core.payment.payload.PaymentMethod; import bisq.core.payment.payload.PaymentMethod;
import bisq.core.provider.price.MarketPrice; import bisq.core.provider.price.MarketPrice;
import bisq.core.provider.price.PriceFeedService; import bisq.core.provider.price.PriceFeedService;
import bisq.core.util.VolumeUtil; import bisq.core.util.VolumeUtil;
import bisq.network.p2p.NodeAddress; import bisq.network.p2p.NodeAddress;
import bisq.common.crypto.KeyRing; import bisq.common.crypto.KeyRing;
@ -82,6 +82,7 @@ public class Offer implements NetworkPayload, PersistablePayload {
public enum State { public enum State {
UNKNOWN, UNKNOWN,
SCHEDULED,
OFFER_FEE_RESERVED, OFFER_FEE_RESERVED,
AVAILABLE, AVAILABLE,
NOT_AVAILABLE, NOT_AVAILABLE,
@ -257,6 +258,11 @@ public class Offer implements NetworkPayload, PersistablePayload {
/////////////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////////////
public void setState(Offer.State state) { public void setState(Offer.State state) {
try {
throw new RuntimeException("Setting offer state: " + state);
} catch (Exception e) {
e.printStackTrace();
}
stateProperty().set(state); stateProperty().set(state);
} }
@ -277,6 +283,15 @@ public class Offer implements NetworkPayload, PersistablePayload {
// Getter // Getter
/////////////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////////////
// get the amount needed for the maker to reserve the offer
public Coin getReserveAmount() {
Coin reserveAmount = getAmount();
reserveAmount = reserveAmount.add(getDirection() == Direction.BUY ?
getBuyerSecurityDeposit() :
getSellerSecurityDeposit());
return reserveAmount;
}
// converted payload properties // converted payload properties
public Coin getTxFee() { public Coin getTxFee() {
return Coin.valueOf(offerPayload.getTxFee()); return Coin.valueOf(offerPayload.getTxFee());

View file

@ -297,9 +297,7 @@ public final class OfferPayload implements ProtectedStoragePayload, ExpirablePay
.setProtocolVersion(protocolVersion) .setProtocolVersion(protocolVersion)
.setArbitratorSigner(arbitratorSigner.toProtoMessage()); .setArbitratorSigner(arbitratorSigner.toProtoMessage());
builder.setOfferFeePaymentTxId(checkNotNull(offerFeePaymentTxId, Optional.ofNullable(offerFeePaymentTxId).ifPresent(builder::setOfferFeePaymentTxId);
"OfferPayload is in invalid state: offerFeePaymentTxID is not set when adding to P2P network."));
Optional.ofNullable(countryCode).ifPresent(builder::setCountryCode); Optional.ofNullable(countryCode).ifPresent(builder::setCountryCode);
Optional.ofNullable(bankId).ifPresent(builder::setBankId); Optional.ofNullable(bankId).ifPresent(builder::setBankId);
Optional.ofNullable(acceptedBankIds).ifPresent(builder::addAllAcceptedBankIds); Optional.ofNullable(acceptedBankIds).ifPresent(builder::addAllAcceptedBankIds);
@ -313,7 +311,6 @@ public final class OfferPayload implements ProtectedStoragePayload, ExpirablePay
} }
public static OfferPayload fromProto(protobuf.OfferPayload proto) { public static OfferPayload fromProto(protobuf.OfferPayload proto) {
checkArgument(!proto.getOfferFeePaymentTxId().isEmpty(), "OfferFeePaymentTxId must be set in PB.OfferPayload");
List<String> acceptedBankIds = proto.getAcceptedBankIdsList().isEmpty() ? List<String> acceptedBankIds = proto.getAcceptedBankIdsList().isEmpty() ?
null : new ArrayList<>(proto.getAcceptedBankIdsList()); null : new ArrayList<>(proto.getAcceptedBankIdsList());
List<String> acceptedCountryCodes = proto.getAcceptedCountryCodesList().isEmpty() ? List<String> acceptedCountryCodes = proto.getAcceptedCountryCodesList().isEmpty() ?
@ -336,7 +333,7 @@ public final class OfferPayload implements ProtectedStoragePayload, ExpirablePay
proto.getCounterCurrencyCode(), proto.getCounterCurrencyCode(),
proto.getPaymentMethodId(), proto.getPaymentMethodId(),
proto.getMakerPaymentAccountId(), proto.getMakerPaymentAccountId(),
proto.getOfferFeePaymentTxId(), ProtoUtil.stringOrNullFromProto(proto.getOfferFeePaymentTxId()),
ProtoUtil.stringOrNullFromProto(proto.getCountryCode()), ProtoUtil.stringOrNullFromProto(proto.getCountryCode()),
acceptedCountryCodes, acceptedCountryCodes,
ProtoUtil.stringOrNullFromProto(proto.getBankId()), ProtoUtil.stringOrNullFromProto(proto.getBankId()),

View file

@ -25,6 +25,7 @@ import bisq.common.Timer;
import bisq.common.UserThread; import bisq.common.UserThread;
import bisq.common.proto.ProtoUtil; import bisq.common.proto.ProtoUtil;
import java.util.Date; import java.util.Date;
import java.util.List;
import java.util.Optional; import java.util.Optional;
import lombok.EqualsAndHashCode; import lombok.EqualsAndHashCode;
@ -42,6 +43,7 @@ public final class OpenOffer implements Tradable {
transient private Timer timeoutTimer; transient private Timer timeoutTimer;
public enum State { public enum State {
SCHEDULED,
AVAILABLE, AVAILABLE,
RESERVED, RESERVED,
CLOSED, CLOSED,
@ -59,10 +61,24 @@ public final class OpenOffer implements Tradable {
private NodeAddress backupArbitrator; private NodeAddress backupArbitrator;
@Setter @Setter
@Getter @Getter
private boolean autoSplit;
@Setter
@Getter
@Nullable
private String scheduledAmount;
@Setter
@Getter
@Nullable
private List<String> scheduledTxHashes;
@Nullable
@Setter
@Getter
private String reserveTxHash; private String reserveTxHash;
@Nullable
@Setter @Setter
@Getter @Getter
private String reserveTxHex; private String reserveTxHex;
@Nullable
@Setter @Setter
@Getter @Getter
private String reserveTxKey; private String reserveTxKey;
@ -77,26 +93,18 @@ public final class OpenOffer implements Tradable {
transient private long mempoolStatus = -1; transient private long mempoolStatus = -1;
public OpenOffer(Offer offer) { public OpenOffer(Offer offer) {
this(offer, 0); this(offer, 0, false);
} }
public OpenOffer(Offer offer, long triggerPrice) { public OpenOffer(Offer offer, long triggerPrice) {
this.offer = offer; this(offer, triggerPrice, false);
this.triggerPrice = triggerPrice;
state = State.AVAILABLE;
} }
public OpenOffer(Offer offer, public OpenOffer(Offer offer, long triggerPrice, boolean autoSplit) {
long triggerPrice,
String reserveTxHash,
String reserveTxHex,
String reserveTxKey) {
this.offer = offer; this.offer = offer;
this.triggerPrice = triggerPrice; this.triggerPrice = triggerPrice;
state = State.AVAILABLE; this.autoSplit = autoSplit;
this.reserveTxHash = reserveTxHash; state = State.SCHEDULED;
this.reserveTxHex = reserveTxHex;
this.reserveTxKey = reserveTxKey;
} }
/////////////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////////////
@ -107,13 +115,18 @@ public final class OpenOffer implements Tradable {
State state, State state,
@Nullable NodeAddress backupArbitrator, @Nullable NodeAddress backupArbitrator,
long triggerPrice, long triggerPrice,
String reserveTxHash, boolean autoSplit,
String reserveTxHex, @Nullable String scheduledAmount,
String reserveTxKey) { @Nullable List<String> scheduledTxHashes,
@Nullable String reserveTxHash,
@Nullable String reserveTxHex,
@Nullable String reserveTxKey) {
this.offer = offer; this.offer = offer;
this.state = state; this.state = state;
this.backupArbitrator = backupArbitrator; this.backupArbitrator = backupArbitrator;
this.triggerPrice = triggerPrice; this.triggerPrice = triggerPrice;
this.autoSplit = autoSplit;
this.scheduledTxHashes = scheduledTxHashes;
this.reserveTxHash = reserveTxHash; this.reserveTxHash = reserveTxHash;
this.reserveTxHex = reserveTxHex; this.reserveTxHex = reserveTxHex;
this.reserveTxKey = reserveTxKey; this.reserveTxKey = reserveTxKey;
@ -128,11 +141,14 @@ public final class OpenOffer implements Tradable {
.setOffer(offer.toProtoMessage()) .setOffer(offer.toProtoMessage())
.setTriggerPrice(triggerPrice) .setTriggerPrice(triggerPrice)
.setState(protobuf.OpenOffer.State.valueOf(state.name())) .setState(protobuf.OpenOffer.State.valueOf(state.name()))
.setReserveTxHash(reserveTxHash) .setAutoSplit(autoSplit);
.setReserveTxHex(reserveTxHex)
.setReserveTxKey(reserveTxKey);
Optional.ofNullable(scheduledAmount).ifPresent(e -> builder.setScheduledAmount(scheduledAmount));
Optional.ofNullable(backupArbitrator).ifPresent(nodeAddress -> builder.setBackupArbitrator(nodeAddress.toProtoMessage())); Optional.ofNullable(backupArbitrator).ifPresent(nodeAddress -> builder.setBackupArbitrator(nodeAddress.toProtoMessage()));
Optional.ofNullable(scheduledTxHashes).ifPresent(e -> builder.addAllScheduledTxHashes(scheduledTxHashes));
Optional.ofNullable(reserveTxHash).ifPresent(e -> builder.setReserveTxHash(reserveTxHash));
Optional.ofNullable(reserveTxHex).ifPresent(e -> builder.setReserveTxHex(reserveTxHex));
Optional.ofNullable(reserveTxKey).ifPresent(e -> builder.setReserveTxKey(reserveTxKey));
return protobuf.Tradable.newBuilder().setOpenOffer(builder).build(); return protobuf.Tradable.newBuilder().setOpenOffer(builder).build();
} }
@ -142,6 +158,9 @@ public final class OpenOffer implements Tradable {
ProtoUtil.enumFromProto(OpenOffer.State.class, proto.getState().name()), ProtoUtil.enumFromProto(OpenOffer.State.class, proto.getState().name()),
proto.hasBackupArbitrator() ? NodeAddress.fromProto(proto.getBackupArbitrator()) : null, proto.hasBackupArbitrator() ? NodeAddress.fromProto(proto.getBackupArbitrator()) : null,
proto.getTriggerPrice(), proto.getTriggerPrice(),
proto.getAutoSplit(),
proto.getScheduledAmount(),
proto.getScheduledTxHashesList(),
proto.getReserveTxHash(), proto.getReserveTxHash(),
proto.getReserveTxHex(), proto.getReserveTxHex(),
proto.getReserveTxKey()); proto.getReserveTxKey());
@ -172,7 +191,7 @@ public final class OpenOffer implements Tradable {
this.state = state; this.state = state;
// We keep it reserved for a limited time, if trade preparation fails we revert to available state // We keep it reserved for a limited time, if trade preparation fails we revert to available state
if (this.state == State.RESERVED) { if (this.state == State.RESERVED) { // TODO (woodser): remove this?
startTimeout(); startTimeout();
} else { } else {
stopTimeout(); stopTimeout();

View file

@ -38,6 +38,7 @@ import bisq.core.support.dispute.arbitration.arbitrator.ArbitratorManager;
import bisq.core.support.dispute.mediation.mediator.Mediator; import bisq.core.support.dispute.mediation.mediator.Mediator;
import bisq.core.support.dispute.mediation.mediator.MediatorManager; import bisq.core.support.dispute.mediation.mediator.MediatorManager;
import bisq.core.trade.TradableList; import bisq.core.trade.TradableList;
import bisq.core.trade.TradeUtils;
import bisq.core.trade.closed.ClosedTradableManager; import bisq.core.trade.closed.ClosedTradableManager;
import bisq.core.trade.handlers.TransactionResultHandler; import bisq.core.trade.handlers.TransactionResultHandler;
import bisq.core.trade.statistics.TradeStatisticsManager; import bisq.core.trade.statistics.TradeStatisticsManager;
@ -85,6 +86,7 @@ import java.util.Map;
import java.util.Optional; import java.util.Optional;
import java.util.Set; import java.util.Set;
import java.util.UUID; import java.util.UUID;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors; import java.util.stream.Collectors;
@ -92,6 +94,10 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import lombok.Getter; import lombok.Getter;
import monero.wallet.model.MoneroIncomingTransfer;
import monero.wallet.model.MoneroTxQuery;
import monero.wallet.model.MoneroTxWallet;
import monero.wallet.model.MoneroWalletListener;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import javax.annotation.Nullable; import javax.annotation.Nullable;
@ -107,7 +113,6 @@ public class OpenOfferManager implements PeerManager.Listener, DecryptedDirectMe
private static final long REFRESH_INTERVAL_MS = TimeUnit.MINUTES.toMillis(6); private static final long REFRESH_INTERVAL_MS = TimeUnit.MINUTES.toMillis(6);
private final CoreContext coreContext; private final CoreContext coreContext;
private final CreateOfferService createOfferService;
private final KeyRing keyRing; private final KeyRing keyRing;
private final User user; private final User user;
private final P2PService p2PService; private final P2PService p2PService;
@ -130,6 +135,7 @@ public class OpenOfferManager implements PeerManager.Listener, DecryptedDirectMe
private final SignedOfferList signedOffers = new SignedOfferList(); private final SignedOfferList signedOffers = new SignedOfferList();
private final PersistenceManager<SignedOfferList> signedOfferPersistenceManager; private final PersistenceManager<SignedOfferList> signedOfferPersistenceManager;
private final Map<String, PlaceOfferProtocol> placeOfferProtocols = new HashMap<String, PlaceOfferProtocol>(); private final Map<String, PlaceOfferProtocol> placeOfferProtocols = new HashMap<String, PlaceOfferProtocol>();
private BigInteger lastUnlockedBalance;
private boolean stopped; private boolean stopped;
private Timer periodicRepublishOffersTimer, periodicRefreshOffersTimer, retryRepublishOffersTimer; private Timer periodicRepublishOffersTimer, periodicRefreshOffersTimer, retryRepublishOffersTimer;
@Getter @Getter
@ -142,7 +148,6 @@ public class OpenOfferManager implements PeerManager.Listener, DecryptedDirectMe
@Inject @Inject
public OpenOfferManager(CoreContext coreContext, public OpenOfferManager(CoreContext coreContext,
CreateOfferService createOfferService,
KeyRing keyRing, KeyRing keyRing,
User user, User user,
P2PService p2PService, P2PService p2PService,
@ -162,7 +167,6 @@ public class OpenOfferManager implements PeerManager.Listener, DecryptedDirectMe
PersistenceManager<TradableList<OpenOffer>> persistenceManager, PersistenceManager<TradableList<OpenOffer>> persistenceManager,
PersistenceManager<SignedOfferList> signedOfferPersistenceManager) { PersistenceManager<SignedOfferList> signedOfferPersistenceManager) {
this.coreContext = coreContext; this.coreContext = coreContext;
this.createOfferService = createOfferService;
this.keyRing = keyRing; this.keyRing = keyRing;
this.user = user; this.user = user;
this.p2PService = p2PService; this.p2PService = p2PService;
@ -223,6 +227,25 @@ public class OpenOfferManager implements PeerManager.Listener, DecryptedDirectMe
openOffers.stream() openOffers.stream()
.forEach(openOffer -> OfferUtil.getInvalidMakerFeeTxErrorMessage(openOffer.getOffer(), btcWalletService) .forEach(openOffer -> OfferUtil.getInvalidMakerFeeTxErrorMessage(openOffer.getOffer(), btcWalletService)
.ifPresent(errorMsg -> invalidOffers.add(new Tuple2<>(openOffer, errorMsg)))); .ifPresent(errorMsg -> invalidOffers.add(new Tuple2<>(openOffer, errorMsg))));
// process unposted offers
lastUnlockedBalance = xmrWalletService.getWallet().getUnlockedBalance(0);
processUnpostedOffers((transaction) -> {}, (errMessage) -> {
log.warn("Error processing unposted offers on new unlocked balance: " + errMessage);
});
// register to process unposted offers when unlocked balance increases
xmrWalletService.addWalletListener(new MoneroWalletListener() {
@Override
public void onBalancesChanged(BigInteger newBalance, BigInteger newUnlockedBalance) {
if (lastUnlockedBalance.compareTo(newUnlockedBalance) < 0) {
processUnpostedOffers((transaction) -> {}, (errMessage) -> {
log.warn("Error processing unposted offers on new unlocked balance: " + errMessage);
});
}
lastUnlockedBalance = newUnlockedBalance;
}
});
} }
private void cleanUpAddressEntries() { private void cleanUpAddressEntries() {
@ -384,55 +407,27 @@ public class OpenOfferManager implements PeerManager.Listener, DecryptedDirectMe
/////////////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////////////
public void placeOffer(Offer offer, public void placeOffer(Offer offer,
double buyerSecurityDeposit,
boolean useSavingsWallet, boolean useSavingsWallet,
long triggerPrice, long triggerPrice,
TransactionResultHandler resultHandler, TransactionResultHandler resultHandler,
ErrorMessageHandler errorMessageHandler) { ErrorMessageHandler errorMessageHandler) {
checkNotNull(offer.getMakerFee(), "makerFee must not be null"); checkNotNull(offer.getMakerFee(), "makerFee must not be null");
Coin reservedFundsForOffer = createOfferService.getReservedFundsForOffer(offer.getDirection(), boolean autoSplit = false; // TODO: support in api
offer.getAmount(),
buyerSecurityDeposit, // TODO (woodser): validate offer
createOfferService.getSellerSecurityDepositAsDouble(buyerSecurityDeposit));
// create open offer
PlaceOfferModel model = new PlaceOfferModel(offer, OpenOffer openOffer = new OpenOffer(offer, triggerPrice, autoSplit);
reservedFundsForOffer,
useSavingsWallet, // process open offer to schedule or post
p2PService, processUnpostedOffer(openOffer, (transaction) -> {
btcWalletService, openOffers.add(openOffer);
xmrWalletService, requestPersistence();
tradeWalletService, resultHandler.handleResult(transaction);
offerBookService, }, (errMessage) -> {
arbitratorManager, errorMessageHandler.handleErrorMessage(errMessage);
mediatorManager, });
tradeStatisticsManager,
user,
keyRing,
filterManager);
PlaceOfferProtocol placeOfferProtocol = new PlaceOfferProtocol(
model,
transaction -> {
// save reserve tx with open offer
OpenOffer openOffer = new OpenOffer(offer, triggerPrice, model.getReserveTx().getHash(), model.getReserveTx().getFullHex(), model.getReserveTx().getKey());
openOffers.add(openOffer);
requestPersistence();
resultHandler.handleResult(transaction);
if (!stopped) {
startPeriodicRepublishOffersTimer();
startPeriodicRefreshOffersTimer();
} else {
log.debug("We have stopped already. We ignore that placeOfferProtocol.placeOffer.onResult call.");
}
},
errorMessageHandler
);
synchronized (placeOfferProtocols) {
placeOfferProtocols.put(offer.getId(), placeOfferProtocol);
}
placeOfferProtocol.placeOffer(); // TODO (woodser): if error placing offer (e.g. bad signature), remove protocol and unfreeze trade funds
} }
// Remove from offerbook // Remove from offerbook
@ -442,11 +437,8 @@ public class OpenOfferManager implements PeerManager.Listener, DecryptedDirectMe
removeOpenOffer(openOfferOptional.get(), resultHandler, errorMessageHandler); removeOpenOffer(openOfferOptional.get(), resultHandler, errorMessageHandler);
} else { } else {
log.warn("Offer was not found in our list of open offers. We still try to remove it from the offerbook."); log.warn("Offer was not found in our list of open offers. We still try to remove it from the offerbook.");
errorMessageHandler.handleErrorMessage("Offer was not found in our list of open offers. " + errorMessageHandler.handleErrorMessage("Offer was not found in our list of open offers. " + "We still try to remove it from the offerbook.");
"We still try to remove it from the offerbook."); offerBookService.removeOffer(offer.getOfferPayload(), () -> offer.setState(Offer.State.REMOVED), null);
offerBookService.removeOffer(offer.getOfferPayload(),
() -> offer.setState(Offer.State.REMOVED),
null);
} }
} }
@ -569,7 +561,9 @@ public class OpenOfferManager implements PeerManager.Listener, DecryptedDirectMe
} }
private void onRemoved(@NotNull OpenOffer openOffer, ResultHandler resultHandler, Offer offer) { private void onRemoved(@NotNull OpenOffer openOffer, ResultHandler resultHandler, Offer offer) {
for (String frozenKeyImage : offer.getOfferPayload().getReserveTxKeyImages()) xmrWalletService.getWallet().thawOutput(frozenKeyImage); if (offer.getOfferPayload().getReserveTxKeyImages() != null) {
for (String frozenKeyImage : offer.getOfferPayload().getReserveTxKeyImages()) xmrWalletService.getWallet().thawOutput(frozenKeyImage);
}
offer.setState(Offer.State.REMOVED); offer.setState(Offer.State.REMOVED);
openOffer.setState(OpenOffer.State.CANCELED); openOffer.setState(OpenOffer.State.CANCELED);
openOffers.remove(openOffer); openOffers.remove(openOffer);
@ -622,6 +616,166 @@ public class OpenOfferManager implements PeerManager.Listener, DecryptedDirectMe
return signedOffers.stream().filter(e -> e.getOfferId().equals(offerId)).findFirst(); return signedOffers.stream().filter(e -> e.getOfferId().equals(offerId)).findFirst();
} }
///////////////////////////////////////////////////////////////////////////////////////////
// Place offer helpers
///////////////////////////////////////////////////////////////////////////////////////////
private void processUnpostedOffers(TransactionResultHandler resultHandler, // TODO (woodser): transaction not needed with result handler
ErrorMessageHandler errorMessageHandler) {
List<String> errorMessages = new ArrayList<String>();
for (OpenOffer scheduledOffer : openOffers.getObservableList()) {
if (scheduledOffer.getState() != OpenOffer.State.SCHEDULED) continue;
CountDownLatch latch = new CountDownLatch(openOffers.list.size());
processUnpostedOffer(scheduledOffer, (transaction) -> {
latch.countDown();
}, errorMessage -> {
latch.countDown();
errorMessages.add(errorMessage);
});
TradeUtils.waitForLatch(latch);
}
requestPersistence();
if (errorMessages.size() > 0) errorMessageHandler.handleErrorMessage(errorMessages.toString());
else resultHandler.handleResult(null);
}
private void processUnpostedOffer(OpenOffer openOffer, TransactionResultHandler resultHandler, ErrorMessageHandler errorMessageHandler) {
try {
// get offer reserve amount
Coin offerReserveAmountCoin = openOffer.getOffer().getReserveAmount();
BigInteger offerReserveAmount = ParsingUtils.centinerosToAtomicUnits(offerReserveAmountCoin.value);
// handle sufficient available balance
if (xmrWalletService.getWallet().getUnlockedBalance(0).compareTo(offerReserveAmount) >= 0) {
// split outputs if applicable
boolean splitOutput = openOffer.isAutoSplit(); // TODO: determine if output needs split
if (splitOutput) {
throw new Error("Post offer with split output option not yet supported"); // TODO: support scheduling offer with split outputs
}
// otherwise sign and post offer
else {
signAndPostOffer(openOffer, offerReserveAmountCoin, true, resultHandler, errorMessageHandler);
}
return;
}
// handle unscheduled offer
if (openOffer.getScheduledTxHashes() == null) {
// check for sufficient balance - scheduled offers amount
if (xmrWalletService.getWallet().getBalance(0).subtract(getScheduledAmount()).compareTo(offerReserveAmount) < 0) {
throw new RuntimeException("Not enough money in Haveno wallet");
}
// get locked txs
List<MoneroTxWallet> lockedTxs = xmrWalletService.getWallet().getTxs(new MoneroTxQuery().setIsLocked(true));
// get earliest unscheduled txs with sufficient incoming amount
List<String> scheduledTxHashes = new ArrayList<String>();
BigInteger scheduledAmount = new BigInteger("0");
for (MoneroTxWallet lockedTx : lockedTxs) {
if (isTxScheduled(lockedTx.getHash())) continue;
if (lockedTx.getIncomingTransfers() == null || lockedTx.getIncomingTransfers().isEmpty()) continue;
scheduledTxHashes.add(lockedTx.getHash());
for (MoneroIncomingTransfer transfer : lockedTx.getIncomingTransfers()) {
if (transfer.getAccountIndex() == 0) scheduledAmount = scheduledAmount.add(transfer.getAmount());
}
if (scheduledAmount.compareTo(offerReserveAmount) >= 0) break;
}
if (scheduledAmount.compareTo(offerReserveAmount) < 0) throw new Error("Not enough funds to schedule offer");
// schedule txs
openOffer.setScheduledTxHashes(scheduledTxHashes);
openOffer.setScheduledAmount(scheduledAmount.toString());
openOffer.getOffer().setState(Offer.State.SCHEDULED);
}
// handle result
resultHandler.handleResult(null);
} catch (Exception e) {
errorMessageHandler.handleErrorMessage(e.getMessage());
}
}
private BigInteger getScheduledAmount() {
BigInteger scheduledAmount = new BigInteger("0");
for (OpenOffer openOffer : openOffers.getObservableList()) {
if (openOffer.getState() != OpenOffer.State.SCHEDULED) continue;
if (openOffer.getScheduledTxHashes() == null) continue;
List<MoneroTxWallet> fundingTxs = xmrWalletService.getWallet().getTxs(openOffer.getScheduledTxHashes());
for (MoneroTxWallet fundingTx : fundingTxs) {
for (MoneroIncomingTransfer transfer : fundingTx.getIncomingTransfers()) {
if (transfer.getAccountIndex() == 0) scheduledAmount = scheduledAmount.add(transfer.getAmount());
}
}
}
return scheduledAmount;
}
private boolean isTxScheduled(String txHash) {
for (OpenOffer openOffer : openOffers.getObservableList()) {
if (openOffer.getState() != OpenOffer.State.SCHEDULED) continue;
if (openOffer.getScheduledTxHashes() == null) continue;
for (String scheduledTxHash : openOffer.getScheduledTxHashes()) {
if (txHash.equals(scheduledTxHash)) return true;
}
}
return false;
}
private void signAndPostOffer(OpenOffer openOffer,
Coin offerReserveAmount, // TODO: switch to BigInteger
boolean useSavingsWallet, // TODO: remove this
TransactionResultHandler resultHandler, ErrorMessageHandler errorMessageHandler) {
// create model
PlaceOfferModel model = new PlaceOfferModel(openOffer.getOffer(),
offerReserveAmount,
useSavingsWallet,
p2PService,
btcWalletService,
xmrWalletService,
tradeWalletService,
offerBookService,
arbitratorManager,
mediatorManager,
tradeStatisticsManager,
user,
keyRing,
filterManager);
// create protocol
PlaceOfferProtocol placeOfferProtocol = new PlaceOfferProtocol(model,
transaction -> {
// set reserve tx on open offer
openOffer.setReserveTxHash(model.getReserveTx().getHash());
openOffer.setReserveTxHex(model.getReserveTx().getHash());
openOffer.setReserveTxKey(model.getReserveTx().getKey());
// set offer state
openOffer.setState(OpenOffer.State.AVAILABLE);
resultHandler.handleResult(transaction);
if (!stopped) {
startPeriodicRepublishOffersTimer();
startPeriodicRefreshOffersTimer();
} else {
log.debug("We have stopped already. We ignore that placeOfferProtocol.placeOffer.onResult call.");
}
},
errorMessageHandler);
// run protocol
synchronized (placeOfferProtocols) {
placeOfferProtocols.put(openOffer.getOffer().getId(), placeOfferProtocol);
}
placeOfferProtocol.placeOffer(); // TODO (woodser): if error placing offer (e.g. bad signature), remove protocol and unfreeze trade funds
}
/////////////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////////////
// Arbitrator Signs Offer // Arbitrator Signs Offer
/////////////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////////////

View file

@ -19,7 +19,7 @@ package bisq.core.offer.placeoffer;
import bisq.core.offer.messages.SignOfferResponse; import bisq.core.offer.messages.SignOfferResponse;
import bisq.core.offer.placeoffer.tasks.AddToOfferBook; import bisq.core.offer.placeoffer.tasks.AddToOfferBook;
import bisq.core.offer.placeoffer.tasks.MakerReservesTradeFunds; import bisq.core.offer.placeoffer.tasks.MakerReservesOfferFunds;
import bisq.core.offer.placeoffer.tasks.MakerSendsSignOfferRequest; import bisq.core.offer.placeoffer.tasks.MakerSendsSignOfferRequest;
import bisq.core.offer.placeoffer.tasks.MakerProcessesSignOfferResponse; import bisq.core.offer.placeoffer.tasks.MakerProcessesSignOfferResponse;
import bisq.core.offer.placeoffer.tasks.ValidateOffer; import bisq.core.offer.placeoffer.tasks.ValidateOffer;
@ -56,7 +56,6 @@ public class PlaceOfferProtocol {
// Called from UI // Called from UI
/////////////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////////////
// TODO (woodser): this returns before offer is placed
public void placeOffer() { public void placeOffer() {
log.debug("placeOffer() " + model.getOffer().getId()); log.debug("placeOffer() " + model.getOffer().getId());
TaskRunner<PlaceOfferModel> taskRunner = new TaskRunner<>(model, TaskRunner<PlaceOfferModel> taskRunner = new TaskRunner<>(model,
@ -71,7 +70,7 @@ public class PlaceOfferProtocol {
); );
taskRunner.addTasks( taskRunner.addTasks(
ValidateOffer.class, ValidateOffer.class,
MakerReservesTradeFunds.class, MakerReservesOfferFunds.class,
MakerSendsSignOfferRequest.class MakerSendsSignOfferRequest.class
); );

View file

@ -29,9 +29,9 @@ import java.util.List;
import monero.daemon.model.MoneroOutput; import monero.daemon.model.MoneroOutput;
import monero.wallet.model.MoneroTxWallet; import monero.wallet.model.MoneroTxWallet;
public class MakerReservesTradeFunds extends Task<PlaceOfferModel> { public class MakerReservesOfferFunds extends Task<PlaceOfferModel> {
public MakerReservesTradeFunds(TaskRunner taskHandler, PlaceOfferModel model) { public MakerReservesOfferFunds(TaskRunner taskHandler, PlaceOfferModel model) {
super(taskHandler, model); super(taskHandler, model);
} }
@ -43,7 +43,7 @@ public class MakerReservesTradeFunds extends Task<PlaceOfferModel> {
try { try {
runInterceptHook(); runInterceptHook();
// freeze trade funds and get reserve tx // freeze offer funds and get reserve tx
String returnAddress = model.getXmrWalletService().getOrCreateAddressEntry(offer.getId(), XmrAddressEntry.Context.TRADE_PAYOUT).getAddressString(); String returnAddress = model.getXmrWalletService().getOrCreateAddressEntry(offer.getId(), XmrAddressEntry.Context.TRADE_PAYOUT).getAddressString();
BigInteger makerFee = ParsingUtils.coinToAtomicUnits(offer.getMakerFee()); BigInteger makerFee = ParsingUtils.coinToAtomicUnits(offer.getMakerFee());
BigInteger depositAmount = ParsingUtils.coinToAtomicUnits(model.getReservedFundsForOffer()); BigInteger depositAmount = ParsingUtils.coinToAtomicUnits(model.getReservedFundsForOffer());

View file

@ -901,7 +901,7 @@ public abstract class Trade implements Tradable, Model {
} }
// create block listener // create block listener
depositTxListener = processModel.getXmrWalletService().new HavenoWalletListener(new MoneroWalletListener() { // TODO (woodser): separate into own class file depositTxListener = new MoneroWalletListener() {
Long unlockHeight = null; Long unlockHeight = null;
@ -939,14 +939,14 @@ public abstract class Trade implements Tradable, Model {
if (unlockHeight != null && height == unlockHeight) { if (unlockHeight != null && height == unlockHeight) {
log.info("Multisig deposits unlocked for trade {}", getId()); log.info("Multisig deposits unlocked for trade {}", getId());
setConfirmedState(); // TODO (woodser): bisq "confirmed" = xmr unlocked after 10 confirmations setConfirmedState(); // TODO (woodser): bisq "confirmed" = xmr unlocked after 10 confirmations
havenoWallet.removeListener(depositTxListener); // remove listener when notified xmrWalletService.removeWalletListener(depositTxListener); // remove listener when notified
depositTxListener = null; // prevent re-applying trade state in subsequent requests depositTxListener = null; // prevent re-applying trade state in subsequent requests
} }
} }
}); };
// register wallet listener // register wallet listener
havenoWallet.addListener(depositTxListener); xmrWalletService.addWalletListener(depositTxListener);
} }
@Nullable @Nullable

View file

@ -27,6 +27,7 @@ import bisq.core.offer.OfferPayload;
import bisq.core.support.dispute.arbitration.arbitrator.Arbitrator; import bisq.core.support.dispute.arbitration.arbitrator.Arbitrator;
import bisq.core.trade.messages.InitTradeRequest; import bisq.core.trade.messages.InitTradeRequest;
import java.util.Objects; import java.util.Objects;
import java.util.concurrent.CountDownLatch;
/** /**
* Collection of utilities for trading. * Collection of utilities for trading.
@ -173,4 +174,12 @@ public class TradeUtils {
// //
// return new Tuple2<>(multiSigAddress.getAddressString(), payoutAddress); // return new Tuple2<>(multiSigAddress.getAddressString(), payoutAddress);
} }
public static void waitForLatch(CountDownLatch latch) {
try {
latch.await();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
} }

View file

@ -2,6 +2,7 @@ package bisq.core.trade.protocol;
import bisq.core.trade.ArbitratorTrade; import bisq.core.trade.ArbitratorTrade;
import bisq.core.trade.Trade; import bisq.core.trade.Trade;
import bisq.core.trade.TradeUtils;
import bisq.core.trade.messages.DepositRequest; import bisq.core.trade.messages.DepositRequest;
import bisq.core.trade.messages.InitMultisigRequest; import bisq.core.trade.messages.InitMultisigRequest;
import bisq.core.trade.messages.InitTradeRequest; import bisq.core.trade.messages.InitTradeRequest;
@ -59,7 +60,7 @@ public class ArbitratorProtocol extends DisputeProtocol {
})) }))
.withTimeout(TRADE_TIMEOUT)) .withTimeout(TRADE_TIMEOUT))
.executeTasks(); .executeTasks();
wait(latch); TradeUtils.waitForLatch(latch);
} }
} }
@ -87,7 +88,7 @@ public class ArbitratorProtocol extends DisputeProtocol {
})) }))
.withTimeout(TRADE_TIMEOUT)) .withTimeout(TRADE_TIMEOUT))
.executeTasks(); .executeTasks();
wait(latch); TradeUtils.waitForLatch(latch);
} }
} }
@ -116,7 +117,7 @@ public class ArbitratorProtocol extends DisputeProtocol {
})) }))
.withTimeout(TRADE_TIMEOUT)) .withTimeout(TRADE_TIMEOUT))
.executeTasks(); .executeTasks();
wait(latch); TradeUtils.waitForLatch(latch);
} }
} }
@ -144,7 +145,7 @@ public class ArbitratorProtocol extends DisputeProtocol {
})) }))
.withTimeout(TRADE_TIMEOUT)) .withTimeout(TRADE_TIMEOUT))
.executeTasks(); .executeTasks();
wait(latch); TradeUtils.waitForLatch(latch);
} }
} }

View file

@ -20,6 +20,7 @@ package bisq.core.trade.protocol;
import bisq.core.trade.BuyerAsMakerTrade; import bisq.core.trade.BuyerAsMakerTrade;
import bisq.core.trade.Trade; import bisq.core.trade.Trade;
import bisq.core.trade.Trade.State; import bisq.core.trade.Trade.State;
import bisq.core.trade.TradeUtils;
import bisq.core.trade.messages.DelayedPayoutTxSignatureRequest; import bisq.core.trade.messages.DelayedPayoutTxSignatureRequest;
import bisq.core.trade.messages.DepositResponse; import bisq.core.trade.messages.DepositResponse;
import bisq.core.trade.messages.DepositTxAndDelayedPayoutTxMessage; import bisq.core.trade.messages.DepositTxAndDelayedPayoutTxMessage;
@ -100,7 +101,7 @@ public class BuyerAsMakerProtocol extends BuyerProtocol implements MakerProtocol
})) }))
.withTimeout(TRADE_TIMEOUT)) .withTimeout(TRADE_TIMEOUT))
.executeTasks(); .executeTasks();
wait(latch); TradeUtils.waitForLatch(latch);
} }
} }
@ -129,7 +130,7 @@ public class BuyerAsMakerProtocol extends BuyerProtocol implements MakerProtocol
})) }))
.withTimeout(TRADE_TIMEOUT)) .withTimeout(TRADE_TIMEOUT))
.executeTasks(); .executeTasks();
wait(latch); TradeUtils.waitForLatch(latch);
} }
} }
@ -158,7 +159,7 @@ public class BuyerAsMakerProtocol extends BuyerProtocol implements MakerProtocol
})) }))
.withTimeout(TRADE_TIMEOUT)) .withTimeout(TRADE_TIMEOUT))
.executeTasks(); .executeTasks();
wait(latch); TradeUtils.waitForLatch(latch);
} }
} }
@ -188,7 +189,7 @@ public class BuyerAsMakerProtocol extends BuyerProtocol implements MakerProtocol
})) }))
.withTimeout(TRADE_TIMEOUT)) .withTimeout(TRADE_TIMEOUT))
.executeTasks(); .executeTasks();
wait(latch); TradeUtils.waitForLatch(latch);
} else { } else {
EasyBind.subscribe(trade.stateProperty(), state -> { EasyBind.subscribe(trade.stateProperty(), state -> {
if (state == State.CONTRACT_SIGNATURE_REQUESTED) handleSignContractResponse(message, sender); if (state == State.CONTRACT_SIGNATURE_REQUESTED) handleSignContractResponse(message, sender);
@ -222,7 +223,7 @@ public class BuyerAsMakerProtocol extends BuyerProtocol implements MakerProtocol
})) }))
.withTimeout(TRADE_TIMEOUT)) .withTimeout(TRADE_TIMEOUT))
.executeTasks(); .executeTasks();
wait(latch); TradeUtils.waitForLatch(latch);
} }
} }
@ -254,7 +255,7 @@ public class BuyerAsMakerProtocol extends BuyerProtocol implements MakerProtocol
})) }))
.withTimeout(TRADE_TIMEOUT)) .withTimeout(TRADE_TIMEOUT))
.executeTasks(); .executeTasks();
wait(latch); TradeUtils.waitForLatch(latch);
} else { } else {
EasyBind.subscribe(trade.stateProperty(), state -> { EasyBind.subscribe(trade.stateProperty(), state -> {
if (state == State.MAKER_RECEIVED_DEPOSIT_TX_PUBLISHED_MSG) handlePaymentAccountPayloadRequest(request, sender); if (state == State.MAKER_RECEIVED_DEPOSIT_TX_PUBLISHED_MSG) handlePaymentAccountPayloadRequest(request, sender);

View file

@ -22,6 +22,7 @@ import bisq.core.offer.Offer;
import bisq.core.trade.BuyerAsTakerTrade; import bisq.core.trade.BuyerAsTakerTrade;
import bisq.core.trade.Trade; import bisq.core.trade.Trade;
import bisq.core.trade.Trade.State; import bisq.core.trade.Trade.State;
import bisq.core.trade.TradeUtils;
import bisq.core.trade.handlers.TradeResultHandler; import bisq.core.trade.handlers.TradeResultHandler;
import bisq.core.trade.messages.DelayedPayoutTxSignatureRequest; import bisq.core.trade.messages.DelayedPayoutTxSignatureRequest;
import bisq.core.trade.messages.DepositResponse; import bisq.core.trade.messages.DepositResponse;
@ -33,7 +34,6 @@ import bisq.core.trade.messages.PaymentReceivedMessage;
import bisq.core.trade.messages.SignContractRequest; import bisq.core.trade.messages.SignContractRequest;
import bisq.core.trade.messages.SignContractResponse; import bisq.core.trade.messages.SignContractResponse;
import bisq.core.trade.messages.TradeMessage; import bisq.core.trade.messages.TradeMessage;
import bisq.core.trade.protocol.TakerProtocol.TakerEvent;
import bisq.core.trade.protocol.tasks.ApplyFilter; import bisq.core.trade.protocol.tasks.ApplyFilter;
import bisq.core.trade.protocol.tasks.ProcessDepositResponse; import bisq.core.trade.protocol.tasks.ProcessDepositResponse;
import bisq.core.trade.protocol.tasks.ProcessInitMultisigRequest; import bisq.core.trade.protocol.tasks.ProcessInitMultisigRequest;
@ -116,7 +116,7 @@ public class BuyerAsTakerProtocol extends BuyerProtocol implements TakerProtocol
})) }))
.withTimeout(TRADE_TIMEOUT)) .withTimeout(TRADE_TIMEOUT))
.executeTasks(); .executeTasks();
wait(latch); TradeUtils.waitForLatch(latch);
} }
} }
@ -145,7 +145,7 @@ public class BuyerAsTakerProtocol extends BuyerProtocol implements TakerProtocol
})) }))
.withTimeout(TRADE_TIMEOUT)) .withTimeout(TRADE_TIMEOUT))
.executeTasks(); .executeTasks();
wait(latch); TradeUtils.waitForLatch(latch);
} }
} }
@ -174,7 +174,7 @@ public class BuyerAsTakerProtocol extends BuyerProtocol implements TakerProtocol
})) }))
.withTimeout(TRADE_TIMEOUT)) .withTimeout(TRADE_TIMEOUT))
.executeTasks(); .executeTasks();
wait(latch); TradeUtils.waitForLatch(latch);
} }
} }
@ -204,7 +204,7 @@ public class BuyerAsTakerProtocol extends BuyerProtocol implements TakerProtocol
})) }))
.withTimeout(TRADE_TIMEOUT)) .withTimeout(TRADE_TIMEOUT))
.executeTasks(); .executeTasks();
wait(latch); TradeUtils.waitForLatch(latch);
} else { } else {
EasyBind.subscribe(trade.stateProperty(), state -> { EasyBind.subscribe(trade.stateProperty(), state -> {
if (state != State.CONTRACT_SIGNATURE_REQUESTED) return; if (state != State.CONTRACT_SIGNATURE_REQUESTED) return;
@ -239,7 +239,7 @@ public class BuyerAsTakerProtocol extends BuyerProtocol implements TakerProtocol
})) }))
.withTimeout(TRADE_TIMEOUT)) .withTimeout(TRADE_TIMEOUT))
.executeTasks(); .executeTasks();
wait(latch); TradeUtils.waitForLatch(latch);
} }
} }
@ -271,7 +271,7 @@ public class BuyerAsTakerProtocol extends BuyerProtocol implements TakerProtocol
})) }))
.withTimeout(TRADE_TIMEOUT)) .withTimeout(TRADE_TIMEOUT))
.executeTasks(); .executeTasks();
wait(latch); TradeUtils.waitForLatch(latch);
} else { } else {
EasyBind.subscribe(trade.stateProperty(), state -> { EasyBind.subscribe(trade.stateProperty(), state -> {
if (state == State.MAKER_RECEIVED_DEPOSIT_TX_PUBLISHED_MSG) handlePaymentAccountPayloadRequest(request, sender); if (state == State.MAKER_RECEIVED_DEPOSIT_TX_PUBLISHED_MSG) handlePaymentAccountPayloadRequest(request, sender);

View file

@ -19,6 +19,7 @@ package bisq.core.trade.protocol;
import bisq.core.trade.BuyerTrade; import bisq.core.trade.BuyerTrade;
import bisq.core.trade.Trade; import bisq.core.trade.Trade;
import bisq.core.trade.TradeUtils;
import bisq.core.trade.messages.DelayedPayoutTxSignatureRequest; import bisq.core.trade.messages.DelayedPayoutTxSignatureRequest;
import bisq.core.trade.messages.DepositTxAndDelayedPayoutTxMessage; import bisq.core.trade.messages.DepositTxAndDelayedPayoutTxMessage;
import bisq.core.trade.messages.PaymentReceivedMessage; import bisq.core.trade.messages.PaymentReceivedMessage;
@ -26,7 +27,6 @@ import bisq.core.trade.messages.TradeMessage;
import bisq.core.trade.protocol.tasks.ApplyFilter; import bisq.core.trade.protocol.tasks.ApplyFilter;
import bisq.core.trade.protocol.tasks.SetupDepositTxsListener; import bisq.core.trade.protocol.tasks.SetupDepositTxsListener;
import bisq.core.trade.protocol.tasks.TradeTask; import bisq.core.trade.protocol.tasks.TradeTask;
import bisq.core.trade.protocol.tasks.UpdateMultisigWithTradingPeer;
import bisq.core.trade.protocol.tasks.buyer.BuyerPreparesPaymentSentMessage; import bisq.core.trade.protocol.tasks.buyer.BuyerPreparesPaymentSentMessage;
import bisq.core.trade.protocol.tasks.buyer.BuyerProcessesPaymentReceivedMessage; import bisq.core.trade.protocol.tasks.buyer.BuyerProcessesPaymentReceivedMessage;
import bisq.core.trade.protocol.tasks.buyer.BuyerSendsPaymentSentMessage; import bisq.core.trade.protocol.tasks.buyer.BuyerSendsPaymentSentMessage;
@ -182,7 +182,7 @@ public abstract class BuyerProtocol extends DisputeProtocol {
handleTaskRunnerFault(peer, message, errorMessage); handleTaskRunnerFault(peer, message, errorMessage);
}))) })))
.executeTasks(); .executeTasks();
wait(latch); TradeUtils.waitForLatch(latch);
} }
} }

View file

@ -21,6 +21,7 @@ package bisq.core.trade.protocol;
import bisq.core.trade.SellerAsMakerTrade; import bisq.core.trade.SellerAsMakerTrade;
import bisq.core.trade.Trade; import bisq.core.trade.Trade;
import bisq.core.trade.Trade.State; import bisq.core.trade.Trade.State;
import bisq.core.trade.TradeUtils;
import bisq.core.trade.messages.PaymentSentMessage; import bisq.core.trade.messages.PaymentSentMessage;
import bisq.core.trade.messages.DepositResponse; import bisq.core.trade.messages.DepositResponse;
import bisq.core.trade.messages.DepositTxMessage; import bisq.core.trade.messages.DepositTxMessage;
@ -100,7 +101,7 @@ public class SellerAsMakerProtocol extends SellerProtocol implements MakerProtoc
})) }))
.withTimeout(TRADE_TIMEOUT)) .withTimeout(TRADE_TIMEOUT))
.executeTasks(); .executeTasks();
wait(latch); TradeUtils.waitForLatch(latch);
} }
} }
@ -129,7 +130,7 @@ public class SellerAsMakerProtocol extends SellerProtocol implements MakerProtoc
})) }))
.withTimeout(TRADE_TIMEOUT)) .withTimeout(TRADE_TIMEOUT))
.executeTasks(); .executeTasks();
wait(latch); TradeUtils.waitForLatch(latch);
} }
} }
@ -158,7 +159,7 @@ public class SellerAsMakerProtocol extends SellerProtocol implements MakerProtoc
})) }))
.withTimeout(TRADE_TIMEOUT)) .withTimeout(TRADE_TIMEOUT))
.executeTasks(); .executeTasks();
wait(latch); TradeUtils.waitForLatch(latch);
} }
} }
@ -188,7 +189,7 @@ public class SellerAsMakerProtocol extends SellerProtocol implements MakerProtoc
})) }))
.withTimeout(TRADE_TIMEOUT)) .withTimeout(TRADE_TIMEOUT))
.executeTasks(); .executeTasks();
wait(latch); TradeUtils.waitForLatch(latch);
} else { } else {
EasyBind.subscribe(trade.stateProperty(), state -> { EasyBind.subscribe(trade.stateProperty(), state -> {
if (state == State.CONTRACT_SIGNATURE_REQUESTED) handleSignContractResponse(message, sender); if (state == State.CONTRACT_SIGNATURE_REQUESTED) handleSignContractResponse(message, sender);
@ -222,7 +223,7 @@ public class SellerAsMakerProtocol extends SellerProtocol implements MakerProtoc
})) }))
.withTimeout(TRADE_TIMEOUT)) .withTimeout(TRADE_TIMEOUT))
.executeTasks(); .executeTasks();
wait(latch); TradeUtils.waitForLatch(latch);
} }
} }
@ -254,7 +255,7 @@ public class SellerAsMakerProtocol extends SellerProtocol implements MakerProtoc
})) }))
.withTimeout(TRADE_TIMEOUT)) .withTimeout(TRADE_TIMEOUT))
.executeTasks(); .executeTasks();
wait(latch); TradeUtils.waitForLatch(latch);
} else { } else {
EasyBind.subscribe(trade.stateProperty(), state -> { EasyBind.subscribe(trade.stateProperty(), state -> {
if (state == State.MAKER_RECEIVED_DEPOSIT_TX_PUBLISHED_MSG) handlePaymentAccountPayloadRequest(request, sender); if (state == State.MAKER_RECEIVED_DEPOSIT_TX_PUBLISHED_MSG) handlePaymentAccountPayloadRequest(request, sender);

View file

@ -22,6 +22,7 @@ import bisq.core.offer.Offer;
import bisq.core.trade.SellerAsTakerTrade; import bisq.core.trade.SellerAsTakerTrade;
import bisq.core.trade.Trade; import bisq.core.trade.Trade;
import bisq.core.trade.Trade.State; import bisq.core.trade.Trade.State;
import bisq.core.trade.TradeUtils;
import bisq.core.trade.handlers.TradeResultHandler; import bisq.core.trade.handlers.TradeResultHandler;
import bisq.core.trade.messages.PaymentSentMessage; import bisq.core.trade.messages.PaymentSentMessage;
import bisq.core.trade.messages.DepositResponse; import bisq.core.trade.messages.DepositResponse;
@ -108,7 +109,7 @@ public class SellerAsTakerProtocol extends SellerProtocol implements TakerProtoc
})) }))
.withTimeout(TRADE_TIMEOUT)) .withTimeout(TRADE_TIMEOUT))
.executeTasks(); .executeTasks();
wait(latch); TradeUtils.waitForLatch(latch);
} }
} }
@ -137,7 +138,7 @@ public class SellerAsTakerProtocol extends SellerProtocol implements TakerProtoc
})) }))
.withTimeout(TRADE_TIMEOUT)) .withTimeout(TRADE_TIMEOUT))
.executeTasks(); .executeTasks();
wait(latch); TradeUtils.waitForLatch(latch);
} }
} }
@ -166,7 +167,7 @@ public class SellerAsTakerProtocol extends SellerProtocol implements TakerProtoc
})) }))
.withTimeout(TRADE_TIMEOUT)) .withTimeout(TRADE_TIMEOUT))
.executeTasks(); .executeTasks();
wait(latch); TradeUtils.waitForLatch(latch);
} }
} }
@ -196,7 +197,7 @@ public class SellerAsTakerProtocol extends SellerProtocol implements TakerProtoc
})) }))
.withTimeout(TRADE_TIMEOUT)) .withTimeout(TRADE_TIMEOUT))
.executeTasks(); .executeTasks();
wait(latch); TradeUtils.waitForLatch(latch);
} else { } else {
EasyBind.subscribe(trade.stateProperty(), state -> { EasyBind.subscribe(trade.stateProperty(), state -> {
if (state != State.CONTRACT_SIGNATURE_REQUESTED) return; if (state != State.CONTRACT_SIGNATURE_REQUESTED) return;
@ -231,7 +232,7 @@ public class SellerAsTakerProtocol extends SellerProtocol implements TakerProtoc
})) }))
.withTimeout(TRADE_TIMEOUT)) .withTimeout(TRADE_TIMEOUT))
.executeTasks(); .executeTasks();
wait(latch); TradeUtils.waitForLatch(latch);
} }
} }
@ -263,7 +264,7 @@ public class SellerAsTakerProtocol extends SellerProtocol implements TakerProtoc
})) }))
.withTimeout(TRADE_TIMEOUT)) .withTimeout(TRADE_TIMEOUT))
.executeTasks(); .executeTasks();
wait(latch); TradeUtils.waitForLatch(latch);
} else { } else {
EasyBind.subscribe(trade.stateProperty(), state -> { EasyBind.subscribe(trade.stateProperty(), state -> {
if (state == State.MAKER_RECEIVED_DEPOSIT_TX_PUBLISHED_MSG) handlePaymentAccountPayloadRequest(request, sender); if (state == State.MAKER_RECEIVED_DEPOSIT_TX_PUBLISHED_MSG) handlePaymentAccountPayloadRequest(request, sender);

View file

@ -20,6 +20,7 @@ package bisq.core.trade.protocol;
import bisq.core.offer.Offer; import bisq.core.offer.Offer;
import bisq.core.trade.Trade; import bisq.core.trade.Trade;
import bisq.core.trade.TradeManager; import bisq.core.trade.TradeManager;
import bisq.core.trade.TradeUtils;
import bisq.core.trade.handlers.TradeResultHandler; import bisq.core.trade.handlers.TradeResultHandler;
import bisq.core.trade.messages.PaymentSentMessage; import bisq.core.trade.messages.PaymentSentMessage;
import bisq.core.trade.messages.DepositTxAndDelayedPayoutTxMessage; import bisq.core.trade.messages.DepositTxAndDelayedPayoutTxMessage;
@ -236,7 +237,7 @@ public abstract class TradeProtocol implements DecryptedDirectMessageListener, D
); );
startTimeout(TRADE_TIMEOUT); startTimeout(TRADE_TIMEOUT);
taskRunner.run(); taskRunner.run();
wait(latch); TradeUtils.waitForLatch(latch);
} }
} }
@ -367,14 +368,6 @@ public abstract class TradeProtocol implements DecryptedDirectMessageListener, D
/////////////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////////////
// Timeout // Timeout
/////////////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////////////
protected void wait(CountDownLatch latch) {
try {
latch.await();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
protected void startTimeout(long timeoutSec) { protected void startTimeout(long timeoutSec) {
stopTimeout(); stopTimeout();

View file

@ -56,7 +56,6 @@ public class OpenOfferManagerTest {
when(p2PService.getPeerManager()).thenReturn(mock(PeerManager.class)); when(p2PService.getPeerManager()).thenReturn(mock(PeerManager.class));
final OpenOfferManager manager = new OpenOfferManager(coreContext, final OpenOfferManager manager = new OpenOfferManager(coreContext,
null,
null, null,
null, null,
p2PService, p2PService,
@ -103,7 +102,6 @@ public class OpenOfferManagerTest {
when(p2PService.getPeerManager()).thenReturn(mock(PeerManager.class)); when(p2PService.getPeerManager()).thenReturn(mock(PeerManager.class));
final OpenOfferManager manager = new OpenOfferManager(coreContext, final OpenOfferManager manager = new OpenOfferManager(coreContext,
null,
null, null,
null, null,
p2PService, p2PService,
@ -144,7 +142,6 @@ public class OpenOfferManagerTest {
final OpenOfferManager manager = new OpenOfferManager(coreContext, final OpenOfferManager manager = new OpenOfferManager(coreContext,
null,
null, null,
null, null,
p2PService, p2PService,

View file

@ -39,7 +39,7 @@ public class TradableListTest {
// test adding an OpenOffer and convert toProto // test adding an OpenOffer and convert toProto
Offer offer = new Offer(offerPayload); Offer offer = new Offer(offerPayload);
OpenOffer openOffer = new OpenOffer(offer, 0, "", "", ""); OpenOffer openOffer = new OpenOffer(offer, 0);
openOfferTradableList.add(openOffer); openOfferTradableList.add(openOffer);
message = (protobuf.PersistableEnvelope) openOfferTradableList.toProtoMessage(); message = (protobuf.PersistableEnvelope) openOfferTradableList.toProtoMessage();
assertEquals(message.getMessageCase(), TRADABLE_LIST); assertEquals(message.getMessageCase(), TRADABLE_LIST);

View file

@ -24,7 +24,7 @@ import bisq.desktop.components.TitledGroupBg;
import bisq.core.offer.availability.tasks.ProcessOfferAvailabilityResponse; import bisq.core.offer.availability.tasks.ProcessOfferAvailabilityResponse;
import bisq.core.offer.availability.tasks.SendOfferAvailabilityRequest; import bisq.core.offer.availability.tasks.SendOfferAvailabilityRequest;
import bisq.core.offer.placeoffer.tasks.AddToOfferBook; import bisq.core.offer.placeoffer.tasks.AddToOfferBook;
import bisq.core.offer.placeoffer.tasks.MakerReservesTradeFunds; import bisq.core.offer.placeoffer.tasks.MakerReservesOfferFunds;
import bisq.core.offer.placeoffer.tasks.ValidateOffer; import bisq.core.offer.placeoffer.tasks.ValidateOffer;
import bisq.core.trade.protocol.tasks.ApplyFilter; import bisq.core.trade.protocol.tasks.ApplyFilter;
import bisq.core.trade.protocol.tasks.VerifyPeersAccountAgeWitness; import bisq.core.trade.protocol.tasks.VerifyPeersAccountAgeWitness;
@ -109,7 +109,7 @@ public class DebugView extends InitializableView<GridPane, Void> {
addGroup("PlaceOfferProtocol", addGroup("PlaceOfferProtocol",
FXCollections.observableArrayList(Arrays.asList( FXCollections.observableArrayList(Arrays.asList(
ValidateOffer.class, ValidateOffer.class,
MakerReservesTradeFunds.class, MakerReservesOfferFunds.class,
AddToOfferBook.class) AddToOfferBook.class)
)); ));

View file

@ -315,7 +315,6 @@ public abstract class MutableOfferDataModel extends OfferDataModel {
void onPlaceOffer(Offer offer, TransactionResultHandler resultHandler) { void onPlaceOffer(Offer offer, TransactionResultHandler resultHandler) {
openOfferManager.placeOffer(offer, openOfferManager.placeOffer(offer,
buyerSecurityDeposit.get(),
useSavingsWallet, useSavingsWallet,
triggerPrice, triggerPrice,
resultHandler, resultHandler,

View file

@ -1450,11 +1450,12 @@ message Offer {
enum State { enum State {
PB_ERROR = 0; PB_ERROR = 0;
UNKNOWN = 1; UNKNOWN = 1;
OFFER_FEE_PAID = 2; SCHEDULED = 2;
AVAILABLE = 3; OFFER_FEE_RESERVED = 3;
NOT_AVAILABLE = 4; AVAILABLE = 4;
REMOVED = 5; NOT_AVAILABLE = 5;
MAKER_OFFLINE = 6; REMOVED = 6;
MAKER_OFFLINE = 7;
} }
OfferPayload offer_payload = 1; OfferPayload offer_payload = 1;
@ -1474,20 +1475,24 @@ message SignedOffer {
message OpenOffer { message OpenOffer {
enum State { enum State {
PB_ERROR = 0; PB_ERROR = 0;
AVAILABLE = 1; SCHEDULED = 1;
RESERVED = 2; AVAILABLE = 2;
CLOSED = 3; RESERVED = 3;
CANCELED = 4; CLOSED = 4;
DEACTIVATED = 5; CANCELED = 5;
DEACTIVATED = 6;
} }
Offer offer = 1; Offer offer = 1;
State state = 2; State state = 2;
NodeAddress backup_arbitrator = 3; NodeAddress backup_arbitrator = 3;
int64 trigger_price = 4; int64 trigger_price = 4;
string reserve_tx_hash = 5; bool auto_split = 5;
string reserve_tx_hex = 6; repeated string scheduled_tx_hashes = 6;
string reserve_tx_key = 7; string scheduled_amount = 7; // BigInteger
string reserve_tx_hash = 8;
string reserve_tx_hex = 9;
string reserve_tx_key = 10;
} }
message Tradable { message Tradable {