diff --git a/core/src/main/java/haveno/core/api/CoreApi.java b/core/src/main/java/haveno/core/api/CoreApi.java index e8e83978..a31d0746 100644 --- a/core/src/main/java/haveno/core/api/CoreApi.java +++ b/core/src/main/java/haveno/core/api/CoreApi.java @@ -102,6 +102,7 @@ public class CoreApi { private final CoreWalletsService walletsService; private final TradeStatisticsManager tradeStatisticsManager; private final CoreNotificationService notificationService; + private final CoreNetworkService networkService; private final XmrConnectionService xmrConnectionService; private final XmrLocalNode xmrLocalNode; @@ -119,6 +120,7 @@ public class CoreApi { CoreWalletsService walletsService, TradeStatisticsManager tradeStatisticsManager, CoreNotificationService notificationService, + CoreNetworkService networkService, XmrConnectionService xmrConnectionService, XmrLocalNode xmrLocalNode) { this.config = config; @@ -134,6 +136,7 @@ public class CoreApi { this.walletsService = walletsService; this.tradeStatisticsManager = tradeStatisticsManager; this.notificationService = notificationService; + this.networkService = networkService; this.xmrConnectionService = xmrConnectionService; this.xmrLocalNode = xmrLocalNode; } @@ -355,6 +358,14 @@ public class CoreApi { notificationService.sendNotification(notification); } + /////////////////////////////////////////////////////////////////////////////////////////// + // Network + /////////////////////////////////////////////////////////////////////////////////////////// + + public void addNetworkListener(NetworkListener listener) { + networkService.addListener(listener); + } + /////////////////////////////////////////////////////////////////////////////////////////// // Disputes /////////////////////////////////////////////////////////////////////////////////////////// diff --git a/core/src/main/java/haveno/core/api/CoreNetworkService.java b/core/src/main/java/haveno/core/api/CoreNetworkService.java new file mode 100644 index 00000000..6beffe39 --- /dev/null +++ b/core/src/main/java/haveno/core/api/CoreNetworkService.java @@ -0,0 +1,41 @@ +package haveno.core.api; + +import com.google.inject.Singleton; + +import java.util.Iterator; +import java.util.LinkedList; +import java.util.List; + +import haveno.proto.grpc.NetworkMessage; + +import lombok.NonNull; +import lombok.extern.slf4j.Slf4j; + +@Singleton +@Slf4j +public class CoreNetworkService { + + private final Object lock = new Object(); + private final List listeners = new LinkedList<>(); + + public void addListener(@NonNull NetworkListener listener) { + synchronized (lock) { + listeners.add(listener); + } + } + + public void sendNetworMessage(@NonNull NetworkMessage network_message) { + synchronized (lock) { + for (Iterator iter = listeners.iterator(); iter.hasNext(); ) { + NetworkListener listener = iter.next(); + try { + listener.onMessage(network_message); + } catch (RuntimeException e) { + log.warn("Failed to send network envelope to listener {}: {}", listener, e.getMessage()); + iter.remove(); + } + } + } + } + +} diff --git a/core/src/main/java/haveno/core/api/NetworkListener.java b/core/src/main/java/haveno/core/api/NetworkListener.java new file mode 100644 index 00000000..c6b240bc --- /dev/null +++ b/core/src/main/java/haveno/core/api/NetworkListener.java @@ -0,0 +1,9 @@ +package haveno.core.api; + +import haveno.proto.grpc.NetworkMessage; + +import lombok.NonNull; + +public interface NetworkListener { + void onMessage(@NonNull NetworkMessage network_message); +} \ No newline at end of file diff --git a/core/src/main/java/haveno/core/xmr/wallet/XmrWalletService.java b/core/src/main/java/haveno/core/xmr/wallet/XmrWalletService.java index d0209c1f..4a97becc 100644 --- a/core/src/main/java/haveno/core/xmr/wallet/XmrWalletService.java +++ b/core/src/main/java/haveno/core/xmr/wallet/XmrWalletService.java @@ -107,11 +107,11 @@ public class XmrWalletService extends XmrWalletBase { // monero configuration public static final int NUM_BLOCKS_UNLOCK = 10; - public static final String MONERO_BINS_DIR = Config.appDataDir().getAbsolutePath(); + public static final String MONERO_BINS_DIR = System.getenv().getOrDefault("MONERO_BINS_DIR", Config.appDataDir().getAbsolutePath()); public static final String MONERO_WALLET_RPC_NAME = Utilities.isWindows() ? "monero-wallet-rpc.exe" : "monero-wallet-rpc"; public static final String MONERO_WALLET_RPC_PATH = MONERO_BINS_DIR + File.separator + MONERO_WALLET_RPC_NAME; public static final MoneroTxPriority PROTOCOL_FEE_PRIORITY = MoneroTxPriority.DEFAULT; - public static final int MONERO_LOG_LEVEL = 1; // monero library log level, -1 to disable + public static final int MONERO_LOG_LEVEL = -1; // monero library log level, -1 to disable private static final MoneroNetworkType MONERO_NETWORK_TYPE = getMoneroNetworkType(); private static final MoneroWalletRpcManager MONERO_WALLET_RPC_MANAGER = new MoneroWalletRpcManager(); private static final String MONERO_WALLET_RPC_USERNAME = "haveno_user"; diff --git a/daemon/src/main/java/haveno/daemon/grpc/GrpcNetworkService.java b/daemon/src/main/java/haveno/daemon/grpc/GrpcNetworkService.java new file mode 100644 index 00000000..421d6127 --- /dev/null +++ b/daemon/src/main/java/haveno/daemon/grpc/GrpcNetworkService.java @@ -0,0 +1,81 @@ +package haveno.daemon.grpc; + +import com.google.inject.Inject; + +import haveno.core.api.CoreApi; +import haveno.core.api.NetworkListener; +import haveno.daemon.grpc.interceptor.CallRateMeteringInterceptor; +import haveno.daemon.grpc.interceptor.GrpcCallRateMeter; +import static haveno.daemon.grpc.interceptor.GrpcServiceRateMeteringConfig.getCustomRateMeteringInterceptor; +import haveno.proto.grpc.NetworkGrpc.NetworkImplBase; +import haveno.proto.grpc.NetworkMessage; +import static haveno.proto.grpc.NetworkGrpc.getRegisterNetworkListenerMethod; +import haveno.proto.grpc.RegisterNetworkListenerRequest; +import io.grpc.Context; +import io.grpc.ServerInterceptor; +import io.grpc.stub.ServerCallStreamObserver; +import io.grpc.stub.StreamObserver; + +import java.util.HashMap; +import java.util.Optional; +import static java.util.concurrent.TimeUnit.SECONDS; + +import lombok.NonNull; +import lombok.Value; +import lombok.extern.slf4j.Slf4j; + +@Slf4j +class GrpcNetworkService extends NetworkImplBase { + + private final CoreApi coreApi; + private final GrpcExceptionHandler exceptionHandler; + + @Inject + public GrpcNetworkService(CoreApi coreApi, GrpcExceptionHandler exceptionHandler) { + this.coreApi = coreApi; + this.exceptionHandler = exceptionHandler; + } + + @Override + public void registerNetworkListener(RegisterNetworkListenerRequest request, + StreamObserver responseObserver) { + Context ctx = Context.current().fork(); // context is independent for long-lived request + ctx.run(() -> { + try { + coreApi.addNetworkListener(new GrpcNetworkListener(responseObserver)); + // No onNext / onCompleted, as the response observer should be kept open + } catch (Throwable t) { + exceptionHandler.handleException(log, t, responseObserver); + } + }); + } + + @Value + private static class GrpcNetworkListener implements NetworkListener { + + @NonNull + StreamObserver responseObserver; + + @Override + public void onMessage(@NonNull NetworkMessage network_message) { + if (!((ServerCallStreamObserver) responseObserver).isCancelled()) { + responseObserver.onNext(network_message); + } + } + } + + final ServerInterceptor[] interceptors() { + Optional rateMeteringInterceptor = rateMeteringInterceptor(); + return rateMeteringInterceptor.map(serverInterceptor -> + new ServerInterceptor[]{serverInterceptor}).orElseGet(() -> new ServerInterceptor[0]); + } + + final Optional rateMeteringInterceptor() { + return getCustomRateMeteringInterceptor(coreApi.getConfig().appDataDir, this.getClass()) + .or(() -> Optional.of(CallRateMeteringInterceptor.valueOf( + new HashMap<>() {{ + put(getRegisterNetworkListenerMethod().getFullMethodName(), new GrpcCallRateMeter(10, SECONDS)); + }} + ))); + } +} diff --git a/gpg_keys/woodser.asc b/gpg_keys/woodser.asc deleted file mode 100644 index 4bbc755e..00000000 --- a/gpg_keys/woodser.asc +++ /dev/null @@ -1,65 +0,0 @@ ------BEGIN PGP PUBLIC KEY BLOCK----- - -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 -=4SFl ------END PGP PUBLIC KEY BLOCK----- \ No newline at end of file diff --git a/proto/src/main/proto/grpc.proto b/proto/src/main/proto/grpc.proto index 2d1b5b5f..ae4b13f6 100644 --- a/proto/src/main/proto/grpc.proto +++ b/proto/src/main/proto/grpc.proto @@ -1125,6 +1125,62 @@ message AddressBalanceInfo { bool is_address_unused = 4; } +/////////////////////////////////////////////////////////////////////////////////////////// +// Network +/////////////////////////////////////////////////////////////////////////////////////////// + +service Network { + rpc GetOnlinePeers (GetOnlinePeersRequest) returns (GetOnlinePeersReply) { + } + rpc GetSeednodes (GetSeednodesRequest) returns (GetSeednodesReply) { + } + rpc GetRegisteredArbitrators (GetRegisteredArbitratorsRequest) returns (GetRegisteredArbitratorsReply) { + } + rpc GetNetworkFilter (GetNetworkFilterRequest) returns (GetNetworkFilterReply) { + } + rpc GetAlerts (GetAlertsRequest) returns (GetAlertsRequest) { + } + rpc RegisterNetworkListener (RegisterNetworkListenerRequest) returns (stream NetworkMessage) { + } +} + +message RegisterNetworkListenerRequest { +} + +message NetworkMessage { + NetworkEnvelope network_envelope = 1; +} + +message GetOnlinePeersRequest {} + +message GetOnlinePeersReply { + repeated Peer peers = 1; +} + +message GetSeednodesRequest {} + +message GetSeednodesReply { + repeated NodeAddress seednodes = 1; +} + +message GetRegisteredArbitratorsRequest {} + +message GetRegisteredArbitratorsReply { + repeated Arbitrator arbitrators = 1; +} + +message GetNetworkFilterRequest {} + +message GetNetworkFilterReply { + Filter filter = 1; +} + +message GetAlertsRequest {} + +message GetAlertsReply { + repeated Alert alerts = 1; +} + /////////////////////////////////////////////////////////////////////////////////////////// // Marketplace Protospec