wallet tx and addresses updating

This commit is contained in:
Craig Raw 2020-06-25 11:25:35 +02:00
parent d4d61b8d41
commit 150f65e7bd
11 changed files with 139 additions and 50 deletions

View file

@ -176,6 +176,7 @@ public class AppController implements Initializable {
openTransactionIdItem.disableProperty().bind(onlineProperty.not()); openTransactionIdItem.disableProperty().bind(onlineProperty.not());
openWalletFile(new File("/Users/scy/.sparrow/wallets/sparta.json")); openWalletFile(new File("/Users/scy/.sparrow/wallets/sparta.json"));
openWalletFile(new File("/Users/scy/.sparrow/wallets/sparta-test.json"));
} }
private ElectrumServer.ConnectionService createConnectionService() { private ElectrumServer.ConnectionService createConnectionService() {

View file

@ -43,8 +43,8 @@ public class AddressTreeTable extends TreeTableView<Entry> {
labelCol.setSortable(false); labelCol.setSortable(false);
getColumns().add(labelCol); getColumns().add(labelCol);
TreeTableColumn<Entry, Long> amountCol = new TreeTableColumn<>("Value"); TreeTableColumn<Entry, Number> amountCol = new TreeTableColumn<>("Value");
amountCol.setCellValueFactory((TreeTableColumn.CellDataFeatures<Entry, Long> param) -> { amountCol.setCellValueFactory((TreeTableColumn.CellDataFeatures<Entry, Number> param) -> {
return new ReadOnlyObjectWrapper<>(param.getValue().getValue().getValue()); return new ReadOnlyObjectWrapper<>(param.getValue().getValue().getValue());
}); });
amountCol.setCellFactory(p -> new AmountCell()); amountCol.setCellFactory(p -> new AmountCell());
@ -54,6 +54,9 @@ public class AddressTreeTable extends TreeTableView<Entry> {
setEditable(true); setEditable(true);
setColumnResizePolicy(TreeTableView.CONSTRAINED_RESIZE_POLICY); setColumnResizePolicy(TreeTableView.CONSTRAINED_RESIZE_POLICY);
addressCol.setSortType(TreeTableColumn.SortType.ASCENDING);
getSortOrder().add(addressCol);
Integer highestUsedIndex = rootEntry.getNode().getHighestUsedIndex(); Integer highestUsedIndex = rootEntry.getNode().getHighestUsedIndex();
if(highestUsedIndex != null) { if(highestUsedIndex != null) {
scrollTo(highestUsedIndex); scrollTo(highestUsedIndex);
@ -80,18 +83,29 @@ public class AddressTreeTable extends TreeTableView<Entry> {
RecursiveTreeItem<Entry> rootItem = new RecursiveTreeItem<>(rootEntry, Entry::getChildren); RecursiveTreeItem<Entry> rootItem = new RecursiveTreeItem<>(rootEntry, Entry::getChildren);
setRoot(rootItem); setRoot(rootItem);
rootItem.setExpanded(true); rootItem.setExpanded(true);
if(getColumns().size() > 0 && getSortOrder().isEmpty()) {
TreeTableColumn<Entry, ?> addressCol = getColumns().get(0);
getSortOrder().add(addressCol);
addressCol.setSortType(TreeTableColumn.SortType.ASCENDING);
}
} }
public void updateHistory(List<WalletNode> updatedNodes) { public void updateHistory(List<WalletNode> updatedNodes) {
NodeEntry rootEntry = (NodeEntry)getRoot().getValue(); NodeEntry rootEntry = (NodeEntry)getRoot().getValue();
for(WalletNode updatedNode : updatedNodes) { for(WalletNode updatedNode : updatedNodes) {
NodeEntry nodeEntry = new NodeEntry(rootEntry.getWallet(), updatedNode);
Optional<Entry> optEntry = rootEntry.getChildren().stream().filter(childEntry -> ((NodeEntry)childEntry).getNode().equals(updatedNode)).findFirst(); Optional<Entry> optEntry = rootEntry.getChildren().stream().filter(childEntry -> ((NodeEntry)childEntry).getNode().equals(updatedNode)).findFirst();
if(optEntry.isPresent()) { if(optEntry.isPresent()) {
int index = rootEntry.getChildren().indexOf(optEntry.get()); int index = rootEntry.getChildren().indexOf(optEntry.get());
NodeEntry nodeEntry = new NodeEntry(rootEntry.getWallet(), updatedNode);
rootEntry.getChildren().set(index, nodeEntry); rootEntry.getChildren().set(index, nodeEntry);
} else {
rootEntry.getChildren().add(nodeEntry);
} }
} }
sort();
} }
} }

View file

@ -11,14 +11,14 @@ import javafx.scene.layout.Region;
import java.util.Locale; import java.util.Locale;
class AmountCell extends TreeTableCell<Entry, Long> { class AmountCell extends TreeTableCell<Entry, Number> {
public AmountCell() { public AmountCell() {
super(); super();
getStyleClass().add("amount-cell"); getStyleClass().add("amount-cell");
} }
@Override @Override
protected void updateItem(Long amount, boolean empty) { protected void updateItem(Number amount, boolean empty) {
super.updateItem(amount, empty); super.updateItem(amount, empty);
if(empty || amount == null) { if(empty || amount == null) {
@ -28,7 +28,7 @@ class AmountCell extends TreeTableCell<Entry, Long> {
Entry entry = getTreeTableView().getTreeItem(getIndex()).getValue(); Entry entry = getTreeTableView().getTreeItem(getIndex()).getValue();
EntryCell.applyRowStyles(this, entry); EntryCell.applyRowStyles(this, entry);
String satsValue = String.format(Locale.ENGLISH, "%,d", amount); String satsValue = String.format(Locale.ENGLISH, "%,d", amount.longValue());
final String btcValue = CoinLabel.getBTCFormat().format(amount.doubleValue() / Transaction.SATOSHIS_PER_BITCOIN) + " BTC"; final String btcValue = CoinLabel.getBTCFormat().format(amount.doubleValue() / Transaction.SATOSHIS_PER_BITCOIN) + " BTC";
if(entry instanceof TransactionEntry) { if(entry instanceof TransactionEntry) {
@ -71,7 +71,6 @@ class AmountCell extends TreeTableCell<Entry, Long> {
} }
setText(satsValue); setText(satsValue);
} }
} }
} }

View file

@ -50,8 +50,12 @@ public class RecursiveTreeItem<T> extends TreeItem<T> {
while(change.next()){ while(change.next()){
if(change.wasAdded()){ if(change.wasAdded()){
if(change.getFrom() >= RecursiveTreeItem.this.getChildren().size()) {
change.getAddedSubList().forEach(t-> RecursiveTreeItem.this.getChildren().add(new RecursiveTreeItem<>(t, this.graphicsFactory, childrenFactory)));
} else {
change.getAddedSubList().forEach(t-> RecursiveTreeItem.this.getChildren().add(change.getFrom(), new RecursiveTreeItem<>(t, this.graphicsFactory, childrenFactory))); change.getAddedSubList().forEach(t-> RecursiveTreeItem.this.getChildren().add(change.getFrom(), new RecursiveTreeItem<>(t, this.graphicsFactory, childrenFactory)));
} }
}
if(change.wasRemoved()){ if(change.wasRemoved()){
change.getRemoved().forEach(t->{ change.getRemoved().forEach(t->{

View file

@ -34,17 +34,17 @@ public class TransactionsTreeTable extends TreeTableView<Entry> {
labelCol.setSortable(true); labelCol.setSortable(true);
getColumns().add(labelCol); getColumns().add(labelCol);
TreeTableColumn<Entry, Long> amountCol = new TreeTableColumn<>("Value"); TreeTableColumn<Entry, Number> amountCol = new TreeTableColumn<>("Value");
amountCol.setCellValueFactory((TreeTableColumn.CellDataFeatures<Entry, Long> param) -> { amountCol.setCellValueFactory((TreeTableColumn.CellDataFeatures<Entry, Number> param) -> {
return new ReadOnlyObjectWrapper<>(param.getValue().getValue().getValue()); return new ReadOnlyObjectWrapper<>(param.getValue().getValue().getValue());
}); });
amountCol.setCellFactory(p -> new AmountCell()); amountCol.setCellFactory(p -> new AmountCell());
amountCol.setSortable(true); amountCol.setSortable(true);
getColumns().add(amountCol); getColumns().add(amountCol);
TreeTableColumn<Entry, Long> balanceCol = new TreeTableColumn<>("Balance"); TreeTableColumn<Entry, Number> balanceCol = new TreeTableColumn<>("Balance");
balanceCol.setCellValueFactory((TreeTableColumn.CellDataFeatures<Entry, Long> param) -> { balanceCol.setCellValueFactory((TreeTableColumn.CellDataFeatures<Entry, Number> param) -> {
return new ReadOnlyObjectWrapper<>(param.getValue().getValue() instanceof TransactionEntry ? ((TransactionEntry)param.getValue().getValue()).getBalance() : null); return param.getValue().getValue() instanceof TransactionEntry ? ((TransactionEntry)param.getValue().getValue()).balanceProperty() : new ReadOnlyObjectWrapper<>(null);
}); });
balanceCol.setCellFactory(p -> new AmountCell()); balanceCol.setCellFactory(p -> new AmountCell());
balanceCol.setSortable(true); balanceCol.setSortable(true);
@ -62,7 +62,7 @@ public class TransactionsTreeTable extends TreeTableView<Entry> {
setRoot(rootItem); setRoot(rootItem);
rootItem.setExpanded(true); rootItem.setExpanded(true);
if(getColumns().size() > 0) { if(getColumns().size() > 0 && getSortOrder().isEmpty()) {
TreeTableColumn<Entry, ?> dateCol = getColumns().get(0); TreeTableColumn<Entry, ?> dateCol = getColumns().get(0);
getSortOrder().add(dateCol); getSortOrder().add(dateCol);
dateCol.setSortType(TreeTableColumn.SortType.DESCENDING); dateCol.setSortType(TreeTableColumn.SortType.DESCENDING);
@ -72,5 +72,6 @@ public class TransactionsTreeTable extends TreeTableView<Entry> {
public void updateHistory(List<WalletNode> updatedNodes) { public void updateHistory(List<WalletNode> updatedNodes) {
WalletTransactionsEntry rootEntry = (WalletTransactionsEntry)getRoot().getValue(); WalletTransactionsEntry rootEntry = (WalletTransactionsEntry)getRoot().getValue();
rootEntry.updateTransactions(); rootEntry.updateTransactions();
sort();
} }
} }

View file

@ -366,7 +366,10 @@ public class Storage {
@Override @Override
public WalletNode deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException { public WalletNode deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {
WalletNode node = getGson(false).fromJson(json, typeOfT); WalletNode node = getGson(false).fromJson(json, typeOfT);
node.parseDerivation();
for(WalletNode childNode : node.getChildren()) { for(WalletNode childNode : node.getChildren()) {
childNode.parseDerivation();
if(childNode.getChildren() == null) { if(childNode.getChildren() == null) {
childNode.setChildren(new TreeSet<>()); childNode.setChildren(new TreeSet<>());
} }

View file

@ -11,7 +11,7 @@ public abstract class Entry {
private final ObservableList<Entry> children; private final ObservableList<Entry> children;
public Entry(String label, List<Entry> entries) { public Entry(String label, List<Entry> entries) {
this.labelProperty = new SimpleStringProperty(label); this.labelProperty = new SimpleStringProperty(this, "label", label);
this.children = FXCollections.observableList(entries); this.children = FXCollections.observableList(entries);
} }

View file

@ -9,7 +9,7 @@ import com.sparrowwallet.sparrow.event.WalletEntryLabelChangedEvent;
import java.util.stream.Collectors; import java.util.stream.Collectors;
public class NodeEntry extends Entry { public class NodeEntry extends Entry implements Comparable<NodeEntry> {
private final Wallet wallet; private final Wallet wallet;
private final WalletNode node; private final WalletNode node;
@ -56,4 +56,9 @@ public class NodeEntry extends Entry {
return node.getUnspentValue(); return node.getUnspentValue();
} }
@Override
public int compareTo(NodeEntry other) {
return node.compareTo(other.node);
}
} }

View file

@ -10,6 +10,8 @@ import com.sparrowwallet.sparrow.event.WalletBlockHeightChangedEvent;
import com.sparrowwallet.sparrow.event.WalletEntryLabelChangedEvent; import com.sparrowwallet.sparrow.event.WalletEntryLabelChangedEvent;
import javafx.beans.property.IntegerProperty; import javafx.beans.property.IntegerProperty;
import javafx.beans.property.IntegerPropertyBase; import javafx.beans.property.IntegerPropertyBase;
import javafx.beans.property.LongProperty;
import javafx.beans.property.LongPropertyBase;
import java.util.*; import java.util.*;
import java.util.stream.Collectors; import java.util.stream.Collectors;
@ -20,7 +22,6 @@ public class TransactionEntry extends Entry implements Comparable<TransactionEnt
private final Wallet wallet; private final Wallet wallet;
private final BlockTransaction blockTransaction; private final BlockTransaction blockTransaction;
private WalletTransactionsEntry parent;
public TransactionEntry(Wallet wallet, BlockTransaction blockTransaction, Map<BlockTransactionHashIndex, KeyPurpose> inputs, Map<BlockTransactionHashIndex, KeyPurpose> outputs) { public TransactionEntry(Wallet wallet, BlockTransaction blockTransaction, Map<BlockTransactionHashIndex, KeyPurpose> inputs, Map<BlockTransactionHashIndex, KeyPurpose> outputs) {
super(blockTransaction.getLabel(), createChildEntries(wallet, inputs, outputs)); super(blockTransaction.getLabel(), createChildEntries(wallet, inputs, outputs));
@ -42,10 +43,6 @@ public class TransactionEntry extends Entry implements Comparable<TransactionEnt
return wallet; return wallet;
} }
void setParent(WalletTransactionsEntry walletTransactionsEntry) {
this.parent = walletTransactionsEntry;
}
public BlockTransaction getBlockTransaction() { public BlockTransaction getBlockTransaction() {
return blockTransaction; return blockTransaction;
} }
@ -65,10 +62,6 @@ public class TransactionEntry extends Entry implements Comparable<TransactionEnt
return value; return value;
} }
public Long getBalance() {
return parent.getBalance(this);
}
public boolean isConfirming() { public boolean isConfirming() {
return getConfirmations() < BLOCKS_TO_CONFIRM; return getConfirmations() < BLOCKS_TO_CONFIRM;
} }
@ -128,13 +121,12 @@ public class TransactionEntry extends Entry implements Comparable<TransactionEnt
if (o == null || getClass() != o.getClass()) return false; if (o == null || getClass() != o.getClass()) return false;
TransactionEntry that = (TransactionEntry) o; TransactionEntry that = (TransactionEntry) o;
return wallet.equals(that.wallet) && return wallet.equals(that.wallet) &&
blockTransaction.equals(that.blockTransaction) && blockTransaction.equals(that.blockTransaction);
parent.equals(that.parent);
} }
@Override @Override
public int hashCode() { public int hashCode() {
return Objects.hash(wallet, blockTransaction, parent); return Objects.hash(wallet, blockTransaction);
} }
@Override @Override
@ -175,6 +167,39 @@ public class TransactionEntry extends Entry implements Comparable<TransactionEnt
return confirmations; return confirmations;
} }
/**
* Defines the wallet balance at the historical point of this transaction, as defined by BlockTransaction's compareTo method.
*/
private LongProperty balance;
public final void setBalance(long value) {
if(balance != null || value != 0) {
balanceProperty().set(value);
}
}
public final long getBalance() {
return balance == null ? 0L : balance.get();
}
public final LongProperty balanceProperty() {
if(balance == null) {
balance = new LongPropertyBase(0L) {
@Override
public Object getBean() {
return TransactionEntry.this;
}
@Override
public String getName() {
return "balance";
}
};
}
return balance;
}
@Subscribe @Subscribe
public void blockHeightChanged(WalletBlockHeightChangedEvent event) { public void blockHeightChanged(WalletBlockHeightChangedEvent event) {
if(getWallet().equals(event.getWallet())) { if(getWallet().equals(event.getWallet())) {

View file

@ -90,6 +90,8 @@ public class WalletForm {
if(!currentNode.getTransactionOutputs().equals(previousNode.getTransactionOutputs())) { if(!currentNode.getTransactionOutputs().equals(previousNode.getTransactionOutputs())) {
changedNodes.add(currentNode); changedNodes.add(currentNode);
} }
} else {
changedNodes.add(currentNode);
} }
} }

View file

@ -5,6 +5,8 @@ import com.sparrowwallet.drongo.wallet.BlockTransaction;
import com.sparrowwallet.drongo.wallet.BlockTransactionHashIndex; import com.sparrowwallet.drongo.wallet.BlockTransactionHashIndex;
import com.sparrowwallet.drongo.wallet.Wallet; import com.sparrowwallet.drongo.wallet.Wallet;
import com.sparrowwallet.drongo.wallet.WalletNode; import com.sparrowwallet.drongo.wallet.WalletNode;
import javafx.beans.property.LongProperty;
import javafx.beans.property.LongPropertyBase;
import java.util.*; import java.util.*;
import java.util.stream.Collectors; import java.util.stream.Collectors;
@ -15,40 +17,42 @@ public class WalletTransactionsEntry extends Entry {
public WalletTransactionsEntry(Wallet wallet) { public WalletTransactionsEntry(Wallet wallet) {
super(wallet.getName(), getWalletTransactions(wallet).stream().map(WalletTransaction::getTransactionEntry).collect(Collectors.toList())); super(wallet.getName(), getWalletTransactions(wallet).stream().map(WalletTransaction::getTransactionEntry).collect(Collectors.toList()));
this.wallet = wallet; this.wallet = wallet;
getChildren().forEach(entry -> ((TransactionEntry)entry).setParent(this)); calculateBalances();
} }
@Override @Override
public Long getValue() { public Long getValue() {
return getBalance(null); return getBalance();
} }
protected Long getBalance(TransactionEntry transactionEntry) { protected void calculateBalances() {
long balance = 0L; long balance = 0L;
//Note transaction entries must be in ascending order. This sorting is ultimately done according to BlockTransactions' comparator
getChildren().sort(Comparator.comparing(TransactionEntry.class::cast));
for(Entry entry : getChildren()) { for(Entry entry : getChildren()) {
TransactionEntry transactionEntry = (TransactionEntry)entry;
balance += entry.getValue(); balance += entry.getValue();
transactionEntry.setBalance(balance);
if(entry == transactionEntry) {
return balance;
}
} }
return balance; setBalance(balance);
} }
public void updateTransactions() { public void updateTransactions() {
List<Entry> current = getWalletTransactions(wallet).stream().map(WalletTransaction::getTransactionEntry).peek(entry -> entry.setParent(this)).collect(Collectors.toList()); List<Entry> current = getWalletTransactions(wallet).stream().map(WalletTransaction::getTransactionEntry).collect(Collectors.toList());
List<Entry> previous = new ArrayList<>(getChildren()); List<Entry> previous = new ArrayList<>(getChildren());
for(Entry currentEntry : current) {
int index = previous.indexOf(currentEntry);
if (index > -1) {
getChildren().set(index, currentEntry);
} else {
getChildren().add(currentEntry);
}
}
getChildren().sort(Comparator.comparing(TransactionEntry.class::cast)); List<Entry> entriesAdded = new ArrayList<>(current);
entriesAdded.removeAll(previous);
getChildren().addAll(entriesAdded);
List<Entry> entriesRemoved = new ArrayList<>(previous);
entriesRemoved.removeAll(current);
getChildren().removeAll(entriesRemoved);
calculateBalances();
} }
private static Collection<WalletTransaction> getWalletTransactions(Wallet wallet) { private static Collection<WalletTransaction> getWalletTransactions(Wallet wallet) {
@ -57,9 +61,7 @@ public class WalletTransactionsEntry extends Entry {
getWalletTransactions(wallet, walletTransactionMap, wallet.getNode(KeyPurpose.RECEIVE)); getWalletTransactions(wallet, walletTransactionMap, wallet.getNode(KeyPurpose.RECEIVE));
getWalletTransactions(wallet, walletTransactionMap, wallet.getNode(KeyPurpose.CHANGE)); getWalletTransactions(wallet, walletTransactionMap, wallet.getNode(KeyPurpose.CHANGE));
List<WalletTransaction> walletTxList = new ArrayList<>(walletTransactionMap.values()); return new ArrayList<>(walletTransactionMap.values());
Collections.reverse(walletTxList);
return walletTxList;
} }
private static void getWalletTransactions(Wallet wallet, Map<BlockTransaction, WalletTransaction> walletTransactionMap, WalletNode purposeNode) { private static void getWalletTransactions(Wallet wallet, Map<BlockTransaction, WalletTransaction> walletTransactionMap, WalletNode purposeNode) {
@ -87,6 +89,39 @@ public class WalletTransactionsEntry extends Entry {
} }
} }
/**
* Defines the wallet balance in total.
*/
private LongProperty balance;
public final void setBalance(long value) {
if(balance != null || value != 0) {
balanceProperty().set(value);
}
}
public final long getBalance() {
return balance == null ? 0L : balance.get();
}
public final LongProperty balanceProperty() {
if(balance == null) {
balance = new LongPropertyBase(0L) {
@Override
public Object getBean() {
return WalletTransactionsEntry.this;
}
@Override
public String getName() {
return "balance";
}
};
}
return balance;
}
private static class WalletTransaction { private static class WalletTransaction {
private final Wallet wallet; private final Wallet wallet;
private final BlockTransaction blockTransaction; private final BlockTransaction blockTransaction;