mirror of
https://github.com/aljazceru/lnd-manageJ.git
synced 2026-01-30 03:04:38 +01:00
periodically log channel balances
This commit is contained in:
@@ -3,9 +3,11 @@ plugins {
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation project(':web')
|
||||
runtimeOnly project(':web')
|
||||
runtimeOnly project(':statistics')
|
||||
runtimeOnly 'org.postgresql:postgresql'
|
||||
testRuntimeOnly 'com.h2database:h2'
|
||||
testImplementation project(':backend')
|
||||
testImplementation project(':grpc-adapter')
|
||||
}
|
||||
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
package de.cotto.lndmanagej;
|
||||
|
||||
import de.cotto.lndmanagej.controller.LegacyController;
|
||||
import de.cotto.lndmanagej.grpc.GrpcRouterService;
|
||||
import de.cotto.lndmanagej.grpc.GrpcService;
|
||||
import de.cotto.lndmanagej.service.ChannelService;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.test.context.SpringBootTest;
|
||||
@@ -13,7 +13,7 @@ import static org.assertj.core.api.Assertions.assertThat;
|
||||
@SpringBootTest
|
||||
class ApplicationContextIT {
|
||||
@Autowired
|
||||
private LegacyController legacyController;
|
||||
private ChannelService channelService;
|
||||
|
||||
@MockBean
|
||||
@SuppressWarnings("unused")
|
||||
@@ -25,6 +25,6 @@ class ApplicationContextIT {
|
||||
|
||||
@Test
|
||||
void contextStarts() {
|
||||
assertThat(legacyController).isNotNull();
|
||||
assertThat(channelService).isNotNull();
|
||||
}
|
||||
}
|
||||
@@ -36,10 +36,10 @@ class BalanceServiceTest {
|
||||
@Test
|
||||
void getBalanceInformation_for_pubkey() {
|
||||
BalanceInformation expected = new BalanceInformation(
|
||||
Coins.ofSatoshis(3_000),
|
||||
Coins.ofSatoshis(300),
|
||||
Coins.ofSatoshis(346),
|
||||
Coins.ofSatoshis(30)
|
||||
Coins.ofSatoshis(4_000),
|
||||
Coins.ofSatoshis(400),
|
||||
Coins.ofSatoshis(446),
|
||||
Coins.ofSatoshis(40)
|
||||
);
|
||||
when(channelService.getOpenChannelsWith(PUBKEY)).thenReturn(Set.of(LOCAL_OPEN_CHANNEL, LOCAL_OPEN_CHANNEL_2));
|
||||
when(grpcChannels.getChannel(CHANNEL_ID)).thenReturn(Optional.of(LOCAL_OPEN_CHANNEL_MORE_BALANCE));
|
||||
|
||||
@@ -67,7 +67,7 @@ public class LocalOpenChannelFixtures {
|
||||
CAPACITY,
|
||||
PUBKEY,
|
||||
PUBKEY_2,
|
||||
BALANCE_INFORMATION,
|
||||
BALANCE_INFORMATION_2,
|
||||
REMOTE,
|
||||
false
|
||||
);
|
||||
|
||||
@@ -6,5 +6,6 @@ include 'grpc-adapter'
|
||||
include 'grpc-client'
|
||||
include 'metrics'
|
||||
include 'model'
|
||||
include 'statistics'
|
||||
include 'transactions'
|
||||
include 'web'
|
||||
|
||||
10
statistics/build.gradle
Normal file
10
statistics/build.gradle
Normal file
@@ -0,0 +1,10 @@
|
||||
plugins {
|
||||
id 'lnd-manageJ.java-library-conventions'
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation project(':backend')
|
||||
implementation project(':model')
|
||||
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
|
||||
testFixturesApi testFixtures(project(':model'))
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
package de.cotto.lndmanagej.statistics;
|
||||
|
||||
import de.cotto.lndmanagej.model.BalanceInformation;
|
||||
import de.cotto.lndmanagej.model.ChannelId;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
public record Statistics(
|
||||
LocalDateTime timestamp,
|
||||
ChannelId channelId,
|
||||
BalanceInformation balanceInformation
|
||||
) {
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
package de.cotto.lndmanagej.statistics;
|
||||
|
||||
public interface StatisticsDao {
|
||||
void saveStatistics(Statistics statistics);
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
package de.cotto.lndmanagej.statistics;
|
||||
|
||||
import de.cotto.lndmanagej.model.BalanceInformation;
|
||||
import de.cotto.lndmanagej.model.ChannelId;
|
||||
import de.cotto.lndmanagej.model.LocalOpenChannel;
|
||||
import de.cotto.lndmanagej.service.ChannelService;
|
||||
import org.springframework.scheduling.annotation.Scheduled;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.ZoneOffset;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
public class StatisticsService {
|
||||
private final ChannelService channelService;
|
||||
private final StatisticsDao statisticsDao;
|
||||
|
||||
public StatisticsService(ChannelService channelService, StatisticsDao statisticsDao) {
|
||||
this.channelService = channelService;
|
||||
this.statisticsDao = statisticsDao;
|
||||
}
|
||||
|
||||
@Scheduled(fixedRate = 5, timeUnit = TimeUnit.MINUTES)
|
||||
public void storeBalances() {
|
||||
LocalDateTime timestamp = LocalDateTime.now(ZoneOffset.UTC);
|
||||
channelService.getOpenChannels().forEach(channel -> storeBalance(channel, timestamp));
|
||||
}
|
||||
|
||||
private void storeBalance(LocalOpenChannel channel, LocalDateTime timestamp) {
|
||||
ChannelId channelId = channel.getId();
|
||||
BalanceInformation balanceInformation = channel.getBalanceInformation();
|
||||
Statistics statistics = new Statistics(timestamp, channelId, balanceInformation);
|
||||
statisticsDao.saveStatistics(statistics);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
package de.cotto.lndmanagej.statistics.persistence;
|
||||
|
||||
import de.cotto.lndmanagej.statistics.Statistics;
|
||||
import de.cotto.lndmanagej.statistics.StatisticsDao;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import javax.transaction.Transactional;
|
||||
|
||||
@Component
|
||||
@Transactional
|
||||
public class StatisticsDaoImpl implements StatisticsDao {
|
||||
private final StatisticsRepository statisticsRepository;
|
||||
|
||||
public StatisticsDaoImpl(StatisticsRepository statisticsRepository) {
|
||||
this.statisticsRepository = statisticsRepository;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void saveStatistics(Statistics statistics) {
|
||||
statisticsRepository.save(StatisticsJpaDto.fromModel(statistics));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
package de.cotto.lndmanagej.statistics.persistence;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
public record StatisticsId(Long channelId, long timestamp) implements Serializable {
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
package de.cotto.lndmanagej.statistics.persistence;
|
||||
|
||||
import com.google.common.annotations.VisibleForTesting;
|
||||
import de.cotto.lndmanagej.statistics.Statistics;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
import javax.persistence.Entity;
|
||||
import javax.persistence.Id;
|
||||
import javax.persistence.IdClass;
|
||||
import javax.persistence.Table;
|
||||
import java.time.ZoneOffset;
|
||||
import java.util.Objects;
|
||||
|
||||
@Entity
|
||||
@IdClass(StatisticsId.class)
|
||||
@Table(name = "statistics")
|
||||
class StatisticsJpaDto {
|
||||
@Id
|
||||
private long timestamp;
|
||||
|
||||
@Id
|
||||
@Nullable
|
||||
private Long channelId;
|
||||
|
||||
private long localBalance;
|
||||
private long localReserved;
|
||||
private long remoteBalance;
|
||||
private long remoteReserved;
|
||||
|
||||
StatisticsJpaDto() {
|
||||
// for JPA
|
||||
}
|
||||
|
||||
protected static StatisticsJpaDto fromModel(Statistics statistics) {
|
||||
StatisticsJpaDto dto = new StatisticsJpaDto();
|
||||
dto.timestamp = statistics.timestamp().toEpochSecond(ZoneOffset.UTC);
|
||||
dto.channelId = statistics.channelId().getShortChannelId();
|
||||
dto.localBalance = statistics.balanceInformation().localBalance().satoshis();
|
||||
dto.localReserved = statistics.balanceInformation().localReserve().satoshis();
|
||||
dto.remoteBalance = statistics.balanceInformation().remoteBalance().satoshis();
|
||||
dto.remoteReserved = statistics.balanceInformation().remoteReserve().satoshis();
|
||||
return dto;
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
protected long getTimestamp() {
|
||||
return timestamp;
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
protected long getChannelId() {
|
||||
return Objects.requireNonNull(channelId);
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
protected long getLocalBalance() {
|
||||
return localBalance;
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
protected long getLocalReserved() {
|
||||
return localReserved;
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
protected long getRemoteBalance() {
|
||||
return remoteBalance;
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
protected long getRemoteReserved() {
|
||||
return remoteReserved;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
package de.cotto.lndmanagej.statistics.persistence;
|
||||
|
||||
import org.springframework.data.jpa.repository.JpaRepository;
|
||||
|
||||
public interface StatisticsRepository extends JpaRepository<StatisticsJpaDto, String> {
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
package de.cotto.lndmanagej.statistics;
|
||||
|
||||
import de.cotto.lndmanagej.model.LocalOpenChannel;
|
||||
import de.cotto.lndmanagej.service.ChannelService;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.mockito.ArgumentMatcher;
|
||||
import org.mockito.InjectMocks;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.junit.jupiter.MockitoExtension;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
import static de.cotto.lndmanagej.model.LocalOpenChannelFixtures.LOCAL_OPEN_CHANNEL;
|
||||
import static de.cotto.lndmanagej.model.LocalOpenChannelFixtures.LOCAL_OPEN_CHANNEL_MORE_BALANCE_2;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.ArgumentMatchers.argThat;
|
||||
import static org.mockito.Mockito.times;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.verifyNoMoreInteractions;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
class StatisticsServiceTest {
|
||||
@InjectMocks
|
||||
private StatisticsService statisticsService;
|
||||
|
||||
@Mock
|
||||
private ChannelService channelService;
|
||||
|
||||
@Mock
|
||||
private StatisticsDao statisticsDao;
|
||||
|
||||
@Test
|
||||
void storeBalances() {
|
||||
when(channelService.getOpenChannels()).thenReturn(Set.of(
|
||||
LOCAL_OPEN_CHANNEL,
|
||||
LOCAL_OPEN_CHANNEL_MORE_BALANCE_2
|
||||
));
|
||||
statisticsService.storeBalances();
|
||||
verify(statisticsDao).saveStatistics(argThat(withBalanceInformation(LOCAL_OPEN_CHANNEL_MORE_BALANCE_2)));
|
||||
verify(statisticsDao).saveStatistics(argThat(withChannelId(LOCAL_OPEN_CHANNEL_MORE_BALANCE_2)));
|
||||
verify(statisticsDao).saveStatistics(argThat(withBalanceInformation(LOCAL_OPEN_CHANNEL)));
|
||||
verify(statisticsDao).saveStatistics(argThat(withChannelId(LOCAL_OPEN_CHANNEL)));
|
||||
verify(statisticsDao, times(2)).saveStatistics(any());
|
||||
verifyNoMoreInteractions(statisticsDao);
|
||||
}
|
||||
|
||||
private ArgumentMatcher<Statistics> withChannelId(LocalOpenChannel channel) {
|
||||
return statistics -> statistics.channelId().equals(channel.getId());
|
||||
}
|
||||
|
||||
private ArgumentMatcher<Statistics> withBalanceInformation(LocalOpenChannel channel) {
|
||||
return statistics -> statistics.balanceInformation().equals(channel.getBalanceInformation());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
package de.cotto.lndmanagej.statistics.persistence;
|
||||
|
||||
import de.cotto.lndmanagej.statistics.StatisticsFixtures;
|
||||
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 java.time.ZoneOffset;
|
||||
|
||||
import static de.cotto.lndmanagej.model.BalanceInformationFixtures.BALANCE_INFORMATION;
|
||||
import static de.cotto.lndmanagej.model.ChannelIdFixtures.CHANNEL_ID;
|
||||
import static org.mockito.ArgumentMatchers.argThat;
|
||||
import static org.mockito.Mockito.verify;
|
||||
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
class StatisticsDaoImplTest {
|
||||
|
||||
@InjectMocks
|
||||
private StatisticsDaoImpl statisticsDaoImpl;
|
||||
|
||||
@Mock
|
||||
private StatisticsRepository statisticsRepository;
|
||||
|
||||
@Test
|
||||
void saveStatistics() {
|
||||
statisticsDaoImpl.saveStatistics(StatisticsFixtures.STATISTICS);
|
||||
verify(statisticsRepository).save(argThat(jpaDto ->
|
||||
jpaDto.getTimestamp() == StatisticsFixtures.TIMESTAMP.toEpochSecond(ZoneOffset.UTC)
|
||||
));
|
||||
verify(statisticsRepository).save(argThat(jpaDto ->
|
||||
jpaDto.getChannelId() == CHANNEL_ID.getShortChannelId()));
|
||||
verify(statisticsRepository).save(argThat(jpaDto ->
|
||||
jpaDto.getLocalBalance() == BALANCE_INFORMATION.localBalance().satoshis()
|
||||
));
|
||||
verify(statisticsRepository).save(argThat(jpaDto ->
|
||||
jpaDto.getLocalReserved() == BALANCE_INFORMATION.localReserve().satoshis()
|
||||
));
|
||||
verify(statisticsRepository).save(argThat(jpaDto ->
|
||||
jpaDto.getRemoteBalance() == BALANCE_INFORMATION.remoteBalance().satoshis()
|
||||
));
|
||||
verify(statisticsRepository).save(argThat(jpaDto ->
|
||||
jpaDto.getRemoteReserved() == BALANCE_INFORMATION.remoteReserve().satoshis()
|
||||
));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
package de.cotto.lndmanagej.statistics.persistence;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.time.ZoneOffset;
|
||||
|
||||
import static de.cotto.lndmanagej.model.ChannelIdFixtures.CHANNEL_ID;
|
||||
import static de.cotto.lndmanagej.statistics.StatisticsFixtures.TIMESTAMP;
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
class StatisticsIdTest {
|
||||
|
||||
private static final long CHANNEL_ID_LONG = CHANNEL_ID.getShortChannelId();
|
||||
private static final long TIMESTAMP_LONG = TIMESTAMP.toEpochSecond(ZoneOffset.UTC);
|
||||
|
||||
private final StatisticsId statisticsId = new StatisticsId(CHANNEL_ID_LONG, TIMESTAMP_LONG);
|
||||
|
||||
@Test
|
||||
void test_types() {
|
||||
assertThat(statisticsId.channelId()).isEqualTo(CHANNEL_ID_LONG);
|
||||
assertThat(statisticsId.timestamp()).isEqualTo(TIMESTAMP_LONG);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
package de.cotto.lndmanagej.statistics.persistence;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.time.ZoneOffset;
|
||||
|
||||
import static de.cotto.lndmanagej.model.BalanceInformationFixtures.BALANCE_INFORMATION;
|
||||
import static de.cotto.lndmanagej.model.ChannelIdFixtures.CHANNEL_ID;
|
||||
import static de.cotto.lndmanagej.statistics.StatisticsFixtures.STATISTICS;
|
||||
import static de.cotto.lndmanagej.statistics.StatisticsFixtures.TIMESTAMP;
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
class StatisticsJpaDtoTest {
|
||||
@Test
|
||||
@SuppressWarnings("PMD.JUnitTestContainsTooManyAsserts")
|
||||
void fromModel() {
|
||||
StatisticsJpaDto jpaDto = StatisticsJpaDto.fromModel(STATISTICS);
|
||||
assertThat(jpaDto.getTimestamp()).isEqualTo(TIMESTAMP.toEpochSecond(ZoneOffset.UTC));
|
||||
assertThat(jpaDto.getChannelId()).isEqualTo(CHANNEL_ID.getShortChannelId());
|
||||
assertThat(jpaDto.getLocalBalance()).isEqualTo(BALANCE_INFORMATION.localBalance().satoshis());
|
||||
assertThat(jpaDto.getLocalReserved()).isEqualTo(BALANCE_INFORMATION.localReserve().satoshis());
|
||||
assertThat(jpaDto.getRemoteBalance()).isEqualTo(BALANCE_INFORMATION.remoteBalance().satoshis());
|
||||
assertThat(jpaDto.getRemoteReserved()).isEqualTo(BALANCE_INFORMATION.remoteReserve().satoshis());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
package de.cotto.lndmanagej.statistics;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.ZoneOffset;
|
||||
|
||||
import static de.cotto.lndmanagej.model.BalanceInformationFixtures.BALANCE_INFORMATION;
|
||||
import static de.cotto.lndmanagej.model.ChannelIdFixtures.CHANNEL_ID;
|
||||
|
||||
public class StatisticsFixtures {
|
||||
public static final LocalDateTime TIMESTAMP = LocalDateTime.now(ZoneOffset.UTC);
|
||||
public static final Statistics STATISTICS = new Statistics(TIMESTAMP, CHANNEL_ID, BALANCE_INFORMATION);
|
||||
}
|
||||
Reference in New Issue
Block a user