restructure project

This commit is contained in:
Carsten Otto
2021-11-22 20:24:13 +01:00
parent a82cb13ef7
commit 874e8fffa6
55 changed files with 46 additions and 14 deletions

View File

@@ -1,74 +0,0 @@
package de.cotto.lndmanagej.controller;
import com.codahale.metrics.MetricRegistry;
import de.cotto.lndmanagej.controller.dto.ChannelDetailsDto;
import de.cotto.lndmanagej.controller.dto.ObjectMapperConfiguration;
import de.cotto.lndmanagej.controller.dto.OnChainCostsDto;
import de.cotto.lndmanagej.metrics.Metrics;
import de.cotto.lndmanagej.model.BalanceInformation;
import de.cotto.lndmanagej.model.ChannelId;
import de.cotto.lndmanagej.model.Coins;
import de.cotto.lndmanagej.model.LocalChannel;
import de.cotto.lndmanagej.model.Pubkey;
import de.cotto.lndmanagej.service.BalanceService;
import de.cotto.lndmanagej.service.ChannelService;
import de.cotto.lndmanagej.service.NodeService;
import de.cotto.lndmanagej.service.OnChainCostService;
import org.springframework.context.annotation.Import;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("/api/channel/{channelId}")
@Import(ObjectMapperConfiguration.class)
public class ChannelDetailsController {
private final ChannelService channelService;
private final NodeService nodeService;
private final Metrics metrics;
private final BalanceService balanceService;
private final OnChainCostService onChainCostService;
public ChannelDetailsController(
ChannelService channelService,
NodeService nodeService,
BalanceService balanceService,
OnChainCostService onChainCostService,
Metrics metrics
) {
this.channelService = channelService;
this.nodeService = nodeService;
this.balanceService = balanceService;
this.onChainCostService = onChainCostService;
this.metrics = metrics;
}
@GetMapping("/details")
public ChannelDetailsDto getDetails(@PathVariable ChannelId channelId) throws NotFoundException {
metrics.mark(MetricRegistry.name(getClass(), "getDetails"));
LocalChannel localChannel = channelService.getLocalChannel(channelId).orElse(null);
if (localChannel == null) {
throw new NotFoundException();
}
Pubkey remotePubkey = localChannel.getRemotePubkey();
String remoteAlias = nodeService.getAlias(remotePubkey);
return new ChannelDetailsDto(
localChannel,
remoteAlias,
getBalanceInformation(channelId),
getOnChainCosts(channelId)
);
}
private BalanceInformation getBalanceInformation(ChannelId channelId) {
return balanceService.getBalanceInformation(channelId)
.orElse(BalanceInformation.EMPTY);
}
private OnChainCostsDto getOnChainCosts(ChannelId channelId) {
Coins openCosts = onChainCostService.getOpenCosts(channelId).orElse(Coins.NONE);
Coins closeCosts = onChainCostService.getCloseCosts(channelId).orElse(Coins.NONE);
return new OnChainCostsDto(openCosts, closeCosts);
}
}

View File

@@ -1,24 +0,0 @@
package de.cotto.lndmanagej.controller;
import de.cotto.lndmanagej.model.ChannelId;
import org.springframework.core.convert.converter.Converter;
import org.springframework.stereotype.Component;
import javax.annotation.Nonnull;
@Component
public class ChannelIdConverter implements Converter<String, ChannelId> {
public ChannelIdConverter() {
// default constructor
}
@Override
public ChannelId convert(@Nonnull String source) {
try {
long shortChannelId = Long.parseLong(source);
return ChannelId.fromShortChannelId(shortChannelId);
} catch (NumberFormatException numberFormatException) {
return ChannelId.fromCompactForm(source);
}
}
}

View File

@@ -1,7 +0,0 @@
package de.cotto.lndmanagej.controller;
public class CostException extends Exception {
public CostException(String message) {
super(message);
}
}

View File

@@ -1,20 +0,0 @@
package de.cotto.lndmanagej.controller;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.servlet.mvc.method.annotation.ResponseEntityExceptionHandler;
@ControllerAdvice
public class CostExceptionHandler extends ResponseEntityExceptionHandler {
public CostExceptionHandler() {
super();
}
@ExceptionHandler(CostException.class)
public ResponseEntity<String> handleException(CostException exception) {
return ResponseEntity
.badRequest()
.body(exception.getMessage());
}
}

View File

@@ -1,171 +0,0 @@
package de.cotto.lndmanagej.controller;
import com.codahale.metrics.MetricRegistry;
import de.cotto.lndmanagej.metrics.Metrics;
import de.cotto.lndmanagej.model.Channel;
import de.cotto.lndmanagej.model.ChannelId;
import de.cotto.lndmanagej.model.LocalOpenChannel;
import de.cotto.lndmanagej.model.Pubkey;
import de.cotto.lndmanagej.service.BalanceService;
import de.cotto.lndmanagej.service.ChannelService;
import de.cotto.lndmanagej.service.FeeService;
import de.cotto.lndmanagej.service.NodeService;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.Comparator;
import java.util.stream.Collectors;
import java.util.stream.Stream;
@RestController
@RequestMapping("/legacy")
public class LegacyController {
private static final String NEWLINE = "\n";
private final NodeService nodeService;
private final ChannelService channelService;
private final FeeService feeService;
private final BalanceService balanceService;
private final Metrics metrics;
public LegacyController(
NodeService nodeService,
ChannelService channelService,
FeeService feeService,
BalanceService balanceService,
Metrics metrics
) {
this.nodeService = nodeService;
this.channelService = channelService;
this.feeService = feeService;
this.balanceService = balanceService;
this.metrics = metrics;
}
@GetMapping("/node/{pubkey}/all-channels")
public String getAllChannelIdsForPubkey(@PathVariable Pubkey pubkey) {
mark("getAllChannelIdsForPubkey");
return channelService.getAllChannelsWith(pubkey).stream()
.map(Channel::getId)
.sorted()
.map(ChannelId::toString)
.collect(Collectors.joining(NEWLINE));
}
@GetMapping("/open-channels")
public String getOpenChannelIds() {
mark("getOpenChannelIds");
return getOpenChannelIdsSorted()
.map(ChannelId::toString)
.collect(Collectors.joining(NEWLINE));
}
@GetMapping("/open-channels/pretty")
public String getOpenChannelIdsPretty() {
mark("getOpenChannelIdsPretty");
return channelService.getOpenChannels().stream()
.sorted(Comparator.comparing(LocalOpenChannel::getId))
.map(localOpenChannel -> {
Pubkey pubkey = localOpenChannel.getRemotePubkey();
return localOpenChannel.getId().getCompactForm() +
"\t" + pubkey +
"\t" + localOpenChannel.getCapacity() +
"\t" + nodeService.getAlias(pubkey);
})
.collect(Collectors.joining(NEWLINE));
}
@GetMapping("/closed-channels")
public String getClosedChannelIds() {
mark("getClosedChannelIds");
return getClosedChannelIdsSorted()
.map(ChannelId::toString)
.collect(Collectors.joining(NEWLINE));
}
@GetMapping("/force-closing-channels")
public String getForceClosingChannelIds() {
mark("getForceClosingChannelIds");
return channelService.getForceClosingChannels().stream()
.map(Channel::getId)
.sorted()
.map(ChannelId::toString)
.collect(Collectors.joining(NEWLINE));
}
@GetMapping("/peer-pubkeys")
public String getPeerPubkeys() {
mark("getPeerPubkeys");
return channelService.getOpenChannels().stream()
.map(LocalOpenChannel::getRemotePubkey)
.map(Pubkey::toString)
.sorted()
.distinct()
.collect(Collectors.joining(NEWLINE));
}
@GetMapping("/channel/{channelId}/incoming-fee-rate")
public long getIncomingFeeRate(@PathVariable ChannelId channelId) {
mark("getIncomingFeeRate");
return feeService.getIncomingFeeRate(channelId);
}
@GetMapping("/channel/{channelId}/outgoing-fee-rate")
public long getOutgoingFeeRate(@PathVariable ChannelId channelId) {
mark("getOutgoingFeeRate");
return feeService.getOutgoingFeeRate(channelId);
}
@GetMapping("/channel/{channelId}/incoming-base-fee")
public long getIncomingBaseFee(@PathVariable ChannelId channelId) {
mark("getIncomingBaseFee");
return feeService.getIncomingBaseFee(channelId).milliSatoshis();
}
@GetMapping("/channel/{channelId}/outgoing-base-fee")
public long getOutgoingBaseFee(@PathVariable ChannelId channelId) {
mark("getOutgoingBaseFee");
return feeService.getOutgoingBaseFee(channelId).milliSatoshis();
}
@GetMapping("/channel/{channelId}/available-local-balance")
public long getAvailableLocalBalanceForChannel(@PathVariable ChannelId channelId) {
mark("getAvailableLocalBalanceForChannel");
return balanceService.getAvailableLocalBalance(channelId).satoshis();
}
@GetMapping("/channel/{channelId}/available-remote-balance")
public long getAvailableRemoteBalanceForChannel(@PathVariable ChannelId channelId) {
mark("getAvailableRemoteBalanceForChannel");
return balanceService.getAvailableRemoteBalance(channelId).satoshis();
}
@GetMapping("/node/{pubkey}/available-local-balance")
public long getAvailableLocalBalanceForPeer(@PathVariable Pubkey pubkey) {
mark("getAvailableLocalBalanceForPeer");
return balanceService.getAvailableLocalBalance(pubkey).satoshis();
}
@GetMapping("/node/{pubkey}/available-remote-balance")
public long getAvailableRemoteBalanceForPeer(@PathVariable Pubkey pubkey) {
mark("getAvailableRemoteBalanceForPeer");
return balanceService.getAvailableRemoteBalance(pubkey).satoshis();
}
private Stream<ChannelId> getOpenChannelIdsSorted() {
return channelService.getOpenChannels().stream()
.map(Channel::getId)
.sorted();
}
private Stream<ChannelId> getClosedChannelIdsSorted() {
return channelService.getClosedChannels().stream()
.map(Channel::getId)
.sorted();
}
private void mark(String name) {
metrics.mark(MetricRegistry.name(getClass(), name));
}
}

View File

@@ -1,98 +0,0 @@
package de.cotto.lndmanagej.controller;
import com.codahale.metrics.MetricRegistry;
import de.cotto.lndmanagej.controller.dto.BalanceInformationDto;
import de.cotto.lndmanagej.controller.dto.ChannelsForNodeDto;
import de.cotto.lndmanagej.controller.dto.NodeDetailsDto;
import de.cotto.lndmanagej.controller.dto.ObjectMapperConfiguration;
import de.cotto.lndmanagej.controller.dto.OnChainCostsDto;
import de.cotto.lndmanagej.metrics.Metrics;
import de.cotto.lndmanagej.model.BalanceInformation;
import de.cotto.lndmanagej.model.Channel;
import de.cotto.lndmanagej.model.ChannelId;
import de.cotto.lndmanagej.model.Coins;
import de.cotto.lndmanagej.model.Node;
import de.cotto.lndmanagej.model.Pubkey;
import de.cotto.lndmanagej.service.BalanceService;
import de.cotto.lndmanagej.service.ChannelService;
import de.cotto.lndmanagej.service.NodeService;
import de.cotto.lndmanagej.service.OnChainCostService;
import org.springframework.context.annotation.Import;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;
@RestController
@RequestMapping("/api/node/{pubkey}")
@Import(ObjectMapperConfiguration.class)
public class NodeController {
private final NodeService nodeService;
private final Metrics metrics;
private final ChannelService channelService;
private final OnChainCostService onChainCostService;
private final BalanceService balanceService;
public NodeController(
NodeService nodeService,
ChannelService channelService,
Metrics metrics,
OnChainCostService onChainCostService,
BalanceService balanceService
) {
this.nodeService = nodeService;
this.metrics = metrics;
this.channelService = channelService;
this.onChainCostService = onChainCostService;
this.balanceService = balanceService;
}
@GetMapping("/alias")
public String getAlias(Pubkey pubkey) {
mark("getAlias");
return nodeService.getAlias(pubkey);
}
@GetMapping("/details")
public NodeDetailsDto getDetails(@PathVariable Pubkey pubkey) {
mark("getDetails");
Node node = nodeService.getNode(pubkey);
Coins openCosts = onChainCostService.getOpenCostsWith(pubkey);
Coins closeCosts = onChainCostService.getCloseCostsWith(pubkey);
BalanceInformation balanceInformation = balanceService.getBalanceInformation(pubkey);
return new NodeDetailsDto(
pubkey,
node.alias(),
toSortedList(channelService.getOpenChannelsWith(pubkey)),
toSortedList(channelService.getClosedChannelsWith(pubkey)),
toSortedList(channelService.getWaitingCloseChannelsFor(pubkey)),
toSortedList(channelService.getForceClosingChannelsFor(pubkey)),
new OnChainCostsDto(openCosts, closeCosts),
BalanceInformationDto.createFrom(balanceInformation),
node.online()
);
}
@GetMapping("/open-channels")
public ChannelsForNodeDto getOpenChannelIdsForPubkey(@PathVariable Pubkey pubkey) {
mark("getOpenChannelIdsForPubkey");
List<ChannelId> channels = toSortedList(channelService.getOpenChannelsWith(pubkey));
return new ChannelsForNodeDto(pubkey, channels);
}
private List<ChannelId> toSortedList(Set<? extends Channel> channels) {
return channels.stream()
.map(Channel::getId)
.sorted()
.collect(Collectors.toList());
}
private void mark(String getDetails) {
metrics.mark(MetricRegistry.name(getClass(), getDetails));
}
}

View File

@@ -1,7 +0,0 @@
package de.cotto.lndmanagej.controller;
public class NotFoundException extends Exception {
public NotFoundException() {
super();
}
}

View File

@@ -1,20 +0,0 @@
package de.cotto.lndmanagej.controller;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.servlet.mvc.method.annotation.ResponseEntityExceptionHandler;
@ControllerAdvice
public class NotFoundExceptionHandler extends ResponseEntityExceptionHandler {
public NotFoundExceptionHandler() {
super();
}
@ExceptionHandler(NotFoundException.class)
public ResponseEntity<String> handleException(@SuppressWarnings("unused") NotFoundException exception) {
return ResponseEntity
.notFound()
.build();
}
}

View File

@@ -1,49 +0,0 @@
package de.cotto.lndmanagej.controller;
import com.codahale.metrics.MetricRegistry;
import de.cotto.lndmanagej.controller.dto.OnChainCostsDto;
import de.cotto.lndmanagej.metrics.Metrics;
import de.cotto.lndmanagej.model.ChannelId;
import de.cotto.lndmanagej.model.Coins;
import de.cotto.lndmanagej.model.Pubkey;
import de.cotto.lndmanagej.service.OnChainCostService;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("/api/")
public class OnChainCostsController {
private final OnChainCostService onChainCostService;
private final Metrics metrics;
public OnChainCostsController(OnChainCostService onChainCostService, Metrics metrics) {
this.onChainCostService = onChainCostService;
this.metrics = metrics;
}
@GetMapping("/node/{pubkey}/on-chain-costs")
public OnChainCostsDto getCostsForPeer(@PathVariable Pubkey pubkey) {
metrics.mark(MetricRegistry.name(getClass(), "getCostsForPeer"));
return new OnChainCostsDto(
onChainCostService.getOpenCostsWith(pubkey),
onChainCostService.getCloseCostsWith(pubkey)
);
}
@GetMapping("/channel/{channelId}/open-costs")
public long getOpenCostsForChannel(@PathVariable ChannelId channelId) throws CostException {
metrics.mark(MetricRegistry.name(getClass(), "getOpenCostsForChannel"));
return onChainCostService.getOpenCosts(channelId).map(Coins::satoshis)
.orElseThrow(() -> new CostException("Unable to get open costs for channel with ID " + channelId));
}
@GetMapping("/channel/{channelId}/close-costs")
public long getCloseCostsForChannel(@PathVariable ChannelId channelId) throws CostException {
metrics.mark(MetricRegistry.name(getClass(), "getCloseCostsForChannel"));
return onChainCostService.getCloseCosts(channelId).map(Coins::satoshis)
.orElseThrow(() -> new CostException("Unable to get close costs for channel with ID " + channelId));
}
}

View File

@@ -1,19 +0,0 @@
package de.cotto.lndmanagej.controller;
import de.cotto.lndmanagej.model.Pubkey;
import org.springframework.core.convert.converter.Converter;
import org.springframework.stereotype.Component;
import javax.annotation.Nonnull;
@Component
public class PubkeyConverter implements Converter<String, Pubkey> {
public PubkeyConverter() {
// default constructor
}
@Override
public Pubkey convert(@Nonnull String source) {
return Pubkey.create(source);
}
}

View File

@@ -1,30 +0,0 @@
package de.cotto.lndmanagej.controller;
import com.codahale.metrics.MetricRegistry;
import de.cotto.lndmanagej.controller.dto.ObjectMapperConfiguration;
import de.cotto.lndmanagej.metrics.Metrics;
import de.cotto.lndmanagej.service.OwnNodeService;
import org.springframework.context.annotation.Import;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("/api/status/")
@Import(ObjectMapperConfiguration.class)
public class StatusController {
private final OwnNodeService ownNodeService;
private final Metrics metrics;
public StatusController(OwnNodeService ownNodeService, Metrics metrics) {
this.ownNodeService = ownNodeService;
this.metrics = metrics;
}
@GetMapping("/synced-to-chain")
public boolean isSyncedToChain() {
metrics.mark(MetricRegistry.name(getClass(), "isSyncedToChain"));
return ownNodeService.isSyncedToChain();
}
}

View File

@@ -1,28 +0,0 @@
package de.cotto.lndmanagej.controller.dto;
import de.cotto.lndmanagej.model.BalanceInformation;
import de.cotto.lndmanagej.model.Coins;
public record BalanceInformationDto(
String localBalance,
String localReserve,
String localAvailable,
String remoteBalance,
String remoteReserve,
String remoteAvailable
) {
public static BalanceInformationDto createFrom(BalanceInformation balanceInformation) {
return new BalanceInformationDto(
toString(balanceInformation.localBalance()),
toString(balanceInformation.localReserve()),
toString(balanceInformation.localAvailable()),
toString(balanceInformation.remoteBalance()),
toString(balanceInformation.remoteReserve()),
toString(balanceInformation.remoteAvailable())
);
}
private static String toString(Coins coins) {
return String.valueOf(coins.satoshis());
}
}

View File

@@ -1,38 +0,0 @@
package de.cotto.lndmanagej.controller.dto;
import com.fasterxml.jackson.annotation.JsonProperty;
import de.cotto.lndmanagej.model.BalanceInformation;
import de.cotto.lndmanagej.model.ChannelPoint;
import de.cotto.lndmanagej.model.LocalChannel;
import de.cotto.lndmanagej.model.Pubkey;
public record ChannelDetailsDto(
String channelIdShort,
String channelIdCompact,
String channelIdCompactLnd,
ChannelPoint channelPoint,
Pubkey remotePubkey,
String remoteAlias,
@JsonProperty("private") boolean privateChannel,
BalanceInformationDto balance,
OnChainCostsDto onChainCosts
) {
public ChannelDetailsDto(
LocalChannel localChannel,
String remoteAlias,
BalanceInformation balanceInformation,
OnChainCostsDto onChainCosts
) {
this(
String.valueOf(localChannel.getId().getShortChannelId()),
localChannel.getId().getCompactForm(),
localChannel.getId().getCompactFormLnd(),
localChannel.getChannelPoint(),
localChannel.getRemotePubkey(),
remoteAlias,
localChannel.isPrivateChannel(),
BalanceInformationDto.createFrom(balanceInformation),
onChainCosts
);
}
}

View File

@@ -1,12 +0,0 @@
package de.cotto.lndmanagej.controller.dto;
import de.cotto.lndmanagej.model.ChannelId;
import de.cotto.lndmanagej.model.Pubkey;
import java.util.List;
public record ChannelsForNodeDto(
Pubkey node,
List<ChannelId> channels
) {
}

View File

@@ -1,21 +0,0 @@
package de.cotto.lndmanagej.controller.dto;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import com.fasterxml.jackson.databind.ser.std.ToStringSerializer;
import de.cotto.lndmanagej.model.ChannelId;
import de.cotto.lndmanagej.model.Pubkey;
import java.util.List;
public record NodeDetailsDto(
@JsonSerialize(using = ToStringSerializer.class) Pubkey node,
String alias,
List<ChannelId> channels,
List<ChannelId> closedChannels,
List<ChannelId> waitingCloseChannels,
List<ChannelId> pendingForceClosingChannels,
OnChainCostsDto onChainCosts,
BalanceInformationDto balance,
boolean online
) {
}

View File

@@ -1,28 +0,0 @@
package de.cotto.lndmanagej.controller.dto;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.module.SimpleModule;
import com.fasterxml.jackson.databind.ser.std.ToStringSerializer;
import de.cotto.lndmanagej.model.ChannelId;
import de.cotto.lndmanagej.model.ChannelPoint;
import de.cotto.lndmanagej.model.Pubkey;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
@Configuration
public class ObjectMapperConfiguration {
public ObjectMapperConfiguration() {
// default constructor
}
@Bean
@Primary
public ObjectMapper objectMapper() {
SimpleModule module = new SimpleModule("SimpleModule");
module.addSerializer(Pubkey.class, new ToStringSerializer());
module.addSerializer(ChannelId.class, new ToStringSerializer());
module.addSerializer(ChannelPoint.class, new ToStringSerializer());
return new ObjectMapper().registerModule(module);
}
}

View File

@@ -1,9 +0,0 @@
package de.cotto.lndmanagej.controller.dto;
import de.cotto.lndmanagej.model.Coins;
public record OnChainCostsDto(String openCosts, String closeCosts) {
public OnChainCostsDto(Coins openCosts, Coins closeCosts) {
this(String.valueOf(openCosts.satoshis()), String.valueOf(closeCosts.satoshis()));
}
}

View File

@@ -1,62 +0,0 @@
package de.cotto.lndmanagej.service;
import de.cotto.lndmanagej.grpc.GrpcChannels;
import de.cotto.lndmanagej.model.BalanceInformation;
import de.cotto.lndmanagej.model.Channel;
import de.cotto.lndmanagej.model.ChannelId;
import de.cotto.lndmanagej.model.Coins;
import de.cotto.lndmanagej.model.LocalOpenChannel;
import de.cotto.lndmanagej.model.Pubkey;
import org.springframework.stereotype.Component;
import java.util.Optional;
@Component
public class BalanceService {
private final GrpcChannels grpcChannels;
private final ChannelService channelService;
public BalanceService(GrpcChannels grpcChannels, ChannelService channelService) {
this.grpcChannels = grpcChannels;
this.channelService = channelService;
}
public Coins getAvailableLocalBalance(Pubkey peer) {
return channelService.getOpenChannelsWith(peer).stream()
.map(LocalOpenChannel::getId)
.map(this::getAvailableLocalBalance)
.reduce(Coins.NONE, Coins::add);
}
public Coins getAvailableLocalBalance(ChannelId channelId) {
return getBalanceInformation(channelId)
.map(BalanceInformation::localAvailable)
.orElse(Coins.NONE);
}
public Coins getAvailableRemoteBalance(Pubkey peer) {
return channelService.getOpenChannelsWith(peer).stream()
.map(LocalOpenChannel::getId)
.map(this::getAvailableRemoteBalance)
.reduce(Coins.NONE, Coins::add);
}
public Coins getAvailableRemoteBalance(ChannelId channelId) {
return getBalanceInformation(channelId)
.map(BalanceInformation::remoteAvailable)
.orElse(Coins.NONE);
}
public BalanceInformation getBalanceInformation(Pubkey pubkey) {
return channelService.getOpenChannelsWith(pubkey).stream()
.map(Channel::getId)
.map(this::getBalanceInformation)
.flatMap(Optional::stream)
.reduce(BalanceInformation.EMPTY, BalanceInformation::add);
}
public Optional<BalanceInformation> getBalanceInformation(ChannelId channelId) {
return grpcChannels.getChannel(channelId)
.map(LocalOpenChannel::getBalanceInformation);
}
}

View File

@@ -1,39 +0,0 @@
package de.cotto.lndmanagej.service;
import de.cotto.lndmanagej.model.ChannelId;
import de.cotto.lndmanagej.model.ChannelIdResolver;
import de.cotto.lndmanagej.model.ChannelPoint;
import de.cotto.lndmanagej.transactions.model.Transaction;
import de.cotto.lndmanagej.transactions.service.TransactionService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import java.util.Optional;
@Component
public class ChannelIdResolverImpl implements ChannelIdResolver {
private final TransactionService transactionService;
private final Logger logger = LoggerFactory.getLogger(getClass());
public ChannelIdResolverImpl(TransactionService transactionService) {
this.transactionService = transactionService;
}
@Override
public Optional<ChannelId> resolveFromChannelPoint(ChannelPoint channelPoint) {
String transactionHash = channelPoint.getTransactionHash();
Optional<Transaction> transactionOptional = transactionService.getTransaction(transactionHash);
if (transactionOptional.isEmpty()) {
logger.warn("Unable resolve transaction ID for {}", transactionHash);
}
return transactionOptional.map(transaction -> getChannelId(transaction, channelPoint));
}
private ChannelId getChannelId(Transaction transaction, ChannelPoint channelPoint) {
int block = transaction.blockHeight();
int transactionIndex = transaction.positionInBlock();
int output = channelPoint.getOutput();
return ChannelId.fromCompactForm(block + ":" + transactionIndex + ":" + output);
}
}

View File

@@ -1,134 +0,0 @@
package de.cotto.lndmanagej.service;
import com.github.benmanes.caffeine.cache.LoadingCache;
import de.cotto.lndmanagej.caching.CacheBuilder;
import de.cotto.lndmanagej.grpc.GrpcChannels;
import de.cotto.lndmanagej.grpc.GrpcClosedChannels;
import de.cotto.lndmanagej.model.ChannelId;
import de.cotto.lndmanagej.model.ClosedChannel;
import de.cotto.lndmanagej.model.ForceClosingChannel;
import de.cotto.lndmanagej.model.LocalChannel;
import de.cotto.lndmanagej.model.LocalOpenChannel;
import de.cotto.lndmanagej.model.Pubkey;
import de.cotto.lndmanagej.model.WaitingCloseChannel;
import org.springframework.stereotype.Component;
import java.time.Duration;
import java.util.Collection;
import java.util.Optional;
import java.util.Set;
import java.util.function.Supplier;
import java.util.stream.Collectors;
import java.util.stream.Stream;
@Component
public class ChannelService {
private static final Duration CACHE_EXPIRY = Duration.ofMinutes(1);
private final GrpcChannels grpcChannels;
private final LoadingCache<Object, Set<LocalOpenChannel>> channelsCache;
private final LoadingCache<Object, Set<ClosedChannel>> closedChannelsCache;
private final LoadingCache<Object, Set<ForceClosingChannel>> forceClosingChannelsCache;
private final LoadingCache<Object, Set<WaitingCloseChannel>> waitingCloseChannelsCache;
public ChannelService(GrpcChannels grpcChannels, GrpcClosedChannels grpcClosedChannels) {
this.grpcChannels = grpcChannels;
channelsCache = new CacheBuilder()
.withExpiry(CACHE_EXPIRY)
.build(grpcChannels::getChannels);
closedChannelsCache = new CacheBuilder()
.withExpiry(CACHE_EXPIRY)
.build(grpcClosedChannels::getClosedChannels);
forceClosingChannelsCache = new CacheBuilder()
.withExpiry(CACHE_EXPIRY)
.build(grpcChannels::getForceClosingChannels);
waitingCloseChannelsCache = new CacheBuilder()
.withExpiry(CACHE_EXPIRY)
.build(grpcChannels::getWaitingCloseChannels);
}
public boolean isClosed(ChannelId channelId) {
return getClosedChannel(channelId).isPresent();
}
public Optional<LocalChannel> getLocalChannel(ChannelId channelId) {
return getAllLocalChannels()
.filter(c -> channelId.equals(c.getId()))
.findFirst();
}
public Set<LocalOpenChannel> getOpenChannels() {
return channelsCache.get("");
}
public Optional<LocalOpenChannel> getOpenChannel(ChannelId channelId) {
return grpcChannels.getChannel(channelId);
}
public Set<ClosedChannel> getClosedChannels() {
return closedChannelsCache.get("");
}
public Optional<ClosedChannel> getClosedChannel(ChannelId channelId) {
return getClosedChannels().stream()
.filter(c -> channelId.equals(c.getId()))
.findFirst();
}
public Set<ForceClosingChannel> getForceClosingChannels() {
return forceClosingChannelsCache.get("");
}
public Optional<ForceClosingChannel> getForceClosingChannel(ChannelId channelId) {
return getForceClosingChannels().stream()
.filter(c -> channelId.equals(c.getId()))
.findFirst();
}
public Set<WaitingCloseChannel> getWaitingCloseChannels() {
return waitingCloseChannelsCache.get("");
}
public Set<LocalOpenChannel> getOpenChannelsWith(Pubkey peer) {
return getOpenChannels().stream()
.filter(c -> peer.equals(c.getRemotePubkey()))
.collect(Collectors.toSet());
}
public Set<ClosedChannel> getClosedChannelsWith(Pubkey peer) {
return getClosedChannels().stream()
.filter(c -> peer.equals(c.getRemotePubkey()))
.collect(Collectors.toSet());
}
public Set<WaitingCloseChannel> getWaitingCloseChannelsFor(Pubkey peer) {
return getWaitingCloseChannels().stream()
.filter(c -> peer.equals(c.getRemotePubkey()))
.collect(Collectors.toSet());
}
public Set<ForceClosingChannel> getForceClosingChannelsFor(Pubkey peer) {
return getForceClosingChannels().stream()
.filter(c -> peer.equals(c.getRemotePubkey()))
.collect(Collectors.toSet());
}
public Set<LocalChannel> getAllChannelsWith(Pubkey peer) {
return getAllLocalChannels()
.filter(c -> peer.equals(c.getRemotePubkey()))
.collect(Collectors.toSet());
}
public Stream<LocalChannel> getAllLocalChannels() {
Supplier<Set<LocalOpenChannel>> openChannels = this::getOpenChannels;
Supplier<Set<ClosedChannel>> closedChannels = this::getClosedChannels;
Supplier<Set<WaitingCloseChannel>> waitingCloseChannels = this::getWaitingCloseChannels;
Supplier<Set<ForceClosingChannel>> forceClosingChannels = this::getForceClosingChannels;
return Stream.of(
openChannels,
closedChannels,
waitingCloseChannels,
forceClosingChannels
).map(Supplier::get).flatMap(Collection::stream);
}
}

View File

@@ -1,31 +0,0 @@
package de.cotto.lndmanagej.service;
import de.cotto.lndmanagej.grpc.GrpcFees;
import de.cotto.lndmanagej.model.ChannelId;
import de.cotto.lndmanagej.model.Coins;
import org.springframework.stereotype.Component;
@Component
public class FeeService {
private final GrpcFees grpcFees;
public FeeService(GrpcFees grpcFees) {
this.grpcFees = grpcFees;
}
public long getIncomingFeeRate(ChannelId channelId) {
return grpcFees.getIncomingFeeRate(channelId).orElseThrow(IllegalStateException::new);
}
public long getOutgoingFeeRate(ChannelId channelId) {
return grpcFees.getOutgoingFeeRate(channelId).orElseThrow(IllegalStateException::new);
}
public Coins getOutgoingBaseFee(ChannelId channelId) {
return grpcFees.getOutgoingBaseFee(channelId).orElseThrow(IllegalStateException::new);
}
public Coins getIncomingBaseFee(ChannelId channelId) {
return grpcFees.getIncomingBaseFee(channelId).orElseThrow(IllegalStateException::new);
}
}

View File

@@ -1,54 +0,0 @@
package de.cotto.lndmanagej.service;
import com.github.benmanes.caffeine.cache.LoadingCache;
import de.cotto.lndmanagej.caching.CacheBuilder;
import de.cotto.lndmanagej.grpc.GrpcNodeInfo;
import de.cotto.lndmanagej.model.Node;
import de.cotto.lndmanagej.model.Pubkey;
import org.springframework.stereotype.Component;
import java.time.Duration;
@Component
public class NodeService {
private static final int MAXIMUM_SIZE = 500;
private static final Duration ALIAS_CACHE_EXPIRY = Duration.ofHours(24);
private static final Duration ALIAS_CACHE_REFRESH = Duration.ofMinutes(30);
private static final Duration NODE_CACHE_EXPIRY = Duration.ofSeconds(60);
private final GrpcNodeInfo grpcNodeInfo;
private final LoadingCache<Pubkey, String> aliasCache = new CacheBuilder()
.withExpiry(ALIAS_CACHE_EXPIRY)
.withRefresh(ALIAS_CACHE_REFRESH)
.withMaximumSize(MAXIMUM_SIZE)
.build(this::getAliasWithoutCacheAndUpdateNodeCache);
private final LoadingCache<Pubkey, Node> nodeCache = new CacheBuilder()
.withExpiry(NODE_CACHE_EXPIRY)
.withMaximumSize(MAXIMUM_SIZE)
.build(this::getNodeWithoutCacheAndUpdateAliasCache);
public NodeService(GrpcNodeInfo grpcNodeInfo) {
this.grpcNodeInfo = grpcNodeInfo;
}
public String getAlias(Pubkey pubkey) {
return aliasCache.get(pubkey);
}
public Node getNode(Pubkey pubkey) {
return nodeCache.get(pubkey);
}
private Node getNodeWithoutCacheAndUpdateAliasCache(Pubkey pubkey) {
Node node = grpcNodeInfo.getNode(pubkey);
aliasCache.put(pubkey, node.alias());
return node;
}
private String getAliasWithoutCacheAndUpdateNodeCache(Pubkey pubkey) {
Node node = grpcNodeInfo.getNode(pubkey);
nodeCache.put(pubkey, node);
return node.alias();
}
}

View File

@@ -1,86 +0,0 @@
package de.cotto.lndmanagej.service;
import de.cotto.lndmanagej.model.ChannelId;
import de.cotto.lndmanagej.model.ChannelPoint;
import de.cotto.lndmanagej.model.ClosedChannel;
import de.cotto.lndmanagej.model.Coins;
import de.cotto.lndmanagej.model.LocalChannel;
import de.cotto.lndmanagej.model.OpenInitiator;
import de.cotto.lndmanagej.model.Pubkey;
import de.cotto.lndmanagej.transactions.model.Transaction;
import de.cotto.lndmanagej.transactions.service.TransactionService;
import org.springframework.stereotype.Component;
import java.util.Optional;
@Component
public class OnChainCostService {
private final TransactionService transactionService;
private final ChannelService channelService;
public OnChainCostService(TransactionService transactionService, ChannelService channelService) {
this.transactionService = transactionService;
this.channelService = channelService;
}
public Coins getOpenCostsWith(Pubkey pubkey) {
return channelService.getAllChannelsWith(pubkey).stream()
.map(this::getOpenCosts)
.flatMap(Optional::stream)
.reduce(Coins.NONE, Coins::add);
}
public Optional<Coins> getOpenCosts(ChannelId channelId) {
return channelService.getLocalChannel(channelId).flatMap(this::getOpenCosts);
}
public Optional<Coins> getOpenCosts(LocalChannel localChannel) {
if (localChannel.getOpenInitiator().equals(OpenInitiator.LOCAL)) {
String openTransactionHash = localChannel.getChannelPoint().getTransactionHash();
return transactionService.getTransaction(openTransactionHash)
.map(Transaction::fees)
.map(Coins::satoshis)
.map(sat -> {
long channels = getNumberOfChannelsWithOpenTransactionHash(openTransactionHash);
return Coins.ofSatoshis(sat / channels);
});
}
if (localChannel.getOpenInitiator().equals(OpenInitiator.REMOTE)) {
return Optional.of(Coins.NONE);
}
return Optional.empty();
}
public Coins getCloseCostsWith(Pubkey pubkey) {
return channelService.getClosedChannelsWith(pubkey).stream()
.map(this::getCloseCosts)
.flatMap(Optional::stream)
.reduce(Coins.NONE, Coins::add);
}
public Optional<Coins> getCloseCosts(ChannelId channelId) {
if (channelService.isClosed(channelId)) {
return channelService.getClosedChannel(channelId).flatMap(this::getCloseCosts);
}
return Optional.of(Coins.NONE);
}
public Optional<Coins> getCloseCosts(ClosedChannel closedChannel) {
if (closedChannel.getOpenInitiator().equals(OpenInitiator.LOCAL)) {
return transactionService.getTransaction(closedChannel.getCloseTransactionHash())
.map(Transaction::fees);
}
if (closedChannel.getOpenInitiator().equals(OpenInitiator.REMOTE)) {
return Optional.of(Coins.NONE);
}
return Optional.empty();
}
private long getNumberOfChannelsWithOpenTransactionHash(String openTransactionHash) {
return channelService.getAllLocalChannels()
.map(LocalChannel::getChannelPoint)
.map(ChannelPoint::getTransactionHash)
.filter(x -> x.equals(openTransactionHash))
.count();
}
}

View File

@@ -1,27 +0,0 @@
package de.cotto.lndmanagej.service;
import de.cotto.lndmanagej.model.OpenInitiator;
import de.cotto.lndmanagej.model.OpenInitiatorResolver;
import de.cotto.lndmanagej.transactions.service.TransactionService;
import org.springframework.stereotype.Component;
@Component
public class OpenInitiatorResolverImpl implements OpenInitiatorResolver {
private final TransactionService transactionService;
public OpenInitiatorResolverImpl(TransactionService transactionService) {
this.transactionService = transactionService;
}
@Override
public OpenInitiator resolveFromOpenTransactionHash(String transactionHash) {
Boolean knownByLnd = transactionService.isKnownByLnd(transactionHash).orElse(null);
if (knownByLnd == null) {
return OpenInitiator.UNKNOWN;
}
if (knownByLnd) {
return OpenInitiator.LOCAL;
}
return OpenInitiator.REMOTE;
}
}

View File

@@ -1,30 +0,0 @@
package de.cotto.lndmanagej.service;
import com.github.benmanes.caffeine.cache.LoadingCache;
import de.cotto.lndmanagej.caching.CacheBuilder;
import de.cotto.lndmanagej.grpc.GrpcGetInfo;
import org.springframework.stereotype.Component;
import java.time.Duration;
@Component
public class OwnNodeService {
private static final Duration CACHE_EXPIRY = Duration.ofSeconds(30);
private final GrpcGetInfo grpcGetInfo;
private final LoadingCache<Object, Boolean> syncedToChainCache = new CacheBuilder()
.withExpiry(CACHE_EXPIRY)
.build(this::isSyncedToChainWithoutCache);
public OwnNodeService(GrpcGetInfo grpcGetInfo) {
this.grpcGetInfo = grpcGetInfo;
}
public boolean isSyncedToChain() {
return Boolean.TRUE.equals(syncedToChainCache.get(""));
}
private boolean isSyncedToChainWithoutCache() {
return grpcGetInfo.isSyncedToChain().orElse(false);
}
}

View File

@@ -1,67 +0,0 @@
package de.cotto.lndmanagej.service;
import de.cotto.lndmanagej.model.Channel;
import de.cotto.lndmanagej.model.ChannelPoint;
import de.cotto.lndmanagej.model.ClosedOrClosingChannel;
import de.cotto.lndmanagej.model.ForceClosingChannel;
import de.cotto.lndmanagej.transactions.service.TransactionService;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
import java.util.Collection;
import java.util.stream.Stream;
import static java.util.concurrent.TimeUnit.MINUTES;
@Component
public class TransactionBackgroundLoader {
private final ChannelService channelService;
private final TransactionService transactionService;
public TransactionBackgroundLoader(ChannelService channelService, TransactionService transactionService) {
this.channelService = channelService;
this.transactionService = transactionService;
}
@Scheduled(fixedDelay = 5, timeUnit = MINUTES)
public void loadTransactionForOneChannel() {
getTransactionHashes()
.filter(transactionService::isUnknown)
.findAny()
.ifPresent(transactionService::getTransaction);
}
private Stream<String> getTransactionHashes() {
Stream<String> openTransactionHashes = getOpenTransactionHashes();
Stream<String> closeTransactionHashes = getCloseTransactionHashes();
Stream<String> htlcOutpointHashes = getHtlcOutpointHashes();
return Stream.of(openTransactionHashes, closeTransactionHashes, htlcOutpointHashes)
.flatMap(s -> s);
}
private Stream<String> getOpenTransactionHashes() {
return Stream.of(
channelService.getOpenChannels(),
channelService.getClosedChannels(),
channelService.getForceClosingChannels(),
channelService.getWaitingCloseChannels()
)
.flatMap(Collection::stream)
.map(Channel::getChannelPoint)
.map(ChannelPoint::getTransactionHash);
}
private Stream<String> getCloseTransactionHashes() {
return Stream.of(channelService.getClosedChannels(), channelService.getForceClosingChannels())
.flatMap(Collection::stream)
.map(ClosedOrClosingChannel::getCloseTransactionHash);
}
private Stream<String> getHtlcOutpointHashes() {
return channelService.getForceClosingChannels().stream()
.map(ForceClosingChannel::getHtlcOutpoints)
.flatMap(Collection::stream)
.map(ChannelPoint::getTransactionHash);
}
}