add date and key images to SignedOffer

arbitrator retains failed trades after reserve tx received
legacy ui shows trade details including reserve tx, with or w/o contract
fix npe searching arbitrator tickets due to null payment accounts
synchronize offer book list items
fix npe before key image poller initialized
This commit is contained in:
woodser 2023-01-26 08:26:22 -05:00 committed by GitHub
parent 882f1c070a
commit a0235c8ebd
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 194 additions and 153 deletions

View File

@ -100,6 +100,7 @@ public class OfferBookService {
connectionsService.addListener(new MoneroConnectionManagerListener() { connectionsService.addListener(new MoneroConnectionManagerListener() {
@Override @Override
public void onConnectionChanged(MoneroRpcConnection connection) { public void onConnectionChanged(MoneroRpcConnection connection) {
if (keyImagePoller == null) return;
keyImagePoller.setDaemon(connectionsService.getDaemon()); keyImagePoller.setDaemon(connectionsService.getDaemon());
keyImagePoller.setRefreshPeriodMs(getKeyImageRefreshPeriodMs()); keyImagePoller.setRefreshPeriodMs(getKeyImageRefreshPeriodMs());
} }

View File

@ -925,7 +925,13 @@ public class OpenOfferManager implements PeerManager.Listener, DecryptedDirectMe
signedOfferPayload.setArbitratorSignature(signature); signedOfferPayload.setArbitratorSignature(signature);
// create record of signed offer // create record of signed offer
SignedOffer signedOffer = new SignedOffer(signedOfferPayload.getId(), request.getReserveTxHash(), request.getReserveTxHex(), signature); // TODO (woodser): no need for signature to be part of SignedOffer? SignedOffer signedOffer = new SignedOffer(
System.currentTimeMillis(),
signedOfferPayload.getId(),
request.getReserveTxHash(),
request.getReserveTxHex(),
request.getReserveTxKeyImages(),
signature); // TODO (woodser): no need for signature to be part of SignedOffer?
addSignedOffer(signedOffer); addSignedOffer(signedOffer);
requestPersistence(); requestPersistence();

View File

@ -17,6 +17,8 @@
package bisq.core.offer; package bisq.core.offer;
import java.util.List;
import bisq.common.proto.persistable.PersistablePayload; import bisq.common.proto.persistable.PersistablePayload;
import lombok.EqualsAndHashCode; import lombok.EqualsAndHashCode;
import lombok.Getter; import lombok.Getter;
@ -26,6 +28,8 @@ import lombok.extern.slf4j.Slf4j;
@Slf4j @Slf4j
public final class SignedOffer implements PersistablePayload { public final class SignedOffer implements PersistablePayload {
@Getter
private final long timeStamp;
@Getter @Getter
private final String offerId; private final String offerId;
@Getter @Getter
@ -33,12 +37,16 @@ public final class SignedOffer implements PersistablePayload {
@Getter @Getter
private final String reserveTxHex; private final String reserveTxHex;
@Getter @Getter
private final List<String> reserveTxKeyImages;
@Getter
private final String arbitratorSignature; private final String arbitratorSignature;
public SignedOffer(String offerId, String reserveTxHash, String reserveTxHex, String arbitratorSignature) { public SignedOffer(long timeStamp, String offerId, String reserveTxHash, String reserveTxHex, List<String> reserveTxKeyImages, String arbitratorSignature) {
this.timeStamp = timeStamp;
this.offerId = offerId; this.offerId = offerId;
this.reserveTxHash = reserveTxHash; this.reserveTxHash = reserveTxHash;
this.reserveTxHex = reserveTxHex; this.reserveTxHex = reserveTxHex;
this.reserveTxKeyImages = reserveTxKeyImages;
this.arbitratorSignature = arbitratorSignature; this.arbitratorSignature = arbitratorSignature;
} }
@ -49,16 +57,17 @@ public final class SignedOffer implements PersistablePayload {
@Override @Override
public protobuf.SignedOffer toProtoMessage() { public protobuf.SignedOffer toProtoMessage() {
protobuf.SignedOffer.Builder builder = protobuf.SignedOffer.newBuilder() protobuf.SignedOffer.Builder builder = protobuf.SignedOffer.newBuilder()
.setTimeStamp(timeStamp)
.setOfferId(offerId) .setOfferId(offerId)
.setReserveTxHash(reserveTxHash) .setReserveTxHash(reserveTxHash)
.setReserveTxHex(reserveTxHex) .setReserveTxHex(reserveTxHex)
.addAllReserveTxKeyImages(reserveTxKeyImages)
.setArbitratorSignature(arbitratorSignature); .setArbitratorSignature(arbitratorSignature);
return builder.build(); return builder.build();
} }
public static SignedOffer fromProto(protobuf.SignedOffer proto) { public static SignedOffer fromProto(protobuf.SignedOffer proto) {
return new SignedOffer(proto.getOfferId(), proto.getReserveTxHash(), proto.getReserveTxHex(), proto.getArbitratorSignature()); return new SignedOffer(proto.getTimeStamp(), proto.getOfferId(), proto.getReserveTxHash(), proto.getReserveTxHex(), proto.getReserveTxKeyImagesList(), proto.getArbitratorSignature());
} }
@ -69,9 +78,11 @@ public final class SignedOffer implements PersistablePayload {
@Override @Override
public String toString() { public String toString() {
return "SignedOffer{" + return "SignedOffer{" +
",\n timeStamp=" + timeStamp +
",\n offerId=" + offerId + ",\n offerId=" + offerId +
",\n reserveTxHash=" + reserveTxHash + ",\n reserveTxHash=" + reserveTxHash +
",\n reserveTxHex=" + reserveTxHex + ",\n reserveTxHex=" + reserveTxHex +
",\n reserveTxKeyImages=" + reserveTxKeyImages +
",\n arbitratorSignature=" + arbitratorSignature + ",\n arbitratorSignature=" + arbitratorSignature +
"\n}"; "\n}";
} }

View File

@ -487,7 +487,11 @@ public class TradeManager implements PersistedDataHost, DecryptedDirectMessageLi
((ArbitratorProtocol) getTradeProtocol(trade)).handleInitTradeRequest(request, sender, errorMessage -> { ((ArbitratorProtocol) getTradeProtocol(trade)).handleInitTradeRequest(request, sender, errorMessage -> {
log.warn("Arbitrator error during trade initialization for trade {}: {}", trade.getId(), errorMessage); log.warn("Arbitrator error during trade initialization for trade {}: {}", trade.getId(), errorMessage);
removeTradeOnError(trade); if (trade.getMaker().getReserveTxHash() != null || trade.getTaker().getReserveTxHash() != null) {
onMoveInvalidTradeToFailedTrades(trade); // arbitrator retains failed trades for analysis and penalty
} else {
removeTradeOnError(trade);
}
if (takeOfferRequestErrorMessageHandler != null) takeOfferRequestErrorMessageHandler.handleErrorMessage(errorMessage); if (takeOfferRequestErrorMessageHandler != null) takeOfferRequestErrorMessageHandler.handleErrorMessage(errorMessage);
}); });

View File

@ -89,28 +89,32 @@ public class OfferBook {
// Use offer.equals(offer) to see if the OfferBook list contains an exact // Use offer.equals(offer) to see if the OfferBook list contains an exact
// match -- offer.equals(offer) includes comparisons of payload, state // match -- offer.equals(offer) includes comparisons of payload, state
// and errorMessage. // and errorMessage.
boolean hasSameOffer = offerBookListItems.stream().anyMatch(item -> item.getOffer().equals(offer)); synchronized (offerBookListItems) {
if (!hasSameOffer) { boolean hasSameOffer = offerBookListItems.stream().anyMatch(item -> item.getOffer().equals(offer));
OfferBookListItem newOfferBookListItem = new OfferBookListItem(offer); if (!hasSameOffer) {
removeDuplicateItem(newOfferBookListItem); OfferBookListItem newOfferBookListItem = new OfferBookListItem(offer);
offerBookListItems.add(newOfferBookListItem); // Add replacement. removeDuplicateItem(newOfferBookListItem);
if (log.isDebugEnabled()) { // TODO delete debug stmt in future PR. offerBookListItems.add(newOfferBookListItem); // Add replacement.
log.debug("onAdded: Added new offer {}\n" if (log.isDebugEnabled()) { // TODO delete debug stmt in future PR.
+ "\twith newItem.payloadHash: {}", log.debug("onAdded: Added new offer {}\n"
offer.getId(), + "\twith newItem.payloadHash: {}",
newOfferBookListItem.hashOfPayload.getHex()); offer.getId(),
newOfferBookListItem.hashOfPayload.getHex());
}
} else {
log.debug("We have the exact same offer already in our list and ignore the onAdded call. ID={}", offer.getId());
} }
} else { printOfferBookListItems("After onAdded");
log.debug("We have the exact same offer already in our list and ignore the onAdded call. ID={}", offer.getId());
} }
printOfferBookListItems("After onAdded");
} }
@Override @Override
public void onRemoved(Offer offer) { public void onRemoved(Offer offer) {
printOfferBookListItems("Before onRemoved"); synchronized (offerBookListItems) {
removeOffer(offer); printOfferBookListItems("Before onRemoved");
printOfferBookListItems("After onRemoved"); removeOffer(offer);
printOfferBookListItems("After onRemoved");
}
} }
}); });
@ -122,77 +126,83 @@ public class OfferBook {
} }
private void removeDuplicateItem(OfferBookListItem newOfferBookListItem) { private void removeDuplicateItem(OfferBookListItem newOfferBookListItem) {
String offerId = newOfferBookListItem.getOffer().getId(); synchronized (offerBookListItems) {
// We need to remove any view items with a matching offerId before String offerId = newOfferBookListItem.getOffer().getId();
// a newOfferBookListItem is added to the view.
List<OfferBookListItem> duplicateItems = offerBookListItems.stream() // We need to remove any view items with a matching offerId before
.filter(item -> item.getOffer().getId().equals(offerId)) // a newOfferBookListItem is added to the view.
.collect(Collectors.toList()); List<OfferBookListItem> duplicateItems = offerBookListItems.stream()
duplicateItems.forEach(oldOfferItem -> { .filter(item -> item.getOffer().getId().equals(offerId))
offerBookListItems.remove(oldOfferItem); .collect(Collectors.toList());
if (log.isDebugEnabled()) { // TODO delete debug stmt in future PR. duplicateItems.forEach(oldOfferItem -> {
log.debug("onAdded: Removed old offer {}\n" offerBookListItems.remove(oldOfferItem);
+ "\twith payload hash {} from list.\n" if (log.isDebugEnabled()) { // TODO delete debug stmt in future PR.
+ "\tThis may make a subsequent onRemoved( {} ) call redundant.", log.debug("onAdded: Removed old offer {}\n"
offerId, + "\twith payload hash {} from list.\n"
oldOfferItem.getHashOfPayload().getHex(), + "\tThis may make a subsequent onRemoved( {} ) call redundant.",
oldOfferItem.getOffer().getId()); offerId,
} oldOfferItem.getHashOfPayload().getHex(),
}); oldOfferItem.getOffer().getId());
}
});
}
} }
public void removeOffer(Offer offer) { public void removeOffer(Offer offer) {
// Update state in case that that offer is used in the take offer screen, so it gets updated correctly synchronized (offerBookListItems) {
offer.setState(Offer.State.REMOVED);
offer.cancelAvailabilityRequest();
P2PDataStorage.ByteArray hashOfPayload = new P2PDataStorage.ByteArray(offer.getOfferPayload().getHash()); // Update state in case that that offer is used in the take offer screen, so it gets updated correctly
offer.setState(Offer.State.REMOVED);
offer.cancelAvailabilityRequest();
if (log.isDebugEnabled()) { // TODO delete debug stmt in future PR. P2PDataStorage.ByteArray hashOfPayload = new P2PDataStorage.ByteArray(offer.getOfferPayload().getHash());
log.debug("onRemoved: id = {}\n"
+ "\twith payload-hash = {}",
offer.getId(),
hashOfPayload.getHex());
}
// Find the removal candidate in the OfferBook list with matching offerId and payload-hash.
Optional<OfferBookListItem> candidateWithMatchingPayloadHash = offerBookListItems.stream()
.filter(item -> item.getOffer().getId().equals(offer.getId())
&& item.hashOfPayload.equals(hashOfPayload))
.findAny();
if (!candidateWithMatchingPayloadHash.isPresent()) {
if (log.isDebugEnabled()) { // TODO delete debug stmt in future PR. if (log.isDebugEnabled()) { // TODO delete debug stmt in future PR.
log.debug("UI view list does not contain offer with id {} and payload-hash {}", log.debug("onRemoved: id = {}\n"
+ "\twith payload-hash = {}",
offer.getId(), offer.getId(),
hashOfPayload.getHex()); hashOfPayload.getHex());
} }
return;
}
OfferBookListItem candidate = candidateWithMatchingPayloadHash.get(); // Find the removal candidate in the OfferBook list with matching offerId and payload-hash.
// Remove the candidate only if the candidate's offer payload the hash matches the Optional<OfferBookListItem> candidateWithMatchingPayloadHash = offerBookListItems.stream()
// onRemoved hashOfPayload parameter. We may receive add/remove messages out of .filter(item -> item.getOffer().getId().equals(offer.getId())
// order from the API's 'editoffer' method, and use the offer payload hash to && item.hashOfPayload.equals(hashOfPayload))
// ensure we do not remove an edited offer immediately after it was added. .findAny();
if (candidate.getHashOfPayload().equals(hashOfPayload)) {
// The payload-hash test passed, remove the candidate and print reason.
offerBookListItems.remove(candidate);
if (log.isDebugEnabled()) { // TODO delete debug stmt in future PR. if (!candidateWithMatchingPayloadHash.isPresent()) {
log.debug("Candidate.payload-hash: {} == onRemoved.payload-hash: {} ?" if (log.isDebugEnabled()) { // TODO delete debug stmt in future PR.
+ " Yes, removed old offer", log.debug("UI view list does not contain offer with id {} and payload-hash {}",
candidate.hashOfPayload.getHex(), offer.getId(),
hashOfPayload.getHex()); hashOfPayload.getHex());
}
return;
} }
} else {
if (log.isDebugEnabled()) { // TODO delete debug stmt in future PR. OfferBookListItem candidate = candidateWithMatchingPayloadHash.get();
// Candidate's payload-hash test failed: payload-hash != onRemoved.payload-hash. // Remove the candidate only if the candidate's offer payload the hash matches the
// Print reason for not removing candidate. // onRemoved hashOfPayload parameter. We may receive add/remove messages out of
log.debug("Candidate.payload-hash: {} == onRemoved.payload-hash: {} ?" // order from the API's 'editoffer' method, and use the offer payload hash to
+ " No, old offer not removed", // ensure we do not remove an edited offer immediately after it was added.
candidate.hashOfPayload.getHex(), if (candidate.getHashOfPayload().equals(hashOfPayload)) {
hashOfPayload.getHex()); // The payload-hash test passed, remove the candidate and print reason.
offerBookListItems.remove(candidate);
if (log.isDebugEnabled()) { // TODO delete debug stmt in future PR.
log.debug("Candidate.payload-hash: {} == onRemoved.payload-hash: {} ?"
+ " Yes, removed old offer",
candidate.hashOfPayload.getHex(),
hashOfPayload.getHex());
}
} else {
if (log.isDebugEnabled()) { // TODO delete debug stmt in future PR.
// Candidate's payload-hash test failed: payload-hash != onRemoved.payload-hash.
// Print reason for not removing candidate.
log.debug("Candidate.payload-hash: {} == onRemoved.payload-hash: {} ?"
+ " No, old offer not removed",
candidate.hashOfPayload.getHex(),
hashOfPayload.getHex());
}
} }
} }
} }
@ -202,33 +212,37 @@ public class OfferBook {
} }
public void fillOfferBookListItems() { public void fillOfferBookListItems() {
try { synchronized (offerBookListItems) {
// setAll causes sometimes an UnsupportedOperationException try {
// Investigate why.... // setAll causes sometimes an UnsupportedOperationException
offerBookListItems.clear(); // Investigate why....
offerBookListItems.addAll(offerBookService.getOffers().stream() offerBookListItems.clear();
.filter(this::isOfferAllowed) offerBookListItems.addAll(offerBookService.getOffers().stream()
.map(OfferBookListItem::new) .filter(this::isOfferAllowed)
.collect(Collectors.toList())); .map(OfferBookListItem::new)
.collect(Collectors.toList()));
log.debug("offerBookListItems.size {}", offerBookListItems.size());
fillOfferCountMaps(); log.debug("offerBookListItems.size {}", offerBookListItems.size());
} catch (Throwable t) { fillOfferCountMaps();
log.error("Error at fillOfferBookListItems: " + t); } catch (Throwable t) {
log.error("Error at fillOfferBookListItems: " + t);
}
} }
} }
public void printOfferBookListItems(String msg) { public void printOfferBookListItems(String msg) {
if (log.isDebugEnabled()) { synchronized (offerBookListItems) {
if (offerBookListItems.size() == 0) { if (log.isDebugEnabled()) {
log.debug("{} -> OfferBookListItems: none", msg); if (offerBookListItems.size() == 0) {
return; log.debug("{} -> OfferBookListItems: none", msg);
return;
}
StringBuilder stringBuilder = new StringBuilder(msg + " -> ").append("OfferBookListItems:").append("\n");
offerBookListItems.forEach(i -> stringBuilder.append("\t").append(i.toString()).append("\n"));
stringBuilder.deleteCharAt(stringBuilder.length() - 1);
log.debug(stringBuilder.toString());
} }
StringBuilder stringBuilder = new StringBuilder(msg + " -> ").append("OfferBookListItems:").append("\n");
offerBookListItems.forEach(i -> stringBuilder.append("\t").append(i.toString()).append("\n"));
stringBuilder.deleteCharAt(stringBuilder.length() - 1);
log.debug(stringBuilder.toString());
} }
} }

View File

@ -319,63 +319,66 @@ public class TradeDetailsWindow extends Overlay<TradeDetailsWindow> {
String sellerWitnessHash = trade.getSeller().getAccountAgeWitness() == null ? "null" : Utilities.bytesAsHexString(trade.getSeller().getAccountAgeWitness().getHash()); String sellerWitnessHash = trade.getSeller().getAccountAgeWitness() == null ? "null" : Utilities.bytesAsHexString(trade.getSeller().getAccountAgeWitness().getHash());
String sellerPubKeyRingHash = Utilities.bytesAsHexString(trade.getSeller().getPubKeyRing().getSignaturePubKeyBytes()); String sellerPubKeyRingHash = Utilities.bytesAsHexString(trade.getSeller().getPubKeyRing().getSignaturePubKeyBytes());
if (contract != null) { viewContractButton.setOnAction(e -> {
viewContractButton.setOnAction(e -> { TextArea textArea = new HavenoTextArea();
TextArea textArea = new HavenoTextArea(); textArea.setText(trade.getContractAsJson());
textArea.setText(trade.getContractAsJson()); String data = "Contract as json:\n";
String data = "Contract as json:\n"; data += trade.getContractAsJson();
data += trade.getContractAsJson(); data += "\n\nOther detail data:";
data += "\n\nOther detail data:"; if (!trade.isDepositPublished()) {
if (offer.isFiatOffer()) { data += "\n\n" + (trade.getMaker() == trade.getBuyer() ? "Buyer" : "Seller") + " as maker reserve tx hex: " + trade.getMaker().getReserveTxHex();
data += "\n\nBuyers witness hash,pub key ring hash: " + buyerWitnessHash + "," + buyerPubKeyRingHash; data += "\n\n" + (trade.getTaker() == trade.getBuyer() ? "Buyer" : "Seller") + " as taker reserve tx hex: " + trade.getTaker().getReserveTxHex();
data += "\nBuyers account age: " + buyersAccountAge; }
data += "\nSellers witness hash,pub key ring hash: " + sellerWitnessHash + "," + sellerPubKeyRingHash; if (offer.isFiatOffer()) {
data += "\nSellers account age: " + sellersAccountAge; data += "\n\nBuyers witness hash,pub key ring hash: " + buyerWitnessHash + "," + buyerPubKeyRingHash;
} data += "\nBuyers account age: " + buyersAccountAge;
data += "\nSellers witness hash,pub key ring hash: " + sellerWitnessHash + "," + sellerPubKeyRingHash;
data += "\nSellers account age: " + sellersAccountAge;
}
// TODO (woodser): include maker and taker deposit tx hex in contract? // TODO (woodser): include maker and taker deposit tx hex in contract?
// if (depositTx != null) { // if (depositTx != null) {
// String depositTxAsHex = Utils.HEX.encode(depositTx.bitcoinSerialize(true)); // String depositTxAsHex = Utils.HEX.encode(depositTx.bitcoinSerialize(true));
// data += "\n\nRaw deposit transaction as hex:\n" + depositTxAsHex; // data += "\n\nRaw deposit transaction as hex:\n" + depositTxAsHex;
// } // }
data += "\n\nSelected arbitrator: " + DisputeAgentLookupMap.getMatrixUserName(contract.getArbitratorNodeAddress().getFullAddress());
textArea.setText(data); data += "\n\nSelected arbitrator: " + trade.getArbitrator().getNodeAddress();
textArea.setPrefHeight(50);
textArea.setEditable(false);
textArea.setWrapText(true);
textArea.setPrefSize(800, 600);
Scene viewContractScene = new Scene(textArea); textArea.setText(data);
Stage viewContractStage = new Stage(); textArea.setPrefHeight(50);
viewContractStage.setTitle(Res.get("shared.contract.title", trade.getShortId())); textArea.setEditable(false);
viewContractStage.setScene(viewContractScene); textArea.setWrapText(true);
if (owner == null) textArea.setPrefSize(800, 600);
owner = MainView.getRootContainer();
Scene rootScene = owner.getScene();
viewContractStage.initOwner(rootScene.getWindow());
viewContractStage.initModality(Modality.NONE);
viewContractStage.initStyle(StageStyle.UTILITY);
viewContractStage.setOpacity(0);
viewContractStage.show();
Window window = rootScene.getWindow(); Scene viewContractScene = new Scene(textArea);
double titleBarHeight = window.getHeight() - rootScene.getHeight(); Stage viewContractStage = new Stage();
viewContractStage.setX(Math.round(window.getX() + (owner.getWidth() - viewContractStage.getWidth()) / 2) + 200); viewContractStage.setTitle(Res.get("shared.contract.title", trade.getShortId()));
viewContractStage.setY(Math.round(window.getY() + titleBarHeight + (owner.getHeight() - viewContractStage.getHeight()) / 2) + 50); viewContractStage.setScene(viewContractScene);
// Delay display to next render frame to avoid that the popup is first quickly displayed in default position if (owner == null)
// and after a short moment in the correct position owner = MainView.getRootContainer();
UserThread.execute(() -> viewContractStage.setOpacity(1)); Scene rootScene = owner.getScene();
viewContractStage.initOwner(rootScene.getWindow());
viewContractStage.initModality(Modality.NONE);
viewContractStage.initStyle(StageStyle.UTILITY);
viewContractStage.setOpacity(0);
viewContractStage.show();
viewContractScene.setOnKeyPressed(ev -> { Window window = rootScene.getWindow();
if (ev.getCode() == KeyCode.ESCAPE) { double titleBarHeight = window.getHeight() - rootScene.getHeight();
ev.consume(); viewContractStage.setX(Math.round(window.getX() + (owner.getWidth() - viewContractStage.getWidth()) / 2) + 200);
viewContractStage.hide(); viewContractStage.setY(Math.round(window.getY() + titleBarHeight + (owner.getHeight() - viewContractStage.getHeight()) / 2) + 50);
} // Delay display to next render frame to avoid that the popup is first quickly displayed in default position
}); // and after a short moment in the correct position
UserThread.execute(() -> viewContractStage.setOpacity(1));
viewContractScene.setOnKeyPressed(ev -> {
if (ev.getCode() == KeyCode.ESCAPE) {
ev.consume();
viewContractStage.hide();
}
}); });
} });
closeButton.setOnAction(e -> { closeButton.setOnAction(e -> {
closeHandlerOptional.ifPresent(Runnable::run); closeHandlerOptional.ifPresent(Runnable::run);

View File

@ -445,11 +445,11 @@ public abstract class DisputeView extends ActivatableView<VBox, Void> {
return FilterResult.SELLER_NODE_ADDRESS; return FilterResult.SELLER_NODE_ADDRESS;
} }
if (dispute.getBuyerPaymentAccountPayload().getPaymentDetails().toLowerCase().contains(filter)) { if (dispute.getBuyerPaymentAccountPayload() != null && dispute.getBuyerPaymentAccountPayload().getPaymentDetails().toLowerCase().contains(filter)) {
return FilterResult.BUYER_ACCOUNT_DETAILS; return FilterResult.BUYER_ACCOUNT_DETAILS;
} }
if (dispute.getSellerPaymentAccountPayload().getPaymentDetails().toLowerCase().contains(filter)) { if (dispute.getSellerPaymentAccountPayload() != null && dispute.getSellerPaymentAccountPayload().getPaymentDetails().toLowerCase().contains(filter)) {
return FilterResult.SELLER_ACCOUNT_DETAILS; return FilterResult.SELLER_ACCOUNT_DETAILS;
} }

View File

@ -1509,10 +1509,12 @@ message SignedOfferList {
} }
message SignedOffer { message SignedOffer {
string offer_id = 1; int64 time_stamp = 1;
string reserve_tx_hash = 2; string offer_id = 2;
string reserve_tx_hex = 3; string reserve_tx_hash = 3;
string arbitrator_signature = 4; string reserve_tx_hex = 4;
repeated string reserve_tx_key_images = 5;
string arbitrator_signature = 6;
} }
message OpenOffer { message OpenOffer {