transaction view perf improvements

This commit is contained in:
Craig Raw 2020-06-16 15:55:55 +02:00
parent 7cc330fde9
commit 82ca9552bd
6 changed files with 212 additions and 150 deletions

View file

@ -0,0 +1,159 @@
package com.sparrowwallet.sparrow.control;
import com.sparrowwallet.drongo.Utils;
import com.sparrowwallet.drongo.protocol.*;
import org.fxmisc.richtext.CodeArea;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
public class TransactionHexArea extends CodeArea {
private static final int TRUNCATE_AT = 30000;
private List<TransactionSegment> previousSegmentList = new ArrayList<>();
public void setTransaction(Transaction transaction) {
try {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
transaction.bitcoinSerializeToStream(baos);
String hex = Utils.bytesToHex(baos.toByteArray());
if(hex.length() > TRUNCATE_AT) {
hex = hex.substring(0, TRUNCATE_AT);
hex += "[truncated]";
}
clear();
appendText(hex);
previousSegmentList = new ArrayList<>();
} catch (IOException e) {
throw new IllegalStateException("Can't happen");
}
}
public void applyHighlighting(Transaction transaction, int selectedInputIndex, int selectedOutputIndex) {
List<TransactionSegment> segments = getTransactionSegments(transaction, selectedInputIndex, selectedOutputIndex);
List<TransactionSegment> changedSegments = new ArrayList<>(segments);
changedSegments.removeAll(previousSegmentList);
for(TransactionSegment segment : changedSegments) {
if(segment.start < TRUNCATE_AT) {
setStyleClass(segment.start, Math.min(TRUNCATE_AT, segment.start + segment.length), segment.style);
}
}
previousSegmentList = segments;
}
public List<TransactionSegment> getTransactionSegments(Transaction transaction, int selectedInputIndex, int selectedOutputIndex) {
List<TransactionSegment> segments = new ArrayList<>();
int cursor = 0;
//Version
cursor = addSegment(segments, cursor, 8, "version");
if(transaction.hasWitnesses()) {
//Segwit marker
cursor = addSegment(segments, cursor, 2, "segwit-marker");
//Segwit flag
cursor = addSegment(segments, cursor, 2, "segwit-flag");
}
//Number of inputs
VarInt numInputs = new VarInt(transaction.getInputs().size());
cursor = addSegment(segments, cursor, numInputs.getSizeInBytes() * 2, "num-inputs");
//Inputs
for(int i = 0; i < transaction.getInputs().size(); i++) {
TransactionInput input = transaction.getInputs().get(i);
cursor = addSegment(segments, cursor, 32 * 2, "input-" + getIndexedStyleClass(i, selectedInputIndex, "hash"));
cursor = addSegment(segments, cursor, 4 * 2, "input-" + getIndexedStyleClass(i, selectedInputIndex, "index"));
VarInt scriptLen = new VarInt(input.getScriptBytes().length);
cursor = addSegment(segments, cursor, scriptLen.getSizeInBytes() * 2, "input-" + getIndexedStyleClass(i, selectedInputIndex, "sigscript-length"));
cursor = addSegment(segments, cursor, (int) scriptLen.value * 2, "input-" + getIndexedStyleClass(i, selectedInputIndex, "sigscript"));
cursor = addSegment(segments, cursor, 4 * 2, "input-" + getIndexedStyleClass(i, selectedInputIndex, "sequence"));
}
//Number of outputs
VarInt numOutputs = new VarInt(transaction.getOutputs().size());
cursor = addSegment(segments, cursor, numOutputs.getSizeInBytes() * 2, "num-outputs");
//Outputs
for(int i = 0; i < transaction.getOutputs().size(); i++) {
TransactionOutput output = transaction.getOutputs().get(i);
cursor = addSegment(segments, cursor, 8 * 2, "output-" + getIndexedStyleClass(i, selectedOutputIndex, "value"));
VarInt scriptLen = new VarInt(output.getScriptBytes().length);
cursor = addSegment(segments, cursor, scriptLen.getSizeInBytes() * 2, "output-" + getIndexedStyleClass(i, selectedOutputIndex, "pubkeyscript-length"));
cursor = addSegment(segments, cursor, (int) scriptLen.value * 2, "output-" + getIndexedStyleClass(i, selectedOutputIndex, "pubkeyscript"));
}
if(transaction.hasWitnesses()) {
for (int i = 0; i < transaction.getInputs().size(); i++) {
TransactionInput input = transaction.getInputs().get(i);
if (input.hasWitness()) {
TransactionWitness witness = input.getWitness();
VarInt witnessCount = new VarInt(witness.getPushCount());
cursor = addSegment(segments, cursor, witnessCount.getSizeInBytes() * 2, "witness-" + getIndexedStyleClass(i, selectedInputIndex, "count"));
for (byte[] push : witness.getPushes()) {
VarInt witnessLen = new VarInt(push.length);
cursor = addSegment(segments, cursor, witnessLen.getSizeInBytes() * 2, "witness-" + getIndexedStyleClass(i, selectedInputIndex, "length"));
cursor = addSegment(segments, cursor, (int) witnessLen.value * 2, "witness-" + getIndexedStyleClass(i, selectedInputIndex, "data"));
}
}
}
}
//Locktime
cursor = addSegment(segments, cursor, 8, "locktime");
if(cursor != getLength()) {
//throw new IllegalStateException("Cursor position does not match transaction serialisation " + cursor + ": " + getLength());
}
return segments;
}
private int addSegment(List<TransactionSegment> segments, int start, int length, String style) {
segments.add(new TransactionSegment(start, length, style));
return start + length;
}
private String getIndexedStyleClass(int iterableIndex, int selectedIndex, String styleClass) {
if (selectedIndex == -1 || selectedIndex == iterableIndex) {
return styleClass;
}
return "other";
}
private static class TransactionSegment {
public TransactionSegment(int start, int length, String style) {
this.start = start;
this.length = length;
this.style = style;
}
public int start;
public int length;
public String style;
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
TransactionSegment segment = (TransactionSegment) o;
return start == segment.start &&
length == segment.length &&
style.equals(segment.style);
}
@Override
public int hashCode() {
return Objects.hash(start, length, style);
}
}
}

View file

@ -122,7 +122,9 @@ public class HeadersController extends TransactionFormController implements Init
version.setValueFactory(new SpinnerValueFactory.IntegerSpinnerValueFactory(1, 2, (int)tx.getVersion()));
version.valueProperty().addListener((obs, oldValue, newValue) -> {
tx.setVersion(newValue);
EventManager.get().post(new TransactionChangedEvent(tx));
if(oldValue != null) {
EventManager.get().post(new TransactionChangedEvent(tx));
}
});
version.setDisable(!headersForm.isEditable());
@ -144,7 +146,9 @@ public class HeadersController extends TransactionFormController implements Init
locktimeFieldset.getChildren().remove(locktimeNoneField);
locktimeFieldset.getChildren().add(locktimeNoneField);
tx.setLocktime(0);
EventManager.get().post(new TransactionChangedEvent(tx));
if(old_toggle != null) {
EventManager.get().post(new TransactionChangedEvent(tx));
}
} else if(selection.equals("block")) {
locktimeFieldset.getChildren().remove(locktimeDateField);
locktimeFieldset.getChildren().remove(locktimeBlockField);
@ -153,7 +157,9 @@ public class HeadersController extends TransactionFormController implements Init
Integer block = locktimeBlock.getValue();
if(block != null) {
tx.setLocktime(block);
EventManager.get().post(new TransactionChangedEvent(tx));
if(old_toggle != null) {
EventManager.get().post(new TransactionChangedEvent(tx));
}
}
} else {
locktimeFieldset.getChildren().remove(locktimeBlockField);
@ -164,7 +170,9 @@ public class HeadersController extends TransactionFormController implements Init
if(date != null) {
locktimeDate.setDateTimeValue(date);
tx.setLocktime(date.toEpochSecond(OffsetDateTime.now(ZoneId.systemDefault()).getOffset()));
EventManager.get().post(new TransactionChangedEvent(tx));
if(old_toggle != null) {
EventManager.get().post(new TransactionChangedEvent(tx));
}
}
}
}
@ -189,13 +197,17 @@ public class HeadersController extends TransactionFormController implements Init
locktimeBlock.valueProperty().addListener((obs, oldValue, newValue) -> {
tx.setLocktime(newValue);
EventManager.get().post(new TransactionChangedEvent(tx));
if(oldValue != null) {
EventManager.get().post(new TransactionChangedEvent(tx));
}
});
locktimeDate.setFormat(LOCKTIME_DATE_FORMAT);
locktimeDate.dateTimeValueProperty().addListener((obs, oldValue, newValue) -> {
tx.setLocktime(newValue.toEpochSecond(OffsetDateTime.now(ZoneId.systemDefault()).getOffset()));
EventManager.get().post(new TransactionChangedEvent(tx));
if(oldValue != null) {
EventManager.get().post(new TransactionChangedEvent(tx));
}
});
locktimeNoneType.setDisable(!headersForm.isEditable());

View file

@ -322,12 +322,16 @@ public class InputController extends TransactionFormController implements Initia
locktimeToggleGroup.selectToggle(locktimeAbsoluteType);
} else if(txInput.isAbsoluteTimeLocked()) {
txInput.setSequenceNumber(TransactionInput.SEQUENCE_RBF_ENABLED);
EventManager.get().post(new TransactionChangedEvent(transaction));
if(oldValue != null) {
EventManager.get().post(new TransactionChangedEvent(transaction));
}
}
} else {
if(txInput.isAbsoluteTimeLocked()) {
txInput.setSequenceNumber(TransactionInput.SEQUENCE_LOCKTIME_DISABLED - 1);
EventManager.get().post(new TransactionChangedEvent(transaction));
if(oldValue != null) {
EventManager.get().post(new TransactionChangedEvent(transaction));
}
} else if(txInput.isRelativeTimeLocked()) {
locktimeToggleGroup.selectToggle(locktimeAbsoluteType);
}
@ -348,7 +352,9 @@ public class InputController extends TransactionFormController implements Initia
locktimeAbsoluteField.setDisable(true);
txInput.setSequenceNumber(TransactionInput.SEQUENCE_LOCKTIME_DISABLED);
rbf.setSelected(false);
EventManager.get().post(new TransactionChangedEvent(transaction));
if(old_toggle != null) {
EventManager.get().post(new TransactionChangedEvent(transaction));
}
} else if(selection.equals("absolute")) {
locktimeFieldset.getChildren().removeAll(locktimeRelativeField, locktimeAbsoluteField);
locktimeFieldset.getChildren().add(locktimeAbsoluteField);
@ -359,14 +365,16 @@ public class InputController extends TransactionFormController implements Initia
} else {
txInput.setSequenceNumber(TransactionInput.SEQUENCE_LOCKTIME_DISABLED - 1);
}
EventManager.get().post(new TransactionChangedEvent(transaction));
if(old_toggle != null) {
EventManager.get().post(new TransactionChangedEvent(transaction));
}
} else {
locktimeFieldset.getChildren().removeAll(locktimeRelativeField, locktimeAbsoluteField);
locktimeFieldset.getChildren().add(locktimeRelativeField);
if(locktimeRelativeCombo.getValue() == null) {
locktimeRelativeCombo.getSelectionModel().select(0);
} else {
setRelativeLocktime(txInput, transaction);
setRelativeLocktime(txInput, transaction, old_toggle != null);
}
rbf.setSelected(true);
}
@ -380,7 +388,7 @@ public class InputController extends TransactionFormController implements Initia
boolean blocks = locktimeRelativeCombo.getValue().equals("blocks");
locktimeRelativeSeconds.setVisible(!blocks);
locktimeRelativeBlocks.setVisible(blocks);
setRelativeLocktime(txInput, transaction);
setRelativeLocktime(txInput, transaction, old_toggle != null);
});
locktimeRelativeType.setDisable(!transaction.isRelativeLocktimeAllowed());
@ -400,10 +408,10 @@ public class InputController extends TransactionFormController implements Initia
}
locktimeRelativeBlocks.valueProperty().addListener((obs, oldValue, newValue) -> {
setRelativeLocktime(txInput, transaction);
setRelativeLocktime(txInput, transaction, oldValue != null);
});
locktimeRelativeSeconds.valueProperty().addListener((obs, oldValue, newValue) -> {
setRelativeLocktime(txInput, transaction);
setRelativeLocktime(txInput, transaction, oldValue != null);
});
locktimeNoneType.setDisable(!inputForm.isEditable());
@ -426,7 +434,7 @@ public class InputController extends TransactionFormController implements Initia
}
}
private void setRelativeLocktime(TransactionInput txInput, Transaction transaction) {
private void setRelativeLocktime(TransactionInput txInput, Transaction transaction, boolean changed) {
String relativeSelection = locktimeRelativeCombo.getValue();
if(relativeSelection.equals("blocks")) {
Integer value = locktimeRelativeBlocks.getValue();
@ -435,7 +443,9 @@ public class InputController extends TransactionFormController implements Initia
long value = locktimeRelativeSeconds.getValue().toSeconds() / TransactionInput.RELATIVE_TIMELOCK_SECONDS_INCREMENT;
txInput.setSequenceNumber((value & TransactionInput.RELATIVE_TIMELOCK_VALUE_MASK) | TransactionInput.RELATIVE_TIMELOCK_TYPE_FLAG);
}
EventManager.get().post(new TransactionChangedEvent(transaction));
if(changed) {
EventManager.get().post(new TransactionChangedEvent(transaction));
}
}
public void setModel(InputForm form) {

View file

@ -125,7 +125,8 @@ public class InputsController extends TransactionFormController implements Initi
} else {
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;
}
TransactionOutput output = inputTx.getTransaction().getOutputs().get((int)input.getOutpoint().getIndex());

View file

@ -1,7 +1,6 @@
package com.sparrowwallet.sparrow.transaction;
import com.google.common.eventbus.Subscribe;
import com.sparrowwallet.drongo.Utils;
import com.sparrowwallet.drongo.protocol.*;
import com.sparrowwallet.drongo.psbt.PSBT;
import com.sparrowwallet.drongo.psbt.PSBTInput;
@ -9,6 +8,7 @@ import com.sparrowwallet.drongo.psbt.PSBTOutput;
import com.sparrowwallet.drongo.wallet.BlockTransaction;
import com.sparrowwallet.sparrow.AppController;
import com.sparrowwallet.sparrow.EventManager;
import com.sparrowwallet.sparrow.control.TransactionHexArea;
import com.sparrowwallet.sparrow.event.*;
import com.sparrowwallet.sparrow.io.ElectrumServer;
import javafx.application.Platform;
@ -22,9 +22,7 @@ import javafx.scene.control.cell.TextFieldTreeCell;
import javafx.scene.layout.Pane;
import javafx.util.StringConverter;
import org.controlsfx.control.MasterDetailPane;
import org.fxmisc.richtext.CodeArea;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.net.URL;
import java.util.*;
@ -44,7 +42,7 @@ public class TransactionController implements Initializable {
private Pane txpane;
@FXML
private CodeArea txhex;
private TransactionHexArea txhex;
private TransactionData txdata;
@ -54,23 +52,18 @@ public class TransactionController implements Initializable {
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);
}
public void initializeView() {
highestInputIndex = Math.min(getTransaction().getInputs().size(), PageForm.PAGE_SIZE);
highestOutputIndex = Math.min(getTransaction().getOutputs().size(), PageForm.PAGE_SIZE);
initializeTxTree();
transactionMasterDetail.setShowDetailNode(AppController.showTxHexProperty);
refreshTxHex();
fetchThisAndInputBlockTransactions(0, highestInputIndex);
fetchOutputBlockTransactions(0, highestOutputIndex);
txhex.setTransaction(getTransaction());
highlightTxHex();
fetchThisAndInputBlockTransactions(0, Math.min(getTransaction().getInputs().size(), PageForm.PAGE_SIZE));
fetchOutputBlockTransactions(0, Math.min(getTransaction().getOutputs().size(), PageForm.PAGE_SIZE));
if(TransactionView.INPUT.equals(initialView) && initialIndex >= PageForm.PAGE_SIZE) {
fetchThisAndInputBlockTransactions(initialIndex, initialIndex + 1);
@ -186,15 +179,12 @@ public class TransactionController implements Initializable {
}
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(this::refreshTxHex);
}
} else {
Node detailPane = null;
@ -233,7 +223,7 @@ public class TransactionController implements Initializable {
selectedOutputIndex = outputForm.getTransactionOutput().getIndex();
}
Platform.runLater(this::refreshTxHex);
Platform.runLater(this::highlightTxHex);
}
}
});
@ -297,96 +287,8 @@ public class TransactionController implements Initializable {
return null;
}
void refreshTxHex() {
//TODO: Handle large transactions like efd513fffbbc2977c2d3933dfaab590b5cab5841ee791b3116e531ac9f8034ed better by not replacing text
txhex.clear();
String hex = "";
try {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
getTransaction().bitcoinSerializeToStream(baos);
hex = Utils.bytesToHex(baos.toByteArray());
} catch (IOException e) {
throw new IllegalStateException("Can't happen");
}
int cursor = 0;
//Version
cursor = addText(hex, cursor, 8, "version");
if(getTransaction().hasWitnesses()) {
//Segwit marker
cursor = addText(hex, cursor, 2, "segwit-marker");
//Segwit flag
cursor = addText(hex, cursor, 2, "segwit-flag");
}
//Number of inputs
VarInt numInputs = new VarInt(getTransaction().getInputs().size());
cursor = addText(hex, cursor, numInputs.getSizeInBytes() * 2, "num-inputs");
//Inputs
for(int i = 0; i < getTransaction().getInputs().size(); i++) {
if(i == highestInputIndex) {
txhex.append("...", "");
}
TransactionInput input = getTransaction().getInputs().get(i);
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"), 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
VarInt numOutputs = new VarInt(getTransaction().getOutputs().size());
cursor = addText(hex, cursor, numOutputs.getSizeInBytes() * 2, "num-outputs");
//Outputs
for(int i = 0; i < getTransaction().getOutputs().size(); i++) {
if(i == highestOutputIndex) {
txhex.append("...", "");
}
TransactionOutput output = getTransaction().getOutputs().get(i);
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"), skip);
cursor = addText(hex, cursor, (int) scriptLen.value * 2, "output-" + getIndexedStyleClass(i, selectedOutputIndex, "pubkeyscript"), skip);
}
if(getTransaction().hasWitnesses()) {
for (int i = 0; i < getTransaction().getInputs().size(); i++) {
if(i == highestInputIndex) {
txhex.append("...", "");
}
TransactionInput input = getTransaction().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"), skip);
for (byte[] push : witness.getPushes()) {
VarInt witnessLen = new VarInt(push.length);
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);
}
}
}
}
//Locktime
cursor = addText(hex, cursor, 8, "locktime");
if(cursor != hex.length()) {
throw new IllegalStateException("Cursor position does not match transaction serialisation " + cursor + ": " + hex.length());
}
void highlightTxHex() {
txhex.applyHighlighting(getTransaction(), selectedInputIndex, selectedOutputIndex);
}
private void fetchThisAndInputBlockTransactions(int indexStart, int indexEnd) {
@ -458,26 +360,6 @@ public class TransactionController implements Initializable {
}
}
private String getIndexedStyleClass(int iterableIndex, int selectedIndex, String styleClass) {
if (selectedIndex == -1 || selectedIndex == iterableIndex) {
return styleClass;
}
return "other";
}
private int addText(String hex, int cursor, int length, String styleClass) {
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 Transaction getTransaction() {
return txdata.getTransaction();
}
@ -546,15 +428,12 @@ public class TransactionController implements Initializable {
}
if(event.getInitialView().equals(TransactionView.INPUT)) {
highestInputIndex = Math.min(max, event.getInitialIndex());
fetchThisAndInputBlockTransactions(event.getInitialIndex(), event.getInitialIndex() + 1);
} else {
highestOutputIndex = Math.min(max, event.getInitialIndex());
fetchOutputBlockTransactions(event.getInitialIndex(), event.getInitialIndex() + 1);
}
setTreeSelection(event.getInitialView(), event.getInitialIndex());
Platform.runLater(this::refreshTxHex);
}
}
}
@ -562,7 +441,8 @@ public class TransactionController implements Initializable {
@Subscribe
public void transactionChanged(TransactionChangedEvent event) {
if(event.getTransaction().equals(getTransaction())) {
refreshTxHex();
txhex.setTransaction(getTransaction());
highlightTxHex();
txtree.refresh();
}
}

View file

@ -2,9 +2,9 @@
<?import javafx.scene.control.*?>
<?import javafx.scene.layout.*?>
<?import org.fxmisc.richtext.*?>
<?import org.fxmisc.flowless.VirtualizedScrollPane?>
<?import org.controlsfx.control.MasterDetailPane?>
<?import com.sparrowwallet.sparrow.control.TransactionHexArea?>
<SplitPane fx:id="tabContent" dividerPositions="0.82" orientation="VERTICAL" stylesheets="@transaction.css" VBox.vgrow="ALWAYS" xmlns="http://javafx.com/javafx/10.0.2-internal" xmlns:fx="http://javafx.com/fxml/1" fx:controller="com.sparrowwallet.sparrow.transaction.TransactionController">
<items>
@ -22,7 +22,7 @@
<detailNode>
<VirtualizedScrollPane>
<content>
<CodeArea fx:id="txhex" editable="false" wrapText="true" />
<TransactionHexArea fx:id="txhex" editable="false" wrapText="true" />
</content>
</VirtualizedScrollPane>
</detailNode>