finish perf improvements to tx viewer

This commit is contained in:
Craig Raw 2020-06-17 11:34:26 +02:00
parent 82ca9552bd
commit 9cd8a9b9ee
10 changed files with 125 additions and 28 deletions

View file

@ -2,6 +2,7 @@ package com.sparrowwallet.sparrow.control;
import com.sparrowwallet.drongo.Utils; import com.sparrowwallet.drongo.Utils;
import com.sparrowwallet.drongo.protocol.*; import com.sparrowwallet.drongo.protocol.*;
import javafx.application.Platform;
import org.fxmisc.richtext.CodeArea; import org.fxmisc.richtext.CodeArea;
import java.io.ByteArrayOutputStream; import java.io.ByteArrayOutputStream;
@ -12,6 +13,7 @@ import java.util.Objects;
public class TransactionHexArea extends CodeArea { public class TransactionHexArea extends CodeArea {
private static final int TRUNCATE_AT = 30000; private static final int TRUNCATE_AT = 30000;
private static final int SEGMENTS_INTERVAL = 250;
private List<TransactionSegment> previousSegmentList = new ArrayList<>(); private List<TransactionSegment> previousSegmentList = new ArrayList<>();
@ -38,14 +40,24 @@ public class TransactionHexArea extends CodeArea {
List<TransactionSegment> segments = getTransactionSegments(transaction, selectedInputIndex, selectedOutputIndex); List<TransactionSegment> segments = getTransactionSegments(transaction, selectedInputIndex, selectedOutputIndex);
List<TransactionSegment> changedSegments = new ArrayList<>(segments); List<TransactionSegment> changedSegments = new ArrayList<>(segments);
changedSegments.removeAll(previousSegmentList); changedSegments.removeAll(previousSegmentList);
applyHighlighting(0, changedSegments);
previousSegmentList = segments;
}
for(TransactionSegment segment : changedSegments) { private void applyHighlighting(int start, List<TransactionSegment> segments) {
int end = Math.min(segments.size(), start + SEGMENTS_INTERVAL);
for(int i = start; i < end; i++) {
TransactionSegment segment = segments.get(i);
if(segment.start < TRUNCATE_AT) { if(segment.start < TRUNCATE_AT) {
setStyleClass(segment.start, Math.min(TRUNCATE_AT, segment.start + segment.length), segment.style); setStyleClass(segment.start, Math.min(TRUNCATE_AT, segment.start + segment.length), segment.style);
} }
} }
previousSegmentList = segments; if(end < segments.size()) {
Platform.runLater(() -> {
applyHighlighting(end, segments);
});
}
} }
public List<TransactionSegment> getTransactionSegments(Transaction transaction, int selectedInputIndex, int selectedOutputIndex) { public List<TransactionSegment> getTransactionSegments(Transaction transaction, int selectedInputIndex, int selectedOutputIndex) {

View file

@ -0,0 +1,9 @@
package com.sparrowwallet.sparrow.event;
import com.sparrowwallet.drongo.protocol.Transaction;
public class TransactionLocktimeChangedEvent extends TransactionChangedEvent {
public TransactionLocktimeChangedEvent(Transaction transaction) {
super(transaction);
}
}

View file

@ -10,7 +10,7 @@ public class ViewTransactionEvent {
public final Integer initialIndex; public final Integer initialIndex;
public ViewTransactionEvent(BlockTransaction blockTransaction) { public ViewTransactionEvent(BlockTransaction blockTransaction) {
this(blockTransaction, null, null); this(blockTransaction, TransactionView.HEADERS, null);
} }
public ViewTransactionEvent(BlockTransaction blockTransaction, HashIndexEntry hashIndexEntry) { public ViewTransactionEvent(BlockTransaction blockTransaction, HashIndexEntry hashIndexEntry) {

View file

@ -12,6 +12,7 @@ import com.sparrowwallet.sparrow.control.IdLabel;
import com.sparrowwallet.sparrow.control.CopyableLabel; import com.sparrowwallet.sparrow.control.CopyableLabel;
import com.sparrowwallet.sparrow.event.BlockTransactionFetchedEvent; import com.sparrowwallet.sparrow.event.BlockTransactionFetchedEvent;
import com.sparrowwallet.sparrow.event.TransactionChangedEvent; import com.sparrowwallet.sparrow.event.TransactionChangedEvent;
import com.sparrowwallet.sparrow.event.TransactionLocktimeChangedEvent;
import javafx.fxml.FXML; import javafx.fxml.FXML;
import javafx.fxml.Initializable; import javafx.fxml.Initializable;
import javafx.scene.control.*; import javafx.scene.control.*;
@ -199,6 +200,7 @@ public class HeadersController extends TransactionFormController implements Init
tx.setLocktime(newValue); tx.setLocktime(newValue);
if(oldValue != null) { if(oldValue != null) {
EventManager.get().post(new TransactionChangedEvent(tx)); EventManager.get().post(new TransactionChangedEvent(tx));
EventManager.get().post(new TransactionLocktimeChangedEvent(tx));
} }
}); });
@ -249,9 +251,13 @@ public class HeadersController extends TransactionFormController implements Init
BlockTransaction inputTx = inputTransactions.get(input.getOutpoint().getHash()); BlockTransaction inputTx = inputTransactions.get(input.getOutpoint().getHash());
if(inputTx == null) { if(inputTx == null) {
System.out.println("Cannot find transaction for hash " + input.getOutpoint().getHash() + ", still paging?"); if(headersForm.allInputsFetched()) {
throw new IllegalStateException("Cannot find transaction for hash " + input.getOutpoint().getHash());
} else {
//Still paging
return null; return null;
} }
}
feeAmt += inputTx.getTransaction().getOutputs().get((int)input.getOutpoint().getIndex()).getValue(); feeAmt += inputTx.getTransaction().getOutputs().get((int)input.getOutpoint().getIndex()).getValue();
} }

View file

@ -11,6 +11,7 @@ import com.sparrowwallet.sparrow.EventManager;
import com.sparrowwallet.sparrow.control.*; import com.sparrowwallet.sparrow.control.*;
import com.sparrowwallet.sparrow.event.BlockTransactionFetchedEvent; import com.sparrowwallet.sparrow.event.BlockTransactionFetchedEvent;
import com.sparrowwallet.sparrow.event.TransactionChangedEvent; import com.sparrowwallet.sparrow.event.TransactionChangedEvent;
import com.sparrowwallet.sparrow.event.TransactionLocktimeChangedEvent;
import com.sparrowwallet.sparrow.event.ViewTransactionEvent; import com.sparrowwallet.sparrow.event.ViewTransactionEvent;
import javafx.fxml.FXML; import javafx.fxml.FXML;
import javafx.fxml.Initializable; import javafx.fxml.Initializable;
@ -192,9 +193,13 @@ public class InputController extends TransactionFormController implements Initia
if(!txInput.isCoinBase()) { if(!txInput.isCoinBase()) {
BlockTransaction blockTransaction = inputTransactions.get(txInput.getOutpoint().getHash()); BlockTransaction blockTransaction = inputTransactions.get(txInput.getOutpoint().getHash());
if(blockTransaction == null) { if(blockTransaction == null) {
System.out.println("Could not retrieve block transaction for input #" + inputForm.getIndex()); if(inputForm.getIndex() < inputForm.getMaxInputFetched()) {
throw new IllegalStateException("Could not retrieve block transaction for input #" + inputForm.getIndex());
} else {
//Still paging
return; return;
} }
}
TransactionOutput output = blockTransaction.getTransaction().getOutputs().get((int)txInput.getOutpoint().getIndex()); TransactionOutput output = blockTransaction.getTransaction().getOutputs().get((int)txInput.getOutpoint().getIndex());
updateSpends(output); updateSpends(output);
@ -486,4 +491,11 @@ public class InputController extends TransactionFormController implements Initia
} }
} }
} }
@Subscribe
public void transctionLocktimeChanged(TransactionLocktimeChangedEvent event) {
if(event.getTransaction().equals(inputForm.getTransaction())) {
locktimeAbsolute.setText(Long.toString(event.getTransaction().getLocktime()));
}
}
} }

View file

@ -125,9 +125,13 @@ public class InputsController extends TransactionFormController implements Initi
} else { } else {
BlockTransaction inputTx = inputTransactions.get(input.getOutpoint().getHash()); BlockTransaction inputTx = inputTransactions.get(input.getOutpoint().getHash());
if(inputTx == null) { if(inputTx == null) {
System.out.println("Cannot find transaction for hash " + input.getOutpoint().getHash() + ", still paging?"); if(inputsForm.allInputsFetched()) {
throw new IllegalStateException("Cannot find transaction for hash " + input.getOutpoint().getHash());
} else {
//Still paging
return; return;
} }
}
TransactionOutput output = inputTx.getTransaction().getOutputs().get((int)input.getOutpoint().getIndex()); TransactionOutput output = inputTx.getTransaction().getOutputs().get((int)input.getOutpoint().getIndex());
outputs.add(output); outputs.add(output);

View file

@ -90,7 +90,7 @@ public class TransactionController implements Initializable {
inputsItem.getChildren().add(inputItem); inputsItem.getChildren().add(inputItem);
inputPagingAdded = false; inputPagingAdded = false;
} else if(!inputPagingAdded) { } else if(!inputPagingAdded) {
PageForm pageForm = new PageForm(TransactionView.INPUT, i, i + PageForm.PAGE_SIZE); PageForm pageForm = new PageForm(TransactionView.INPUT, i, Math.min(getTransaction().getInputs().size(), i + PageForm.PAGE_SIZE));
TreeItem<TransactionForm> pageItem = new TreeItem<>(pageForm); TreeItem<TransactionForm> pageItem = new TreeItem<>(pageForm);
inputsItem.getChildren().add(pageItem); inputsItem.getChildren().add(pageItem);
inputPagingAdded = true; inputPagingAdded = true;
@ -110,7 +110,7 @@ public class TransactionController implements Initializable {
outputsItem.getChildren().add(outputItem); outputsItem.getChildren().add(outputItem);
outputPagingAdded = false; outputPagingAdded = false;
} else if(!outputPagingAdded) { } else if(!outputPagingAdded) {
PageForm pageForm = new PageForm(TransactionView.OUTPUT, i, i + PageForm.PAGE_SIZE); PageForm pageForm = new PageForm(TransactionView.OUTPUT, i, Math.min(getTransaction().getOutputs().size(), i + PageForm.PAGE_SIZE));
TreeItem<TransactionForm> pageItem = new TreeItem<>(pageForm); TreeItem<TransactionForm> pageItem = new TreeItem<>(pageForm);
outputsItem.getChildren().add(pageItem); outputsItem.getChildren().add(pageItem);
outputPagingAdded = true; outputPagingAdded = true;
@ -169,7 +169,7 @@ public class TransactionController implements Initializable {
} }
if(pageForm.getPageEnd() < max) { if(pageForm.getPageEnd() < max) {
PageForm nextPageForm = new PageForm(pageForm.getView(), pageForm.getPageStart() + PageForm.PAGE_SIZE, pageForm.getPageEnd() + PageForm.PAGE_SIZE); PageForm nextPageForm = new PageForm(pageForm.getView(), pageForm.getPageEnd(), Math.min(max, pageForm.getPageEnd() + PageForm.PAGE_SIZE));
TreeItem<TransactionForm> nextPageItem = new TreeItem<>(nextPageForm); TreeItem<TransactionForm> nextPageItem = new TreeItem<>(nextPageForm);
if(pageForm.getPageEnd() >= parentItem.getChildren().size()) { if(pageForm.getPageEnd() >= parentItem.getChildren().size()) {
parentItem.getChildren().add(nextPageItem); parentItem.getChildren().add(nextPageItem);
@ -397,16 +397,16 @@ public class TransactionController implements Initializable {
setTreeSelection(event.getInitialView(), event.getInitialIndex()); setTreeSelection(event.getInitialView(), event.getInitialIndex());
} else if(event.getInitialView().equals(TransactionView.INPUT) || event.getInitialView().equals(TransactionView.OUTPUT)) { } else if(event.getInitialView().equals(TransactionView.INPUT) || event.getInitialView().equals(TransactionView.OUTPUT)) {
TreeItem<TransactionForm> parentItem = getTreeItem(event.getInitialView().equals(TransactionView.INPUT) ? TransactionView.INPUTS : TransactionView.OUTPUTS, null); TreeItem<TransactionForm> parentItem = getTreeItem(event.getInitialView().equals(TransactionView.INPUT) ? TransactionView.INPUTS : TransactionView.OUTPUTS, null);
TreeItem<TransactionForm> newItem = event.getInitialView().equals(TransactionView.INPUT) ? createInputTreeItem(event.getInitialIndex()) : createOutputTreeItem(event.getInitialIndex()); TreeItem<TransactionForm> newItem = event.getInitialView().equals(TransactionView.INPUT) ? createInputTreeItem(event.getInitialIndex()) : createOutputTreeItem(event.getInitialIndex());
PageForm nextPageForm = new PageForm(event.getInitialView(), event.getInitialIndex() + 1, event.getInitialIndex() + 1 + PageForm.PAGE_SIZE);
int max = event.getInitialView().equals(TransactionView.INPUT) ? getTransaction().getInputs().size() : getTransaction().getOutputs().size();
PageForm nextPageForm = new PageForm(event.getInitialView(), event.getInitialIndex() + 1, Math.min(max, event.getInitialIndex() + 1 + PageForm.PAGE_SIZE));
TreeItem<TransactionForm> nextPageItem = new TreeItem<>(nextPageForm); TreeItem<TransactionForm> nextPageItem = new TreeItem<>(nextPageForm);
if(existingItem != null) { if(existingItem != null) {
parentItem.getChildren().remove(existingItem); parentItem.getChildren().remove(existingItem);
} }
int max = event.getInitialView().equals(TransactionView.INPUT) ? getTransaction().getInputs().size() : getTransaction().getOutputs().size();
int highestIndex = ((IndexedTransactionForm)parentItem.getChildren().get(parentItem.getChildren().size() - 1).getValue()).getIndex(); int highestIndex = ((IndexedTransactionForm)parentItem.getChildren().get(parentItem.getChildren().size() - 1).getValue()).getIndex();
if(event.getInitialIndex() < highestIndex) { if(event.getInitialIndex() < highestIndex) {
for(int i = 0; i < parentItem.getChildren().size(); i++) { for(int i = 0; i < parentItem.getChildren().size(); i++) {
@ -466,6 +466,7 @@ public class TransactionController implements Initializable {
} else { } else {
txdata.getInputTransactions().putAll(event.getInputTransactions()); txdata.getInputTransactions().putAll(event.getInputTransactions());
} }
txdata.updateInputsFetchedRange(event.getPageStart(), event.getPageEnd());
} }
} }
@ -482,6 +483,7 @@ public class TransactionController implements Initializable {
} }
} }
} }
txdata.updateOutputsFetchedRange(event.getPageStart(), event.getPageEnd());
} }
} }
} }

View file

@ -15,6 +15,11 @@ public class TransactionData {
private Map<Sha256Hash, BlockTransaction> inputTransactions; private Map<Sha256Hash, BlockTransaction> inputTransactions;
private List<BlockTransaction> outputTransactions; private List<BlockTransaction> outputTransactions;
private int minInputFetched;
private int maxInputFetched;
private int minOutputFetched;
private int maxOutputFetched;
public TransactionData(PSBT psbt) { public TransactionData(PSBT psbt) {
this.transaction = psbt.getTransaction(); this.transaction = psbt.getTransaction();
this.psbt = psbt; this.psbt = psbt;
@ -53,6 +58,28 @@ public class TransactionData {
this.inputTransactions = inputTransactions; this.inputTransactions = inputTransactions;
} }
public void updateInputsFetchedRange(int pageStart, int pageEnd) {
if(pageStart < 0 || pageEnd > transaction.getInputs().size()) {
throw new IllegalStateException("Paging outside transaction inputs range");
}
if(pageStart != maxInputFetched) {
//non contiguous range, ignore
return;
}
this.minInputFetched = Math.min(minInputFetched, pageStart);
this.maxInputFetched = Math.max(maxInputFetched, pageEnd);
}
public int getMaxInputFetched() {
return maxInputFetched;
}
public boolean allInputsFetched() {
return minInputFetched == 0 && maxInputFetched == transaction.getOutputs().size();
}
public List<BlockTransaction> getOutputTransactions() { public List<BlockTransaction> getOutputTransactions() {
return outputTransactions; return outputTransactions;
} }
@ -60,4 +87,22 @@ public class TransactionData {
public void setOutputTransactions(List<BlockTransaction> outputTransactions) { public void setOutputTransactions(List<BlockTransaction> outputTransactions) {
this.outputTransactions = outputTransactions; this.outputTransactions = outputTransactions;
} }
public void updateOutputsFetchedRange(int pageStart, int pageEnd) {
if(pageStart < 0 || pageEnd > transaction.getOutputs().size()) {
throw new IllegalStateException("Paging outside transaction outputs range");
}
if(pageStart != maxOutputFetched) {
//non contiguous range, ignore
return;
}
this.minOutputFetched = Math.min(minOutputFetched, pageStart);
this.maxOutputFetched = Math.max(maxOutputFetched, pageEnd);
}
public boolean allOutputsFetched() {
return minOutputFetched == 0 && maxOutputFetched == transaction.getOutputs().size();
}
} }

View file

@ -29,24 +29,24 @@ public abstract class TransactionForm {
return txdata.getBlockTransaction(); return txdata.getBlockTransaction();
} }
public void setBlockTransaction(BlockTransaction blockTransaction) {
txdata.setBlockTransaction(blockTransaction);
}
public Map<Sha256Hash, BlockTransaction> getInputTransactions() { public Map<Sha256Hash, BlockTransaction> getInputTransactions() {
return txdata.getInputTransactions(); return txdata.getInputTransactions();
} }
public void setInputTransactions(Map<Sha256Hash, BlockTransaction> inputTransactions) { public int getMaxInputFetched() {
txdata.setInputTransactions(inputTransactions); return txdata.getMaxInputFetched();
}
public boolean allInputsFetched() {
return txdata.allInputsFetched();
} }
public List<BlockTransaction> getOutputTransactions() { public List<BlockTransaction> getOutputTransactions() {
return txdata.getOutputTransactions(); return txdata.getOutputTransactions();
} }
public void setOutputTransactions(List<BlockTransaction> outputTransactions) { public boolean allOutputsFetched() {
txdata.setOutputTransactions(outputTransactions); return txdata.allOutputsFetched();
} }
public boolean isEditable() { public boolean isEditable() {

View file

@ -16,18 +16,21 @@ import javafx.scene.input.ClipboardContent;
import java.util.List; import java.util.List;
public abstract class TransactionFormController extends BaseController { public abstract class TransactionFormController extends BaseController {
private static final int MAX_PIE_SEGMENTS = 200;
protected void addPieData(PieChart pie, List<TransactionOutput> outputs) { protected void addPieData(PieChart pie, List<TransactionOutput> outputs) {
ObservableList<PieChart.Data> outputsPieData = FXCollections.observableArrayList(); ObservableList<PieChart.Data> outputsPieData = FXCollections.observableArrayList();
long totalAmt = 0; long totalAmt = 0;
for(TransactionOutput output : outputs) { for(int i = 0; i < outputs.size(); i++) {
String name = "Unknown"; TransactionOutput output = outputs.get(i);
String name = "#" + i;
try { try {
Address[] addresses = output.getScript().getToAddresses(); Address[] addresses = output.getScript().getToAddresses();
if(addresses.length == 1) { if(addresses.length == 1) {
name = addresses[0].getAddress(); name = name + " " + addresses[0].getAddress();
} else { } else {
name = "[" + addresses[0].getAddress() + ",...]"; name = name + " [" + addresses[0].getAddress() + ",...]";
} }
} catch(NonStandardScriptException e) { } catch(NonStandardScriptException e) {
//ignore //ignore
@ -46,12 +49,16 @@ public abstract class TransactionFormController extends BaseController {
} }
private void addPieData(PieChart pie, ObservableList<PieChart.Data> outputsPieData) { private void addPieData(PieChart pie, ObservableList<PieChart.Data> outputsPieData) {
if(outputsPieData.size() > MAX_PIE_SEGMENTS) {
return;
}
pie.setData(outputsPieData); pie.setData(outputsPieData);
final double totalSum = outputsPieData.stream().map(PieChart.Data::getPieValue).mapToDouble(Double::doubleValue).sum(); final double totalSum = outputsPieData.stream().map(PieChart.Data::getPieValue).mapToDouble(Double::doubleValue).sum();
pie.getData().forEach(data -> { pie.getData().forEach(data -> {
Tooltip tooltip = new Tooltip(); Tooltip tooltip = new Tooltip();
double percent = 100.0 * (data.getPieValue() / totalSum); double percent = 100.0 * (data.getPieValue() / totalSum);
tooltip.setText(String.format("%.1f", percent) + "%"); tooltip.setText(data.getName() + " " + String.format("%.1f", percent) + "%");
Tooltip.install(data.getNode(), tooltip); Tooltip.install(data.getNode(), tooltip);
data.pieValueProperty().addListener((observable, oldValue, newValue) -> tooltip.setText(newValue + "%")); data.pieValueProperty().addListener((observable, oldValue, newValue) -> tooltip.setText(newValue + "%"));
}); });