From ebc2f9442e14bdfd24753b784ea8fcc94f542c51 Mon Sep 17 00:00:00 2001 From: Craig Raw Date: Mon, 15 Jun 2020 10:00:49 +0200 Subject: [PATCH] transaction view paging support initial commit --- .../sparrowwallet/sparrow/AppController.java | 7 +- .../event/BlockTransactionFetchedEvent.java | 7 +- .../BlockTransactionOutputsFetchedEvent.java | 5 +- .../sparrow/event/PagedEvent.java | 19 ++ .../sparrow/io/ElectrumServer.java | 16 +- .../transaction/HeadersController.java | 10 +- .../transaction/IndexedTransactionForm.java | 28 ++ .../sparrow/transaction/InputController.java | 7 +- .../sparrow/transaction/InputForm.java | 10 +- .../sparrow/transaction/OutputController.java | 2 +- .../sparrow/transaction/OutputForm.java | 8 +- .../sparrow/transaction/PageForm.java | 44 +++ .../transaction/TransactionController.java | 257 +++++++++++++----- 13 files changed, 329 insertions(+), 91 deletions(-) create mode 100644 src/main/java/com/sparrowwallet/sparrow/event/PagedEvent.java create mode 100644 src/main/java/com/sparrowwallet/sparrow/transaction/IndexedTransactionForm.java create mode 100644 src/main/java/com/sparrowwallet/sparrow/transaction/PageForm.java diff --git a/src/main/java/com/sparrowwallet/sparrow/AppController.java b/src/main/java/com/sparrowwallet/sparrow/AppController.java index 9d99e0b0..319f4f07 100644 --- a/src/main/java/com/sparrowwallet/sparrow/AppController.java +++ b/src/main/java/com/sparrowwallet/sparrow/AppController.java @@ -621,13 +621,10 @@ public class AppController implements Initializable { controller.setPSBT(psbt); controller.setBlockTransaction(blockTransaction); - controller.setTransaction(transaction); - if(initialView != null) { - controller.setTreeSelection(initialView, initialIndex); - } else { - controller.setTreeSelection(TransactionView.HEADERS, null); + controller.setInitialView(initialView, initialIndex); } + controller.setTransaction(transaction); tabs.getTabs().add(tab); return tab; diff --git a/src/main/java/com/sparrowwallet/sparrow/event/BlockTransactionFetchedEvent.java b/src/main/java/com/sparrowwallet/sparrow/event/BlockTransactionFetchedEvent.java index 56c2d506..b53546e2 100644 --- a/src/main/java/com/sparrowwallet/sparrow/event/BlockTransactionFetchedEvent.java +++ b/src/main/java/com/sparrowwallet/sparrow/event/BlockTransactionFetchedEvent.java @@ -5,12 +5,17 @@ import com.sparrowwallet.drongo.wallet.BlockTransaction; import java.util.Map; -public class BlockTransactionFetchedEvent { +public class BlockTransactionFetchedEvent extends PagedEvent { private final Sha256Hash txId; private final BlockTransaction blockTransaction; private final Map inputTransactions; public BlockTransactionFetchedEvent(Sha256Hash txId, BlockTransaction blockTransaction, Map inputTransactions) { + this(txId, blockTransaction, inputTransactions, 0, blockTransaction.getTransaction().getInputs().size()); + } + + public BlockTransactionFetchedEvent(Sha256Hash txId, BlockTransaction blockTransaction, Map inputTransactions, int pageStart, int pageEnd) { + super(pageStart, pageEnd); this.txId = txId; this.blockTransaction = blockTransaction; this.inputTransactions = inputTransactions; diff --git a/src/main/java/com/sparrowwallet/sparrow/event/BlockTransactionOutputsFetchedEvent.java b/src/main/java/com/sparrowwallet/sparrow/event/BlockTransactionOutputsFetchedEvent.java index eee175ff..cef66ba2 100644 --- a/src/main/java/com/sparrowwallet/sparrow/event/BlockTransactionOutputsFetchedEvent.java +++ b/src/main/java/com/sparrowwallet/sparrow/event/BlockTransactionOutputsFetchedEvent.java @@ -5,11 +5,12 @@ import com.sparrowwallet.drongo.wallet.BlockTransaction; import java.util.List; -public class BlockTransactionOutputsFetchedEvent { +public class BlockTransactionOutputsFetchedEvent extends PagedEvent { private final Sha256Hash txId; private final List outputTransactions; - public BlockTransactionOutputsFetchedEvent(Sha256Hash txId, List outputTransactions) { + public BlockTransactionOutputsFetchedEvent(Sha256Hash txId, List outputTransactions, int pageStart, int pageEnd) { + super(pageStart, pageEnd); this.txId = txId; this.outputTransactions = outputTransactions; } diff --git a/src/main/java/com/sparrowwallet/sparrow/event/PagedEvent.java b/src/main/java/com/sparrowwallet/sparrow/event/PagedEvent.java new file mode 100644 index 00000000..8bb00b82 --- /dev/null +++ b/src/main/java/com/sparrowwallet/sparrow/event/PagedEvent.java @@ -0,0 +1,19 @@ +package com.sparrowwallet.sparrow.event; + +public class PagedEvent { + private final int pageStart; + private final int pageEnd; + + public PagedEvent(int pageStart, int pageEnd) { + this.pageStart = pageStart; + this.pageEnd = pageEnd; + } + + public int getPageStart() { + return pageStart; + } + + public int getPageEnd() { + return pageEnd; + } +} diff --git a/src/main/java/com/sparrowwallet/sparrow/io/ElectrumServer.java b/src/main/java/com/sparrowwallet/sparrow/io/ElectrumServer.java index 49d8bb4d..337327c9 100644 --- a/src/main/java/com/sparrowwallet/sparrow/io/ElectrumServer.java +++ b/src/main/java/com/sparrowwallet/sparrow/io/ElectrumServer.java @@ -200,11 +200,11 @@ public class ElectrumServer { } @SuppressWarnings("unchecked") - public List> getOutputTransactionReferences(Transaction transaction) throws ServerException { + public List> getOutputTransactionReferences(Transaction transaction, int indexStart, int indexEnd) throws ServerException { try { JsonRpcClient client = new JsonRpcClient(getTransport()); BatchRequestBuilder batchRequest = client.createBatchRequest().keysType(Integer.class).returnType(ScriptHashTx[].class); - for(int i = 0; i < transaction.getOutputs().size(); i++) { + for(int i = indexStart; i < transaction.getOutputs().size() && i < indexEnd; i++) { TransactionOutput output = transaction.getOutputs().get(i); batchRequest.add(i, "blockchain.scripthash.get_history", getScriptHash(output)); } @@ -910,9 +910,19 @@ public class ElectrumServer { public static class TransactionOutputsReferenceService extends Service> { private final Transaction transaction; + private final int indexStart; + private final int indexEnd; public TransactionOutputsReferenceService(Transaction transaction) { this.transaction = transaction; + this.indexStart = 0; + this.indexEnd = transaction.getOutputs().size(); + } + + public TransactionOutputsReferenceService(Transaction transaction, int indexStart, int indexEnd) { + this.transaction = transaction; + this.indexStart = Math.min(transaction.getOutputs().size(), indexStart); + this.indexEnd = Math.min(transaction.getOutputs().size(), indexEnd); } @Override @@ -920,7 +930,7 @@ public class ElectrumServer { return new Task<>() { protected List call() throws ServerException { ElectrumServer electrumServer = new ElectrumServer(); - List> outputTransactionReferences = electrumServer.getOutputTransactionReferences(transaction); + List> outputTransactionReferences = electrumServer.getOutputTransactionReferences(transaction, indexStart, indexEnd); Set setReferences = new HashSet<>(); for(Set outputReferences : outputTransactionReferences) { diff --git a/src/main/java/com/sparrowwallet/sparrow/transaction/HeadersController.java b/src/main/java/com/sparrowwallet/sparrow/transaction/HeadersController.java index ae17a2cb..eda6b44d 100644 --- a/src/main/java/com/sparrowwallet/sparrow/transaction/HeadersController.java +++ b/src/main/java/com/sparrowwallet/sparrow/transaction/HeadersController.java @@ -228,7 +228,7 @@ public class HeadersController extends TransactionFormController implements Init } } - private long calculateFee(Map inputTransactions) { + private Long calculateFee(Map inputTransactions) { long feeAmt = 0L; for(TransactionInput input : headersForm.getTransaction().getInputs()) { if(input.isCoinBase()) { @@ -237,7 +237,8 @@ public class HeadersController extends TransactionFormController implements Init BlockTransaction inputTx = inputTransactions.get(input.getOutpoint().getHash()); if(inputTx == null) { - throw new IllegalStateException("Cannot find transaction for hash " + input.getOutpoint().getHash()); + System.out.println("Cannot find transaction for hash " + input.getOutpoint().getHash() + ", still paging?"); + return null; } feeAmt += inputTx.getTransaction().getOutputs().get((int)input.getOutpoint().getIndex()).getValue(); @@ -319,7 +320,10 @@ public class HeadersController extends TransactionFormController implements Init updateBlockchainForm(event.getBlockTransaction()); } - updateFee(calculateFee(event.getInputTransactions())); + Long feeAmt = calculateFee(event.getInputTransactions()); + if(feeAmt != null) { + updateFee(feeAmt); + } } } } diff --git a/src/main/java/com/sparrowwallet/sparrow/transaction/IndexedTransactionForm.java b/src/main/java/com/sparrowwallet/sparrow/transaction/IndexedTransactionForm.java new file mode 100644 index 00000000..b1222139 --- /dev/null +++ b/src/main/java/com/sparrowwallet/sparrow/transaction/IndexedTransactionForm.java @@ -0,0 +1,28 @@ +package com.sparrowwallet.sparrow.transaction; + +import com.sparrowwallet.drongo.protocol.Transaction; +import com.sparrowwallet.drongo.psbt.PSBT; +import com.sparrowwallet.drongo.wallet.BlockTransaction; + +public abstract class IndexedTransactionForm extends TransactionForm { + private final int index; + + public IndexedTransactionForm(PSBT psbt, int index) { + super(psbt); + this.index = index; + } + + public IndexedTransactionForm(BlockTransaction blockTransaction, int index) { + super(blockTransaction); + this.index = index; + } + + public IndexedTransactionForm(Transaction transaction, int index) { + super(transaction); + this.index = index; + } + + public int getIndex() { + return index; + } +} diff --git a/src/main/java/com/sparrowwallet/sparrow/transaction/InputController.java b/src/main/java/com/sparrowwallet/sparrow/transaction/InputController.java index 19152a38..e843f163 100644 --- a/src/main/java/com/sparrowwallet/sparrow/transaction/InputController.java +++ b/src/main/java/com/sparrowwallet/sparrow/transaction/InputController.java @@ -191,6 +191,11 @@ public class InputController extends TransactionFormController implements Initia TransactionInput txInput = inputForm.getTransactionInput(); 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; + } + TransactionOutput output = blockTransaction.getTransaction().getOutputs().get((int)txInput.getOutpoint().getIndex()); updateSpends(output); } @@ -464,7 +469,7 @@ public class InputController extends TransactionFormController implements Initia @Subscribe public void blockTransactionFetched(BlockTransactionFetchedEvent event) { - if(event.getTxId().equals(inputForm.getTransaction().getTxId())) { + if(event.getTxId().equals(inputForm.getTransaction().getTxId()) && inputForm.getIndex() >= event.getPageStart() && inputForm.getIndex() < event.getPageEnd()) { updateOutpoint(event.getInputTransactions()); if(inputForm.getPsbt() == null) { updateSpends(event.getInputTransactions()); diff --git a/src/main/java/com/sparrowwallet/sparrow/transaction/InputForm.java b/src/main/java/com/sparrowwallet/sparrow/transaction/InputForm.java index e70743ba..7728bf31 100644 --- a/src/main/java/com/sparrowwallet/sparrow/transaction/InputForm.java +++ b/src/main/java/com/sparrowwallet/sparrow/transaction/InputForm.java @@ -12,23 +12,23 @@ import javafx.scene.Node; import java.io.IOException; -public class InputForm extends TransactionForm { - private TransactionInput transactionInput; +public class InputForm extends IndexedTransactionForm { + private final TransactionInput transactionInput; private PSBTInput psbtInput; public InputForm(PSBT psbt, PSBTInput psbtInput) { - super(psbt); + super(psbt, psbt.getPsbtInputs().indexOf(psbtInput)); this.transactionInput = psbt.getTransaction().getInputs().get(psbt.getPsbtInputs().indexOf(psbtInput)); this.psbtInput = psbtInput; } public InputForm(BlockTransaction blockTransaction, TransactionInput transactionInput) { - super(blockTransaction); + super(blockTransaction, blockTransaction.getTransaction().getInputs().indexOf(transactionInput)); this.transactionInput = transactionInput; } public InputForm(Transaction transaction, TransactionInput transactionInput) { - super(transaction); + super(transaction, transaction.getInputs().indexOf(transactionInput)); this.transactionInput = transactionInput; } diff --git a/src/main/java/com/sparrowwallet/sparrow/transaction/OutputController.java b/src/main/java/com/sparrowwallet/sparrow/transaction/OutputController.java index 61a1b329..0b4f8d22 100644 --- a/src/main/java/com/sparrowwallet/sparrow/transaction/OutputController.java +++ b/src/main/java/com/sparrowwallet/sparrow/transaction/OutputController.java @@ -134,7 +134,7 @@ public class OutputController extends TransactionFormController implements Initi @Subscribe public void blockTransactionOutputsFetched(BlockTransactionOutputsFetchedEvent event) { - if(event.getTxId().equals(outputForm.getTransaction().getTxId()) && outputForm.getPsbt() == null) { + if(event.getTxId().equals(outputForm.getTransaction().getTxId()) && outputForm.getPsbt() == null && outputForm.getIndex() >= event.getPageStart() && outputForm.getIndex() < event.getPageEnd()) { updateSpent(event.getOutputTransactions()); } } diff --git a/src/main/java/com/sparrowwallet/sparrow/transaction/OutputForm.java b/src/main/java/com/sparrowwallet/sparrow/transaction/OutputForm.java index 2a037e02..5cb6913d 100644 --- a/src/main/java/com/sparrowwallet/sparrow/transaction/OutputForm.java +++ b/src/main/java/com/sparrowwallet/sparrow/transaction/OutputForm.java @@ -10,23 +10,23 @@ import javafx.scene.Node; import java.io.IOException; -public class OutputForm extends TransactionForm { +public class OutputForm extends IndexedTransactionForm { private final TransactionOutput transactionOutput; private PSBTOutput psbtOutput; public OutputForm(PSBT psbt, PSBTOutput psbtOutput) { - super(psbt); + super(psbt, psbt.getPsbtOutputs().indexOf(psbtOutput)); this.transactionOutput = psbt.getTransaction().getOutputs().get(psbt.getPsbtOutputs().indexOf(psbtOutput)); this.psbtOutput = psbtOutput; } public OutputForm(BlockTransaction blockTransaction, TransactionOutput transactionOutput) { - super(blockTransaction); + super(blockTransaction, blockTransaction.getTransaction().getOutputs().indexOf(transactionOutput)); this.transactionOutput = transactionOutput; } public OutputForm(Transaction transaction, TransactionOutput transactionOutput) { - super(transaction); + super(transaction, transaction.getOutputs().indexOf(transactionOutput)); this.transactionOutput = transactionOutput; } diff --git a/src/main/java/com/sparrowwallet/sparrow/transaction/PageForm.java b/src/main/java/com/sparrowwallet/sparrow/transaction/PageForm.java new file mode 100644 index 00000000..a4d32277 --- /dev/null +++ b/src/main/java/com/sparrowwallet/sparrow/transaction/PageForm.java @@ -0,0 +1,44 @@ +package com.sparrowwallet.sparrow.transaction; + +import com.sparrowwallet.sparrow.io.ElectrumServer; +import javafx.scene.Node; + +import java.io.IOException; + +public class PageForm extends IndexedTransactionForm { + public static final int PAGE_SIZE = 50; + + private final TransactionView view; + private final int pageStart; + private final int pageEnd; + + public PageForm(TransactionView view, int pageStart, int pageEnd) { + super(ElectrumServer.UNFETCHABLE_BLOCK_TRANSACTION, pageStart); + this.view = view; + this.pageStart = pageStart; + this.pageEnd = pageEnd; + } + + @Override + public Node getContents() throws IOException { + return null; + } + + @Override + public TransactionView getView() { + return view; + } + + public int getPageStart() { + return pageStart; + } + + public int getPageEnd() { + return pageEnd; + } + + @Override + public String toString() { + return "Load More..."; + } +} diff --git a/src/main/java/com/sparrowwallet/sparrow/transaction/TransactionController.java b/src/main/java/com/sparrowwallet/sparrow/transaction/TransactionController.java index 9a427422..706f467d 100644 --- a/src/main/java/com/sparrowwallet/sparrow/transaction/TransactionController.java +++ b/src/main/java/com/sparrowwallet/sparrow/transaction/TransactionController.java @@ -50,20 +50,29 @@ public class TransactionController implements Initializable { private PSBT psbt; private BlockTransaction blockTransaction; + private TransactionView initialView; + private Integer initialIndex; + private int selectedInputIndex = -1; private int selectedOutputIndex = -1; + private int highestInputIndex; + private int highestOutputIndex; + @Override public void initialize(URL location, ResourceBundle resources) { EventManager.get().register(this); } private void initializeView() { + highestInputIndex = Math.min(transaction.getInputs().size(), PageForm.PAGE_SIZE); + highestOutputIndex = Math.min(transaction.getOutputs().size(), PageForm.PAGE_SIZE); + initializeTxTree(); transactionMasterDetail.setShowDetailNode(AppController.showTxHexProperty); refreshTxHex(); - fetchThisAndInputBlockTransactions(); - fetchOutputBlockTransactions(); + fetchThisAndInputBlockTransactions(0, highestInputIndex); + fetchOutputBlockTransactions(0, highestOutputIndex); } private void initializeTxTree() { @@ -74,27 +83,33 @@ public class TransactionController implements Initializable { InputsForm inputsForm = (psbt != null ? new InputsForm(psbt) : (blockTransaction != null ? new InputsForm(blockTransaction) : new InputsForm(transaction))); TreeItem inputsItem = new TreeItem<>(inputsForm); inputsItem.setExpanded(true); - for (TransactionInput txInput : transaction.getInputs()) { - PSBTInput psbtInput = null; - if (psbt != null && psbt.getPsbtInputs().size() > txInput.getIndex()) { - psbtInput = psbt.getPsbtInputs().get(txInput.getIndex()); + boolean inputPagingAdded = false; + for(int i = 0; i < transaction.getInputs().size(); i++) { + if(i < PageForm.PAGE_SIZE || (TransactionView.INPUT.equals(initialView) && i == initialIndex)) { + TreeItem inputItem = createInputTreeItem(i); + inputsItem.getChildren().add(inputItem); + } else if(!inputPagingAdded) { + PageForm pageForm = new PageForm(TransactionView.INPUT, i, i + PageForm.PAGE_SIZE); + TreeItem pageItem = new TreeItem<>(pageForm); + inputsItem.getChildren().add(pageItem); + inputPagingAdded = true; } - InputForm inputForm = (psbt != null ? new InputForm(psbt, psbtInput) : (blockTransaction != null ? new InputForm(blockTransaction, txInput) : new InputForm(transaction, txInput))); - TreeItem inputItem = new TreeItem<>(inputForm); - inputsItem.getChildren().add(inputItem); } OutputsForm outputsForm = (psbt != null ? new OutputsForm(psbt) : (blockTransaction != null ? new OutputsForm(blockTransaction) : new OutputsForm(transaction))); TreeItem outputsItem = new TreeItem<>(outputsForm); outputsItem.setExpanded(true); - for (TransactionOutput txOutput : transaction.getOutputs()) { - PSBTOutput psbtOutput = null; - if (psbt != null && psbt.getPsbtOutputs().size() > txOutput.getIndex()) { - psbtOutput = psbt.getPsbtOutputs().get(txOutput.getIndex()); + boolean outputPagingAdded = false; + for(int i = 0; i < transaction.getOutputs().size(); i++) { + if(i < PageForm.PAGE_SIZE || (TransactionView.OUTPUT.equals(initialView) && i == initialIndex)) { + TreeItem outputItem = createOutputTreeItem(i); + outputsItem.getChildren().add(outputItem); + } else if(!outputPagingAdded) { + PageForm pageForm = new PageForm(TransactionView.OUTPUT, i, i + PageForm.PAGE_SIZE); + TreeItem pageItem = new TreeItem<>(pageForm); + outputsItem.getChildren().add(pageItem); + outputPagingAdded = true; } - OutputForm outputForm = (psbt != null ? new OutputForm(psbt, psbtOutput) : (blockTransaction != null ? new OutputForm(blockTransaction, txOutput) : new OutputForm(transaction, txOutput))); - TreeItem outputItem = new TreeItem<>(outputForm); - outputsItem.getChildren().add(outputItem); } rootItem.getChildren().add(inputsItem); @@ -113,36 +128,97 @@ public class TransactionController implements Initializable { } })); - txtree.getSelectionModel().selectedItemProperty().addListener((observable, old_val, new_val) -> { - TransactionForm transactionForm = new_val.getValue(); - try { - Node node = transactionForm.getContents(); - txpane.getChildren().clear(); - txpane.getChildren().add(node); + txtree.getSelectionModel().selectedItemProperty().addListener((observable, old_val, selectedItem) -> { + TransactionForm transactionForm = selectedItem.getValue(); + if(transactionForm instanceof PageForm) { + PageForm pageForm = (PageForm)transactionForm; + Optional> optParentItem = txtree.getRoot().getChildren().stream() + .filter(item -> item.getValue().getView().equals(pageForm.getView().equals(TransactionView.INPUT) ? TransactionView.INPUTS : TransactionView.OUTPUTS)).findFirst(); - if (node instanceof Parent) { - Parent parent = (Parent) node; - txhex.getStylesheets().clear(); - txhex.getStylesheets().addAll(parent.getStylesheets()); + if(optParentItem.isPresent()) { + TreeItem parentItem = optParentItem.get(); + parentItem.getChildren().remove(selectedItem); - selectedInputIndex = -1; - selectedOutputIndex = -1; - if (transactionForm instanceof InputForm) { - InputForm inputForm = (InputForm) transactionForm; - selectedInputIndex = inputForm.getTransactionInput().getIndex(); - } else if (transactionForm instanceof OutputForm) { - OutputForm outputForm = (OutputForm) transactionForm; - selectedOutputIndex = outputForm.getTransactionOutput().getIndex(); + int max = pageForm.getView().equals(TransactionView.INPUT) ? transaction.getInputs().size() : transaction.getOutputs().size(); + for(int i = pageForm.getPageStart(); i < max && i < pageForm.getPageEnd(); i++) { + TreeItem newItem = pageForm.getView().equals(TransactionView.INPUT) ? createInputTreeItem(i) : createOutputTreeItem(i); + parentItem.getChildren().add(newItem); } - Platform.runLater(this::refreshTxHex); + if(pageForm.getPageEnd() < max) { + PageForm nextPageForm = new PageForm(pageForm.getView(), pageForm.getPageStart() + PageForm.PAGE_SIZE, pageForm.getPageEnd() + PageForm.PAGE_SIZE); + TreeItem nextPageItem = new TreeItem<>(nextPageForm); + parentItem.getChildren().add(nextPageItem); + } + + if(pageForm.getView().equals(TransactionView.INPUT)) { + highestInputIndex = Math.min(max, pageForm.getPageEnd()); + fetchThisAndInputBlockTransactions(pageForm.getPageStart(), Math.min(max, pageForm.getPageEnd())); + } else { + highestOutputIndex = Math.min(max, pageForm.getPageEnd()); + fetchOutputBlockTransactions(pageForm.getPageStart(), Math.min(max, pageForm.getPageEnd())); + } + + setTreeSelection(pageForm.getView(), pageForm.getPageStart()); + Platform.runLater(() -> { + txtree.scrollTo(pageForm.getPageStart()); + refreshTxHex(); + }); + } + } else { + try { + Node node = transactionForm.getContents(); + txpane.getChildren().clear(); + txpane.getChildren().add(node); + + if (node instanceof Parent) { + Parent parent = (Parent) node; + txhex.getStylesheets().clear(); + txhex.getStylesheets().addAll(parent.getStylesheets()); + + selectedInputIndex = -1; + selectedOutputIndex = -1; + if (transactionForm instanceof InputForm) { + InputForm inputForm = (InputForm) transactionForm; + selectedInputIndex = inputForm.getTransactionInput().getIndex(); + } else if (transactionForm instanceof OutputForm) { + OutputForm outputForm = (OutputForm) transactionForm; + selectedOutputIndex = outputForm.getTransactionOutput().getIndex(); + } + + Platform.runLater(this::refreshTxHex); + } + } catch (IOException e) { + throw new IllegalStateException("Can't find pane", e); } - } catch (IOException e) { - throw new IllegalStateException("Can't find pane", e); } }); - txtree.getSelectionModel().select(txtree.getRoot()); + if(initialView != null) { + setTreeSelection(initialView, initialIndex); + } else { + txtree.getSelectionModel().select(txtree.getRoot()); + } + } + + private TreeItem createInputTreeItem(int inputIndex) { + TransactionInput txInput = transaction.getInputs().get(inputIndex); + PSBTInput psbtInput = null; + if (psbt != null && psbt.getPsbtInputs().size() > txInput.getIndex()) { + psbtInput = psbt.getPsbtInputs().get(txInput.getIndex()); + } + InputForm inputForm = (psbt != null ? new InputForm(psbt, psbtInput) : (blockTransaction != null ? new InputForm(blockTransaction, txInput) : new InputForm(transaction, txInput))); + return new TreeItem<>(inputForm); + } + + private TreeItem createOutputTreeItem(int outputIndex) { + TransactionOutput txOutput = transaction.getOutputs().get(outputIndex); + PSBTOutput psbtOutput = null; + if (psbt != null && psbt.getPsbtOutputs().size() > txOutput.getIndex()) { + psbtOutput = psbt.getPsbtOutputs().get(txOutput.getIndex()); + } + OutputForm outputForm = (psbt != null ? new OutputForm(psbt, psbtOutput) : (blockTransaction != null ? new OutputForm(blockTransaction, txOutput) : new OutputForm(transaction, txOutput))); + return new TreeItem<>(outputForm); } public void setTreeSelection(TransactionView view, Integer index) { @@ -150,9 +226,10 @@ public class TransactionController implements Initializable { } private void select(TreeItem treeItem, TransactionView view, Integer index) { - if (treeItem.getValue().getView().equals(view)) { - if (view.equals(TransactionView.INPUT) || view.equals(TransactionView.OUTPUT)) { - if (treeItem.getParent().getChildren().indexOf(treeItem) == index) { + if(treeItem.getValue().getView().equals(view)) { + if(view.equals(TransactionView.INPUT) || view.equals(TransactionView.OUTPUT)) { + IndexedTransactionForm txForm = (IndexedTransactionForm)treeItem.getValue(); + if(txForm.getIndex() == index) { txtree.getSelectionModel().select(treeItem); return; } @@ -162,7 +239,7 @@ public class TransactionController implements Initializable { } } - for (TreeItem childItem : treeItem.getChildren()) { + for(TreeItem childItem : treeItem.getChildren()) { select(childItem, view, index); } } @@ -198,13 +275,18 @@ public class TransactionController implements Initializable { //Inputs for (int i = 0; i < transaction.getInputs().size(); i++) { + if(i == highestInputIndex) { + txhex.append("...", ""); + } + TransactionInput input = transaction.getInputs().get(i); - cursor = addText(hex, cursor, 32 * 2, "input-" + getIndexedStyleClass(i, selectedInputIndex, "hash")); - cursor = addText(hex, cursor, 4 * 2, "input-" + getIndexedStyleClass(i, selectedInputIndex, "index")); + boolean skip = (i >= highestInputIndex); + cursor = addText(hex, cursor, 32 * 2, "input-" + getIndexedStyleClass(i, selectedInputIndex, "hash"), skip); + cursor = addText(hex, cursor, 4 * 2, "input-" + getIndexedStyleClass(i, selectedInputIndex, "index"), skip); VarInt scriptLen = new VarInt(input.getScriptBytes().length); - cursor = addText(hex, cursor, scriptLen.getSizeInBytes() * 2, "input-" + getIndexedStyleClass(i, selectedInputIndex, "sigscript-length")); - cursor = addText(hex, cursor, (int) scriptLen.value * 2, "input-" + getIndexedStyleClass(i, selectedInputIndex, "sigscript")); - cursor = addText(hex, cursor, 4 * 2, "input-" + getIndexedStyleClass(i, selectedInputIndex, "sequence")); + cursor = addText(hex, cursor, scriptLen.getSizeInBytes() * 2, "input-" + getIndexedStyleClass(i, selectedInputIndex, "sigscript-length"), skip); + cursor = addText(hex, cursor, (int) scriptLen.value * 2, "input-" + getIndexedStyleClass(i, selectedInputIndex, "sigscript"), skip); + cursor = addText(hex, cursor, 4 * 2, "input-" + getIndexedStyleClass(i, selectedInputIndex, "sequence"), skip); } //Number of outputs @@ -213,24 +295,34 @@ public class TransactionController implements Initializable { //Outputs for (int i = 0; i < transaction.getOutputs().size(); i++) { + if(i == highestOutputIndex) { + txhex.append("...", ""); + } + TransactionOutput output = transaction.getOutputs().get(i); - cursor = addText(hex, cursor, 8 * 2, "output-" + getIndexedStyleClass(i, selectedOutputIndex, "value")); + boolean skip = (i >= highestOutputIndex); + cursor = addText(hex, cursor, 8 * 2, "output-" + getIndexedStyleClass(i, selectedOutputIndex, "value"), skip); VarInt scriptLen = new VarInt(output.getScriptBytes().length); - cursor = addText(hex, cursor, scriptLen.getSizeInBytes() * 2, "output-" + getIndexedStyleClass(i, selectedOutputIndex, "pubkeyscript-length")); - cursor = addText(hex, cursor, (int) scriptLen.value * 2, "output-" + getIndexedStyleClass(i, selectedOutputIndex, "pubkeyscript")); + cursor = addText(hex, cursor, scriptLen.getSizeInBytes() * 2, "output-" + getIndexedStyleClass(i, selectedOutputIndex, "pubkeyscript-length"), skip); + cursor = addText(hex, cursor, (int) scriptLen.value * 2, "output-" + getIndexedStyleClass(i, selectedOutputIndex, "pubkeyscript"), skip); } if (transaction.hasWitnesses()) { for (int i = 0; i < transaction.getInputs().size(); i++) { + if(i == highestInputIndex) { + txhex.append("...", ""); + } + TransactionInput input = transaction.getInputs().get(i); + boolean skip = (i >= highestInputIndex); if (input.hasWitness()) { TransactionWitness witness = input.getWitness(); VarInt witnessCount = new VarInt(witness.getPushCount()); - cursor = addText(hex, cursor, witnessCount.getSizeInBytes() * 2, "witness-" + getIndexedStyleClass(i, selectedInputIndex, "count")); + cursor = addText(hex, cursor, witnessCount.getSizeInBytes() * 2, "witness-" + getIndexedStyleClass(i, selectedInputIndex, "count"), skip); for (byte[] push : witness.getPushes()) { VarInt witnessLen = new VarInt(push.length); - cursor = addText(hex, cursor, witnessLen.getSizeInBytes() * 2, "witness-" + getIndexedStyleClass(i, selectedInputIndex, "length")); - cursor = addText(hex, cursor, (int) witnessLen.value * 2, "witness-" + getIndexedStyleClass(i, selectedInputIndex, "data")); + cursor = addText(hex, cursor, witnessLen.getSizeInBytes() * 2, "witness-" + getIndexedStyleClass(i, selectedInputIndex, "length"), skip); + cursor = addText(hex, cursor, (int) witnessLen.value * 2, "witness-" + getIndexedStyleClass(i, selectedInputIndex, "data"), skip); } } } @@ -239,23 +331,30 @@ public class TransactionController implements Initializable { //Locktime cursor = addText(hex, cursor, 8, "locktime"); - if (cursor != hex.length()) { + if(cursor != hex.length()) { throw new IllegalStateException("Cursor position does not match transaction serialisation " + cursor + ": " + hex.length()); } } - private void fetchThisAndInputBlockTransactions() { - if (AppController.isOnline()) { + private void fetchThisAndInputBlockTransactions(int indexStart, int indexEnd) { + if(AppController.isOnline() && indexStart < transaction.getInputs().size()) { Set references = new HashSet<>(); if (psbt == null) { references.add(transaction.getTxId()); } - for (TransactionInput input : transaction.getInputs()) { + + int maxIndex = Math.min(transaction.getInputs().size(), indexEnd); + for(int i = indexStart; i < maxIndex; i++) { + TransactionInput input = transaction.getInputs().get(i); if(!input.isCoinBase()) { references.add(input.getOutpoint().getHash()); } } + if(references.isEmpty()) { + return; + } + ElectrumServer.TransactionReferenceService transactionReferenceService = new ElectrumServer.TransactionReferenceService(references); transactionReferenceService.setOnSucceeded(successEvent -> { Map transactionMap = transactionReferenceService.getValue(); @@ -279,7 +378,7 @@ public class TransactionController implements Initializable { final BlockTransaction blockTx = thisBlockTx; Platform.runLater(() -> { - EventManager.get().post(new BlockTransactionFetchedEvent(transaction.getTxId(), blockTx, inputTransactions)); + EventManager.get().post(new BlockTransactionFetchedEvent(transaction.getTxId(), blockTx, inputTransactions, indexStart, maxIndex)); }); }); transactionReferenceService.setOnFailed(failedEvent -> { @@ -289,13 +388,14 @@ public class TransactionController implements Initializable { } } - private void fetchOutputBlockTransactions() { - if (AppController.isOnline() && psbt == null) { - ElectrumServer.TransactionOutputsReferenceService transactionOutputsReferenceService = new ElectrumServer.TransactionOutputsReferenceService(transaction); + private void fetchOutputBlockTransactions(int indexStart, int indexEnd) { + if(AppController.isOnline() && psbt == null && indexStart < transaction.getOutputs().size()) { + int maxIndex = Math.min(transaction.getOutputs().size(), indexEnd); + ElectrumServer.TransactionOutputsReferenceService transactionOutputsReferenceService = new ElectrumServer.TransactionOutputsReferenceService(transaction, indexStart, maxIndex); transactionOutputsReferenceService.setOnSucceeded(successEvent -> { List outputTransactions = transactionOutputsReferenceService.getValue(); Platform.runLater(() -> { - EventManager.get().post(new BlockTransactionOutputsFetchedEvent(transaction.getTxId(), outputTransactions)); + EventManager.get().post(new BlockTransactionOutputsFetchedEvent(transaction.getTxId(), outputTransactions, indexStart, maxIndex)); }); }); transactionOutputsReferenceService.setOnFailed(failedEvent -> { @@ -314,8 +414,15 @@ public class TransactionController implements Initializable { } private int addText(String hex, int cursor, int length, String styleClass) { - txhex.append(hex.substring(cursor, cursor += length), styleClass); - return cursor; + return addText(hex, cursor, length, styleClass, false); + } + + private int addText(String hex, int cursor, int length, String styleClass, boolean skip) { + if(!skip) { + txhex.append(hex.substring(cursor, cursor + length), styleClass); + } + + return cursor + length; } public void setTransaction(Transaction transaction) { @@ -332,6 +439,11 @@ public class TransactionController implements Initializable { this.blockTransaction = blockTransaction; } + public void setInitialView(TransactionView initialView, Integer initialIndex) { + this.initialView = initialView; + this.initialIndex = initialIndex; + } + @Subscribe public void transactionChanged(TransactionChangedEvent event) { if (event.getTransaction().equals(transaction)) { @@ -360,7 +472,11 @@ public class TransactionController implements Initializable { private void setBlockTransaction(TreeItem treeItem, BlockTransactionFetchedEvent event) { TransactionForm form = treeItem.getValue(); form.setBlockTransaction(event.getBlockTransaction()); - form.setInputTransactions(event.getInputTransactions()); + if(form.getInputTransactions() == null) { + form.setInputTransactions(event.getInputTransactions()); + } else { + form.getInputTransactions().putAll(event.getInputTransactions()); + } for (TreeItem childItem : treeItem.getChildren()) { setBlockTransaction(childItem, event); @@ -376,7 +492,16 @@ public class TransactionController implements Initializable { private void setBlockTransactionOutputs(TreeItem treeItem, BlockTransactionOutputsFetchedEvent event) { TransactionForm form = treeItem.getValue(); - form.setOutputTransactions(event.getOutputTransactions()); + if(form.getOutputTransactions() == null) { + form.setOutputTransactions(event.getOutputTransactions()); + } else { + for(int i = 0; i < event.getOutputTransactions().size(); i++) { + BlockTransaction outputTransaction = event.getOutputTransactions().get(i); + if(outputTransaction != null) { + form.getOutputTransactions().set(i, outputTransaction); + } + } + } for (TreeItem childItem : treeItem.getChildren()) { setBlockTransactionOutputs(childItem, event);