-
Notifications
You must be signed in to change notification settings - Fork 6
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
add support for lnd sweep transactions
- Loading branch information
Showing
13 changed files
with
479 additions
and
3 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,33 @@ | ||
# LND Support | ||
BitBook has basic support for [LND (lightning network daemon)](https://github.com/lightningnetwork/lnd) so that you can | ||
add ownership information and helpful descriptions for transactions/addresses managed by your LND instance without | ||
entering these manually. | ||
|
||
This is implemented by parsing JSON files generated by the `lncli` command line tool. As such, this involves a bit of | ||
manual effort, but it also keeps things simple. By copying these files youc an easily have BitBook and lnd run on | ||
different computers. Furthermore, by not giving BitBook access to lnd, there's no risk of undesired side effects. | ||
|
||
### Sweeps | ||
After a channel is closed, your funds (on the "local" side of the channel) are sent to an address that is not derived | ||
from your lnd wallet seed. To avoid loss of funds, lnd automatically "sweeps" those funds to another address. As such, | ||
there may be several sweep transactions that transfer funds from one address to another. | ||
|
||
BitBook offers the command `lnd-add-from-sweeps` which parses lnd sweep information and does the following for each | ||
transaction: | ||
|
||
- sets the transaction description to "LND sweep transaction" | ||
- sets the target address description to "LND" | ||
- sets the source address description to "LND" if it is not set | ||
(a better description can be obtained when parsing channel close data) | ||
- marks both addresses as owned | ||
|
||
To run the command: | ||
1. first create the JSON file using lnd: `$ lncli wallet listsweeps > lnd-sweeps.json` | ||
2. transfer the JSON file to the host where you are running BitBook: `$ scp server:/home/lnd/lnd-sweeps.json /tmp/` | ||
3. start BitBook | ||
|
||
Then you can use the command as follows: | ||
``` | ||
BitBook$ lnd-add-from-sweeps /tmp/lnd-sweeps.json | ||
Added information for 86 sweep transactions | ||
``` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
plugins { | ||
id 'bitbook.java-library-conventions' | ||
} | ||
|
||
dependencies { | ||
implementation project(':cli-base') | ||
implementation project(':lnd') | ||
} |
29 changes: 29 additions & 0 deletions
29
lnd-cli/src/main/java/de/cotto/bitbook/lnd/cli/LndCommands.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,29 @@ | ||
package de.cotto.bitbook.lnd.cli; | ||
|
||
import de.cotto.bitbook.lnd.LndService; | ||
import org.springframework.shell.standard.ShellComponent; | ||
import org.springframework.shell.standard.ShellMethod; | ||
|
||
import java.io.File; | ||
import java.io.IOException; | ||
import java.nio.charset.StandardCharsets; | ||
import java.nio.file.Files; | ||
|
||
@ShellComponent | ||
public class LndCommands { | ||
private final LndService lndService; | ||
|
||
public LndCommands(LndService lndService) { | ||
this.lndService = lndService; | ||
} | ||
|
||
@ShellMethod("Add information from lnd sweep information obtained by `lncli wallet listsweeps`") | ||
public String lndAddFromSweeps(File jsonFile) throws IOException { | ||
String content = Files.readString(jsonFile.toPath(), StandardCharsets.US_ASCII); | ||
long numberOfSweepTransactions = lndService.lndAddFromSweeps(content); | ||
if (numberOfSweepTransactions == 0) { | ||
return "Unable to find sweep transactions in file"; | ||
} | ||
return "Added information for " + numberOfSweepTransactions + " sweep transactions"; | ||
} | ||
} |
46 changes: 46 additions & 0 deletions
46
lnd-cli/src/test/java/de/cotto/bitbook/lnd/cli/LndCommandsTest.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,46 @@ | ||
package de.cotto.bitbook.lnd.cli; | ||
|
||
import de.cotto.bitbook.lnd.LndService; | ||
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.io.File; | ||
import java.io.IOException; | ||
import java.nio.file.Files; | ||
|
||
import static org.assertj.core.api.Assertions.assertThat; | ||
import static org.mockito.ArgumentMatchers.any; | ||
import static org.mockito.Mockito.verify; | ||
import static org.mockito.Mockito.when; | ||
|
||
@ExtendWith(MockitoExtension.class) | ||
class LndCommandsTest { | ||
@InjectMocks | ||
private LndCommands lndCommands; | ||
|
||
@Mock | ||
private LndService lndService; | ||
|
||
@Test | ||
void lndAddFromSweeps() throws IOException { | ||
when(lndService.lndAddFromSweeps(any())).thenReturn(123L); | ||
String json = "{\"foo\": \"bar\"}"; | ||
File file = File.createTempFile("temp", "bitbook"); | ||
Files.writeString(file.toPath(), json); | ||
|
||
assertThat(lndCommands.lndAddFromSweeps(file)).isEqualTo("Added information for 123 sweep transactions"); | ||
|
||
verify(lndService).lndAddFromSweeps(json); | ||
} | ||
|
||
@Test | ||
void lndAddFromSweeps_failure() throws IOException { | ||
when(lndService.lndAddFromSweeps(any())).thenReturn(0L); | ||
File file = File.createTempFile("temp", "bitbook"); | ||
|
||
assertThat(lndCommands.lndAddFromSweeps(file)).isEqualTo("Unable to find sweep transactions in file"); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
plugins { | ||
id 'bitbook.java-library-conventions' | ||
id 'java-test-fixtures' | ||
} | ||
|
||
dependencies { | ||
implementation project(':backend-transaction') | ||
implementation project(':ownership') | ||
testImplementation testFixtures(project(':backend-transaction-models')) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,132 @@ | ||
package de.cotto.bitbook.lnd; | ||
|
||
import com.fasterxml.jackson.core.JsonParser; | ||
import com.fasterxml.jackson.databind.JsonNode; | ||
import com.fasterxml.jackson.databind.ObjectMapper; | ||
import de.cotto.bitbook.backend.AddressDescriptionService; | ||
import de.cotto.bitbook.backend.TransactionDescriptionService; | ||
import de.cotto.bitbook.backend.model.AddressWithDescription; | ||
import de.cotto.bitbook.backend.transaction.TransactionService; | ||
import de.cotto.bitbook.backend.transaction.model.Transaction; | ||
import de.cotto.bitbook.ownership.AddressOwnershipService; | ||
import org.slf4j.Logger; | ||
import org.slf4j.LoggerFactory; | ||
import org.springframework.stereotype.Component; | ||
|
||
import javax.annotation.Nullable; | ||
import java.io.IOException; | ||
import java.util.LinkedHashSet; | ||
import java.util.Set; | ||
|
||
@Component | ||
public class LndService { | ||
private final Logger logger = LoggerFactory.getLogger(getClass()); | ||
private final ObjectMapper objectMapper; | ||
private final TransactionService transactionService; | ||
private final TransactionDescriptionService transactionDescriptionService; | ||
private final AddressDescriptionService addressDescriptionService; | ||
private final AddressOwnershipService addressOwnershipService; | ||
|
||
public LndService( | ||
TransactionService transactionService, | ||
AddressDescriptionService addressDescriptionService, | ||
TransactionDescriptionService transactionDescriptionService, | ||
AddressOwnershipService addressOwnershipService, | ||
ObjectMapper objectMapper | ||
) { | ||
this.transactionService = transactionService; | ||
this.addressDescriptionService = addressDescriptionService; | ||
this.transactionDescriptionService = transactionDescriptionService; | ||
this.addressOwnershipService = addressOwnershipService; | ||
this.objectMapper = objectMapper; | ||
} | ||
|
||
public long lndAddFromSweeps(String json) { | ||
Set<String> hashes = parseHashes(json); | ||
return hashes.stream() | ||
.map(this::getGetTransactionDetails) | ||
.filter(this::isSweepTransaction) | ||
.map(this::addTransactionDescription) | ||
.map(this::addAddressDescriptions) | ||
.map(this::setAddressesAsOwned) | ||
.count(); | ||
} | ||
|
||
private Set<String> parseHashes(String json) { | ||
try (JsonParser parser = objectMapper.createParser(json)) { | ||
JsonNode rootNode = parser.getCodec().readTree(parser); | ||
return parseHashes(rootNode); | ||
} catch (IOException e) { | ||
return Set.of(); | ||
} | ||
} | ||
|
||
private Set<String> parseHashes(@Nullable JsonNode rootNode) { | ||
if (rootNode == null) { | ||
return Set.of(); | ||
} | ||
JsonNode sweeps = rootNode.get("Sweeps"); | ||
if (sweeps == null) { | ||
return Set.of(); | ||
} | ||
JsonNode transactionIds = sweeps.get("TransactionIds"); | ||
if (transactionIds == null) { | ||
return Set.of(); | ||
} | ||
JsonNode hashesArray = transactionIds.get("transaction_ids"); | ||
if (hashesArray == null) { | ||
return Set.of(); | ||
} | ||
Set<String> hashes = new LinkedHashSet<>(); | ||
for (JsonNode hash : hashesArray) { | ||
hashes.add(hash.textValue()); | ||
} | ||
return hashes; | ||
} | ||
|
||
private Transaction getGetTransactionDetails(String transactionHash) { | ||
Transaction transactionDetails = transactionService.getTransactionDetails(transactionHash); | ||
if (transactionDetails.isInvalid()) { | ||
logger.warn("Unable to find transaction {}", transactionHash); | ||
} | ||
return transactionDetails; | ||
} | ||
|
||
private boolean isSweepTransaction(Transaction transaction) { | ||
boolean hasSingleOutput = transaction.getOutputs().size() == 1; | ||
if (!hasSingleOutput && !transaction.equals(Transaction.UNKNOWN)) { | ||
logger.warn("Not a sweep transaction: {}", transaction); | ||
} | ||
return hasSingleOutput; | ||
} | ||
|
||
private Transaction addTransactionDescription(Transaction transaction) { | ||
transactionDescriptionService.set(transaction.getHash(), "LND sweep transaction"); | ||
return transaction; | ||
} | ||
|
||
private Transaction addAddressDescriptions(Transaction transaction) { | ||
String inputAddress = getInputAddress(transaction); | ||
AddressWithDescription addressWithDescription = addressDescriptionService.get(inputAddress); | ||
if (addressWithDescription.getDescription().isBlank()) { | ||
addressDescriptionService.set(inputAddress, "LND"); | ||
} | ||
addressDescriptionService.set(getOutputAddress(transaction), "LND"); | ||
return transaction; | ||
} | ||
|
||
@SuppressWarnings("PMD.LinguisticNaming") | ||
private Transaction setAddressesAsOwned(Transaction transaction) { | ||
addressOwnershipService.setAddressAsOwned(getInputAddress(transaction)); | ||
addressOwnershipService.setAddressAsOwned(getOutputAddress(transaction)); | ||
return transaction; | ||
} | ||
|
||
private String getOutputAddress(Transaction transaction) { | ||
return transaction.getOutputs().get(0).getAddress(); | ||
} | ||
|
||
private String getInputAddress(Transaction transaction) { | ||
return transaction.getInputs().get(0).getAddress(); | ||
} | ||
} |
Oops, something went wrong.