allow users to specify resolutions manually

This commit is contained in:
Carsten Otto
2021-12-12 21:02:17 +01:00
parent 848afefc84
commit 67e8bc6637
44 changed files with 541 additions and 100 deletions

10
hardcoded/build.gradle Normal file
View File

@@ -0,0 +1,10 @@
plugins {
id 'lnd-manageJ.java-library-conventions'
}
dependencies {
implementation project(':model')
implementation project(':caching')
implementation 'org.ini4j:ini4j:0.5.4'
testImplementation testFixtures(project(':model'))
}

View File

@@ -0,0 +1,55 @@
package de.cotto.lndmanagej.hardcoded;
import com.google.common.base.Splitter;
import de.cotto.lndmanagej.model.ChannelId;
import de.cotto.lndmanagej.model.Resolution;
import de.cotto.lndmanagej.model.TransactionHash;
import org.springframework.stereotype.Component;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.stream.Stream;
import static java.util.stream.Collectors.toSet;
@Component
public class HardcodedService {
private static final int EXPECTED_NUMBER_OF_COMPONENTS = 3;
private static final String RESOLUTIONS_SECTION = "resolutions";
private static final Splitter SPLITTER = Splitter.on(":");
private final IniFileReader iniFileReader;
public HardcodedService(IniFileReader iniFileReader) {
this.iniFileReader = iniFileReader;
}
public Set<Resolution> getResolutions(ChannelId channelId) {
Map<String, Set<String>> values = iniFileReader.getValues(RESOLUTIONS_SECTION);
Set<String> forShortChannelId = values.getOrDefault(String.valueOf(channelId.getShortChannelId()), Set.of());
Set<String> forCompactForm = values.getOrDefault(channelId.getCompactForm(), Set.of());
Set<String> forCompactFormLnd = values.getOrDefault(channelId.getCompactFormLnd(), Set.of());
return Stream.of(forShortChannelId, forCompactForm, forCompactFormLnd)
.flatMap(Set::stream)
.map(this::parseResolution)
.flatMap(Optional::stream)
.collect(toSet());
}
private Optional<Resolution> parseResolution(String encodedResolution) {
try {
List<String> split = SPLITTER.splitToList(encodedResolution);
if (split.size() != EXPECTED_NUMBER_OF_COMPONENTS) {
return Optional.empty();
}
String resolutionType = split.get(0);
String outcome = split.get(1);
TransactionHash sweepTransaction = TransactionHash.create(split.get(2));
return Optional.of(new Resolution(Optional.of(sweepTransaction), resolutionType, outcome));
} catch (IllegalArgumentException exception) {
return Optional.empty();
}
}
}

View File

@@ -0,0 +1,57 @@
package de.cotto.lndmanagej.hardcoded;
import com.github.benmanes.caffeine.cache.LoadingCache;
import de.cotto.lndmanagej.caching.CacheBuilder;
import org.ini4j.Ini;
import org.ini4j.Profile;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import java.io.File;
import java.io.IOException;
import java.time.Duration;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
@Component
public class IniFileReader {
private final String path;
private final LoadingCache<String, Map<String, Set<String>>> cache;
public IniFileReader(@Value("${lndmanagej.hardcoded-path:}") String path) {
this.path = path;
cache = new CacheBuilder()
.withRefresh(Duration.ofSeconds(5))
.withExpiry(Duration.ofSeconds(10))
.build(this::getValuesWithoutCache);
}
public Map<String, Set<String>> getValues(String sectionName) {
return cache.get(sectionName);
}
private Map<String, Set<String>> getValuesWithoutCache(String sectionName) {
return getIni().map(ini -> ini.get(sectionName))
.map(this::toMultiValueMap)
.orElse(Map.of());
}
private Map<String, Set<String>> toMultiValueMap(Profile.Section section) {
LinkedHashMap<String, Set<String>> result = new LinkedHashMap<>();
for (String key : section.keySet()) {
result.put(key, new HashSet<>(section.getAll(key)));
}
return result;
}
private Optional<Ini> getIni() {
try {
return Optional.of(new Ini(new File(path)));
} catch (IOException e) {
return Optional.empty();
}
}
}

View File

@@ -0,0 +1,77 @@
package de.cotto.lndmanagej.hardcoded;
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.util.Map;
import java.util.Set;
import static de.cotto.lndmanagej.model.ChannelIdFixtures.CHANNEL_ID;
import static de.cotto.lndmanagej.model.ResolutionFixtures.ANCHOR_CLAIMED;
import static de.cotto.lndmanagej.model.ResolutionFixtures.COMMIT_CLAIMED;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.when;
@ExtendWith(MockitoExtension.class)
class HardcodedServiceTest {
private static final String SECTION = "resolutions";
private static final String COMMIT_CLAIMED_STRING
= "COMMIT:CLAIMED:abc222abc000abc000abc000abc000abc000abc000abc000abc000abc000abc0";
private static final String ANCHOR_CLAIMED_STRING
= "ANCHOR:CLAIMED:abc222abc000abc000abc000abc000abc000abc000abc000abc000abc000abc0";
@InjectMocks
private HardcodedService hardcodedService;
@Mock
private IniFileReader iniFileReader;
@Test
void getResolutions_empty() {
when(iniFileReader.getValues(SECTION)).thenReturn(Map.of());
assertThat(hardcodedService.getResolutions(CHANNEL_ID)).isEmpty();
}
@Test
void getResolutions_one_resolution_short_channel_id() {
when(iniFileReader.getValues(SECTION))
.thenReturn(Map.of(String.valueOf(CHANNEL_ID.getShortChannelId()), Set.of(COMMIT_CLAIMED_STRING)));
assertThat(hardcodedService.getResolutions(CHANNEL_ID)).containsExactly(COMMIT_CLAIMED);
}
@Test
void getResolutions_one_resolution_compact_form() {
when(iniFileReader.getValues(SECTION))
.thenReturn(Map.of(CHANNEL_ID.getCompactForm(), Set.of(COMMIT_CLAIMED_STRING)));
assertThat(hardcodedService.getResolutions(CHANNEL_ID)).containsExactly(COMMIT_CLAIMED);
}
@Test
void getResolutions_one_resolution_compact_form_lnd() {
when(iniFileReader.getValues(SECTION))
.thenReturn(Map.of(CHANNEL_ID.getCompactFormLnd(), Set.of(COMMIT_CLAIMED_STRING)));
assertThat(hardcodedService.getResolutions(CHANNEL_ID)).containsExactly(COMMIT_CLAIMED);
}
@Test
void getResolutions_two_resolutions() {
when(iniFileReader.getValues(SECTION)).thenReturn(Map.of(
CHANNEL_ID.getCompactFormLnd(),
Set.of(COMMIT_CLAIMED_STRING, ANCHOR_CLAIMED_STRING)
));
assertThat(hardcodedService.getResolutions(CHANNEL_ID))
.containsExactlyInAnyOrder(COMMIT_CLAIMED, ANCHOR_CLAIMED);
}
@Test
void getResolutions_bogus_string() {
when(iniFileReader.getValues(SECTION)).thenReturn(Map.of(
CHANNEL_ID.getCompactFormLnd(),
Set.of("hello", "hello:peter", "a:b:c")
));
assertThat(hardcodedService.getResolutions(CHANNEL_ID)).isEmpty();
}
}

View File

@@ -0,0 +1,96 @@
package de.cotto.lndmanagej.hardcoded;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.junit.jupiter.MockitoExtension;
import java.io.BufferedWriter;
import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.util.Map;
import java.util.Set;
import static org.assertj.core.api.Assertions.assertThat;
@ExtendWith(MockitoExtension.class)
class IniFileReaderTest {
private static final String SECTION = "section";
private static final String SECTION_2 = "another-section";
@Test
void file_does_not_exist() throws IOException {
File file = createTempFile();
//noinspection ResultOfMethodCallIgnored
file.delete();
IniFileReader iniFileReader = new IniFileReader(file.getPath());
assertThat(iniFileReader.getValues(SECTION)).isEmpty();
}
@Test
void path_does_not_exist() {
IniFileReader iniFileReader = new IniFileReader("/blabla/this/does/not/exist/foo.conf");
assertThat(iniFileReader.getValues(SECTION)).isEmpty();
}
@Test
void empty_file() throws IOException {
File file = createTempFile();
IniFileReader iniFileReader = new IniFileReader(file.getPath());
assertThat(iniFileReader.getValues(SECTION)).isEmpty();
}
@Test
void section_without_values() throws IOException {
File file = createTempFile();
addLineToFile(file, "[" + SECTION + "]");
IniFileReader iniFileReader = new IniFileReader(file.getPath());
assertThat(iniFileReader.getValues(SECTION)).isEmpty();
}
@Test
void section_with_value() throws IOException {
File file = createTempFile();
addLineToFile(file, "[" + SECTION + "]", "x=y");
IniFileReader iniFileReader = new IniFileReader(file.getPath());
assertThat(iniFileReader.getValues(SECTION)).isEqualTo(Map.of("x", Set.of("y")));
}
@Test
void section_with_two_values() throws IOException {
File file = createTempFile();
addLineToFile(file, "[" + SECTION + "]", "x=y", "a=b");
IniFileReader iniFileReader = new IniFileReader(file.getPath());
assertThat(iniFileReader.getValues(SECTION)).isEqualTo(Map.of("x", Set.of("y"), "a", Set.of("b")));
}
@Test
void two_sections_with_two_values() throws IOException {
File file = createTempFile();
addLineToFile(file, "[" + SECTION + "]", "x=y", "a=b", "[" + SECTION_2 + "]", "x=1", "a=2");
IniFileReader iniFileReader = new IniFileReader(file.getPath());
assertThat(iniFileReader.getValues(SECTION)).isEqualTo(Map.of("x", Set.of("y"), "a", Set.of("b")));
assertThat(iniFileReader.getValues(SECTION_2)).isEqualTo(Map.of("x", Set.of("1"), "a", Set.of("2")));
}
@Test
void section_with_two_values_for_key() throws IOException {
File file = createTempFile();
addLineToFile(file, "[" + SECTION + "]", "a=y", "a=b");
IniFileReader iniFileReader = new IniFileReader(file.getPath());
assertThat(iniFileReader.getValues(SECTION)).isEqualTo(Map.of("a", Set.of("y", "b")));
}
private void addLineToFile(File file, String... lines) throws IOException {
try (BufferedWriter writer = Files.newBufferedWriter(file.toPath())) {
for (String line : lines) {
writer.write(line + "\n");
}
}
}
private File createTempFile() throws IOException {
return File.createTempFile("hardcoded", "temp");
}
}