mirror of
https://github.com/haveno-dex/haveno.git
synced 2025-01-14 08:49:57 -05:00
remove and repost offers if signing arbitrator unregistered
This commit is contained in:
parent
b2b4706f14
commit
f112760432
@ -131,9 +131,6 @@ public class OfferFilterService {
|
|||||||
if (isMyInsufficientTradeLimit(offer)) {
|
if (isMyInsufficientTradeLimit(offer)) {
|
||||||
return Result.IS_MY_INSUFFICIENT_TRADE_LIMIT;
|
return Result.IS_MY_INSUFFICIENT_TRADE_LIMIT;
|
||||||
}
|
}
|
||||||
if (!hasValidArbitrator(offer)) {
|
|
||||||
return Result.ARBITRATOR_NOT_VALIDATED;
|
|
||||||
}
|
|
||||||
if (!hasValidSignature(offer)) {
|
if (!hasValidSignature(offer)) {
|
||||||
return Result.SIGNATURE_NOT_VALIDATED;
|
return Result.SIGNATURE_NOT_VALIDATED;
|
||||||
}
|
}
|
||||||
@ -222,26 +219,23 @@ public class OfferFilterService {
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean hasValidArbitrator(Offer offer) {
|
public boolean hasValidSignature(Offer offer) {
|
||||||
|
|
||||||
|
// get accepted arbitrator by address
|
||||||
Arbitrator arbitrator = user.getAcceptedArbitratorByAddress(offer.getOfferPayload().getArbitratorSigner());
|
Arbitrator arbitrator = user.getAcceptedArbitratorByAddress(offer.getOfferPayload().getArbitratorSigner());
|
||||||
if (arbitrator != null) return true;
|
|
||||||
|
|
||||||
// accepted arbitrator is null if we are the signing arbitrator
|
// accepted arbitrator is null if we are the signing arbitrator
|
||||||
if (offer.getOfferPayload().getArbitratorSigner() != null) {
|
if (arbitrator == null && offer.getOfferPayload().getArbitratorSigner() != null) {
|
||||||
Arbitrator thisArbitrator = user.getRegisteredArbitrator();
|
Arbitrator thisArbitrator = user.getRegisteredArbitrator();
|
||||||
if (thisArbitrator != null && thisArbitrator.getNodeAddress().equals(offer.getOfferPayload().getArbitratorSigner())) {
|
if (thisArbitrator != null && thisArbitrator.getNodeAddress().equals(offer.getOfferPayload().getArbitratorSigner())) {
|
||||||
if (thisArbitrator.getNodeAddress().equals(p2PService.getNetworkNode().getNodeAddress())) return true; // TODO: unnecessary to compare arbitrator and p2pservice address?
|
if (thisArbitrator.getNodeAddress().equals(p2PService.getNetworkNode().getNodeAddress())) arbitrator = thisArbitrator; // TODO: unnecessary to compare arbitrator and p2pservice address?
|
||||||
}
|
}
|
||||||
|
|
||||||
// otherwise log warning
|
// otherwise log warning
|
||||||
List<NodeAddress> arbitratorAddresses = user.getAcceptedArbitrators().stream().map(Arbitrator::getNodeAddress).collect(Collectors.toList());
|
List<NodeAddress> arbitratorAddresses = user.getAcceptedArbitrators().stream().map(Arbitrator::getNodeAddress).collect(Collectors.toList());
|
||||||
log.warn("No arbitrator is registered with offer's signer. offerId={}, arbitrator signer={}, accepted arbitrators={}", offer.getId(), offer.getOfferPayload().getArbitratorSigner(), arbitratorAddresses);
|
log.warn("No arbitrator is registered with offer's signer. offerId={}, arbitrator signer={}, accepted arbitrators={}", offer.getId(), offer.getOfferPayload().getArbitratorSigner(), arbitratorAddresses);
|
||||||
}
|
}
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean hasValidSignature(Offer offer) {
|
|
||||||
Arbitrator arbitrator = user.getAcceptedArbitratorByAddress(offer.getOfferPayload().getArbitratorSigner());
|
|
||||||
if (arbitrator == null) return false; // invalid arbitrator
|
if (arbitrator == null) return false; // invalid arbitrator
|
||||||
return HavenoUtils.isArbitratorSignatureValid(offer.getOfferPayload(), arbitrator);
|
return HavenoUtils.isArbitratorSignatureValid(offer.getOfferPayload(), arbitrator);
|
||||||
}
|
}
|
||||||
|
@ -160,7 +160,7 @@ public final class OfferPayload implements ProtectedStoragePayload, ExpirablePay
|
|||||||
|
|
||||||
public OfferPayload(String id,
|
public OfferPayload(String id,
|
||||||
long date,
|
long date,
|
||||||
NodeAddress ownerNodeAddress,
|
@Nullable NodeAddress ownerNodeAddress,
|
||||||
PubKeyRing pubKeyRing,
|
PubKeyRing pubKeyRing,
|
||||||
OfferDirection direction,
|
OfferDirection direction,
|
||||||
long price,
|
long price,
|
||||||
@ -249,7 +249,7 @@ public final class OfferPayload implements ProtectedStoragePayload, ExpirablePay
|
|||||||
OfferPayload signee = new OfferPayload(
|
OfferPayload signee = new OfferPayload(
|
||||||
id,
|
id,
|
||||||
date,
|
date,
|
||||||
ownerNodeAddress,
|
null,
|
||||||
pubKeyRing,
|
pubKeyRing,
|
||||||
direction,
|
direction,
|
||||||
price,
|
price,
|
||||||
@ -317,7 +317,6 @@ public final class OfferPayload implements ProtectedStoragePayload, ExpirablePay
|
|||||||
protobuf.OfferPayload.Builder builder = protobuf.OfferPayload.newBuilder()
|
protobuf.OfferPayload.Builder builder = protobuf.OfferPayload.newBuilder()
|
||||||
.setId(id)
|
.setId(id)
|
||||||
.setDate(date)
|
.setDate(date)
|
||||||
.setOwnerNodeAddress(ownerNodeAddress.toProtoMessage())
|
|
||||||
.setPubKeyRing(pubKeyRing.toProtoMessage())
|
.setPubKeyRing(pubKeyRing.toProtoMessage())
|
||||||
.setDirection(OfferDirection.toProtoMessage(direction))
|
.setDirection(OfferDirection.toProtoMessage(direction))
|
||||||
.setPrice(price)
|
.setPrice(price)
|
||||||
@ -342,7 +341,7 @@ public final class OfferPayload implements ProtectedStoragePayload, ExpirablePay
|
|||||||
.setUpperClosePrice(upperClosePrice)
|
.setUpperClosePrice(upperClosePrice)
|
||||||
.setIsPrivateOffer(isPrivateOffer)
|
.setIsPrivateOffer(isPrivateOffer)
|
||||||
.setProtocolVersion(protocolVersion);
|
.setProtocolVersion(protocolVersion);
|
||||||
Optional.ofNullable(arbitratorSigner).ifPresent(e -> builder.setArbitratorSigner(arbitratorSigner.toProtoMessage()));
|
Optional.ofNullable(ownerNodeAddress).ifPresent(e -> builder.setOwnerNodeAddress(ownerNodeAddress.toProtoMessage()));
|
||||||
Optional.ofNullable(offerFeeTxId).ifPresent(builder::setOfferFeeTxId);
|
Optional.ofNullable(offerFeeTxId).ifPresent(builder::setOfferFeeTxId);
|
||||||
Optional.ofNullable(countryCode).ifPresent(builder::setCountryCode);
|
Optional.ofNullable(countryCode).ifPresent(builder::setCountryCode);
|
||||||
Optional.ofNullable(bankId).ifPresent(builder::setBankId);
|
Optional.ofNullable(bankId).ifPresent(builder::setBankId);
|
||||||
@ -350,6 +349,7 @@ public final class OfferPayload implements ProtectedStoragePayload, ExpirablePay
|
|||||||
Optional.ofNullable(acceptedCountryCodes).ifPresent(builder::addAllAcceptedCountryCodes);
|
Optional.ofNullable(acceptedCountryCodes).ifPresent(builder::addAllAcceptedCountryCodes);
|
||||||
Optional.ofNullable(hashOfChallenge).ifPresent(builder::setHashOfChallenge);
|
Optional.ofNullable(hashOfChallenge).ifPresent(builder::setHashOfChallenge);
|
||||||
Optional.ofNullable(extraDataMap).ifPresent(builder::putAllExtraData);
|
Optional.ofNullable(extraDataMap).ifPresent(builder::putAllExtraData);
|
||||||
|
Optional.ofNullable(arbitratorSigner).ifPresent(e -> builder.setArbitratorSigner(arbitratorSigner.toProtoMessage()));
|
||||||
Optional.ofNullable(arbitratorSignature).ifPresent(e -> builder.setArbitratorSignature(ByteString.copyFrom(e)));
|
Optional.ofNullable(arbitratorSignature).ifPresent(e -> builder.setArbitratorSignature(ByteString.copyFrom(e)));
|
||||||
Optional.ofNullable(reserveTxKeyImages).ifPresent(builder::addAllReserveTxKeyImages);
|
Optional.ofNullable(reserveTxKeyImages).ifPresent(builder::addAllReserveTxKeyImages);
|
||||||
|
|
||||||
@ -367,7 +367,7 @@ public final class OfferPayload implements ProtectedStoragePayload, ExpirablePay
|
|||||||
|
|
||||||
return new OfferPayload(proto.getId(),
|
return new OfferPayload(proto.getId(),
|
||||||
proto.getDate(),
|
proto.getDate(),
|
||||||
NodeAddress.fromProto(proto.getOwnerNodeAddress()),
|
proto.hasOwnerNodeAddress() ? NodeAddress.fromProto(proto.getOwnerNodeAddress()) : null,
|
||||||
PubKeyRing.fromProto(proto.getPubKeyRing()),
|
PubKeyRing.fromProto(proto.getPubKeyRing()),
|
||||||
OfferDirection.fromProto(proto.getDirection()),
|
OfferDirection.fromProto(proto.getDirection()),
|
||||||
proto.getPrice(),
|
proto.getPrice(),
|
||||||
@ -443,8 +443,8 @@ public final class OfferPayload implements ProtectedStoragePayload, ExpirablePay
|
|||||||
",\r\n upperClosePrice=" + upperClosePrice +
|
",\r\n upperClosePrice=" + upperClosePrice +
|
||||||
",\r\n isPrivateOffer=" + isPrivateOffer +
|
",\r\n isPrivateOffer=" + isPrivateOffer +
|
||||||
",\r\n hashOfChallenge='" + hashOfChallenge + '\'' +
|
",\r\n hashOfChallenge='" + hashOfChallenge + '\'' +
|
||||||
",\n arbitratorSigner=" + arbitratorSigner +
|
",\r\n arbitratorSigner=" + arbitratorSigner +
|
||||||
",\n arbitratorSignature=" + Utilities.bytesAsHexString(arbitratorSignature) +
|
",\r\n arbitratorSignature=" + Utilities.bytesAsHexString(arbitratorSignature) +
|
||||||
"\r\n} ";
|
"\r\n} ";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -629,7 +629,7 @@ public class OpenOfferManager implements PeerManager.Listener, DecryptedDirectMe
|
|||||||
|
|
||||||
addOpenOffer(editedOpenOffer);
|
addOpenOffer(editedOpenOffer);
|
||||||
|
|
||||||
if (!editedOpenOffer.isDeactivated())
|
if (editedOpenOffer.isAvailable())
|
||||||
republishOffer(editedOpenOffer);
|
republishOffer(editedOpenOffer);
|
||||||
|
|
||||||
offersToBeEdited.remove(openOffer.getId());
|
offersToBeEdited.remove(openOffer.getId());
|
||||||
@ -1138,7 +1138,7 @@ public class OpenOfferManager implements PeerManager.Listener, DecryptedDirectMe
|
|||||||
}
|
}
|
||||||
|
|
||||||
// verify offer not seen before
|
// verify offer not seen before
|
||||||
Optional<OpenOffer> openOfferOptional = getOpenOfferById(request.offerId);
|
Optional<OpenOffer> openOfferOptional = getOpenOfferById(request.offerId); // TODO: check if offer is on books, not open offer
|
||||||
if (openOfferOptional.isPresent()) {
|
if (openOfferOptional.isPresent()) {
|
||||||
errorMessage = "We already got a request to sign offer id " + request.offerId;
|
errorMessage = "We already got a request to sign offer id " + request.offerId;
|
||||||
log.info(errorMessage);
|
log.info(errorMessage);
|
||||||
@ -1431,8 +1431,6 @@ public class OpenOfferManager implements PeerManager.Listener, DecryptedDirectMe
|
|||||||
// Update persisted offer if a new capability is required after a software update
|
// Update persisted offer if a new capability is required after a software update
|
||||||
///////////////////////////////////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
// TODO (woodser): arbitrator signature will be invalid if offer updated (exclude updateable fields from signature? re-sign?)
|
|
||||||
|
|
||||||
private void maybeUpdatePersistedOffers() {
|
private void maybeUpdatePersistedOffers() {
|
||||||
// We need to clone to avoid ConcurrentModificationException
|
// We need to clone to avoid ConcurrentModificationException
|
||||||
List<OpenOffer> openOffersClone = getOpenOffers();
|
List<OpenOffer> openOffersClone = getOpenOffers();
|
||||||
@ -1570,7 +1568,7 @@ public class OpenOfferManager implements PeerManager.Listener, DecryptedDirectMe
|
|||||||
synchronized (openOffers) {
|
synchronized (openOffers) {
|
||||||
contained = openOffers.contains(openOffer);
|
contained = openOffers.contains(openOffer);
|
||||||
}
|
}
|
||||||
if (contained && !openOffer.isDeactivated() && openOffer.getOffer().getOfferPayload().getReserveTxKeyImages() != null && !openOffer.getOffer().getOfferPayload().getReserveTxKeyImages().isEmpty()) {
|
if (contained && openOffer.isAvailable()) {
|
||||||
// TODO It is not clear yet if it is better for the node and the network to send out all add offer
|
// TODO It is not clear yet if it is better for the node and the network to send out all add offer
|
||||||
// messages in one go or to spread it over a delay. With power users who have 100-200 offers that can have
|
// messages in one go or to spread it over a delay. With power users who have 100-200 offers that can have
|
||||||
// some significant impact to user experience and the network
|
// some significant impact to user experience and the network
|
||||||
@ -1591,10 +1589,16 @@ public class OpenOfferManager implements PeerManager.Listener, DecryptedDirectMe
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void republishOffer(OpenOffer openOffer, @Nullable Runnable completeHandler) {
|
private void republishOffer(OpenOffer openOffer, @Nullable Runnable completeHandler) {
|
||||||
offerBookService.addOffer(openOffer.getOffer(),
|
|
||||||
|
// re-add to offer book if signature is valid
|
||||||
|
Arbitrator arbitrator = user.getAcceptedArbitratorByAddress(openOffer.getOffer().getOfferPayload().getArbitratorSigner());
|
||||||
|
boolean isValid = arbitrator != null && HavenoUtils.isArbitratorSignatureValid(openOffer.getOffer().getOfferPayload(), arbitrator);
|
||||||
|
if (isValid) {
|
||||||
|
offerBookService.addOffer(openOffer.getOffer(),
|
||||||
() -> {
|
() -> {
|
||||||
if (!stopped) {
|
if (!stopped) {
|
||||||
// Refresh means we send only the data needed to refresh the TTL (hash, signature and sequence no.)
|
|
||||||
|
// refresh means we send only the data needed to refresh the TTL (hash, signature and sequence no.)
|
||||||
if (periodicRefreshOffersTimer == null) {
|
if (periodicRefreshOffersTimer == null) {
|
||||||
startPeriodicRefreshOffersTimer();
|
startPeriodicRefreshOffersTimer();
|
||||||
}
|
}
|
||||||
@ -1609,12 +1613,38 @@ public class OpenOfferManager implements PeerManager.Listener, DecryptedDirectMe
|
|||||||
stopRetryRepublishOffersTimer();
|
stopRetryRepublishOffersTimer();
|
||||||
retryRepublishOffersTimer = UserThread.runAfter(OpenOfferManager.this::republishOffers,
|
retryRepublishOffersTimer = UserThread.runAfter(OpenOfferManager.this::republishOffers,
|
||||||
RETRY_REPUBLISH_DELAY_SEC);
|
RETRY_REPUBLISH_DELAY_SEC);
|
||||||
|
if (completeHandler != null) completeHandler.run();
|
||||||
if (completeHandler != null) {
|
|
||||||
completeHandler.run();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// cancel and recreate offer
|
||||||
|
log.warn("Offer {} has invalid arbitrator signature, reposting", openOffer.getId());
|
||||||
|
onCancelled(openOffer);
|
||||||
|
Offer updatedOffer = new Offer(openOffer.getOffer().getOfferPayload());
|
||||||
|
updatedOffer.setPriceFeedService(priceFeedService);
|
||||||
|
OpenOffer updatedOpenOffer = new OpenOffer(updatedOffer, openOffer.getTriggerPrice());
|
||||||
|
|
||||||
|
// repost offer
|
||||||
|
new Thread(() -> {
|
||||||
|
synchronized (processOffersLock) {
|
||||||
|
CountDownLatch latch = new CountDownLatch(1);
|
||||||
|
processUnpostedOffer(getOpenOffers(), updatedOpenOffer, (transaction) -> {
|
||||||
|
addOpenOffer(updatedOpenOffer);
|
||||||
|
requestPersistence();
|
||||||
|
latch.countDown();
|
||||||
|
if (completeHandler != null) completeHandler.run();
|
||||||
|
}, (errorMessage) -> {
|
||||||
|
log.warn("Error reposting offer {} with invalid signature: {}", updatedOpenOffer.getId(), errorMessage);
|
||||||
|
onCancelled(updatedOpenOffer);
|
||||||
|
updatedOffer.setErrorMessage(errorMessage);
|
||||||
|
latch.countDown();
|
||||||
|
if (completeHandler != null) completeHandler.run();
|
||||||
|
});
|
||||||
|
HavenoUtils.awaitLatch(latch);
|
||||||
|
}
|
||||||
|
}).start();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void startPeriodicRepublishOffersTimer() {
|
private void startPeriodicRepublishOffersTimer() {
|
||||||
|
@ -245,17 +245,17 @@ public class HavenoUtils {
|
|||||||
return sign(keyRing.getSignatureKeyPair().getPrivate(), message);
|
return sign(keyRing.getSignatureKeyPair().getPrivate(), message);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static byte[] sign(KeyRing keyRing, byte[] bytes) {
|
public static byte[] sign(KeyRing keyRing, byte[] message) {
|
||||||
return sign(keyRing.getSignatureKeyPair().getPrivate(), bytes);
|
return sign(keyRing.getSignatureKeyPair().getPrivate(), message);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static byte[] sign(PrivateKey privateKey, String message) {
|
public static byte[] sign(PrivateKey privateKey, String message) {
|
||||||
return sign(privateKey, message.getBytes(Charsets.UTF_8));
|
return sign(privateKey, message.getBytes(Charsets.UTF_8));
|
||||||
}
|
}
|
||||||
|
|
||||||
public static byte[] sign(PrivateKey privateKey, byte[] bytes) {
|
public static byte[] sign(PrivateKey privateKey, byte[] message) {
|
||||||
try {
|
try {
|
||||||
return Sig.sign(privateKey, bytes);
|
return Sig.sign(privateKey, message);
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
throw new IllegalArgumentException(e);
|
throw new IllegalArgumentException(e);
|
||||||
}
|
}
|
||||||
@ -265,9 +265,9 @@ public class HavenoUtils {
|
|||||||
verifySignature(pubKeyRing, message.getBytes(Charsets.UTF_8), signature);
|
verifySignature(pubKeyRing, message.getBytes(Charsets.UTF_8), signature);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void verifySignature(PubKeyRing pubKeyRing, byte[] bytes, byte[] signature) {
|
public static void verifySignature(PubKeyRing pubKeyRing, byte[] message, byte[] signature) {
|
||||||
try {
|
try {
|
||||||
boolean isValid = Sig.verify(pubKeyRing.getSignaturePubKey(), bytes, signature);
|
boolean isValid = Sig.verify(pubKeyRing.getSignaturePubKey(), message, signature);
|
||||||
if (!isValid) throw new IllegalArgumentException("Signature verification failed.");
|
if (!isValid) throw new IllegalArgumentException("Signature verification failed.");
|
||||||
} catch (CryptoException e) {
|
} catch (CryptoException e) {
|
||||||
throw new IllegalArgumentException(e);
|
throw new IllegalArgumentException(e);
|
||||||
@ -278,9 +278,9 @@ public class HavenoUtils {
|
|||||||
return isSignatureValid(pubKeyRing, message.getBytes(Charsets.UTF_8), signature);
|
return isSignatureValid(pubKeyRing, message.getBytes(Charsets.UTF_8), signature);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static boolean isSignatureValid(PubKeyRing pubKeyRing, byte[] bytes, byte[] signature) {
|
public static boolean isSignatureValid(PubKeyRing pubKeyRing, byte[] message, byte[] signature) {
|
||||||
try {
|
try {
|
||||||
verifySignature(pubKeyRing, bytes, signature);
|
verifySignature(pubKeyRing, message, signature);
|
||||||
return true;
|
return true;
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
return false;
|
return false;
|
||||||
|
Loading…
Reference in New Issue
Block a user