mirror of
https://github.com/sparrowwallet/sparrow.git
synced 2024-12-24 12:46:45 +00:00
detach and store labels before a wallet refresh, and label matching entries from this store as the wallet is updated
This commit is contained in:
parent
4e4fd7501c
commit
7aeca7ebd3
10 changed files with 118 additions and 27 deletions
2
drongo
2
drongo
|
@ -1 +1 @@
|
||||||
Subproject commit ee732fb2235fbe242d75366fc37d3f53e2082519
|
Subproject commit de87ab1102db12cad8bbfe814a1346078cf957a5
|
|
@ -3,25 +3,37 @@ package com.sparrowwallet.sparrow.event;
|
||||||
import com.sparrowwallet.drongo.wallet.Wallet;
|
import com.sparrowwallet.drongo.wallet.Wallet;
|
||||||
import com.sparrowwallet.sparrow.wallet.Entry;
|
import com.sparrowwallet.sparrow.wallet.Entry;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.*;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This event is fired when a wallet entry (transaction, txi or txo) label is changed.
|
* This event is fired when a wallet entry (transaction, txi or txo) label is changed.
|
||||||
*/
|
*/
|
||||||
public class WalletEntryLabelsChangedEvent extends WalletChangedEvent {
|
public class WalletEntryLabelsChangedEvent extends WalletChangedEvent {
|
||||||
private final List<Entry> entries;
|
//Contains the changed entry mapped to the entry that changed it, if changed recursively (otherwise null)
|
||||||
|
private final Map<Entry, Entry> entrySourceMap;
|
||||||
|
|
||||||
public WalletEntryLabelsChangedEvent(Wallet wallet, Entry entry) {
|
public WalletEntryLabelsChangedEvent(Wallet wallet, Entry entry) {
|
||||||
super(wallet);
|
this(wallet, List.of(entry));
|
||||||
this.entries = List.of(entry);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public WalletEntryLabelsChangedEvent(Wallet wallet, List<Entry> entries) {
|
public WalletEntryLabelsChangedEvent(Wallet wallet, List<Entry> entries) {
|
||||||
super(wallet);
|
super(wallet);
|
||||||
this.entries = entries;
|
this.entrySourceMap = new LinkedHashMap<>();
|
||||||
|
for(Entry entry : entries) {
|
||||||
|
entrySourceMap.put(entry, null);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public List<Entry> getEntries() {
|
public WalletEntryLabelsChangedEvent(Wallet wallet, Map<Entry, Entry> entrySourceMap) {
|
||||||
return entries;
|
super(wallet);
|
||||||
|
this.entrySourceMap = entrySourceMap;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Collection<Entry> getEntries() {
|
||||||
|
return entrySourceMap.keySet();
|
||||||
|
}
|
||||||
|
|
||||||
|
public Entry getSource(Entry entry) {
|
||||||
|
return entrySourceMap.get(entry);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -227,6 +227,8 @@ public class DbPersistence implements Persistence {
|
||||||
if(dirtyPersistables.clearHistory) {
|
if(dirtyPersistables.clearHistory) {
|
||||||
WalletNodeDao walletNodeDao = handle.attach(WalletNodeDao.class);
|
WalletNodeDao walletNodeDao = handle.attach(WalletNodeDao.class);
|
||||||
BlockTransactionDao blockTransactionDao = handle.attach(BlockTransactionDao.class);
|
BlockTransactionDao blockTransactionDao = handle.attach(BlockTransactionDao.class);
|
||||||
|
DetachedLabelDao detachedLabelDao = handle.attach(DetachedLabelDao.class);
|
||||||
|
detachedLabelDao.clearAndAddAll(wallet);
|
||||||
walletNodeDao.clearHistory(wallet);
|
walletNodeDao.clearHistory(wallet);
|
||||||
blockTransactionDao.clear(wallet.getId());
|
blockTransactionDao.clear(wallet.getId());
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,38 @@
|
||||||
|
package com.sparrowwallet.sparrow.io.db;
|
||||||
|
|
||||||
|
import com.sparrowwallet.drongo.wallet.Wallet;
|
||||||
|
import org.jdbi.v3.sqlobject.config.RegisterRowMapper;
|
||||||
|
import org.jdbi.v3.sqlobject.statement.SqlBatch;
|
||||||
|
import org.jdbi.v3.sqlobject.statement.SqlQuery;
|
||||||
|
import org.jdbi.v3.sqlobject.statement.SqlUpdate;
|
||||||
|
|
||||||
|
import java.util.*;
|
||||||
|
|
||||||
|
public interface DetachedLabelDao {
|
||||||
|
@SqlQuery("select entry, label from detachedLabel")
|
||||||
|
@RegisterRowMapper(DetachedLabelMapper.class)
|
||||||
|
Map<String, String> getAll();
|
||||||
|
|
||||||
|
@SqlBatch("insert into detachedLabel (entry, label) values (?, ?)")
|
||||||
|
void insertDetachedLabels(List<String> entries, List<String> labels);
|
||||||
|
|
||||||
|
@SqlUpdate("delete from detachedLabel")
|
||||||
|
void clear();
|
||||||
|
|
||||||
|
default void clearAndAddAll(Wallet wallet) {
|
||||||
|
clear();
|
||||||
|
|
||||||
|
List<String> entries = new ArrayList<>();
|
||||||
|
List<String> labels = new ArrayList<>();
|
||||||
|
for(Map.Entry<String, String> labelEntry : new HashSet<>(wallet.getDetachedLabels().entrySet())) {
|
||||||
|
entries.add(truncate(labelEntry.getKey(), 80));
|
||||||
|
labels.add(truncate(labelEntry.getValue(), 255));
|
||||||
|
}
|
||||||
|
|
||||||
|
insertDetachedLabels(entries, labels);
|
||||||
|
}
|
||||||
|
|
||||||
|
default String truncate(String label, int length) {
|
||||||
|
return (label != null && label.length() > length ? label.substring(0, length) : label);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,33 @@
|
||||||
|
package com.sparrowwallet.sparrow.io.db;
|
||||||
|
|
||||||
|
import org.jdbi.v3.core.mapper.RowMapper;
|
||||||
|
import org.jdbi.v3.core.statement.StatementContext;
|
||||||
|
|
||||||
|
import java.sql.ResultSet;
|
||||||
|
import java.sql.SQLException;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
public class DetachedLabelMapper implements RowMapper<Map.Entry<String, String>> {
|
||||||
|
@Override
|
||||||
|
public Map.Entry<String, String> map(ResultSet rs, StatementContext ctx) throws SQLException {
|
||||||
|
String entry = rs.getString("entry");
|
||||||
|
String label = rs.getString("label");
|
||||||
|
|
||||||
|
return new Map.Entry<>() {
|
||||||
|
@Override
|
||||||
|
public String getKey() {
|
||||||
|
return entry;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getValue() {
|
||||||
|
return label;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String setValue(String value) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
|
@ -30,6 +30,9 @@ public interface WalletDao {
|
||||||
@CreateSqlObject
|
@CreateSqlObject
|
||||||
BlockTransactionDao createBlockTransactionDao();
|
BlockTransactionDao createBlockTransactionDao();
|
||||||
|
|
||||||
|
@CreateSqlObject
|
||||||
|
DetachedLabelDao createDetachedLabelDao();
|
||||||
|
|
||||||
@CreateSqlObject
|
@CreateSqlObject
|
||||||
MixConfigDao createMixConfigDao();
|
MixConfigDao createMixConfigDao();
|
||||||
|
|
||||||
|
@ -100,9 +103,12 @@ public interface WalletDao {
|
||||||
List<WalletNode> walletNodes = createWalletNodeDao().getForWalletId(wallet.getId());
|
List<WalletNode> walletNodes = createWalletNodeDao().getForWalletId(wallet.getId());
|
||||||
wallet.getPurposeNodes().addAll(walletNodes.stream().filter(walletNode -> walletNode.getDerivation().size() == 1).collect(Collectors.toList()));
|
wallet.getPurposeNodes().addAll(walletNodes.stream().filter(walletNode -> walletNode.getDerivation().size() == 1).collect(Collectors.toList()));
|
||||||
|
|
||||||
Map<Sha256Hash, BlockTransaction> blockTransactions = createBlockTransactionDao().getForWalletId(wallet.getId()); //.stream().collect(Collectors.toMap(BlockTransaction::getHash, Function.identity(), (existing, replacement) -> existing, LinkedHashMap::new));
|
Map<Sha256Hash, BlockTransaction> blockTransactions = createBlockTransactionDao().getForWalletId(wallet.getId());
|
||||||
wallet.updateTransactions(blockTransactions);
|
wallet.updateTransactions(blockTransactions);
|
||||||
|
|
||||||
|
Map<String, String> detachedLabels = createDetachedLabelDao().getAll();
|
||||||
|
wallet.getDetachedLabels().putAll(detachedLabels);
|
||||||
|
|
||||||
wallet.setMixConfig(createMixConfigDao().getForWalletId(wallet.getId()));
|
wallet.setMixConfig(createMixConfigDao().getForWalletId(wallet.getId()));
|
||||||
|
|
||||||
Map<Sha256Hash, UtxoMixData> utxoMixes = createUtxoMixDataDao().getForWalletId(wallet.getId());
|
Map<Sha256Hash, UtxoMixData> utxoMixes = createUtxoMixDataDao().getForWalletId(wallet.getId());
|
||||||
|
@ -120,6 +126,7 @@ public interface WalletDao {
|
||||||
createKeystoreDao().addKeystores(wallet);
|
createKeystoreDao().addKeystores(wallet);
|
||||||
createWalletNodeDao().addWalletNodes(wallet);
|
createWalletNodeDao().addWalletNodes(wallet);
|
||||||
createBlockTransactionDao().addBlockTransactions(wallet);
|
createBlockTransactionDao().addBlockTransactions(wallet);
|
||||||
|
createDetachedLabelDao().clearAndAddAll(wallet);
|
||||||
createMixConfigDao().addMixConfig(wallet);
|
createMixConfigDao().addMixConfig(wallet);
|
||||||
createUtxoMixDataDao().addUtxoMixData(wallet);
|
createUtxoMixDataDao().addUtxoMixData(wallet);
|
||||||
} finally {
|
} finally {
|
||||||
|
|
|
@ -320,7 +320,7 @@ public class ElectrumServer {
|
||||||
//The gap limit size takes the highest used index in the retrieved history and adds the gap limit (plus one to be comparable to the number of children since index is zero based)
|
//The gap limit size takes the highest used index in the retrieved history and adds the gap limit (plus one to be comparable to the number of children since index is zero based)
|
||||||
int gapLimitSize = getGapLimitSize(wallet, nodeTransactionMap);
|
int gapLimitSize = getGapLimitSize(wallet, nodeTransactionMap);
|
||||||
while(historySize < gapLimitSize) {
|
while(historySize < gapLimitSize) {
|
||||||
purposeNode.fillToIndex(gapLimitSize - 1);
|
purposeNode.fillToIndex(wallet, gapLimitSize - 1);
|
||||||
subscribeWalletNodes(wallet, getAddressNodes(wallet, purposeNode), nodeTransactionMap, historySize);
|
subscribeWalletNodes(wallet, getAddressNodes(wallet, purposeNode), nodeTransactionMap, historySize);
|
||||||
getReferences(wallet, nodeTransactionMap.keySet(), nodeTransactionMap, historySize);
|
getReferences(wallet, nodeTransactionMap.keySet(), nodeTransactionMap, historySize);
|
||||||
getReferencedTransactions(wallet, nodeTransactionMap);
|
getReferencedTransactions(wallet, nodeTransactionMap);
|
||||||
|
@ -718,7 +718,7 @@ public class ElectrumServer {
|
||||||
}
|
}
|
||||||
|
|
||||||
if(!transactionOutputs.equals(node.getTransactionOutputs())) {
|
if(!transactionOutputs.equals(node.getTransactionOutputs())) {
|
||||||
node.updateTransactionOutputs(transactionOutputs);
|
node.updateTransactionOutputs(wallet, transactionOutputs);
|
||||||
copyPostmixLabels(wallet, transactionOutputs);
|
copyPostmixLabels(wallet, transactionOutputs);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -64,8 +64,8 @@ public class SettingsWalletForm extends WalletForm {
|
||||||
AppServices.clearTransactionHistoryCache(wallet);
|
AppServices.clearTransactionHistoryCache(wallet);
|
||||||
}
|
}
|
||||||
|
|
||||||
//Clear node tree
|
//Clear node tree, detaching and saving any labels from the existing wallet
|
||||||
walletCopy.clearNodes();
|
walletCopy.clearNodes(wallet);
|
||||||
|
|
||||||
Integer childIndex = wallet.isMasterWallet() ? null : wallet.getMasterWallet().getChildWallets().indexOf(wallet);
|
Integer childIndex = wallet.isMasterWallet() ? null : wallet.getMasterWallet().getChildWallets().indexOf(wallet);
|
||||||
|
|
||||||
|
|
|
@ -472,48 +472,46 @@ public class WalletForm {
|
||||||
@Subscribe
|
@Subscribe
|
||||||
public void walletLabelsChanged(WalletEntryLabelsChangedEvent event) {
|
public void walletLabelsChanged(WalletEntryLabelsChangedEvent event) {
|
||||||
if(event.getWallet() == wallet) {
|
if(event.getWallet() == wallet) {
|
||||||
List<Entry> labelChangedEntries = new ArrayList<>();
|
Map<Entry, Entry> labelChangedEntries = new LinkedHashMap<>();
|
||||||
for(Entry entry : event.getEntries()) {
|
for(Entry entry : event.getEntries()) {
|
||||||
if(entry.getLabel() != null && !entry.getLabel().isEmpty()) {
|
if(entry.getLabel() != null && !entry.getLabel().isEmpty()) {
|
||||||
if(entry instanceof TransactionEntry) {
|
if(entry instanceof TransactionEntry transactionEntry) {
|
||||||
TransactionEntry transactionEntry = (TransactionEntry)entry;
|
|
||||||
for(KeyPurpose keyPurpose : KeyPurpose.DEFAULT_PURPOSES) {
|
for(KeyPurpose keyPurpose : KeyPurpose.DEFAULT_PURPOSES) {
|
||||||
for(WalletNode childNode : wallet.getNode(keyPurpose).getChildren()) {
|
for(WalletNode childNode : wallet.getNode(keyPurpose).getChildren()) {
|
||||||
for(BlockTransactionHashIndex receivedRef : childNode.getTransactionOutputs()) {
|
for(BlockTransactionHashIndex receivedRef : childNode.getTransactionOutputs()) {
|
||||||
if(receivedRef.getHash().equals(transactionEntry.getBlockTransaction().getHash())) {
|
if(receivedRef.getHash().equals(transactionEntry.getBlockTransaction().getHash())) {
|
||||||
if((receivedRef.getLabel() == null || receivedRef.getLabel().isEmpty()) && wallet.getStandardAccountType() != StandardAccount.WHIRLPOOL_PREMIX) {
|
if((receivedRef.getLabel() == null || receivedRef.getLabel().isEmpty()) && wallet.getStandardAccountType() != StandardAccount.WHIRLPOOL_PREMIX) {
|
||||||
receivedRef.setLabel(entry.getLabel() + (keyPurpose == KeyPurpose.CHANGE ? " (change)" : " (received)"));
|
receivedRef.setLabel(entry.getLabel() + (keyPurpose == KeyPurpose.CHANGE ? " (change)" : " (received)"));
|
||||||
labelChangedEntries.add(new HashIndexEntry(event.getWallet(), receivedRef, HashIndexEntry.Type.OUTPUT, keyPurpose));
|
labelChangedEntries.put(new HashIndexEntry(event.getWallet(), receivedRef, HashIndexEntry.Type.OUTPUT, keyPurpose), entry);
|
||||||
}
|
}
|
||||||
if(childNode.getLabel() == null || childNode.getLabel().isEmpty()) {
|
//Avoid recursive changes to address labels - only initial transaction label changes can change address labels
|
||||||
|
if((childNode.getLabel() == null || childNode.getLabel().isEmpty()) && event.getSource(entry) == null) {
|
||||||
childNode.setLabel(entry.getLabel());
|
childNode.setLabel(entry.getLabel());
|
||||||
labelChangedEntries.add(new NodeEntry(event.getWallet(), childNode));
|
labelChangedEntries.put(new NodeEntry(event.getWallet(), childNode), entry);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if(receivedRef.isSpent() && receivedRef.getSpentBy().getHash().equals(transactionEntry.getBlockTransaction().getHash()) && (receivedRef.getSpentBy().getLabel() == null || receivedRef.getSpentBy().getLabel().isEmpty())) {
|
if(receivedRef.isSpent() && receivedRef.getSpentBy().getHash().equals(transactionEntry.getBlockTransaction().getHash()) && (receivedRef.getSpentBy().getLabel() == null || receivedRef.getSpentBy().getLabel().isEmpty())) {
|
||||||
receivedRef.getSpentBy().setLabel(entry.getLabel() + " (input)");
|
receivedRef.getSpentBy().setLabel(entry.getLabel() + " (input)");
|
||||||
labelChangedEntries.add(new HashIndexEntry(event.getWallet(), receivedRef.getSpentBy(), HashIndexEntry.Type.INPUT, keyPurpose));
|
labelChangedEntries.put(new HashIndexEntry(event.getWallet(), receivedRef.getSpentBy(), HashIndexEntry.Type.INPUT, keyPurpose), entry);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if(entry instanceof NodeEntry) {
|
if(entry instanceof NodeEntry nodeEntry) {
|
||||||
NodeEntry nodeEntry = (NodeEntry)entry;
|
|
||||||
for(BlockTransactionHashIndex receivedRef : nodeEntry.getNode().getTransactionOutputs()) {
|
for(BlockTransactionHashIndex receivedRef : nodeEntry.getNode().getTransactionOutputs()) {
|
||||||
BlockTransaction blockTransaction = event.getWallet().getTransactions().get(receivedRef.getHash());
|
BlockTransaction blockTransaction = event.getWallet().getTransactions().get(receivedRef.getHash());
|
||||||
if(blockTransaction.getLabel() == null || blockTransaction.getLabel().isEmpty()) {
|
if(blockTransaction.getLabel() == null || blockTransaction.getLabel().isEmpty()) {
|
||||||
blockTransaction.setLabel(entry.getLabel());
|
blockTransaction.setLabel(entry.getLabel());
|
||||||
labelChangedEntries.add(new TransactionEntry(event.getWallet(), blockTransaction, Collections.emptyMap(), Collections.emptyMap()));
|
labelChangedEntries.put(new TransactionEntry(event.getWallet(), blockTransaction, Collections.emptyMap(), Collections.emptyMap()), entry);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if(entry instanceof HashIndexEntry) {
|
if(entry instanceof HashIndexEntry hashIndexEntry) {
|
||||||
HashIndexEntry hashIndexEntry = (HashIndexEntry)entry;
|
|
||||||
BlockTransaction blockTransaction = hashIndexEntry.getBlockTransaction();
|
BlockTransaction blockTransaction = hashIndexEntry.getBlockTransaction();
|
||||||
if(blockTransaction.getLabel() == null || blockTransaction.getLabel().isEmpty()) {
|
if(blockTransaction.getLabel() == null || blockTransaction.getLabel().isEmpty()) {
|
||||||
blockTransaction.setLabel(entry.getLabel());
|
blockTransaction.setLabel(entry.getLabel());
|
||||||
labelChangedEntries.add(new TransactionEntry(event.getWallet(), blockTransaction, Collections.emptyMap(), Collections.emptyMap()));
|
labelChangedEntries.put(new TransactionEntry(event.getWallet(), blockTransaction, Collections.emptyMap(), Collections.emptyMap()), entry);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -573,7 +571,7 @@ public class WalletForm {
|
||||||
Optional<WalletNode> optPurposeNode = wallet.getPurposeNodes().stream().filter(node -> node.getKeyPurpose() == keyPurpose).findFirst();
|
Optional<WalletNode> optPurposeNode = wallet.getPurposeNodes().stream().filter(node -> node.getKeyPurpose() == keyPurpose).findFirst();
|
||||||
if(optPurposeNode.isPresent()) {
|
if(optPurposeNode.isPresent()) {
|
||||||
WalletNode purposeNode = optPurposeNode.get();
|
WalletNode purposeNode = optPurposeNode.get();
|
||||||
newNodes.addAll(purposeNode.fillToIndex(wallet.getLookAheadIndex(purposeNode)));
|
newNodes.addAll(purposeNode.fillToIndex(wallet, wallet.getLookAheadIndex(purposeNode)));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1 @@
|
||||||
|
create table detachedLabel (entry varchar(80) primary key not null, label varchar(255) not null);
|
Loading…
Reference in a new issue