mirror of
https://github.com/aljazceru/lnd-manageJ.git
synced 2026-01-31 03:34:36 +01:00
log settled and failed HTLCs
This commit is contained in:
@@ -4,9 +4,9 @@ plugins {
|
||||
|
||||
dependencies {
|
||||
implementation project(':grpc-adapter')
|
||||
implementation project(':graph')
|
||||
implementation project(':model')
|
||||
runtimeOnly 'org.postgresql:postgresql'
|
||||
testImplementation testFixtures(project(':graph'))
|
||||
testImplementation testFixtures(project(':model'))
|
||||
}
|
||||
|
||||
bootJar {
|
||||
@@ -18,13 +18,13 @@ jacocoTestCoverageVerification {
|
||||
rules.forEach {rule ->
|
||||
rule.limits.forEach {limit ->
|
||||
if (limit.counter == 'CLASS') {
|
||||
limit.minimum = 0.5
|
||||
limit.minimum = 0.6
|
||||
}
|
||||
if (limit.counter == 'INSTRUCTION') {
|
||||
limit.minimum = 0.81
|
||||
limit.minimum = 0.94
|
||||
}
|
||||
if (limit.counter == 'METHOD') {
|
||||
limit.minimum = 0.5
|
||||
limit.minimum = 0.7
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
package de.cotto.lndmanagej;
|
||||
|
||||
import de.cotto.lndmanagej.grpc.GrpcHtlcEvents;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.scheduling.annotation.Scheduled;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
@Component
|
||||
public class HtlcLogger {
|
||||
private final Logger logger = LoggerFactory.getLogger(getClass());
|
||||
private final GrpcHtlcEvents grpcHtlcEvents;
|
||||
|
||||
public HtlcLogger(GrpcHtlcEvents grpcHtlcEvents) {
|
||||
this.grpcHtlcEvents = grpcHtlcEvents;
|
||||
}
|
||||
|
||||
@Scheduled(fixedDelay = 1_000)
|
||||
public void logForwardFailures() {
|
||||
grpcHtlcEvents.getForwardFailures()
|
||||
.forEach(forwardFailure -> logger.info("Forward Failure: {}", forwardFailure));
|
||||
}
|
||||
|
||||
@Scheduled(fixedDelay = 1_000)
|
||||
public void logSettledForwards() {
|
||||
grpcHtlcEvents.getSettledForwards()
|
||||
.forEach(settledForward -> logger.info("Settled Forward: {}", settledForward));
|
||||
}
|
||||
}
|
||||
@@ -15,7 +15,7 @@ public class InfoLogger {
|
||||
this.grpcGetInfo = grpcGetInfo;
|
||||
}
|
||||
|
||||
@Scheduled(fixedRate = 10_000)
|
||||
@Scheduled(fixedRate = 60_000)
|
||||
public void logDetails() {
|
||||
logger.info("Alias: {}", grpcGetInfo.getAlias());
|
||||
logger.info("Pubkey: {}", grpcGetInfo.getPubkey());
|
||||
|
||||
@@ -3,6 +3,7 @@ spring.main.banner-mode=off
|
||||
spring.profiles.active=default
|
||||
logging.level.root=info
|
||||
logging.pattern.console=%d %clr(%-5p) %logger: %m%rEx{2}%n
|
||||
spring.task.scheduling.pool.size=20
|
||||
|
||||
lndmanagej.macaroon-file=${user.home}/.lnd/data/chain/bitcoin/mainnet/admin.macaroon
|
||||
lndmanagej.cert-file=${user.home}/.lnd/tls.cert
|
||||
|
||||
@@ -0,0 +1,44 @@
|
||||
package de.cotto.lndmanagej;
|
||||
|
||||
import de.cotto.lndmanagej.grpc.GrpcHtlcEvents;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.mockito.InjectMocks;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.junit.jupiter.MockitoExtension;
|
||||
import uk.org.lidalia.slf4jtest.TestLogger;
|
||||
import uk.org.lidalia.slf4jtest.TestLoggerFactory;
|
||||
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import static de.cotto.lndmanagej.model.ForwardFailureFixtures.FORWARD_FAILURE;
|
||||
import static de.cotto.lndmanagej.model.SettledForwardFixtures.SETTLED_FORWARD;
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.mockito.Mockito.when;
|
||||
import static uk.org.lidalia.slf4jtest.LoggingEvent.info;
|
||||
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
class HtlcLoggerTest {
|
||||
private final TestLogger logger = TestLoggerFactory.getTestLogger(HtlcLogger.class);
|
||||
|
||||
@InjectMocks
|
||||
private HtlcLogger htlcLogger;
|
||||
|
||||
@Mock
|
||||
@SuppressWarnings("unused")
|
||||
private GrpcHtlcEvents grpcHtlcEvents;
|
||||
|
||||
@Test
|
||||
void logSettledForwards() {
|
||||
when(grpcHtlcEvents.getSettledForwards()).thenReturn(Stream.of(SETTLED_FORWARD));
|
||||
htlcLogger.logSettledForwards();
|
||||
assertThat(logger.getLoggingEvents()).contains(info("Settled Forward: {}", SETTLED_FORWARD));
|
||||
}
|
||||
|
||||
@Test
|
||||
void logForwardFailures() {
|
||||
when(grpcHtlcEvents.getForwardFailures()).thenReturn(Stream.of(FORWARD_FAILURE));
|
||||
htlcLogger.logForwardFailures();
|
||||
assertThat(logger.getLoggingEvents()).contains(info("Forward Failure: {}", FORWARD_FAILURE));
|
||||
}
|
||||
}
|
||||
@@ -9,7 +9,7 @@ import org.mockito.junit.jupiter.MockitoExtension;
|
||||
import uk.org.lidalia.slf4jtest.TestLogger;
|
||||
import uk.org.lidalia.slf4jtest.TestLoggerFactory;
|
||||
|
||||
import static de.cotto.lndmanagej.graph.model.NodeFixtures.ALIAS;
|
||||
import static de.cotto.lndmanagej.model.NodeFixtures.ALIAS;
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.mockito.Mockito.when;
|
||||
import static uk.org.lidalia.slf4jtest.LoggingEvent.info;
|
||||
|
||||
@@ -1,16 +0,0 @@
|
||||
package de.cotto.lndmanagej.graph.model;
|
||||
|
||||
import static de.cotto.lndmanagej.graph.model.ChannelIdFixtures.CHANNEL_ID;
|
||||
import static de.cotto.lndmanagej.graph.model.NodeFixtures.NODE;
|
||||
import static de.cotto.lndmanagej.graph.model.NodeFixtures.NODE_2;
|
||||
|
||||
public class ChannelFixtures {
|
||||
public static final Coins CAPACITY = Coins.ofSatoshis(21_000_000L);
|
||||
|
||||
public static final Channel CHANNEL = Channel.builder()
|
||||
.withChannelId(CHANNEL_ID)
|
||||
.withCapacity(CAPACITY)
|
||||
.withNode1(NODE)
|
||||
.withNode2(NODE_2)
|
||||
.build();
|
||||
}
|
||||
@@ -1,5 +0,0 @@
|
||||
package de.cotto.lndmanagej.graph.model;
|
||||
|
||||
public class ChannelIdFixtures {
|
||||
public static final ChannelId CHANNEL_ID = ChannelId.fromCompactForm("712345:123:1");
|
||||
}
|
||||
@@ -4,6 +4,8 @@ plugins {
|
||||
|
||||
dependencies {
|
||||
implementation project(':grpc-client')
|
||||
implementation project(':model')
|
||||
testImplementation testFixtures(project(':model'))
|
||||
}
|
||||
|
||||
jacocoTestCoverageVerification {
|
||||
|
||||
@@ -0,0 +1,109 @@
|
||||
package de.cotto.lndmanagej.grpc;
|
||||
|
||||
import de.cotto.lndmanagej.model.ForwardAttempt;
|
||||
import de.cotto.lndmanagej.model.ForwardFailure;
|
||||
import de.cotto.lndmanagej.model.HtlcDetails;
|
||||
import de.cotto.lndmanagej.model.SettledForward;
|
||||
import org.springframework.stereotype.Component;
|
||||
import routerrpc.RouterOuterClass;
|
||||
import routerrpc.RouterOuterClass.HtlcEvent;
|
||||
|
||||
import java.util.Iterator;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.function.UnaryOperator;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
@Component
|
||||
public class GrpcHtlcEvents {
|
||||
private final GrpcService grpcService;
|
||||
private final Map<HtlcDetails, ForwardAttempt> previousAttempts;
|
||||
|
||||
public GrpcHtlcEvents(GrpcService grpcService) {
|
||||
this.grpcService = grpcService;
|
||||
previousAttempts = new ConcurrentHashMap<>();
|
||||
}
|
||||
|
||||
public Stream<ForwardFailure> getForwardFailures() {
|
||||
return getEventStream()
|
||||
.filter(this::isForwardFailure)
|
||||
.map(this::createForwardFailure)
|
||||
.flatMap(Optional::stream);
|
||||
}
|
||||
|
||||
public Stream<SettledForward> getSettledForwards() {
|
||||
return getEventStream()
|
||||
.filter(this::isSettledForward)
|
||||
.map(this::createSettledForward)
|
||||
.flatMap(Optional::stream);
|
||||
}
|
||||
|
||||
private Stream<HtlcEvent> getEventStream() {
|
||||
return Stream.iterate(grpcService.getHtlcEvents(), Iterator::hasNext, UnaryOperator.identity())
|
||||
.map(Iterator::next)
|
||||
.map(this::storeAttempt);
|
||||
}
|
||||
|
||||
private HtlcEvent storeAttempt(HtlcEvent htlcEvent) {
|
||||
if (isForwardAttempt(htlcEvent)) {
|
||||
previousAttempts.put(getHtlcDetails(htlcEvent).withoutTimestamp(), createForwardAttempt(htlcEvent));
|
||||
}
|
||||
return htlcEvent;
|
||||
}
|
||||
|
||||
private boolean isForwardAttempt(HtlcEvent htlcEvent) {
|
||||
return htlcEvent.hasForwardEvent()
|
||||
&& htlcEvent.getIncomingChannelId() > 0
|
||||
&& htlcEvent.getOutgoingChannelId() > 0;
|
||||
}
|
||||
|
||||
private boolean isForwardFailure(HtlcEvent htlcEvent) {
|
||||
return htlcEvent.hasForwardFailEvent()
|
||||
&& htlcEvent.getIncomingChannelId() > 0
|
||||
&& htlcEvent.getOutgoingChannelId() > 0;
|
||||
}
|
||||
|
||||
private boolean isSettledForward(HtlcEvent htlcEvent) {
|
||||
return htlcEvent.hasSettleEvent()
|
||||
&& htlcEvent.getIncomingChannelId() > 0
|
||||
&& htlcEvent.getOutgoingChannelId() > 0;
|
||||
}
|
||||
|
||||
private ForwardAttempt createForwardAttempt(HtlcEvent event) {
|
||||
RouterOuterClass.HtlcInfo info = event.getForwardEvent().getInfo();
|
||||
return ForwardAttempt.builder()
|
||||
.withHtlcDetails(getHtlcDetails(event))
|
||||
.withIncomingTimelock(info.getIncomingTimelock())
|
||||
.withOutgoingTimelock(info.getOutgoingTimelock())
|
||||
.withIncomingAmount(info.getIncomingAmtMsat())
|
||||
.withOutgoingAmount(info.getOutgoingAmtMsat())
|
||||
.build();
|
||||
}
|
||||
|
||||
private Optional<ForwardFailure> createForwardFailure(HtlcEvent event) {
|
||||
HtlcDetails htlcDetails = getHtlcDetails(event);
|
||||
return getForwardAttemptFor(htlcDetails).map(attempt -> new ForwardFailure(htlcDetails, attempt));
|
||||
}
|
||||
|
||||
private Optional<SettledForward> createSettledForward(HtlcEvent event) {
|
||||
HtlcDetails htlcDetails = getHtlcDetails(event);
|
||||
return getForwardAttemptFor(htlcDetails).map(attempt -> new SettledForward(htlcDetails, attempt));
|
||||
}
|
||||
|
||||
private Optional<ForwardAttempt> getForwardAttemptFor(HtlcDetails htlcDetails) {
|
||||
ForwardAttempt forwardAttempt = previousAttempts.remove(htlcDetails.withoutTimestamp());
|
||||
return Optional.ofNullable(forwardAttempt);
|
||||
}
|
||||
|
||||
private HtlcDetails getHtlcDetails(HtlcEvent event) {
|
||||
return HtlcDetails.builder()
|
||||
.withTimestamp(event.getTimestampNs())
|
||||
.withIncomingChannelId(event.getIncomingChannelId())
|
||||
.withOutgoingChannelId(event.getOutgoingChannelId())
|
||||
.withIncomingHtlcId(event.getIncomingHtlcId())
|
||||
.withOutgoingHtlcId(event.getOutgoingHtlcId())
|
||||
.build();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -7,17 +7,24 @@ import lnrpc.LightningGrpc;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.stereotype.Component;
|
||||
import routerrpc.RouterGrpc;
|
||||
import routerrpc.RouterOuterClass;
|
||||
import routerrpc.RouterOuterClass.SubscribeHtlcEventsRequest;
|
||||
|
||||
import javax.annotation.PreDestroy;
|
||||
import java.io.IOException;
|
||||
import java.util.Collections;
|
||||
import java.util.Iterator;
|
||||
import java.util.Optional;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
@Component
|
||||
public class GrpcService {
|
||||
private final Logger logger = LoggerFactory.getLogger(getClass());
|
||||
|
||||
private final LightningGrpc.LightningBlockingStub lightningStub;
|
||||
private final StubCreator stubCreator;
|
||||
private final LightningGrpc.LightningBlockingStub lightningStub;
|
||||
private final RouterGrpc.RouterBlockingStub routerStub;
|
||||
|
||||
public GrpcService(LndConfiguration lndConfiguration) throws IOException {
|
||||
stubCreator = new StubCreator(
|
||||
@@ -26,7 +33,8 @@ public class GrpcService {
|
||||
lndConfiguration.getPort(),
|
||||
lndConfiguration.getHost()
|
||||
);
|
||||
lightningStub = this.stubCreator.getLightningStub();
|
||||
lightningStub = stubCreator.getLightningStub();
|
||||
routerStub = stubCreator.getRouterStub();
|
||||
}
|
||||
|
||||
@PreDestroy
|
||||
@@ -35,8 +43,17 @@ public class GrpcService {
|
||||
}
|
||||
|
||||
Optional<GetInfoResponse> getInfo() {
|
||||
return get(() -> lightningStub.getInfo(lnrpc.GetInfoRequest.getDefaultInstance()));
|
||||
}
|
||||
|
||||
Iterator<RouterOuterClass.HtlcEvent> getHtlcEvents() {
|
||||
return get(() -> routerStub.subscribeHtlcEvents(SubscribeHtlcEventsRequest.newBuilder().build()))
|
||||
.orElse(Collections.emptyIterator());
|
||||
}
|
||||
|
||||
private <X> Optional<X> get(Supplier<X> supplier) {
|
||||
try {
|
||||
return Optional.of(lightningStub.getInfo(lnrpc.GetInfoRequest.getDefaultInstance()));
|
||||
return Optional.ofNullable(supplier.get());
|
||||
} catch (StatusRuntimeException exception) {
|
||||
logger.warn("Exception while connecting to lnd: ", exception);
|
||||
return Optional.empty();
|
||||
|
||||
@@ -0,0 +1,207 @@
|
||||
package de.cotto.lndmanagej.grpc;
|
||||
|
||||
import org.junit.jupiter.api.Nested;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.mockito.InjectMocks;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.junit.jupiter.MockitoExtension;
|
||||
import routerrpc.RouterOuterClass;
|
||||
import routerrpc.RouterOuterClass.ForwardEvent;
|
||||
import routerrpc.RouterOuterClass.ForwardFailEvent;
|
||||
import routerrpc.RouterOuterClass.HtlcEvent;
|
||||
import routerrpc.RouterOuterClass.SettleEvent;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
import static de.cotto.lndmanagej.model.ChannelIdFixtures.CHANNEL_ID;
|
||||
import static de.cotto.lndmanagej.model.ChannelIdFixtures.CHANNEL_ID_2;
|
||||
import static de.cotto.lndmanagej.model.ChannelIdFixtures.CHANNEL_ID_3;
|
||||
import static de.cotto.lndmanagej.model.ForwardFailureFixtures.FORWARD_FAILURE;
|
||||
import static de.cotto.lndmanagej.model.SettledForwardFixtures.SETTLED_FORWARD;
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
class GrpcHtlcEventsTest {
|
||||
@InjectMocks
|
||||
private GrpcHtlcEvents grpcHtlcEvents;
|
||||
|
||||
@Mock
|
||||
@SuppressWarnings("unused")
|
||||
private GrpcService grpcService;
|
||||
|
||||
private final HtlcEvent attemptEvent = getAttempt(CHANNEL_ID.getShortChannelId(), CHANNEL_ID_2.getShortChannelId());
|
||||
|
||||
@Nested
|
||||
class GetForwardFailures {
|
||||
|
||||
private final HtlcEvent failEvent = getFailEvent(
|
||||
CHANNEL_ID.getShortChannelId(),
|
||||
CHANNEL_ID_2.getShortChannelId()
|
||||
);
|
||||
|
||||
@Test
|
||||
void empty() {
|
||||
when(grpcService.getHtlcEvents()).thenReturn(Collections.emptyIterator());
|
||||
assertThat(grpcHtlcEvents.getForwardFailures()).isEmpty();
|
||||
}
|
||||
|
||||
@Test
|
||||
void with_other_event() {
|
||||
when(grpcService.getHtlcEvents()).thenReturn(Set.of(HtlcEvent.getDefaultInstance()).iterator());
|
||||
assertThat(grpcHtlcEvents.getForwardFailures()).isEmpty();
|
||||
}
|
||||
|
||||
@Test
|
||||
void with_fail_event() {
|
||||
when(grpcService.getHtlcEvents()).thenReturn(Set.of(failEvent).iterator());
|
||||
assertThat(grpcHtlcEvents.getForwardFailures()).isEmpty();
|
||||
}
|
||||
|
||||
@Test
|
||||
void with_attempt_and_fail_event() {
|
||||
when(grpcService.getHtlcEvents()).thenReturn(List.of(attemptEvent, failEvent).iterator());
|
||||
assertThat(grpcHtlcEvents.getForwardFailures()).containsExactly(FORWARD_FAILURE);
|
||||
}
|
||||
|
||||
@Test
|
||||
void attempt_is_deleted_when_used() {
|
||||
when(grpcService.getHtlcEvents()).thenReturn(List.of(attemptEvent, failEvent, failEvent).iterator());
|
||||
assertThat(grpcHtlcEvents.getForwardFailures()).hasSize(1);
|
||||
}
|
||||
|
||||
@Test
|
||||
void with_other_attempt_and_fail_event() {
|
||||
HtlcEvent attemptEvent = getAttempt(CHANNEL_ID.getShortChannelId(), CHANNEL_ID_3.getShortChannelId());
|
||||
when(grpcService.getHtlcEvents()).thenReturn(List.of(attemptEvent, failEvent).iterator());
|
||||
assertThat(grpcHtlcEvents.getForwardFailures()).isEmpty();
|
||||
}
|
||||
|
||||
@Test
|
||||
void ignores_failure_event_with_zero_incoming_channel_id() {
|
||||
HtlcEvent failEvent = getFailEvent(0, CHANNEL_ID_2.getShortChannelId());
|
||||
when(grpcService.getHtlcEvents()).thenReturn(List.of(failEvent).iterator());
|
||||
assertThat(grpcHtlcEvents.getForwardFailures()).isEmpty();
|
||||
}
|
||||
|
||||
@Test
|
||||
void ignores_failure_event_with_zero_outgoing_channel_id() {
|
||||
HtlcEvent failEvent = getFailEvent(CHANNEL_ID.getShortChannelId(), 0);
|
||||
when(grpcService.getHtlcEvents()).thenReturn(List.of(failEvent).iterator());
|
||||
assertThat(grpcHtlcEvents.getForwardFailures()).isEmpty();
|
||||
}
|
||||
|
||||
private HtlcEvent getFailEvent(long incomingChannelId, long outgoingChannelId) {
|
||||
ForwardFailEvent forwardFailEvent = ForwardFailEvent.getDefaultInstance();
|
||||
return getBuilderWithDefaults(incomingChannelId, outgoingChannelId)
|
||||
.setForwardFailEvent(forwardFailEvent)
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
@Nested
|
||||
class GetSettledForwards {
|
||||
|
||||
private final HtlcEvent settleEvent = getSettleEvent(
|
||||
CHANNEL_ID.getShortChannelId(),
|
||||
CHANNEL_ID_2.getShortChannelId()
|
||||
);
|
||||
|
||||
@Test
|
||||
void empty() {
|
||||
when(grpcService.getHtlcEvents()).thenReturn(Collections.emptyIterator());
|
||||
assertThat(grpcHtlcEvents.getSettledForwards()).isEmpty();
|
||||
}
|
||||
|
||||
@Test
|
||||
void with_other_event() {
|
||||
when(grpcService.getHtlcEvents()).thenReturn(Set.of(HtlcEvent.getDefaultInstance()).iterator());
|
||||
assertThat(grpcHtlcEvents.getSettledForwards()).isEmpty();
|
||||
}
|
||||
|
||||
@Test
|
||||
void with_settle_event() {
|
||||
when(grpcService.getHtlcEvents()).thenReturn(Set.of(settleEvent).iterator());
|
||||
assertThat(grpcHtlcEvents.getSettledForwards()).isEmpty();
|
||||
}
|
||||
|
||||
@Test
|
||||
void with_attempt_and_settle_event() {
|
||||
when(grpcService.getHtlcEvents()).thenReturn(List.of(attemptEvent, settleEvent).iterator());
|
||||
assertThat(grpcHtlcEvents.getSettledForwards()).containsExactly(SETTLED_FORWARD);
|
||||
}
|
||||
|
||||
@Test
|
||||
void attempt_is_deleted_when_used() {
|
||||
when(grpcService.getHtlcEvents()).thenReturn(List.of(attemptEvent, settleEvent, settleEvent).iterator());
|
||||
assertThat(grpcHtlcEvents.getSettledForwards()).hasSize(1);
|
||||
}
|
||||
|
||||
@Test
|
||||
void with_other_attempt_and_settle_event() {
|
||||
HtlcEvent attemptEvent = getAttempt(CHANNEL_ID.getShortChannelId(), CHANNEL_ID_3.getShortChannelId());
|
||||
when(grpcService.getHtlcEvents()).thenReturn(List.of(attemptEvent, settleEvent).iterator());
|
||||
assertThat(grpcHtlcEvents.getSettledForwards()).isEmpty();
|
||||
}
|
||||
|
||||
@Test
|
||||
void ignores_attempt_with_zero_incoming_channel_id() {
|
||||
HtlcEvent attemptEvent = getAttempt(0, CHANNEL_ID_2.getShortChannelId());
|
||||
when(grpcService.getHtlcEvents()).thenReturn(List.of(attemptEvent).iterator());
|
||||
assertThat(grpcHtlcEvents.getSettledForwards()).isEmpty();
|
||||
}
|
||||
|
||||
@Test
|
||||
void ignores_attempt_with_zero_outgoing_channel_id() {
|
||||
HtlcEvent attemptEvent = getAttempt(CHANNEL_ID.getShortChannelId(), 0);
|
||||
when(grpcService.getHtlcEvents()).thenReturn(List.of(attemptEvent).iterator());
|
||||
assertThat(grpcHtlcEvents.getSettledForwards()).isEmpty();
|
||||
}
|
||||
|
||||
@Test
|
||||
void ignores_settle_event_with_zero_incoming_channel_id() {
|
||||
HtlcEvent settleEvent = getSettleEvent(0, CHANNEL_ID_2.getShortChannelId());
|
||||
when(grpcService.getHtlcEvents()).thenReturn(List.of(settleEvent).iterator());
|
||||
assertThat(grpcHtlcEvents.getSettledForwards()).isEmpty();
|
||||
}
|
||||
|
||||
@Test
|
||||
void ignores_settle_event_with_zero_outgoing_channel_id() {
|
||||
HtlcEvent settleEvent = getSettleEvent(CHANNEL_ID.getShortChannelId(), 0);
|
||||
when(grpcService.getHtlcEvents()).thenReturn(List.of(settleEvent).iterator());
|
||||
assertThat(grpcHtlcEvents.getSettledForwards()).isEmpty();
|
||||
}
|
||||
|
||||
private HtlcEvent getSettleEvent(long incomingChannelId, long outgoingChannelId) {
|
||||
SettleEvent settleEvent = SettleEvent.getDefaultInstance();
|
||||
return getBuilderWithDefaults(incomingChannelId, outgoingChannelId)
|
||||
.setSettleEvent(settleEvent)
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
private HtlcEvent getAttempt(long incomingChannelId, long outgoingChannelId) {
|
||||
return getBuilderWithDefaults(incomingChannelId, outgoingChannelId)
|
||||
.setForwardEvent(ForwardEvent.newBuilder()
|
||||
.setInfo(RouterOuterClass.HtlcInfo.newBuilder()
|
||||
.setIncomingTimelock(1)
|
||||
.setOutgoingTimelock(2)
|
||||
.setIncomingAmtMsat(100)
|
||||
.setOutgoingAmtMsat(200)
|
||||
.build())
|
||||
.build())
|
||||
.build();
|
||||
}
|
||||
|
||||
private HtlcEvent.Builder getBuilderWithDefaults(long incomingChannelId, long outgoingChannelId) {
|
||||
return HtlcEvent.newBuilder()
|
||||
.setIncomingHtlcId(1)
|
||||
.setOutgoingHtlcId(2)
|
||||
.setTimestampNs(789)
|
||||
.setIncomingChannelId(incomingChannelId)
|
||||
.setOutgoingChannelId(outgoingChannelId);
|
||||
}
|
||||
}
|
||||
@@ -5,6 +5,7 @@ import io.grpc.netty.GrpcSslContexts;
|
||||
import io.grpc.netty.NettyChannelBuilder;
|
||||
import io.netty.handler.ssl.SslContext;
|
||||
import lnrpc.LightningGrpc;
|
||||
import routerrpc.RouterGrpc;
|
||||
|
||||
import javax.net.ssl.SSLException;
|
||||
import java.io.File;
|
||||
@@ -14,6 +15,7 @@ public class StubCreator {
|
||||
private static final int FIFTY_MEGA_BYTE = 50 * 1024 * 1024;
|
||||
|
||||
private final LightningGrpc.LightningBlockingStub stub;
|
||||
private final RouterGrpc.RouterBlockingStub routerStub;
|
||||
private final ManagedChannel channel;
|
||||
private final File macaroonFile;
|
||||
private final File certFile;
|
||||
@@ -27,12 +29,17 @@ public class StubCreator {
|
||||
this.host = host;
|
||||
channel = getChannel();
|
||||
stub = createLightningStub();
|
||||
routerStub = createRouterStub();
|
||||
}
|
||||
|
||||
public LightningGrpc.LightningBlockingStub getLightningStub() {
|
||||
return stub;
|
||||
}
|
||||
|
||||
public RouterGrpc.RouterBlockingStub getRouterStub() {
|
||||
return routerStub;
|
||||
}
|
||||
|
||||
public void shutdown() {
|
||||
channel.shutdown();
|
||||
}
|
||||
@@ -48,4 +55,11 @@ public class StubCreator {
|
||||
.withMaxInboundMessageSize(FIFTY_MEGA_BYTE)
|
||||
.withCallCredentials(new MacaroonCallCredential(macaroonFile));
|
||||
}
|
||||
|
||||
private RouterGrpc.RouterBlockingStub createRouterStub() throws IOException {
|
||||
return RouterGrpc
|
||||
.newBlockingStub(channel)
|
||||
.withMaxInboundMessageSize(FIFTY_MEGA_BYTE)
|
||||
.withCallCredentials(new MacaroonCallCredential(macaroonFile));
|
||||
}
|
||||
}
|
||||
|
||||
800
grpc-client/src/main/proto/router.proto
Normal file
800
grpc-client/src/main/proto/router.proto
Normal file
@@ -0,0 +1,800 @@
|
||||
syntax = "proto3";
|
||||
|
||||
import "lightning.proto";
|
||||
|
||||
package routerrpc;
|
||||
|
||||
option go_package = "github.com/lightningnetwork/lnd/lnrpc/routerrpc";
|
||||
|
||||
// Router is a service that offers advanced interaction with the router
|
||||
// subsystem of the daemon.
|
||||
service Router {
|
||||
/*
|
||||
SendPaymentV2 attempts to route a payment described by the passed
|
||||
PaymentRequest to the final destination. The call returns a stream of
|
||||
payment updates.
|
||||
*/
|
||||
rpc SendPaymentV2 (SendPaymentRequest) returns (stream lnrpc.Payment);
|
||||
|
||||
/*
|
||||
TrackPaymentV2 returns an update stream for the payment identified by the
|
||||
payment hash.
|
||||
*/
|
||||
rpc TrackPaymentV2 (TrackPaymentRequest) returns (stream lnrpc.Payment);
|
||||
|
||||
/*
|
||||
EstimateRouteFee allows callers to obtain a lower bound w.r.t how much it
|
||||
may cost to send an HTLC to the target end destination.
|
||||
*/
|
||||
rpc EstimateRouteFee (RouteFeeRequest) returns (RouteFeeResponse);
|
||||
|
||||
/*
|
||||
Deprecated, use SendToRouteV2. SendToRoute attempts to make a payment via
|
||||
the specified route. This method differs from SendPayment in that it
|
||||
allows users to specify a full route manually. This can be used for
|
||||
things like rebalancing, and atomic swaps. It differs from the newer
|
||||
SendToRouteV2 in that it doesn't return the full HTLC information.
|
||||
*/
|
||||
rpc SendToRoute (SendToRouteRequest) returns (SendToRouteResponse) {
|
||||
option deprecated = true;
|
||||
}
|
||||
|
||||
/*
|
||||
SendToRouteV2 attempts to make a payment via the specified route. This
|
||||
method differs from SendPayment in that it allows users to specify a full
|
||||
route manually. This can be used for things like rebalancing, and atomic
|
||||
swaps.
|
||||
*/
|
||||
rpc SendToRouteV2 (SendToRouteRequest) returns (lnrpc.HTLCAttempt);
|
||||
|
||||
/*
|
||||
ResetMissionControl clears all mission control state and starts with a clean
|
||||
slate.
|
||||
*/
|
||||
rpc ResetMissionControl (ResetMissionControlRequest)
|
||||
returns (ResetMissionControlResponse);
|
||||
|
||||
/*
|
||||
QueryMissionControl exposes the internal mission control state to callers.
|
||||
It is a development feature.
|
||||
*/
|
||||
rpc QueryMissionControl (QueryMissionControlRequest)
|
||||
returns (QueryMissionControlResponse);
|
||||
|
||||
/*
|
||||
XImportMissionControl is an experimental API that imports the state provided
|
||||
to the internal mission control's state, using all results which are more
|
||||
recent than our existing values. These values will only be imported
|
||||
in-memory, and will not be persisted across restarts.
|
||||
*/
|
||||
rpc XImportMissionControl (XImportMissionControlRequest)
|
||||
returns (XImportMissionControlResponse);
|
||||
|
||||
/*
|
||||
GetMissionControlConfig returns mission control's current config.
|
||||
*/
|
||||
rpc GetMissionControlConfig (GetMissionControlConfigRequest)
|
||||
returns (GetMissionControlConfigResponse);
|
||||
|
||||
/*
|
||||
SetMissionControlConfig will set mission control's config, if the config
|
||||
provided is valid.
|
||||
*/
|
||||
rpc SetMissionControlConfig (SetMissionControlConfigRequest)
|
||||
returns (SetMissionControlConfigResponse);
|
||||
|
||||
/*
|
||||
QueryProbability returns the current success probability estimate for a
|
||||
given node pair and amount.
|
||||
*/
|
||||
rpc QueryProbability (QueryProbabilityRequest)
|
||||
returns (QueryProbabilityResponse);
|
||||
|
||||
/*
|
||||
BuildRoute builds a fully specified route based on a list of hop public
|
||||
keys. It retrieves the relevant channel policies from the graph in order to
|
||||
calculate the correct fees and time locks.
|
||||
*/
|
||||
rpc BuildRoute (BuildRouteRequest) returns (BuildRouteResponse);
|
||||
|
||||
/*
|
||||
SubscribeHtlcEvents creates a uni-directional stream from the server to
|
||||
the client which delivers a stream of htlc events.
|
||||
*/
|
||||
rpc SubscribeHtlcEvents (SubscribeHtlcEventsRequest)
|
||||
returns (stream HtlcEvent);
|
||||
|
||||
/*
|
||||
Deprecated, use SendPaymentV2. SendPayment attempts to route a payment
|
||||
described by the passed PaymentRequest to the final destination. The call
|
||||
returns a stream of payment status updates.
|
||||
*/
|
||||
rpc SendPayment (SendPaymentRequest) returns (stream PaymentStatus) {
|
||||
option deprecated = true;
|
||||
}
|
||||
|
||||
/*
|
||||
Deprecated, use TrackPaymentV2. TrackPayment returns an update stream for
|
||||
the payment identified by the payment hash.
|
||||
*/
|
||||
rpc TrackPayment (TrackPaymentRequest) returns (stream PaymentStatus) {
|
||||
option deprecated = true;
|
||||
}
|
||||
|
||||
/**
|
||||
HtlcInterceptor dispatches a bi-directional streaming RPC in which
|
||||
Forwarded HTLC requests are sent to the client and the client responds with
|
||||
a boolean that tells LND if this htlc should be intercepted.
|
||||
In case of interception, the htlc can be either settled, cancelled or
|
||||
resumed later by using the ResolveHoldForward endpoint.
|
||||
*/
|
||||
rpc HtlcInterceptor (stream ForwardHtlcInterceptResponse)
|
||||
returns (stream ForwardHtlcInterceptRequest);
|
||||
|
||||
/*
|
||||
UpdateChanStatus attempts to manually set the state of a channel
|
||||
(enabled, disabled, or auto). A manual "disable" request will cause the
|
||||
channel to stay disabled until a subsequent manual request of either
|
||||
"enable" or "auto".
|
||||
*/
|
||||
rpc UpdateChanStatus (UpdateChanStatusRequest)
|
||||
returns (UpdateChanStatusResponse);
|
||||
}
|
||||
|
||||
message SendPaymentRequest {
|
||||
// The identity pubkey of the payment recipient
|
||||
bytes dest = 1;
|
||||
|
||||
/*
|
||||
Number of satoshis to send.
|
||||
|
||||
The fields amt and amt_msat are mutually exclusive.
|
||||
*/
|
||||
int64 amt = 2;
|
||||
|
||||
/*
|
||||
Number of millisatoshis to send.
|
||||
|
||||
The fields amt and amt_msat are mutually exclusive.
|
||||
*/
|
||||
int64 amt_msat = 12;
|
||||
|
||||
// The hash to use within the payment's HTLC
|
||||
bytes payment_hash = 3;
|
||||
|
||||
/*
|
||||
The CLTV delta from the current height that should be used to set the
|
||||
timelock for the final hop.
|
||||
*/
|
||||
int32 final_cltv_delta = 4;
|
||||
|
||||
// An optional payment addr to be included within the last hop of the route.
|
||||
bytes payment_addr = 20;
|
||||
|
||||
/*
|
||||
A bare-bones invoice for a payment within the Lightning Network. With the
|
||||
details of the invoice, the sender has all the data necessary to send a
|
||||
payment to the recipient. The amount in the payment request may be zero. In
|
||||
that case it is required to set the amt field as well. If no payment request
|
||||
is specified, the following fields are required: dest, amt and payment_hash.
|
||||
*/
|
||||
string payment_request = 5;
|
||||
|
||||
/*
|
||||
An upper limit on the amount of time we should spend when attempting to
|
||||
fulfill the payment. This is expressed in seconds. If we cannot make a
|
||||
successful payment within this time frame, an error will be returned.
|
||||
This field must be non-zero.
|
||||
*/
|
||||
int32 timeout_seconds = 6;
|
||||
|
||||
/*
|
||||
The maximum number of satoshis that will be paid as a fee of the payment.
|
||||
If this field is left to the default value of 0, only zero-fee routes will
|
||||
be considered. This usually means single hop routes connecting directly to
|
||||
the destination. To send the payment without a fee limit, use max int here.
|
||||
|
||||
The fields fee_limit_sat and fee_limit_msat are mutually exclusive.
|
||||
*/
|
||||
int64 fee_limit_sat = 7;
|
||||
|
||||
/*
|
||||
The maximum number of millisatoshis that will be paid as a fee of the
|
||||
payment. If this field is left to the default value of 0, only zero-fee
|
||||
routes will be considered. This usually means single hop routes connecting
|
||||
directly to the destination. To send the payment without a fee limit, use
|
||||
max int here.
|
||||
|
||||
The fields fee_limit_sat and fee_limit_msat are mutually exclusive.
|
||||
*/
|
||||
int64 fee_limit_msat = 13;
|
||||
|
||||
/*
|
||||
Deprecated, use outgoing_chan_ids. The channel id of the channel that must
|
||||
be taken to the first hop. If zero, any channel may be used (unless
|
||||
outgoing_chan_ids are set).
|
||||
*/
|
||||
uint64 outgoing_chan_id = 8 [jstype = JS_STRING, deprecated = true];
|
||||
|
||||
/*
|
||||
The channel ids of the channels are allowed for the first hop. If empty,
|
||||
any channel may be used.
|
||||
*/
|
||||
repeated uint64 outgoing_chan_ids = 19;
|
||||
|
||||
/*
|
||||
The pubkey of the last hop of the route. If empty, any hop may be used.
|
||||
*/
|
||||
bytes last_hop_pubkey = 14;
|
||||
|
||||
/*
|
||||
An optional maximum total time lock for the route. This should not exceed
|
||||
lnd's `--max-cltv-expiry` setting. If zero, then the value of
|
||||
`--max-cltv-expiry` is enforced.
|
||||
*/
|
||||
int32 cltv_limit = 9;
|
||||
|
||||
/*
|
||||
Optional route hints to reach the destination through private channels.
|
||||
*/
|
||||
repeated lnrpc.RouteHint route_hints = 10;
|
||||
|
||||
/*
|
||||
An optional field that can be used to pass an arbitrary set of TLV records
|
||||
to a peer which understands the new records. This can be used to pass
|
||||
application specific data during the payment attempt. Record types are
|
||||
required to be in the custom range >= 65536. When using REST, the values
|
||||
must be encoded as base64.
|
||||
*/
|
||||
map<uint64, bytes> dest_custom_records = 11;
|
||||
|
||||
// If set, circular payments to self are permitted.
|
||||
bool allow_self_payment = 15;
|
||||
|
||||
/*
|
||||
Features assumed to be supported by the final node. All transitive feature
|
||||
dependencies must also be set properly. For a given feature bit pair, either
|
||||
optional or remote may be set, but not both. If this field is nil or empty,
|
||||
the router will try to load destination features from the graph as a
|
||||
fallback.
|
||||
*/
|
||||
repeated lnrpc.FeatureBit dest_features = 16;
|
||||
|
||||
/*
|
||||
The maximum number of partial payments that may be use to complete the full
|
||||
amount.
|
||||
*/
|
||||
uint32 max_parts = 17;
|
||||
|
||||
/*
|
||||
If set, only the final payment update is streamed back. Intermediate updates
|
||||
that show which htlcs are still in flight are suppressed.
|
||||
*/
|
||||
bool no_inflight_updates = 18;
|
||||
|
||||
/*
|
||||
The largest payment split that should be attempted when making a payment if
|
||||
splitting is necessary. Setting this value will effectively cause lnd to
|
||||
split more aggressively, vs only when it thinks it needs to. Note that this
|
||||
value is in milli-satoshis.
|
||||
*/
|
||||
uint64 max_shard_size_msat = 21;
|
||||
|
||||
/*
|
||||
If set, an AMP-payment will be attempted.
|
||||
*/
|
||||
bool amp = 22;
|
||||
}
|
||||
|
||||
message TrackPaymentRequest {
|
||||
// The hash of the payment to look up.
|
||||
bytes payment_hash = 1;
|
||||
|
||||
/*
|
||||
If set, only the final payment update is streamed back. Intermediate updates
|
||||
that show which htlcs are still in flight are suppressed.
|
||||
*/
|
||||
bool no_inflight_updates = 2;
|
||||
}
|
||||
|
||||
message RouteFeeRequest {
|
||||
/*
|
||||
The destination once wishes to obtain a routing fee quote to.
|
||||
*/
|
||||
bytes dest = 1;
|
||||
|
||||
/*
|
||||
The amount one wishes to send to the target destination.
|
||||
*/
|
||||
int64 amt_sat = 2;
|
||||
}
|
||||
|
||||
message RouteFeeResponse {
|
||||
/*
|
||||
A lower bound of the estimated fee to the target destination within the
|
||||
network, expressed in milli-satoshis.
|
||||
*/
|
||||
int64 routing_fee_msat = 1;
|
||||
|
||||
/*
|
||||
An estimate of the worst case time delay that can occur. Note that callers
|
||||
will still need to factor in the final CLTV delta of the last hop into this
|
||||
value.
|
||||
*/
|
||||
int64 time_lock_delay = 2;
|
||||
}
|
||||
|
||||
message SendToRouteRequest {
|
||||
// The payment hash to use for the HTLC.
|
||||
bytes payment_hash = 1;
|
||||
|
||||
// Route that should be used to attempt to complete the payment.
|
||||
lnrpc.Route route = 2;
|
||||
}
|
||||
|
||||
message SendToRouteResponse {
|
||||
// The preimage obtained by making the payment.
|
||||
bytes preimage = 1;
|
||||
|
||||
// The failure message in case the payment failed.
|
||||
lnrpc.Failure failure = 2;
|
||||
}
|
||||
|
||||
message ResetMissionControlRequest {
|
||||
}
|
||||
|
||||
message ResetMissionControlResponse {
|
||||
}
|
||||
|
||||
message QueryMissionControlRequest {
|
||||
}
|
||||
|
||||
// QueryMissionControlResponse contains mission control state.
|
||||
message QueryMissionControlResponse {
|
||||
reserved 1;
|
||||
|
||||
// Node pair-level mission control state.
|
||||
repeated PairHistory pairs = 2;
|
||||
}
|
||||
|
||||
message XImportMissionControlRequest {
|
||||
// Node pair-level mission control state to be imported.
|
||||
repeated PairHistory pairs = 1;
|
||||
}
|
||||
|
||||
message XImportMissionControlResponse {
|
||||
}
|
||||
|
||||
// PairHistory contains the mission control state for a particular node pair.
|
||||
message PairHistory {
|
||||
// The source node pubkey of the pair.
|
||||
bytes node_from = 1;
|
||||
|
||||
// The destination node pubkey of the pair.
|
||||
bytes node_to = 2;
|
||||
|
||||
reserved 3, 4, 5, 6;
|
||||
|
||||
PairData history = 7;
|
||||
}
|
||||
|
||||
message PairData {
|
||||
// Time of last failure.
|
||||
int64 fail_time = 1;
|
||||
|
||||
/*
|
||||
Lowest amount that failed to forward rounded to whole sats. This may be
|
||||
set to zero if the failure is independent of amount.
|
||||
*/
|
||||
int64 fail_amt_sat = 2;
|
||||
|
||||
/*
|
||||
Lowest amount that failed to forward in millisats. This may be
|
||||
set to zero if the failure is independent of amount.
|
||||
*/
|
||||
int64 fail_amt_msat = 4;
|
||||
|
||||
reserved 3;
|
||||
|
||||
// Time of last success.
|
||||
int64 success_time = 5;
|
||||
|
||||
// Highest amount that we could successfully forward rounded to whole sats.
|
||||
int64 success_amt_sat = 6;
|
||||
|
||||
// Highest amount that we could successfully forward in millisats.
|
||||
int64 success_amt_msat = 7;
|
||||
}
|
||||
|
||||
message GetMissionControlConfigRequest {
|
||||
}
|
||||
|
||||
message GetMissionControlConfigResponse {
|
||||
/*
|
||||
Mission control's currently active config.
|
||||
*/
|
||||
MissionControlConfig config = 1;
|
||||
}
|
||||
|
||||
message SetMissionControlConfigRequest {
|
||||
/*
|
||||
The config to set for mission control. Note that all values *must* be set,
|
||||
because the full config will be applied.
|
||||
*/
|
||||
MissionControlConfig config = 1;
|
||||
}
|
||||
|
||||
message SetMissionControlConfigResponse {
|
||||
}
|
||||
|
||||
message MissionControlConfig {
|
||||
/*
|
||||
The amount of time mission control will take to restore a penalized node
|
||||
or channel back to 50% success probability, expressed in seconds. Setting
|
||||
this value to a higher value will penalize failures for longer, making
|
||||
mission control less likely to route through nodes and channels that we
|
||||
have previously recorded failures for.
|
||||
*/
|
||||
uint64 half_life_seconds = 1;
|
||||
|
||||
/*
|
||||
The probability of success mission control should assign to hop in a route
|
||||
where it has no other information available. Higher values will make mission
|
||||
control more willing to try hops that we have no information about, lower
|
||||
values will discourage trying these hops.
|
||||
*/
|
||||
float hop_probability = 2;
|
||||
|
||||
/*
|
||||
The importance that mission control should place on historical results,
|
||||
expressed as a value in [0;1]. Setting this value to 1 will ignore all
|
||||
historical payments and just use the hop probability to assess the
|
||||
probability of success for each hop. A zero value ignores hop probability
|
||||
completely and relies entirely on historical results, unless none are
|
||||
available.
|
||||
*/
|
||||
float weight = 3;
|
||||
|
||||
/*
|
||||
The maximum number of payment results that mission control will store.
|
||||
*/
|
||||
uint32 maximum_payment_results = 4;
|
||||
|
||||
/*
|
||||
The minimum time that must have passed since the previously recorded failure
|
||||
before we raise the failure amount.
|
||||
*/
|
||||
uint64 minimum_failure_relax_interval = 5;
|
||||
}
|
||||
|
||||
message QueryProbabilityRequest {
|
||||
// The source node pubkey of the pair.
|
||||
bytes from_node = 1;
|
||||
|
||||
// The destination node pubkey of the pair.
|
||||
bytes to_node = 2;
|
||||
|
||||
// The amount for which to calculate a probability.
|
||||
int64 amt_msat = 3;
|
||||
}
|
||||
|
||||
message QueryProbabilityResponse {
|
||||
// The success probability for the requested pair.
|
||||
double probability = 1;
|
||||
|
||||
// The historical data for the requested pair.
|
||||
PairData history = 2;
|
||||
}
|
||||
|
||||
message BuildRouteRequest {
|
||||
/*
|
||||
The amount to send expressed in msat. If set to zero, the minimum routable
|
||||
amount is used.
|
||||
*/
|
||||
int64 amt_msat = 1;
|
||||
|
||||
/*
|
||||
CLTV delta from the current height that should be used for the timelock
|
||||
of the final hop
|
||||
*/
|
||||
int32 final_cltv_delta = 2;
|
||||
|
||||
/*
|
||||
The channel id of the channel that must be taken to the first hop. If zero,
|
||||
any channel may be used.
|
||||
*/
|
||||
uint64 outgoing_chan_id = 3 [jstype = JS_STRING];
|
||||
|
||||
/*
|
||||
A list of hops that defines the route. This does not include the source hop
|
||||
pubkey.
|
||||
*/
|
||||
repeated bytes hop_pubkeys = 4;
|
||||
|
||||
// An optional payment addr to be included within the last hop of the route.
|
||||
bytes payment_addr = 5;
|
||||
}
|
||||
|
||||
message BuildRouteResponse {
|
||||
/*
|
||||
Fully specified route that can be used to execute the payment.
|
||||
*/
|
||||
lnrpc.Route route = 1;
|
||||
}
|
||||
|
||||
message SubscribeHtlcEventsRequest {
|
||||
}
|
||||
|
||||
/*
|
||||
HtlcEvent contains the htlc event that was processed. These are served on a
|
||||
best-effort basis; events are not persisted, delivery is not guaranteed
|
||||
(in the event of a crash in the switch, forward events may be lost) and
|
||||
some events may be replayed upon restart. Events consumed from this package
|
||||
should be de-duplicated by the htlc's unique combination of incoming and
|
||||
outgoing channel id and htlc id. [EXPERIMENTAL]
|
||||
*/
|
||||
message HtlcEvent {
|
||||
/*
|
||||
The short channel id that the incoming htlc arrived at our node on. This
|
||||
value is zero for sends.
|
||||
*/
|
||||
uint64 incoming_channel_id = 1;
|
||||
|
||||
/*
|
||||
The short channel id that the outgoing htlc left our node on. This value
|
||||
is zero for receives.
|
||||
*/
|
||||
uint64 outgoing_channel_id = 2;
|
||||
|
||||
/*
|
||||
Incoming id is the index of the incoming htlc in the incoming channel.
|
||||
This value is zero for sends.
|
||||
*/
|
||||
uint64 incoming_htlc_id = 3;
|
||||
|
||||
/*
|
||||
Outgoing id is the index of the outgoing htlc in the outgoing channel.
|
||||
This value is zero for receives.
|
||||
*/
|
||||
uint64 outgoing_htlc_id = 4;
|
||||
|
||||
/*
|
||||
The time in unix nanoseconds that the event occurred.
|
||||
*/
|
||||
uint64 timestamp_ns = 5;
|
||||
|
||||
enum EventType {
|
||||
UNKNOWN = 0;
|
||||
SEND = 1;
|
||||
RECEIVE = 2;
|
||||
FORWARD = 3;
|
||||
}
|
||||
|
||||
/*
|
||||
The event type indicates whether the htlc was part of a send, receive or
|
||||
forward.
|
||||
*/
|
||||
EventType event_type = 6;
|
||||
|
||||
oneof event {
|
||||
ForwardEvent forward_event = 7;
|
||||
ForwardFailEvent forward_fail_event = 8;
|
||||
SettleEvent settle_event = 9;
|
||||
LinkFailEvent link_fail_event = 10;
|
||||
}
|
||||
}
|
||||
|
||||
message HtlcInfo {
|
||||
// The timelock on the incoming htlc.
|
||||
uint32 incoming_timelock = 1;
|
||||
|
||||
// The timelock on the outgoing htlc.
|
||||
uint32 outgoing_timelock = 2;
|
||||
|
||||
// The amount of the incoming htlc.
|
||||
uint64 incoming_amt_msat = 3;
|
||||
|
||||
// The amount of the outgoing htlc.
|
||||
uint64 outgoing_amt_msat = 4;
|
||||
}
|
||||
|
||||
message ForwardEvent {
|
||||
// Info contains details about the htlc that was forwarded.
|
||||
HtlcInfo info = 1;
|
||||
}
|
||||
|
||||
message ForwardFailEvent {
|
||||
}
|
||||
|
||||
message SettleEvent {
|
||||
// The revealed preimage.
|
||||
bytes preimage = 1;
|
||||
}
|
||||
|
||||
message LinkFailEvent {
|
||||
// Info contains details about the htlc that we failed.
|
||||
HtlcInfo info = 1;
|
||||
|
||||
// FailureCode is the BOLT error code for the failure.
|
||||
lnrpc.Failure.FailureCode wire_failure = 2;
|
||||
|
||||
/*
|
||||
FailureDetail provides additional information about the reason for the
|
||||
failure. This detail enriches the information provided by the wire message
|
||||
and may be 'no detail' if the wire message requires no additional metadata.
|
||||
*/
|
||||
FailureDetail failure_detail = 3;
|
||||
|
||||
// A string representation of the link failure.
|
||||
string failure_string = 4;
|
||||
}
|
||||
|
||||
enum FailureDetail {
|
||||
UNKNOWN = 0;
|
||||
NO_DETAIL = 1;
|
||||
ONION_DECODE = 2;
|
||||
LINK_NOT_ELIGIBLE = 3;
|
||||
ON_CHAIN_TIMEOUT = 4;
|
||||
HTLC_EXCEEDS_MAX = 5;
|
||||
INSUFFICIENT_BALANCE = 6;
|
||||
INCOMPLETE_FORWARD = 7;
|
||||
HTLC_ADD_FAILED = 8;
|
||||
FORWARDS_DISABLED = 9;
|
||||
INVOICE_CANCELED = 10;
|
||||
INVOICE_UNDERPAID = 11;
|
||||
INVOICE_EXPIRY_TOO_SOON = 12;
|
||||
INVOICE_NOT_OPEN = 13;
|
||||
MPP_INVOICE_TIMEOUT = 14;
|
||||
ADDRESS_MISMATCH = 15;
|
||||
SET_TOTAL_MISMATCH = 16;
|
||||
SET_TOTAL_TOO_LOW = 17;
|
||||
SET_OVERPAID = 18;
|
||||
UNKNOWN_INVOICE = 19;
|
||||
INVALID_KEYSEND = 20;
|
||||
MPP_IN_PROGRESS = 21;
|
||||
CIRCULAR_ROUTE = 22;
|
||||
}
|
||||
|
||||
enum PaymentState {
|
||||
/*
|
||||
Payment is still in flight.
|
||||
*/
|
||||
IN_FLIGHT = 0;
|
||||
|
||||
/*
|
||||
Payment completed successfully.
|
||||
*/
|
||||
SUCCEEDED = 1;
|
||||
|
||||
/*
|
||||
There are more routes to try, but the payment timeout was exceeded.
|
||||
*/
|
||||
FAILED_TIMEOUT = 2;
|
||||
|
||||
/*
|
||||
All possible routes were tried and failed permanently. Or were no
|
||||
routes to the destination at all.
|
||||
*/
|
||||
FAILED_NO_ROUTE = 3;
|
||||
|
||||
/*
|
||||
A non-recoverable error has occured.
|
||||
*/
|
||||
FAILED_ERROR = 4;
|
||||
|
||||
/*
|
||||
Payment details incorrect (unknown hash, invalid amt or
|
||||
invalid final cltv delta)
|
||||
*/
|
||||
FAILED_INCORRECT_PAYMENT_DETAILS = 5;
|
||||
|
||||
/*
|
||||
Insufficient local balance.
|
||||
*/
|
||||
FAILED_INSUFFICIENT_BALANCE = 6;
|
||||
}
|
||||
|
||||
message PaymentStatus {
|
||||
// Current state the payment is in.
|
||||
PaymentState state = 1;
|
||||
|
||||
/*
|
||||
The pre-image of the payment when state is SUCCEEDED.
|
||||
*/
|
||||
bytes preimage = 2;
|
||||
|
||||
reserved 3;
|
||||
|
||||
/*
|
||||
The HTLCs made in attempt to settle the payment [EXPERIMENTAL].
|
||||
*/
|
||||
repeated lnrpc.HTLCAttempt htlcs = 4;
|
||||
}
|
||||
|
||||
message CircuitKey {
|
||||
/// The id of the channel that the is part of this circuit.
|
||||
uint64 chan_id = 1;
|
||||
|
||||
/// The index of the incoming htlc in the incoming channel.
|
||||
uint64 htlc_id = 2;
|
||||
}
|
||||
|
||||
message ForwardHtlcInterceptRequest {
|
||||
/*
|
||||
The key of this forwarded htlc. It defines the incoming channel id and
|
||||
the index in this channel.
|
||||
*/
|
||||
CircuitKey incoming_circuit_key = 1;
|
||||
|
||||
// The incoming htlc amount.
|
||||
uint64 incoming_amount_msat = 5;
|
||||
|
||||
// The incoming htlc expiry.
|
||||
uint32 incoming_expiry = 6;
|
||||
|
||||
/*
|
||||
The htlc payment hash. This value is not guaranteed to be unique per
|
||||
request.
|
||||
*/
|
||||
bytes payment_hash = 2;
|
||||
|
||||
// The requested outgoing channel id for this forwarded htlc. Because of
|
||||
// non-strict forwarding, this isn't necessarily the channel over which the
|
||||
// packet will be forwarded eventually. A different channel to the same peer
|
||||
// may be selected as well.
|
||||
uint64 outgoing_requested_chan_id = 7;
|
||||
|
||||
// The outgoing htlc amount.
|
||||
uint64 outgoing_amount_msat = 3;
|
||||
|
||||
// The outgoing htlc expiry.
|
||||
uint32 outgoing_expiry = 4;
|
||||
|
||||
// Any custom records that were present in the payload.
|
||||
map<uint64, bytes> custom_records = 8;
|
||||
|
||||
// The onion blob for the next hop
|
||||
bytes onion_blob = 9;
|
||||
}
|
||||
|
||||
/**
|
||||
ForwardHtlcInterceptResponse enables the caller to resolve a previously hold
|
||||
forward. The caller can choose either to:
|
||||
- `Resume`: Execute the default behavior (usually forward).
|
||||
- `Reject`: Fail the htlc backwards.
|
||||
- `Settle`: Settle this htlc with a given preimage.
|
||||
*/
|
||||
message ForwardHtlcInterceptResponse {
|
||||
/**
|
||||
The key of this forwarded htlc. It defines the incoming channel id and
|
||||
the index in this channel.
|
||||
*/
|
||||
CircuitKey incoming_circuit_key = 1;
|
||||
|
||||
// The resolve action for this intercepted htlc.
|
||||
ResolveHoldForwardAction action = 2;
|
||||
|
||||
// The preimage in case the resolve action is Settle.
|
||||
bytes preimage = 3;
|
||||
}
|
||||
|
||||
enum ResolveHoldForwardAction {
|
||||
SETTLE = 0;
|
||||
FAIL = 1;
|
||||
RESUME = 2;
|
||||
}
|
||||
|
||||
message UpdateChanStatusRequest {
|
||||
lnrpc.ChannelPoint chan_point = 1;
|
||||
|
||||
ChanStatusAction action = 2;
|
||||
}
|
||||
|
||||
enum ChanStatusAction {
|
||||
ENABLE = 0;
|
||||
DISABLE = 1;
|
||||
AUTO = 2;
|
||||
}
|
||||
|
||||
message UpdateChanStatusResponse {
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
package de.cotto.lndmanagej.graph.model;
|
||||
package de.cotto.lndmanagej.model;
|
||||
|
||||
import org.springframework.lang.Nullable;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
package de.cotto.lndmanagej.graph.model;
|
||||
package de.cotto.lndmanagej.model;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
@@ -51,4 +51,9 @@ public final class ChannelId {
|
||||
public int hashCode() {
|
||||
return Objects.hash(shortChannelId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return String.valueOf(shortChannelId);
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
package de.cotto.lndmanagej.graph.model;
|
||||
package de.cotto.lndmanagej.model;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.util.Locale;
|
||||
@@ -0,0 +1,112 @@
|
||||
package de.cotto.lndmanagej.model;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
import java.util.Objects;
|
||||
|
||||
public class ForwardAttempt {
|
||||
private final HtlcDetails htlcDetails;
|
||||
private final int incomingTimelock;
|
||||
private final int outgoingTimelock;
|
||||
private final Coins incomingAmount;
|
||||
private final Coins outgoingAmount;
|
||||
|
||||
private ForwardAttempt(
|
||||
HtlcDetails htlcDetails,
|
||||
int incomingTimelock,
|
||||
int outgoingTimelock,
|
||||
Coins incomingAmount,
|
||||
Coins outgoingAmount
|
||||
) {
|
||||
this.htlcDetails = htlcDetails;
|
||||
this.incomingTimelock = incomingTimelock;
|
||||
this.outgoingTimelock = outgoingTimelock;
|
||||
this.incomingAmount = incomingAmount;
|
||||
this.outgoingAmount = outgoingAmount;
|
||||
}
|
||||
|
||||
public static Builder builder() {
|
||||
return new Builder();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "ForwardAttempt{" +
|
||||
"htlcDetails=" + htlcDetails +
|
||||
", incomingTimelock=" + incomingTimelock +
|
||||
", outgoingTimelock=" + outgoingTimelock +
|
||||
", incomingAmount=" + incomingAmount +
|
||||
", outgoingAmount=" + outgoingAmount +
|
||||
'}';
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object other) {
|
||||
if (this == other) {
|
||||
return true;
|
||||
}
|
||||
if (other == null || getClass() != other.getClass()) {
|
||||
return false;
|
||||
}
|
||||
ForwardAttempt that = (ForwardAttempt) other;
|
||||
return incomingTimelock == that.incomingTimelock
|
||||
&& outgoingTimelock == that.outgoingTimelock
|
||||
&& Objects.equals(htlcDetails, that.htlcDetails)
|
||||
&& Objects.equals(incomingAmount, that.incomingAmount)
|
||||
&& Objects.equals(outgoingAmount, that.outgoingAmount);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(htlcDetails, incomingTimelock, outgoingTimelock, incomingAmount, outgoingAmount);
|
||||
}
|
||||
|
||||
public static class Builder {
|
||||
private int incomingTimelock;
|
||||
|
||||
private int outgoingTimelock;
|
||||
|
||||
@Nullable
|
||||
private Coins incomingAmount;
|
||||
|
||||
@Nullable
|
||||
private Coins outgoingAmount;
|
||||
|
||||
@Nullable
|
||||
private HtlcDetails htlcDetails;
|
||||
|
||||
public Builder withIncomingTimelock(int incomingTimelock) {
|
||||
this.incomingTimelock = incomingTimelock;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder withOutgoingTimelock(int outgoingTimelock) {
|
||||
this.outgoingTimelock = outgoingTimelock;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder withIncomingAmount(long incomingAmtMsat) {
|
||||
this.incomingAmount = Coins.ofMilliSatoshis(incomingAmtMsat);
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder withOutgoingAmount(long outgoingAmtMsat) {
|
||||
this.outgoingAmount = Coins.ofMilliSatoshis(outgoingAmtMsat);
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder withHtlcDetails(HtlcDetails htlcDetails) {
|
||||
this.htlcDetails = htlcDetails;
|
||||
return this;
|
||||
}
|
||||
|
||||
public ForwardAttempt build() {
|
||||
return new ForwardAttempt(
|
||||
Objects.requireNonNull(htlcDetails),
|
||||
incomingTimelock,
|
||||
outgoingTimelock,
|
||||
Objects.requireNonNull(incomingAmount),
|
||||
Objects.requireNonNull(outgoingAmount)
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
package de.cotto.lndmanagej.model;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
public class ForwardFailure {
|
||||
private final HtlcDetails htlcDetails;
|
||||
private final ForwardAttempt forwardAttempt;
|
||||
|
||||
public ForwardFailure(HtlcDetails htlcDetails, ForwardAttempt forwardAttempt) {
|
||||
this.htlcDetails = htlcDetails;
|
||||
this.forwardAttempt = forwardAttempt;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "ForwardFailure{" +
|
||||
"htlcDetails=" + htlcDetails +
|
||||
", forwardAttempt=" + forwardAttempt +
|
||||
'}';
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object other) {
|
||||
if (this == other) {
|
||||
return true;
|
||||
}
|
||||
if (other == null || getClass() != other.getClass()) {
|
||||
return false;
|
||||
}
|
||||
ForwardFailure that = (ForwardFailure) other;
|
||||
return Objects.equals(htlcDetails, that.htlcDetails) && Objects.equals(forwardAttempt, that.forwardAttempt);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(htlcDetails, forwardAttempt);
|
||||
}
|
||||
}
|
||||
123
model/src/main/java/de/cotto/lndmanagej/model/HtlcDetails.java
Normal file
123
model/src/main/java/de/cotto/lndmanagej/model/HtlcDetails.java
Normal file
@@ -0,0 +1,123 @@
|
||||
package de.cotto.lndmanagej.model;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
import java.time.Instant;
|
||||
import java.util.Objects;
|
||||
|
||||
public class HtlcDetails {
|
||||
private final ChannelId incomingChannelId;
|
||||
private final ChannelId outgoingChannelId;
|
||||
private final long incomingHtlcId;
|
||||
private final long outgoingHtlcId;
|
||||
private final Instant timestamp;
|
||||
|
||||
private HtlcDetails(
|
||||
ChannelId incomingChannelId,
|
||||
ChannelId outgoingChannelId,
|
||||
long incomingHtlcId,
|
||||
long outgoingHtlcId,
|
||||
Instant timestamp
|
||||
) {
|
||||
this.incomingChannelId = incomingChannelId;
|
||||
this.outgoingChannelId = outgoingChannelId;
|
||||
this.incomingHtlcId = incomingHtlcId;
|
||||
this.outgoingHtlcId = outgoingHtlcId;
|
||||
this.timestamp = timestamp;
|
||||
}
|
||||
|
||||
public static Builder builder() {
|
||||
return new Builder();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "HtlcDetails{" +
|
||||
"incomingChannelId=" + incomingChannelId +
|
||||
", outgoingChannelId=" + outgoingChannelId +
|
||||
", incomingHtlcId=" + incomingHtlcId +
|
||||
", outgoingHtlcId=" + outgoingHtlcId +
|
||||
", timestamp=" + timestamp +
|
||||
'}';
|
||||
}
|
||||
|
||||
public HtlcDetails withoutTimestamp() {
|
||||
return new HtlcDetails(
|
||||
incomingChannelId, outgoingChannelId, incomingHtlcId, outgoingHtlcId, Instant.ofEpochMilli(0)
|
||||
);
|
||||
}
|
||||
|
||||
public Instant getTimestamp() {
|
||||
return timestamp;
|
||||
}
|
||||
|
||||
public static class Builder {
|
||||
@Nullable
|
||||
private ChannelId incomingChannelId;
|
||||
|
||||
@Nullable
|
||||
private ChannelId outgoingChannelId;
|
||||
|
||||
private long incomingHtlcId;
|
||||
|
||||
private long outgoingHtlcId;
|
||||
|
||||
@Nullable
|
||||
private Instant timestamp;
|
||||
|
||||
public Builder withIncomingChannelId(long incomingChannelId) {
|
||||
this.incomingChannelId = ChannelId.fromShortChannelId(incomingChannelId);
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder withOutgoingChannelId(long outgoingChannelId) {
|
||||
this.outgoingChannelId = ChannelId.fromShortChannelId(outgoingChannelId);
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder withIncomingHtlcId(long incomingHtlcId) {
|
||||
this.incomingHtlcId = incomingHtlcId;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder withOutgoingHtlcId(long outgoingHtlcId) {
|
||||
this.outgoingHtlcId = outgoingHtlcId;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder withTimestamp(long timestampNs) {
|
||||
this.timestamp = Instant.ofEpochSecond(0, timestampNs);
|
||||
return this;
|
||||
}
|
||||
|
||||
public HtlcDetails build() {
|
||||
return new HtlcDetails(
|
||||
Objects.requireNonNull(incomingChannelId),
|
||||
Objects.requireNonNull(outgoingChannelId),
|
||||
incomingHtlcId,
|
||||
outgoingHtlcId,
|
||||
Objects.requireNonNull(timestamp)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object other) {
|
||||
if (this == other) {
|
||||
return true;
|
||||
}
|
||||
if (other == null || getClass() != other.getClass()) {
|
||||
return false;
|
||||
}
|
||||
HtlcDetails that = (HtlcDetails) other;
|
||||
return incomingHtlcId == that.incomingHtlcId
|
||||
&& outgoingHtlcId == that.outgoingHtlcId
|
||||
&& Objects.equals(incomingChannelId, that.incomingChannelId)
|
||||
&& Objects.equals(outgoingChannelId, that.outgoingChannelId)
|
||||
&& Objects.equals(timestamp, that.timestamp);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(incomingChannelId, outgoingChannelId, incomingHtlcId, outgoingHtlcId, timestamp);
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
package de.cotto.lndmanagej.graph.model;
|
||||
package de.cotto.lndmanagej.model;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
import java.util.Objects;
|
||||
@@ -0,0 +1,38 @@
|
||||
package de.cotto.lndmanagej.model;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
public class SettledForward {
|
||||
private final HtlcDetails htlcDetails;
|
||||
private final ForwardAttempt attempt;
|
||||
|
||||
public SettledForward(HtlcDetails htlcDetails, ForwardAttempt attempt) {
|
||||
this.htlcDetails = htlcDetails;
|
||||
this.attempt = attempt;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "SettledForward{" +
|
||||
"htlcDetails=" + htlcDetails +
|
||||
", attempt=" + attempt +
|
||||
'}';
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object other) {
|
||||
if (this == other) {
|
||||
return true;
|
||||
}
|
||||
if (other == null || getClass() != other.getClass()) {
|
||||
return false;
|
||||
}
|
||||
SettledForward that = (SettledForward) other;
|
||||
return Objects.equals(htlcDetails, that.htlcDetails) && Objects.equals(attempt, that.attempt);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(htlcDetails, attempt);
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
package de.cotto.lndmanagej.graph.model;
|
||||
package de.cotto.lndmanagej.model;
|
||||
|
||||
import nl.jqno.equalsverifier.EqualsVerifier;
|
||||
import org.junit.jupiter.api.Nested;
|
||||
@@ -108,4 +108,10 @@ class ChannelIdTest {
|
||||
void testEquals() {
|
||||
EqualsVerifier.forClass(ChannelId.class).verify();
|
||||
}
|
||||
|
||||
@Test
|
||||
void testToString() {
|
||||
String expectedString = String.valueOf(ChannelIdFixtures.CHANNEL_ID.getShortChannelId());
|
||||
assertThat(ChannelIdFixtures.CHANNEL_ID).hasToString(expectedString);
|
||||
}
|
||||
}
|
||||
@@ -1,13 +1,12 @@
|
||||
package de.cotto.lndmanagej.graph.model;
|
||||
package de.cotto.lndmanagej.model;
|
||||
|
||||
import nl.jqno.equalsverifier.EqualsVerifier;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import static de.cotto.lndmanagej.graph.model.ChannelFixtures.CAPACITY;
|
||||
import static de.cotto.lndmanagej.graph.model.ChannelFixtures.CHANNEL;
|
||||
import static de.cotto.lndmanagej.graph.model.ChannelIdFixtures.CHANNEL_ID;
|
||||
import static de.cotto.lndmanagej.graph.model.NodeFixtures.NODE;
|
||||
import static de.cotto.lndmanagej.graph.model.NodeFixtures.NODE_2;
|
||||
import static de.cotto.lndmanagej.model.ChannelFixtures.CAPACITY;
|
||||
import static de.cotto.lndmanagej.model.ChannelFixtures.CHANNEL;
|
||||
import static de.cotto.lndmanagej.model.NodeFixtures.NODE;
|
||||
import static de.cotto.lndmanagej.model.NodeFixtures.NODE_2;
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.assertThatNullPointerException;
|
||||
|
||||
@@ -34,7 +33,7 @@ class ChannelTest {
|
||||
void builder_without_capacity() {
|
||||
assertThatNullPointerException().isThrownBy(
|
||||
() -> Channel.builder()
|
||||
.withChannelId(CHANNEL_ID)
|
||||
.withChannelId(ChannelIdFixtures.CHANNEL_ID)
|
||||
.withNode1(NODE)
|
||||
.withNode2(NODE_2)
|
||||
.build()
|
||||
@@ -45,7 +44,7 @@ class ChannelTest {
|
||||
void builder_without_node1() {
|
||||
assertThatNullPointerException().isThrownBy(
|
||||
() -> Channel.builder()
|
||||
.withChannelId(CHANNEL_ID)
|
||||
.withChannelId(ChannelIdFixtures.CHANNEL_ID)
|
||||
.withCapacity(CAPACITY)
|
||||
.withNode2(NODE_2)
|
||||
.build()
|
||||
@@ -56,7 +55,7 @@ class ChannelTest {
|
||||
void builder_without_node2() {
|
||||
assertThatNullPointerException().isThrownBy(
|
||||
() -> Channel.builder()
|
||||
.withChannelId(CHANNEL_ID)
|
||||
.withChannelId(ChannelIdFixtures.CHANNEL_ID)
|
||||
.withCapacity(CAPACITY)
|
||||
.withNode1(NODE)
|
||||
.build()
|
||||
@@ -66,7 +65,7 @@ class ChannelTest {
|
||||
@Test
|
||||
void builder_with_all_arguments() {
|
||||
Channel channel = Channel.builder()
|
||||
.withChannelId(CHANNEL_ID)
|
||||
.withChannelId(ChannelIdFixtures.CHANNEL_ID)
|
||||
.withCapacity(CAPACITY)
|
||||
.withNode1(NODE)
|
||||
.withNode2(NODE_2)
|
||||
@@ -76,7 +75,7 @@ class ChannelTest {
|
||||
|
||||
@Test
|
||||
void getId() {
|
||||
assertThat(CHANNEL.getId()).isEqualTo(CHANNEL_ID);
|
||||
assertThat(CHANNEL.getId()).isEqualTo(ChannelIdFixtures.CHANNEL_ID);
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -97,7 +96,7 @@ class ChannelTest {
|
||||
@Test
|
||||
void testEquals_reversed_nodes() {
|
||||
Channel channel = Channel.builder()
|
||||
.withChannelId(CHANNEL_ID)
|
||||
.withChannelId(ChannelIdFixtures.CHANNEL_ID)
|
||||
.withCapacity(CAPACITY)
|
||||
.withNode1(NODE_2)
|
||||
.withNode2(NODE)
|
||||
@@ -1,4 +1,4 @@
|
||||
package de.cotto.lndmanagej.graph.model;
|
||||
package de.cotto.lndmanagej.model;
|
||||
|
||||
import nl.jqno.equalsverifier.EqualsVerifier;
|
||||
import org.junit.jupiter.api.Test;
|
||||
@@ -0,0 +1,48 @@
|
||||
package de.cotto.lndmanagej.model;
|
||||
|
||||
import nl.jqno.equalsverifier.EqualsVerifier;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import static de.cotto.lndmanagej.model.ForwardAttemptFixtures.FORWARD_ATTEMPT;
|
||||
import static de.cotto.lndmanagej.model.HtlcDetailsFixtures.HTLC_DETAILS;
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.assertThatNullPointerException;
|
||||
|
||||
class ForwardAttemptTest {
|
||||
@Test
|
||||
void builder_without_arguments() {
|
||||
assertThatNullPointerException().isThrownBy(() ->
|
||||
ForwardAttempt.builder().build()
|
||||
);
|
||||
}
|
||||
|
||||
@Test
|
||||
void builder_with_all_arguments() {
|
||||
ForwardAttempt forwardAttempt = ForwardAttempt.builder()
|
||||
.withHtlcDetails(HTLC_DETAILS)
|
||||
.withIncomingAmount(123)
|
||||
.withOutgoingAmount(456)
|
||||
.withIncomingTimelock(1)
|
||||
.withOutgoingTimelock(2)
|
||||
.build();
|
||||
assertThat(forwardAttempt).isNotNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
void testToString() {
|
||||
assertThat(FORWARD_ATTEMPT).hasToString(
|
||||
"ForwardAttempt{" +
|
||||
"htlcDetails=" + HTLC_DETAILS +
|
||||
", incomingTimelock=1" +
|
||||
", outgoingTimelock=2" +
|
||||
", incomingAmount=0.100" +
|
||||
", outgoingAmount=0.200" +
|
||||
"}"
|
||||
);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testEquals() {
|
||||
EqualsVerifier.forClass(ForwardAttempt.class).usingGetClass().verify();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
package de.cotto.lndmanagej.model;
|
||||
|
||||
import nl.jqno.equalsverifier.EqualsVerifier;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import static de.cotto.lndmanagej.model.ForwardFailureFixtures.FORWARD_FAILURE;
|
||||
import static de.cotto.lndmanagej.model.HtlcDetailsFixtures.HTLC_DETAILS;
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
class ForwardFailureTest {
|
||||
@Test
|
||||
void testToString() {
|
||||
assertThat(FORWARD_FAILURE).hasToString(
|
||||
"ForwardFailure{" +
|
||||
"htlcDetails=" + HTLC_DETAILS +
|
||||
", forwardAttempt=ForwardAttempt{" +
|
||||
"htlcDetails=" + HTLC_DETAILS +
|
||||
", incomingTimelock=1" +
|
||||
", outgoingTimelock=2" +
|
||||
", incomingAmount=0.100" +
|
||||
", outgoingAmount=0.200" +
|
||||
"}" +
|
||||
"}"
|
||||
);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testEquals() {
|
||||
EqualsVerifier.forClass(ForwardFailure.class).usingGetClass().verify();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
package de.cotto.lndmanagej.model;
|
||||
|
||||
import nl.jqno.equalsverifier.EqualsVerifier;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.time.Instant;
|
||||
|
||||
import static de.cotto.lndmanagej.model.ChannelIdFixtures.CHANNEL_ID;
|
||||
import static de.cotto.lndmanagej.model.ChannelIdFixtures.CHANNEL_ID_2;
|
||||
import static de.cotto.lndmanagej.model.HtlcDetailsFixtures.HTLC_DETAILS;
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.assertThatNullPointerException;
|
||||
|
||||
class HtlcDetailsTest {
|
||||
@Test
|
||||
void builder_without_arguments() {
|
||||
assertThatNullPointerException().isThrownBy(() ->
|
||||
HtlcDetails.builder().build()
|
||||
);
|
||||
}
|
||||
|
||||
@Test
|
||||
void builder_with_all_arguments() {
|
||||
HtlcDetails htlcDetails = HtlcDetails.builder()
|
||||
.withIncomingChannelId(CHANNEL_ID.getShortChannelId())
|
||||
.withOutgoingChannelId(CHANNEL_ID_2.getShortChannelId())
|
||||
.withTimestamp(789)
|
||||
.withIncomingHtlcId(1)
|
||||
.withOutgoingHtlcId(2)
|
||||
.build();
|
||||
assertThat(htlcDetails).isNotNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
void getTimestamp() {
|
||||
assertThat(HTLC_DETAILS.getTimestamp()).isEqualTo(Instant.ofEpochSecond(0, 789));
|
||||
}
|
||||
|
||||
@Test
|
||||
void withoutTimestamp() {
|
||||
assertThat(HTLC_DETAILS.withoutTimestamp().getTimestamp()).isEqualTo(Instant.ofEpochSecond(0));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testToString() {
|
||||
assertThat(HTLC_DETAILS).hasToString(
|
||||
"HtlcDetails{" +
|
||||
"incomingChannelId=783231610496155649" +
|
||||
", outgoingChannelId=879608202739056642" +
|
||||
", incomingHtlcId=1" +
|
||||
", outgoingHtlcId=2" +
|
||||
", timestamp=1970-01-01T00:00:00.000000789Z" +
|
||||
"}"
|
||||
);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testEquals() {
|
||||
EqualsVerifier.forClass(HtlcDetails.class).usingGetClass().verify();
|
||||
}
|
||||
}
|
||||
@@ -1,10 +1,10 @@
|
||||
package de.cotto.lndmanagej.graph.model;
|
||||
package de.cotto.lndmanagej.model;
|
||||
|
||||
import nl.jqno.equalsverifier.EqualsVerifier;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import static de.cotto.lndmanagej.graph.model.NodeFixtures.NODE;
|
||||
import static de.cotto.lndmanagej.graph.model.NodeFixtures.NODE_WITHOUT_ALIAS;
|
||||
import static de.cotto.lndmanagej.model.NodeFixtures.NODE;
|
||||
import static de.cotto.lndmanagej.model.NodeFixtures.NODE_WITHOUT_ALIAS;
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.assertThatNullPointerException;
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
package de.cotto.lndmanagej.model;
|
||||
|
||||
import nl.jqno.equalsverifier.EqualsVerifier;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import static de.cotto.lndmanagej.model.ForwardAttemptFixtures.FORWARD_ATTEMPT;
|
||||
import static de.cotto.lndmanagej.model.HtlcDetailsFixtures.HTLC_DETAILS;
|
||||
import static de.cotto.lndmanagej.model.SettledForwardFixtures.SETTLED_FORWARD;
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
class SettledForwardTest {
|
||||
@Test
|
||||
void testToString() {
|
||||
assertThat(SETTLED_FORWARD).hasToString(
|
||||
"SettledForward{" +
|
||||
"htlcDetails=" + HTLC_DETAILS +
|
||||
", attempt=" + FORWARD_ATTEMPT +
|
||||
"}"
|
||||
);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testEquals() {
|
||||
EqualsVerifier.forClass(SettledForward.class).usingGetClass().verify();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
package de.cotto.lndmanagej.model;
|
||||
|
||||
import static de.cotto.lndmanagej.model.NodeFixtures.NODE;
|
||||
import static de.cotto.lndmanagej.model.NodeFixtures.NODE_2;
|
||||
|
||||
public class ChannelFixtures {
|
||||
public static final Coins CAPACITY = Coins.ofSatoshis(21_000_000L);
|
||||
|
||||
public static final Channel CHANNEL = Channel.builder()
|
||||
.withChannelId(ChannelIdFixtures.CHANNEL_ID)
|
||||
.withCapacity(CAPACITY)
|
||||
.withNode1(NODE)
|
||||
.withNode2(NODE_2)
|
||||
.build();
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
package de.cotto.lndmanagej.model;
|
||||
|
||||
public class ChannelIdFixtures {
|
||||
public static final ChannelId CHANNEL_ID = ChannelId.fromCompactForm("712345:123:1");
|
||||
public static final ChannelId CHANNEL_ID_2 = ChannelId.fromCompactForm("799999:456:2");
|
||||
public static final ChannelId CHANNEL_ID_3 = ChannelId.fromCompactForm("799999:456:3");
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
package de.cotto.lndmanagej.model;
|
||||
|
||||
import static de.cotto.lndmanagej.model.HtlcDetailsFixtures.HTLC_DETAILS;
|
||||
|
||||
public class ForwardAttemptFixtures {
|
||||
public static final ForwardAttempt FORWARD_ATTEMPT = ForwardAttempt.builder()
|
||||
.withIncomingTimelock(1)
|
||||
.withOutgoingTimelock(2)
|
||||
.withHtlcDetails(HTLC_DETAILS)
|
||||
.withOutgoingAmount(200)
|
||||
.withIncomingAmount(100)
|
||||
.build();
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
package de.cotto.lndmanagej.model;
|
||||
|
||||
import static de.cotto.lndmanagej.model.ForwardAttemptFixtures.FORWARD_ATTEMPT;
|
||||
import static de.cotto.lndmanagej.model.HtlcDetailsFixtures.HTLC_DETAILS;
|
||||
|
||||
public class ForwardFailureFixtures {
|
||||
public static final ForwardFailure FORWARD_FAILURE = new ForwardFailure(HTLC_DETAILS, FORWARD_ATTEMPT);
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
package de.cotto.lndmanagej.model;
|
||||
|
||||
import static de.cotto.lndmanagej.model.ChannelIdFixtures.CHANNEL_ID;
|
||||
import static de.cotto.lndmanagej.model.ChannelIdFixtures.CHANNEL_ID_2;
|
||||
|
||||
public class HtlcDetailsFixtures {
|
||||
public static final HtlcDetails HTLC_DETAILS = HtlcDetails.builder()
|
||||
.withIncomingChannelId(CHANNEL_ID.getShortChannelId())
|
||||
.withOutgoingChannelId(CHANNEL_ID_2.getShortChannelId())
|
||||
.withTimestamp(789)
|
||||
.withIncomingHtlcId(1)
|
||||
.withOutgoingHtlcId(2)
|
||||
.build();
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
package de.cotto.lndmanagej.graph.model;
|
||||
package de.cotto.lndmanagej.model;
|
||||
|
||||
import java.time.Instant;
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
package de.cotto.lndmanagej.model;
|
||||
|
||||
import static de.cotto.lndmanagej.model.ForwardAttemptFixtures.FORWARD_ATTEMPT;
|
||||
import static de.cotto.lndmanagej.model.HtlcDetailsFixtures.HTLC_DETAILS;
|
||||
|
||||
public class SettledForwardFixtures {
|
||||
public static final SettledForward SETTLED_FORWARD = new SettledForward(HTLC_DETAILS, FORWARD_ATTEMPT);
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
rootProject.name = 'lnd-manageJ'
|
||||
include 'application'
|
||||
include 'graph'
|
||||
include 'model'
|
||||
include 'grpc-client'
|
||||
include 'grpc-adapter'
|
||||
Reference in New Issue
Block a user