add channel balance fluctuation warning

This commit is contained in:
Carsten Otto
2022-02-27 15:02:24 +01:00
parent 0b87aa1534
commit 81281ab484
13 changed files with 462 additions and 1 deletions

View File

@@ -7,6 +7,7 @@ dependencies {
implementation project(':forwarding-history')
implementation project(':grpc-adapter')
implementation project(':model')
implementation project(':balances')
implementation project(':onlinepeers')
implementation project(':selfpayments')
implementation project(':transactions')

View File

@@ -1,6 +1,7 @@
package de.cotto.lndmanagej.service;
import com.codahale.metrics.annotation.Timed;
import de.cotto.lndmanagej.balances.BalancesDao;
import de.cotto.lndmanagej.grpc.GrpcChannels;
import de.cotto.lndmanagej.model.BalanceInformation;
import de.cotto.lndmanagej.model.Channel;
@@ -16,10 +17,16 @@ import java.util.Optional;
public class BalanceService {
private final GrpcChannels grpcChannels;
private final ChannelService channelService;
private final BalancesDao balancesDao;
public BalanceService(GrpcChannels grpcChannels, ChannelService channelService) {
public BalanceService(
GrpcChannels grpcChannels,
ChannelService channelService,
BalancesDao balancesDao
) {
this.grpcChannels = grpcChannels;
this.channelService = channelService;
this.balancesDao = balancesDao;
}
@Timed
@@ -66,4 +73,14 @@ public class BalanceService {
return grpcChannels.getChannel(channelId)
.map(LocalOpenChannel::getBalanceInformation);
}
@Timed
public Optional<Coins> getLocalBalanceMinimum(ChannelId channelId, int days) {
return balancesDao.getLocalBalanceMinimum(channelId, days);
}
@Timed
public Optional<Coins> getLocalBalanceMaximum(ChannelId channelId, int days) {
return balancesDao.getLocalBalanceMaximum(channelId, days);
}
}

View File

@@ -0,0 +1,50 @@
package de.cotto.lndmanagej.service;
import de.cotto.lndmanagej.model.ChannelId;
import de.cotto.lndmanagej.model.Coins;
import de.cotto.lndmanagej.model.LocalChannel;
import de.cotto.lndmanagej.model.warnings.ChannelBalanceFluctuationWarning;
import de.cotto.lndmanagej.model.warnings.ChannelWarning;
import de.cotto.lndmanagej.service.warnings.ChannelWarningsProvider;
import org.springframework.stereotype.Component;
import java.util.Optional;
import java.util.stream.Stream;
@Component
public class ChannelBalanceFluctuationWarningsProvider implements ChannelWarningsProvider {
private static final int LOWER_THRESHOLD = 10;
private static final int UPPER_THRESHOLD = 90;
private static final int DAYS = 14;
private final ChannelService channelService;
private final BalanceService balanceService;
public ChannelBalanceFluctuationWarningsProvider(ChannelService channelService, BalanceService balanceService) {
this.channelService = channelService;
this.balanceService = balanceService;
}
@Override
public Stream<ChannelWarning> getChannelWarnings(ChannelId channelId) {
return Stream.of(getBalanceFluctuatingWarning(channelId)).flatMap(Optional::stream);
}
private Optional<ChannelWarning> getBalanceFluctuatingWarning(ChannelId channelId) {
Coins capacity = channelService.getLocalChannel(channelId).map(LocalChannel::getCapacity).orElse(null);
if (capacity == null) {
return Optional.empty();
}
Coins min = balanceService.getLocalBalanceMinimum(channelId, DAYS).orElse(null);
Coins max = balanceService.getLocalBalanceMaximum(channelId, DAYS).orElse(null);
if (min == null || max == null) {
return Optional.empty();
}
int minPercentage = (int) (min.milliSatoshis() * 100.0 / capacity.milliSatoshis());
int maxPercentage = (int) (max.milliSatoshis() * 100.0 / capacity.milliSatoshis());
if (minPercentage < LOWER_THRESHOLD && maxPercentage > UPPER_THRESHOLD) {
return Optional.of(new ChannelBalanceFluctuationWarning(minPercentage, maxPercentage, DAYS));
}
return Optional.empty();
}
}

View File

@@ -1,5 +1,6 @@
package de.cotto.lndmanagej.service;
import de.cotto.lndmanagej.balances.BalancesDao;
import de.cotto.lndmanagej.grpc.GrpcChannels;
import de.cotto.lndmanagej.model.BalanceInformation;
import de.cotto.lndmanagej.model.Coins;
@@ -33,6 +34,9 @@ class BalanceServiceTest {
@Mock
private ChannelService channelService;
@Mock
private BalancesDao balancesDao;
@Test
void getBalanceInformation_for_pubkey() {
BalanceInformation expected = new BalanceInformation(
@@ -104,6 +108,32 @@ class BalanceServiceTest {
assertThat(balanceService.getAvailableRemoteBalanceForPeer(PUBKEY)).isEqualTo(Coins.NONE);
}
@Test
void getLocalBalanceMinimum_empty() {
assertThat(balanceService.getLocalBalanceMinimum(CHANNEL_ID, 7)).isEmpty();
}
@Test
void getLocalBalanceMinimum() {
int days = 7;
Coins coins = Coins.ofSatoshis(123);
when(balancesDao.getLocalBalanceMinimum(CHANNEL_ID, days)).thenReturn(Optional.of(coins));
assertThat(balanceService.getLocalBalanceMinimum(CHANNEL_ID, days)).contains(coins);
}
@Test
void getLocalBalanceMaximum_empty() {
assertThat(balanceService.getLocalBalanceMaximum(CHANNEL_ID, 7)).isEmpty();
}
@Test
void getLocalBalanceMaximum() {
int days = 7;
Coins coins = Coins.ofSatoshis(123);
when(balancesDao.getLocalBalanceMaximum(CHANNEL_ID, days)).thenReturn(Optional.of(coins));
assertThat(balanceService.getLocalBalanceMaximum(CHANNEL_ID, days)).contains(coins);
}
private void mockChannels() {
when(grpcChannels.getChannel(CHANNEL_ID)).thenReturn(Optional.of(LOCAL_OPEN_CHANNEL));
when(grpcChannels.getChannel(CHANNEL_ID_2)).thenReturn(Optional.of(LOCAL_OPEN_CHANNEL_2));

View File

@@ -0,0 +1,109 @@
package de.cotto.lndmanagej.service;
import de.cotto.lndmanagej.model.Coins;
import de.cotto.lndmanagej.model.warnings.ChannelBalanceFluctuationWarning;
import org.junit.jupiter.api.BeforeEach;
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 javax.annotation.Nullable;
import java.util.Optional;
import static de.cotto.lndmanagej.model.ChannelIdFixtures.CHANNEL_ID;
import static de.cotto.lndmanagej.model.LocalOpenChannelFixtures.LOCAL_OPEN_CHANNEL;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.when;
@ExtendWith(MockitoExtension.class)
class ChannelBalanceFluctuationWarningsProviderTest {
private static final int LOWER_THRESHOLD = 10;
private static final int UPPER_THRESHOLD = 90;
private static final int DAYS = 14;
@InjectMocks
private ChannelBalanceFluctuationWarningsProvider warningsProvider;
@Mock
private ChannelService channelService;
@Mock
private BalanceService balanceService;
@BeforeEach
void setUp() {
when(channelService.getLocalChannel(CHANNEL_ID)).thenReturn(Optional.of(LOCAL_OPEN_CHANNEL));
}
@Test
void getChannelWarnings_open_channel_not_found() {
when(channelService.getLocalChannel(CHANNEL_ID)).thenReturn(Optional.empty());
assertThat(warningsProvider.getChannelWarnings(CHANNEL_ID)).isEmpty();
}
@Test
void getChannelWarnings_both_within_bounds() {
mockMinMax(LOWER_THRESHOLD, UPPER_THRESHOLD);
assertThat(warningsProvider.getChannelWarnings(CHANNEL_ID)).isEmpty();
}
@Test
void getChannelWarnings_minimum_not_found() {
mockMinMax(null, UPPER_THRESHOLD);
assertThat(warningsProvider.getChannelWarnings(CHANNEL_ID)).isEmpty();
}
@Test
void getChannelWarnings_maximum_not_found() {
mockMinMax(LOWER_THRESHOLD, null);
assertThat(warningsProvider.getChannelWarnings(CHANNEL_ID)).isEmpty();
}
@Test
void getChannelWarnings_minimum_and_maximum_not_found() {
mockMinMax(null, null);
assertThat(warningsProvider.getChannelWarnings(CHANNEL_ID)).isEmpty();
}
@Test
void getChannelWarnings_just_below_minimum() {
mockMinMax(LOWER_THRESHOLD - 1, UPPER_THRESHOLD);
assertThat(warningsProvider.getChannelWarnings(CHANNEL_ID)).isEmpty();
}
@Test
void getChannelWarnings_just_above_maximum() {
mockMinMax(LOWER_THRESHOLD, UPPER_THRESHOLD + 1);
assertThat(warningsProvider.getChannelWarnings(CHANNEL_ID)).isEmpty();
}
@Test
void getChannelWarnings() {
mockMinMax(LOWER_THRESHOLD - 1, UPPER_THRESHOLD + 1);
ChannelBalanceFluctuationWarning expectedWarning =
new ChannelBalanceFluctuationWarning(LOWER_THRESHOLD - 1, UPPER_THRESHOLD + 1, DAYS);
assertThat(warningsProvider.getChannelWarnings(CHANNEL_ID)).contains(expectedWarning);
}
private void mockMinMax(@Nullable Integer min, @Nullable Integer max) {
Coins capacity = LOCAL_OPEN_CHANNEL.getCapacity();
if (min == null) {
when(balanceService.getLocalBalanceMinimum(CHANNEL_ID, DAYS))
.thenReturn(Optional.empty());
} else {
Coins minLocalAvailable = Coins.ofSatoshis((long) (capacity.satoshis() / 100.0 * min));
when(balanceService.getLocalBalanceMinimum(CHANNEL_ID, DAYS))
.thenReturn(Optional.of(minLocalAvailable));
}
if (max == null) {
when(balanceService.getLocalBalanceMaximum(CHANNEL_ID, DAYS))
.thenReturn(Optional.empty());
} else {
Coins maxLocalAvailable = Coins.ofSatoshis((long) (capacity.satoshis() / 100.0 * max));
when(balanceService.getLocalBalanceMaximum(CHANNEL_ID, DAYS))
.thenReturn(Optional.of(maxLocalAvailable));
}
}
}

View File

@@ -1,14 +1,26 @@
package de.cotto.lndmanagej.balances.persistence;
import de.cotto.lndmanagej.balances.Balances;
import de.cotto.lndmanagej.model.BalanceInformation;
import de.cotto.lndmanagej.model.Coins;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest;
import java.time.LocalDateTime;
import java.time.ZoneOffset;
import static de.cotto.lndmanagej.balances.BalancesFixtures.BALANCES;
import static de.cotto.lndmanagej.balances.BalancesFixtures.BALANCES_OLD;
import static de.cotto.lndmanagej.balances.BalancesFixtures.TIMESTAMP;
import static de.cotto.lndmanagej.model.BalanceInformationFixtures.LOCAL_BALANCE;
import static de.cotto.lndmanagej.model.BalanceInformationFixtures.LOCAL_RESERVE;
import static de.cotto.lndmanagej.model.BalanceInformationFixtures.REMOTE_BALANCE;
import static de.cotto.lndmanagej.model.BalanceInformationFixtures.REMOTE_RESERVE;
import static de.cotto.lndmanagej.model.ChannelIdFixtures.CHANNEL_ID;
import static de.cotto.lndmanagej.model.ChannelIdFixtures.CHANNEL_ID_2;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assumptions.assumeThat;
@DataJpaTest
class BalancesRepositoryIT {
@@ -48,4 +60,94 @@ class BalancesRepositoryIT {
assertThat(repository.findTopByChannelIdOrderByTimestampDesc(CHANNEL_ID.getShortChannelId()))
.map(BalancesJpaDto::toModel).contains(BALANCES);
}
@Test
void minimum_empty() {
assertThat(repository.findTopByChannelIdAndTimestampAfterOrderByLocalBalance(
CHANNEL_ID.getShortChannelId(),
14
)).isEmpty();
}
@Test
void minimum() {
assumeThat(localBalance(BALANCES) < localBalance(BALANCES_OLD)).isTrue();
repository.save(BalancesJpaDto.fromModel(BALANCES));
repository.save(BalancesJpaDto.fromModel(BALANCES_OLD));
assertThat(repository.findTopByChannelIdAndTimestampAfterOrderByLocalBalance(
CHANNEL_ID.getShortChannelId(),
14
)).map(BalancesJpaDto::toModel)
.map(Balances::balanceInformation)
.map(BalanceInformation::localBalance)
.contains(BALANCES.balanceInformation().localBalance());
}
@Test
void minimum_too_old() {
LocalDateTime now = TIMESTAMP;
Balances balancesMoreThanMinimum = createBalances(LOCAL_BALANCE.add(Coins.ofSatoshis(1)), now);
Balances balancesMinimumButOld = createBalances(LOCAL_BALANCE, now.minusDays(1));
long timestamp = balancesMinimumButOld.timestamp().toEpochSecond(ZoneOffset.UTC);
repository.save(BalancesJpaDto.fromModel(balancesMinimumButOld));
repository.save(BalancesJpaDto.fromModel(balancesMoreThanMinimum));
assertThat(repository.findTopByChannelIdAndTimestampAfterOrderByLocalBalance(
CHANNEL_ID.getShortChannelId(),
timestamp
)).map(BalancesJpaDto::toModel)
.map(Balances::balanceInformation)
.map(BalanceInformation::localBalance)
.contains(balancesMoreThanMinimum.balanceInformation().localBalance());
}
private Balances createBalances(Coins localBalance, LocalDateTime timestamp) {
BalanceInformation balanceInformation =
new BalanceInformation(localBalance, LOCAL_RESERVE, REMOTE_BALANCE, REMOTE_RESERVE);
return new Balances(timestamp, CHANNEL_ID, balanceInformation);
}
@Test
void maximum_empty() {
assertThat(repository.findTopByChannelIdAndTimestampAfterOrderByLocalBalanceDesc(
CHANNEL_ID.getShortChannelId(),
14
)).isEmpty();
}
@Test
void maximum() {
assumeThat(localBalance(BALANCES_OLD) > localBalance(BALANCES)).isTrue();
repository.save(BalancesJpaDto.fromModel(BALANCES));
repository.save(BalancesJpaDto.fromModel(BALANCES_OLD));
assertThat(repository.findTopByChannelIdAndTimestampAfterOrderByLocalBalanceDesc(
CHANNEL_ID.getShortChannelId(),
14
)).map(BalancesJpaDto::toModel)
.map(Balances::balanceInformation)
.map(BalanceInformation::localBalance)
.contains(BALANCES_OLD.balanceInformation().localBalance());
}
@Test
void maximum_too_old() {
LocalDateTime now = TIMESTAMP;
Balances balancesLessThanMaximum = createBalances(LOCAL_BALANCE.subtract(Coins.ofSatoshis(1)), now);
Balances balancesMaximumButOld = createBalances(LOCAL_BALANCE, now.minusDays(1));
long timestamp = balancesMaximumButOld.timestamp().toEpochSecond(ZoneOffset.UTC);
repository.save(BalancesJpaDto.fromModel(balancesMaximumButOld));
repository.save(BalancesJpaDto.fromModel(balancesLessThanMaximum));
assertThat(repository.findTopByChannelIdAndTimestampAfterOrderByLocalBalance(
CHANNEL_ID.getShortChannelId(),
timestamp
)).map(BalancesJpaDto::toModel)
.map(Balances::balanceInformation)
.map(BalanceInformation::localBalance)
.contains(balancesLessThanMaximum.balanceInformation().localBalance());
}
private long localBalance(Balances balances) {
return balances.balanceInformation().localBalance().milliSatoshis();
}
}

View File

@@ -1,6 +1,7 @@
package de.cotto.lndmanagej.balances;
import de.cotto.lndmanagej.model.ChannelId;
import de.cotto.lndmanagej.model.Coins;
import java.util.Optional;
@@ -8,4 +9,8 @@ public interface BalancesDao {
void saveBalances(Balances balances);
Optional<Balances> getMostRecentBalances(ChannelId channelId);
Optional<Coins> getLocalBalanceMinimum(ChannelId channelId, int days);
Optional<Coins> getLocalBalanceMaximum(ChannelId channelId, int days);
}

View File

@@ -2,10 +2,14 @@ package de.cotto.lndmanagej.balances.persistence;
import de.cotto.lndmanagej.balances.Balances;
import de.cotto.lndmanagej.balances.BalancesDao;
import de.cotto.lndmanagej.model.BalanceInformation;
import de.cotto.lndmanagej.model.ChannelId;
import de.cotto.lndmanagej.model.Coins;
import org.springframework.stereotype.Component;
import javax.transaction.Transactional;
import java.time.ZoneOffset;
import java.time.ZonedDateTime;
import java.util.Optional;
@Component
@@ -27,4 +31,36 @@ class BalancesDaoImpl implements BalancesDao {
return balancesRepository.findTopByChannelIdOrderByTimestampDesc(channelId.getShortChannelId())
.map(BalancesJpaDto::toModel);
}
@Override
public Optional<Coins> getLocalBalanceMinimum(ChannelId channelId, int days) {
return getLocalBalance(
balancesRepository.findTopByChannelIdAndTimestampAfterOrderByLocalBalance(
channelId.getShortChannelId(),
getTimestamp(days)
)
);
}
@Override
public Optional<Coins> getLocalBalanceMaximum(ChannelId channelId, int days) {
long timestamp = getTimestamp(days);
return getLocalBalance(
balancesRepository.findTopByChannelIdAndTimestampAfterOrderByLocalBalanceDesc(
channelId.getShortChannelId(),
timestamp
)
);
}
private Optional<Coins> getLocalBalance(Optional<BalancesJpaDto> balances) {
return balances
.map(BalancesJpaDto::toModel)
.map(Balances::balanceInformation)
.map(BalanceInformation::localBalance);
}
private long getTimestamp(int daysInPast) {
return ZonedDateTime.now(ZoneOffset.UTC).minusDays(daysInPast).toEpochSecond();
}
}

View File

@@ -6,4 +6,8 @@ import java.util.Optional;
public interface BalancesRepository extends JpaRepository<BalancesJpaDto, String> {
Optional<BalancesJpaDto> findTopByChannelIdOrderByTimestampDesc(long channelId);
Optional<BalancesJpaDto> findTopByChannelIdAndTimestampAfterOrderByLocalBalance(long channelId, long timestamp);
Optional<BalancesJpaDto> findTopByChannelIdAndTimestampAfterOrderByLocalBalanceDesc(long channelId, long timestamp);
}

View File

@@ -1,5 +1,8 @@
package de.cotto.lndmanagej.balances.persistence;
import de.cotto.lndmanagej.balances.Balances;
import de.cotto.lndmanagej.model.BalanceInformation;
import de.cotto.lndmanagej.model.Coins;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.InjectMocks;
@@ -7,14 +10,21 @@ import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import java.time.ZoneOffset;
import java.time.ZonedDateTime;
import java.util.Optional;
import static de.cotto.lndmanagej.balances.BalancesFixtures.BALANCES;
import static de.cotto.lndmanagej.balances.BalancesFixtures.TIMESTAMP;
import static de.cotto.lndmanagej.model.BalanceInformationFixtures.BALANCE_INFORMATION;
import static de.cotto.lndmanagej.model.BalanceInformationFixtures.LOCAL_RESERVE;
import static de.cotto.lndmanagej.model.BalanceInformationFixtures.REMOTE_BALANCE;
import static de.cotto.lndmanagej.model.BalanceInformationFixtures.REMOTE_RESERVE;
import static de.cotto.lndmanagej.model.ChannelIdFixtures.CHANNEL_ID;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.ArgumentMatchers.anyLong;
import static org.mockito.ArgumentMatchers.argThat;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.ArgumentMatchers.longThat;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@@ -60,4 +70,54 @@ class BalancesDaoImplTest {
.thenReturn(Optional.of(BalancesJpaDto.fromModel(BALANCES)));
assertThat(dao.getMostRecentBalances(CHANNEL_ID)).contains(BALANCES);
}
@Test
void getLocalBalanceMinimum_empty() {
assertThat(dao.getLocalBalanceMinimum(CHANNEL_ID, 7)).isEmpty();
}
@Test
void getLocalBalanceMinimum() {
int days = 14;
Coins localBalance = Coins.ofSatoshis(456);
Balances balances = getWithLocalBalance(localBalance);
when(balancesRepository.findTopByChannelIdAndTimestampAfterOrderByLocalBalance(
eq(CHANNEL_ID.getShortChannelId()),
anyLong()
)).thenReturn(Optional.of(BalancesJpaDto.fromModel(balances)));
assertThat(dao.getLocalBalanceMinimum(CHANNEL_ID, days)).contains(localBalance);
long expectedTimestamp = ZonedDateTime.now(ZoneOffset.UTC).minusDays(days).toEpochSecond();
verify(balancesRepository).findTopByChannelIdAndTimestampAfterOrderByLocalBalance(anyLong(),
longThat(timestamp -> timestamp > 0.95 * expectedTimestamp && timestamp < 1.05 * expectedTimestamp)
);
}
@Test
void getLocalBalanceMaximum_empty() {
assertThat(dao.getLocalBalanceMaximum(CHANNEL_ID, 7)).isEmpty();
}
@Test
void getLocalBalanceMaximum() {
int days = 14;
Coins localBalance = Coins.ofSatoshis(456);
Balances balances = getWithLocalBalance(localBalance);
when(balancesRepository.findTopByChannelIdAndTimestampAfterOrderByLocalBalanceDesc(
eq(CHANNEL_ID.getShortChannelId()),
anyLong()
)).thenReturn(Optional.of(BalancesJpaDto.fromModel(balances)));
assertThat(dao.getLocalBalanceMaximum(CHANNEL_ID, days)).contains(localBalance);
long expectedTimestamp = ZonedDateTime.now(ZoneOffset.UTC).minusDays(days).toEpochSecond();
verify(balancesRepository).findTopByChannelIdAndTimestampAfterOrderByLocalBalanceDesc(anyLong(),
longThat(timestamp -> timestamp > 0.95 * expectedTimestamp && timestamp < 1.05 * expectedTimestamp)
);
}
private Balances getWithLocalBalance(Coins localBalance) {
BalanceInformation balanceInformation =
new BalanceInformation(localBalance, LOCAL_RESERVE, REMOTE_BALANCE, REMOTE_RESERVE);
return new Balances(TIMESTAMP, CHANNEL_ID, balanceInformation);
}
}

View File

@@ -0,0 +1,16 @@
package de.cotto.lndmanagej.model.warnings;
public record ChannelBalanceFluctuationWarning(
int minLocalBalancePercentage,
int maxLocalBalancePercentage,
int days
) implements ChannelWarning {
@Override
public String description() {
return "Channel balance fluctuated between %d%% and %d%% in the past %d days".formatted(
minLocalBalancePercentage,
maxLocalBalancePercentage,
days
);
}
}

View File

@@ -0,0 +1,29 @@
package de.cotto.lndmanagej.model.warnings;
import org.junit.jupiter.api.Test;
import static de.cotto.lndmanagej.model.warnings.ChannelWarningFixtures.CHANNEL_BALANCE_FLUCTUATION_WARNING;
import static org.assertj.core.api.Assertions.assertThat;
class ChannelBalanceFluctuationWarningTest {
@Test
void minLocalBalancePercentage() {
assertThat(CHANNEL_BALANCE_FLUCTUATION_WARNING.minLocalBalancePercentage()).isEqualTo(2);
}
@Test
void maxLocalBalancePercentage() {
assertThat(CHANNEL_BALANCE_FLUCTUATION_WARNING.maxLocalBalancePercentage()).isEqualTo(97);
}
@Test
void days() {
assertThat(CHANNEL_BALANCE_FLUCTUATION_WARNING.days()).isEqualTo(7);
}
@Test
void description() {
assertThat(CHANNEL_BALANCE_FLUCTUATION_WARNING.description())
.isEqualTo("Channel balance fluctuated between 2% and 97% in the past 7 days");
}
}

View File

@@ -2,4 +2,6 @@ package de.cotto.lndmanagej.model.warnings;
public class ChannelWarningFixtures {
public static final ChannelNumUpdatesWarning CHANNEL_NUM_UPDATES_WARNING = new ChannelNumUpdatesWarning(101_000L);
public static final ChannelBalanceFluctuationWarning CHANNEL_BALANCE_FLUCTUATION_WARNING =
new ChannelBalanceFluctuationWarning(2, 97, 7);
}