From b2ac5dbfa1fcd31427cb639dc1e6d4b484fd506d Mon Sep 17 00:00:00 2001 From: SlowBearDigger Date: Sun, 4 Jan 2026 12:56:53 -0500 Subject: [PATCH 1/2] Add Accepted Countries and City to OfferInfo (#2101) --- .../main/java/haveno/core/api/model/OfferInfo.java | 13 ++++++++++++- .../core/api/model/builder/OfferInfoBuilder.java | 13 +++++++++++++ proto/src/main/proto/grpc.proto | 2 ++ 3 files changed, 27 insertions(+), 1 deletion(-) diff --git a/core/src/main/java/haveno/core/api/model/OfferInfo.java b/core/src/main/java/haveno/core/api/model/OfferInfo.java index 74a715cd48..de7de248f5 100644 --- a/core/src/main/java/haveno/core/api/model/OfferInfo.java +++ b/core/src/main/java/haveno/core/api/model/OfferInfo.java @@ -27,6 +27,7 @@ import lombok.Getter; import lombok.ToString; import javax.annotation.Nullable; +import java.util.List; import java.util.Optional; import static haveno.core.util.PriceUtil.reformatMarketPrice; @@ -80,6 +81,8 @@ public class OfferInfo implements Payload { private final boolean isPrivateOffer; private final String challenge; private final String extraInfo; + private final List acceptedCountryCodes; + private final String city; public OfferInfo(OfferInfoBuilder builder) { this.id = builder.getId(); @@ -116,6 +119,8 @@ public class OfferInfo implements Payload { this.isPrivateOffer = builder.isPrivateOffer(); this.challenge = builder.getChallenge(); this.extraInfo = builder.getExtraInfo(); + this.acceptedCountryCodes = builder.getAcceptedCountryCodes(); + this.city = builder.getCity(); } public static OfferInfo toOfferInfo(Offer offer) { @@ -185,7 +190,9 @@ public class OfferInfo implements Payload { .withArbitratorSigner(offer.getOfferPayload().getArbitratorSigner() == null ? null : offer.getOfferPayload().getArbitratorSigner().getFullAddress()) .withIsPrivateOffer(offer.isPrivateOffer()) .withChallenge(offer.getChallenge()) - .withExtraInfo(offer.getCombinedExtraInfo()); + .withExtraInfo(offer.getCombinedExtraInfo()) + .withAcceptedCountryCodes(offer.getAcceptedCountryCodes()) + .withCity(offer.getF2FCity()); } /////////////////////////////////////////////////////////////////////////////////////////// @@ -229,6 +236,8 @@ public class OfferInfo implements Payload { Optional.ofNullable(splitOutputTxHash).ifPresent(builder::setSplitOutputTxHash); Optional.ofNullable(challenge).ifPresent(builder::setChallenge); Optional.ofNullable(extraInfo).ifPresent(builder::setExtraInfo); + Optional.ofNullable(acceptedCountryCodes).ifPresent(builder::addAllAcceptedCountryCodes); + builder.setCity(city == null ? "" : city); return builder.build(); } @@ -269,6 +278,8 @@ public class OfferInfo implements Payload { .withIsPrivateOffer(proto.getIsPrivateOffer()) .withChallenge(proto.getChallenge()) .withExtraInfo(proto.getExtraInfo()) + .withAcceptedCountryCodes(proto.getAcceptedCountryCodesList()) + .withCity(proto.getCity()) .build(); } } diff --git a/core/src/main/java/haveno/core/api/model/builder/OfferInfoBuilder.java b/core/src/main/java/haveno/core/api/model/builder/OfferInfoBuilder.java index 23e403fcd2..a1492a330f 100644 --- a/core/src/main/java/haveno/core/api/model/builder/OfferInfoBuilder.java +++ b/core/src/main/java/haveno/core/api/model/builder/OfferInfoBuilder.java @@ -18,6 +18,7 @@ package haveno.core.api.model.builder; import haveno.core.api.model.OfferInfo; +import java.util.List; import lombok.Getter; /* @@ -66,6 +67,8 @@ public final class OfferInfoBuilder { private boolean isPrivateOffer; private String challenge; private String extraInfo; + private List acceptedCountryCodes; + private String city; public OfferInfoBuilder withId(String id) { this.id = id; @@ -252,6 +255,16 @@ public final class OfferInfoBuilder { return this; } + public OfferInfoBuilder withAcceptedCountryCodes(List acceptedCountryCodes) { + this.acceptedCountryCodes = acceptedCountryCodes; + return this; + } + + public OfferInfoBuilder withCity(String city) { + this.city = city; + return this; + } + public OfferInfo build() { return new OfferInfo(this); } diff --git a/proto/src/main/proto/grpc.proto b/proto/src/main/proto/grpc.proto index cc8c1f00f6..48559466df 100644 --- a/proto/src/main/proto/grpc.proto +++ b/proto/src/main/proto/grpc.proto @@ -612,6 +612,8 @@ message OfferInfo { bool is_private_offer = 32; string challenge = 33; string extra_info = 34; + repeated string accepted_country_codes = 35; + string city = 36; } message AvailabilityResultWithDescription { From ca68efd8c97786ac988aca0e0a33f8a526160314 Mon Sep 17 00:00:00 2001 From: SlowBearDigger Date: Sun, 4 Jan 2026 17:06:54 -0500 Subject: [PATCH 2/2] Fix #1093: Resolve root connection issues with dynamic Tor RPC timeouts - Increase RPC timeout from 20s to 60s for Tor connections - Add dynamic error threshold (1 for Tor, 3 for clearnet) - Set connection timeout based on proxy usage in onConnectionChanged() Root cause: Monero daemon default timeout is 30 minutes, but Haveno was using 20s for all connections. Tor circuits often exceed 20s latency, causing premature timeouts and 'no connection to daemon' errors. Fix provides sufficient time for slow Tor circuits while maintaining responsiveness for clearnet connections. Tested on stagenet with remote node over Tor. Logs confirm stable connection where previous version failed with 'NoHttpResponseException' after ~1 minute. --- .../haveno/core/api/XmrConnectionService.java | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/core/src/main/java/haveno/core/api/XmrConnectionService.java b/core/src/main/java/haveno/core/api/XmrConnectionService.java index 30497020ea..00a4b8fbcf 100644 --- a/core/src/main/java/haveno/core/api/XmrConnectionService.java +++ b/core/src/main/java/haveno/core/api/XmrConnectionService.java @@ -78,6 +78,10 @@ public final class XmrConnectionService { private static final int MAX_CONSECUTIVE_ERRORS = 3; // max errors before switching connections private static int numConsecutiveErrors = 0; + private int getMaxConsecutiveErrors() { + return isProxyApplied(getConnection()) ? 1 : MAX_CONSECUTIVE_ERRORS; + } + public enum XmrConnectionFallbackType { LOCAL, CUSTOM, @@ -602,7 +606,8 @@ public final class XmrConnectionService { boolean isConnected = false; if (xmrLocalNode.isConnected()) { MoneroRpcConnection conn = connectionManager.getConnectionByUri(connection.getUri()); - conn.checkConnection(connectionManager.getTimeout()); + long timeout = isProxyApplied(conn) ? 60000 : connectionManager.getTimeout(); + conn.checkConnection(timeout); isConnected = Boolean.TRUE.equals(conn.isConnected()); } @@ -736,6 +741,13 @@ public final class XmrConnectionService { connectionList.removeConnection(currentConnection.getUri()); connectionList.addConnection(currentConnection); connectionList.setCurrentConnectionUri(currentConnection.getUri()); + + // set timeout based on connection type + long timeout = isProxyApplied(currentConnection) ? 60000 : REFRESH_PERIOD_HTTP_MS; + connectionManager.setTimeout(timeout); + if (currentConnection instanceof MoneroRpcConnection) { + currentConnection.setTimeout(timeout); + } } // set connection property on user thread @@ -819,7 +831,7 @@ public final class XmrConnectionService { // skip error handling up to max attempts numConsecutiveErrors++; - if (numConsecutiveErrors <= MAX_CONSECUTIVE_ERRORS) { + if (numConsecutiveErrors <= getMaxConsecutiveErrors()) { return; } else { numConsecutiveErrors = 0; // reset error count