From 9cd8a9b9ee8814ff2c28b50017f2c162c491c60f Mon Sep 17 00:00:00 2001 From: Craig Raw Date: Wed, 17 Jun 2020 11:34:26 +0200 Subject: [PATCH] finish perf improvements to tx viewer --- .../sparrow/control/TransactionHexArea.java | 16 ++++++- .../TransactionLocktimeChangedEvent.java | 9 ++++ .../sparrow/event/ViewTransactionEvent.java | 2 +- .../transaction/HeadersController.java | 10 ++++- .../sparrow/transaction/InputController.java | 16 ++++++- .../sparrow/transaction/InputsController.java | 8 +++- .../transaction/TransactionController.java | 14 +++--- .../sparrow/transaction/TransactionData.java | 45 +++++++++++++++++++ .../sparrow/transaction/TransactionForm.java | 16 +++---- .../TransactionFormController.java | 17 ++++--- 10 files changed, 125 insertions(+), 28 deletions(-) create mode 100644 src/main/java/com/sparrowwallet/sparrow/event/TransactionLocktimeChangedEvent.java diff --git a/src/main/java/com/sparrowwallet/sparrow/control/TransactionHexArea.java b/src/main/java/com/sparrowwallet/sparrow/control/TransactionHexArea.java index 6e4f608b..ec661bc3 100644 --- a/src/main/java/com/sparrowwallet/sparrow/control/TransactionHexArea.java +++ b/src/main/java/com/sparrowwallet/sparrow/control/TransactionHexArea.java @@ -2,6 +2,7 @@ package com.sparrowwallet.sparrow.control; import com.sparrowwallet.drongo.Utils; import com.sparrowwallet.drongo.protocol.*; +import javafx.application.Platform; import org.fxmisc.richtext.CodeArea; import java.io.ByteArrayOutputStream; @@ -12,6 +13,7 @@ import java.util.Objects; public class TransactionHexArea extends CodeArea { private static final int TRUNCATE_AT = 30000; + private static final int SEGMENTS_INTERVAL = 250; private List previousSegmentList = new ArrayList<>(); @@ -38,14 +40,24 @@ public class TransactionHexArea extends CodeArea { List segments = getTransactionSegments(transaction, selectedInputIndex, selectedOutputIndex); List changedSegments = new ArrayList<>(segments); changedSegments.removeAll(previousSegmentList); + applyHighlighting(0, changedSegments); + previousSegmentList = segments; + } - for(TransactionSegment segment : changedSegments) { + private void applyHighlighting(int start, List 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) { 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 getTransactionSegments(Transaction transaction, int selectedInputIndex, int selectedOutputIndex) { diff --git a/src/main/java/com/sparrowwallet/sparrow/event/TransactionLocktimeChangedEvent.java b/src/main/java/com/sparrowwallet/sparrow/event/TransactionLocktimeChangedEvent.java new file mode 100644 index 00000000..08c72c90 --- /dev/null +++ b/src/main/java/com/sparrowwallet/sparrow/event/TransactionLocktimeChangedEvent.java @@ -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); + } +} diff --git a/src/main/java/com/sparrowwallet/sparrow/event/ViewTransactionEvent.java b/src/main/java/com/sparrowwallet/sparrow/event/ViewTransactionEvent.java index 91bdfafd..70e91d27 100644 --- a/src/main/java/com/sparrowwallet/sparrow/event/ViewTransactionEvent.java +++ b/src/main/java/com/sparrowwallet/sparrow/event/ViewTransactionEvent.java @@ -10,7 +10,7 @@ public class ViewTransactionEvent { public final Integer initialIndex; public ViewTransactionEvent(BlockTransaction blockTransaction) { - this(blockTransaction, null, null); + this(blockTransaction, TransactionView.HEADERS, null); } public ViewTransactionEvent(BlockTransaction blockTransaction, HashIndexEntry hashIndexEntry) { diff --git a/src/main/java/com/sparrowwallet/sparrow/transaction/HeadersController.java b/src/main/java/com/sparrowwallet/sparrow/transaction/HeadersController.java index 0fe1f00a..09715a6d 100644 --- a/src/main/java/com/sparrowwallet/sparrow/transaction/HeadersController.java +++ b/src/main/java/com/sparrowwallet/sparrow/transaction/HeadersController.java @@ -12,6 +12,7 @@ import com.sparrowwallet.sparrow.control.IdLabel; import com.sparrowwallet.sparrow.control.CopyableLabel; import com.sparrowwallet.sparrow.event.BlockTransactionFetchedEvent; import com.sparrowwallet.sparrow.event.TransactionChangedEvent; +import com.sparrowwallet.sparrow.event.TransactionLocktimeChangedEvent; import javafx.fxml.FXML; import javafx.fxml.Initializable; import javafx.scene.control.*; @@ -199,6 +200,7 @@ public class HeadersController extends TransactionFormController implements Init tx.setLocktime(newValue); if(oldValue != null) { EventManager.get().post(new TransactionChangedEvent(tx)); + EventManager.get().post(new TransactionLocktimeChangedEvent(tx)); } }); @@ -249,8 +251,12 @@ public class HeadersController extends TransactionFormController implements Init BlockTransaction inputTx = inputTransactions.get(input.getOutpoint().getHash()); if(inputTx == null) { - System.out.println("Cannot find transaction for hash " + input.getOutpoint().getHash() + ", still paging?"); - return null; + if(headersForm.allInputsFetched()) { + throw new IllegalStateException("Cannot find transaction for hash " + input.getOutpoint().getHash()); + } else { + //Still paging + return null; + } } feeAmt += inputTx.getTransaction().getOutputs().get((int)input.getOutpoint().getIndex()).getValue(); diff --git a/src/main/java/com/sparrowwallet/sparrow/transaction/InputController.java b/src/main/java/com/sparrowwallet/sparrow/transaction/InputController.java index c45f0c8d..07ef3de9 100644 --- a/src/main/java/com/sparrowwallet/sparrow/transaction/InputController.java +++ b/src/main/java/com/sparrowwallet/sparrow/transaction/InputController.java @@ -11,6 +11,7 @@ import com.sparrowwallet.sparrow.EventManager; import com.sparrowwallet.sparrow.control.*; import com.sparrowwallet.sparrow.event.BlockTransactionFetchedEvent; import com.sparrowwallet.sparrow.event.TransactionChangedEvent; +import com.sparrowwallet.sparrow.event.TransactionLocktimeChangedEvent; import com.sparrowwallet.sparrow.event.ViewTransactionEvent; import javafx.fxml.FXML; import javafx.fxml.Initializable; @@ -192,8 +193,12 @@ public class InputController extends TransactionFormController implements Initia if(!txInput.isCoinBase()) { BlockTransaction blockTransaction = inputTransactions.get(txInput.getOutpoint().getHash()); if(blockTransaction == null) { - System.out.println("Could not retrieve block transaction for input #" + inputForm.getIndex()); - return; + if(inputForm.getIndex() < inputForm.getMaxInputFetched()) { + throw new IllegalStateException("Could not retrieve block transaction for input #" + inputForm.getIndex()); + } else { + //Still paging + return; + } } TransactionOutput output = blockTransaction.getTransaction().getOutputs().get((int)txInput.getOutpoint().getIndex()); @@ -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())); + } + } } diff --git a/src/main/java/com/sparrowwallet/sparrow/transaction/InputsController.java b/src/main/java/com/sparrowwallet/sparrow/transaction/InputsController.java index c2fb06e5..93ea7c39 100644 --- a/src/main/java/com/sparrowwallet/sparrow/transaction/InputsController.java +++ b/src/main/java/com/sparrowwallet/sparrow/transaction/InputsController.java @@ -125,8 +125,12 @@ public class InputsController extends TransactionFormController implements Initi } else { BlockTransaction inputTx = inputTransactions.get(input.getOutpoint().getHash()); if(inputTx == null) { - System.out.println("Cannot find transaction for hash " + input.getOutpoint().getHash() + ", still paging?"); - return; + if(inputsForm.allInputsFetched()) { + throw new IllegalStateException("Cannot find transaction for hash " + input.getOutpoint().getHash()); + } else { + //Still paging + return; + } } TransactionOutput output = inputTx.getTransaction().getOutputs().get((int)input.getOutpoint().getIndex()); diff --git a/src/main/java/com/sparrowwallet/sparrow/transaction/TransactionController.java b/src/main/java/com/sparrowwallet/sparrow/transaction/TransactionController.java index 54838afb..5538be5f 100644 --- a/src/main/java/com/sparrowwallet/sparrow/transaction/TransactionController.java +++ b/src/main/java/com/sparrowwallet/sparrow/transaction/TransactionController.java @@ -90,7 +90,7 @@ public class TransactionController implements Initializable { inputsItem.getChildren().add(inputItem); inputPagingAdded = false; } 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 pageItem = new TreeItem<>(pageForm); inputsItem.getChildren().add(pageItem); inputPagingAdded = true; @@ -110,7 +110,7 @@ public class TransactionController implements Initializable { outputsItem.getChildren().add(outputItem); outputPagingAdded = false; } 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 pageItem = new TreeItem<>(pageForm); outputsItem.getChildren().add(pageItem); outputPagingAdded = true; @@ -169,7 +169,7 @@ public class TransactionController implements Initializable { } 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 nextPageItem = new TreeItem<>(nextPageForm); if(pageForm.getPageEnd() >= parentItem.getChildren().size()) { parentItem.getChildren().add(nextPageItem); @@ -397,16 +397,16 @@ public class TransactionController implements Initializable { setTreeSelection(event.getInitialView(), event.getInitialIndex()); } else if(event.getInitialView().equals(TransactionView.INPUT) || event.getInitialView().equals(TransactionView.OUTPUT)) { TreeItem parentItem = getTreeItem(event.getInitialView().equals(TransactionView.INPUT) ? TransactionView.INPUTS : TransactionView.OUTPUTS, null); - TreeItem 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 nextPageItem = new TreeItem<>(nextPageForm); if(existingItem != null) { 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(); if(event.getInitialIndex() < highestIndex) { for(int i = 0; i < parentItem.getChildren().size(); i++) { @@ -466,6 +466,7 @@ public class TransactionController implements Initializable { } else { 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()); } } } \ No newline at end of file diff --git a/src/main/java/com/sparrowwallet/sparrow/transaction/TransactionData.java b/src/main/java/com/sparrowwallet/sparrow/transaction/TransactionData.java index 4eaea664..9c1fca72 100644 --- a/src/main/java/com/sparrowwallet/sparrow/transaction/TransactionData.java +++ b/src/main/java/com/sparrowwallet/sparrow/transaction/TransactionData.java @@ -15,6 +15,11 @@ public class TransactionData { private Map inputTransactions; private List outputTransactions; + private int minInputFetched; + private int maxInputFetched; + private int minOutputFetched; + private int maxOutputFetched; + public TransactionData(PSBT psbt) { this.transaction = psbt.getTransaction(); this.psbt = psbt; @@ -53,6 +58,28 @@ public class TransactionData { 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 getOutputTransactions() { return outputTransactions; } @@ -60,4 +87,22 @@ public class TransactionData { public void setOutputTransactions(List 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(); + } } diff --git a/src/main/java/com/sparrowwallet/sparrow/transaction/TransactionForm.java b/src/main/java/com/sparrowwallet/sparrow/transaction/TransactionForm.java index 6f310c47..12517ff8 100644 --- a/src/main/java/com/sparrowwallet/sparrow/transaction/TransactionForm.java +++ b/src/main/java/com/sparrowwallet/sparrow/transaction/TransactionForm.java @@ -29,24 +29,24 @@ public abstract class TransactionForm { return txdata.getBlockTransaction(); } - public void setBlockTransaction(BlockTransaction blockTransaction) { - txdata.setBlockTransaction(blockTransaction); - } - public Map getInputTransactions() { return txdata.getInputTransactions(); } - public void setInputTransactions(Map inputTransactions) { - txdata.setInputTransactions(inputTransactions); + public int getMaxInputFetched() { + return txdata.getMaxInputFetched(); + } + + public boolean allInputsFetched() { + return txdata.allInputsFetched(); } public List getOutputTransactions() { return txdata.getOutputTransactions(); } - public void setOutputTransactions(List outputTransactions) { - txdata.setOutputTransactions(outputTransactions); + public boolean allOutputsFetched() { + return txdata.allOutputsFetched(); } public boolean isEditable() { diff --git a/src/main/java/com/sparrowwallet/sparrow/transaction/TransactionFormController.java b/src/main/java/com/sparrowwallet/sparrow/transaction/TransactionFormController.java index 932fd86d..89a3d668 100644 --- a/src/main/java/com/sparrowwallet/sparrow/transaction/TransactionFormController.java +++ b/src/main/java/com/sparrowwallet/sparrow/transaction/TransactionFormController.java @@ -16,18 +16,21 @@ import javafx.scene.input.ClipboardContent; import java.util.List; public abstract class TransactionFormController extends BaseController { + private static final int MAX_PIE_SEGMENTS = 200; + protected void addPieData(PieChart pie, List outputs) { ObservableList outputsPieData = FXCollections.observableArrayList(); long totalAmt = 0; - for(TransactionOutput output : outputs) { - String name = "Unknown"; + for(int i = 0; i < outputs.size(); i++) { + TransactionOutput output = outputs.get(i); + String name = "#" + i; try { Address[] addresses = output.getScript().getToAddresses(); if(addresses.length == 1) { - name = addresses[0].getAddress(); + name = name + " " + addresses[0].getAddress(); } else { - name = "[" + addresses[0].getAddress() + ",...]"; + name = name + " [" + addresses[0].getAddress() + ",...]"; } } catch(NonStandardScriptException e) { //ignore @@ -46,12 +49,16 @@ public abstract class TransactionFormController extends BaseController { } private void addPieData(PieChart pie, ObservableList outputsPieData) { + if(outputsPieData.size() > MAX_PIE_SEGMENTS) { + return; + } + pie.setData(outputsPieData); final double totalSum = outputsPieData.stream().map(PieChart.Data::getPieValue).mapToDouble(Double::doubleValue).sum(); pie.getData().forEach(data -> { Tooltip tooltip = new Tooltip(); 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); data.pieValueProperty().addListener((observable, oldValue, newValue) -> tooltip.setText(newValue + "%")); });