mirror of
https://github.com/sparrowwallet/sparrow.git
synced 2024-11-04 21:36:45 +00:00
transaction view perf improvements
This commit is contained in:
parent
7cc330fde9
commit
82ca9552bd
6 changed files with 212 additions and 150 deletions
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -122,7 +122,9 @@ public class HeadersController extends TransactionFormController implements Init
|
||||||
version.setValueFactory(new SpinnerValueFactory.IntegerSpinnerValueFactory(1, 2, (int)tx.getVersion()));
|
version.setValueFactory(new SpinnerValueFactory.IntegerSpinnerValueFactory(1, 2, (int)tx.getVersion()));
|
||||||
version.valueProperty().addListener((obs, oldValue, newValue) -> {
|
version.valueProperty().addListener((obs, oldValue, newValue) -> {
|
||||||
tx.setVersion(newValue);
|
tx.setVersion(newValue);
|
||||||
EventManager.get().post(new TransactionChangedEvent(tx));
|
if(oldValue != null) {
|
||||||
|
EventManager.get().post(new TransactionChangedEvent(tx));
|
||||||
|
}
|
||||||
});
|
});
|
||||||
version.setDisable(!headersForm.isEditable());
|
version.setDisable(!headersForm.isEditable());
|
||||||
|
|
||||||
|
@ -144,7 +146,9 @@ public class HeadersController extends TransactionFormController implements Init
|
||||||
locktimeFieldset.getChildren().remove(locktimeNoneField);
|
locktimeFieldset.getChildren().remove(locktimeNoneField);
|
||||||
locktimeFieldset.getChildren().add(locktimeNoneField);
|
locktimeFieldset.getChildren().add(locktimeNoneField);
|
||||||
tx.setLocktime(0);
|
tx.setLocktime(0);
|
||||||
EventManager.get().post(new TransactionChangedEvent(tx));
|
if(old_toggle != null) {
|
||||||
|
EventManager.get().post(new TransactionChangedEvent(tx));
|
||||||
|
}
|
||||||
} else if(selection.equals("block")) {
|
} else if(selection.equals("block")) {
|
||||||
locktimeFieldset.getChildren().remove(locktimeDateField);
|
locktimeFieldset.getChildren().remove(locktimeDateField);
|
||||||
locktimeFieldset.getChildren().remove(locktimeBlockField);
|
locktimeFieldset.getChildren().remove(locktimeBlockField);
|
||||||
|
@ -153,7 +157,9 @@ public class HeadersController extends TransactionFormController implements Init
|
||||||
Integer block = locktimeBlock.getValue();
|
Integer block = locktimeBlock.getValue();
|
||||||
if(block != null) {
|
if(block != null) {
|
||||||
tx.setLocktime(block);
|
tx.setLocktime(block);
|
||||||
EventManager.get().post(new TransactionChangedEvent(tx));
|
if(old_toggle != null) {
|
||||||
|
EventManager.get().post(new TransactionChangedEvent(tx));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
locktimeFieldset.getChildren().remove(locktimeBlockField);
|
locktimeFieldset.getChildren().remove(locktimeBlockField);
|
||||||
|
@ -164,7 +170,9 @@ public class HeadersController extends TransactionFormController implements Init
|
||||||
if(date != null) {
|
if(date != null) {
|
||||||
locktimeDate.setDateTimeValue(date);
|
locktimeDate.setDateTimeValue(date);
|
||||||
tx.setLocktime(date.toEpochSecond(OffsetDateTime.now(ZoneId.systemDefault()).getOffset()));
|
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) -> {
|
locktimeBlock.valueProperty().addListener((obs, oldValue, newValue) -> {
|
||||||
tx.setLocktime(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.setFormat(LOCKTIME_DATE_FORMAT);
|
||||||
locktimeDate.dateTimeValueProperty().addListener((obs, oldValue, newValue) -> {
|
locktimeDate.dateTimeValueProperty().addListener((obs, oldValue, newValue) -> {
|
||||||
tx.setLocktime(newValue.toEpochSecond(OffsetDateTime.now(ZoneId.systemDefault()).getOffset()));
|
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());
|
locktimeNoneType.setDisable(!headersForm.isEditable());
|
||||||
|
|
|
@ -322,12 +322,16 @@ public class InputController extends TransactionFormController implements Initia
|
||||||
locktimeToggleGroup.selectToggle(locktimeAbsoluteType);
|
locktimeToggleGroup.selectToggle(locktimeAbsoluteType);
|
||||||
} else if(txInput.isAbsoluteTimeLocked()) {
|
} else if(txInput.isAbsoluteTimeLocked()) {
|
||||||
txInput.setSequenceNumber(TransactionInput.SEQUENCE_RBF_ENABLED);
|
txInput.setSequenceNumber(TransactionInput.SEQUENCE_RBF_ENABLED);
|
||||||
EventManager.get().post(new TransactionChangedEvent(transaction));
|
if(oldValue != null) {
|
||||||
|
EventManager.get().post(new TransactionChangedEvent(transaction));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if(txInput.isAbsoluteTimeLocked()) {
|
if(txInput.isAbsoluteTimeLocked()) {
|
||||||
txInput.setSequenceNumber(TransactionInput.SEQUENCE_LOCKTIME_DISABLED - 1);
|
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()) {
|
} else if(txInput.isRelativeTimeLocked()) {
|
||||||
locktimeToggleGroup.selectToggle(locktimeAbsoluteType);
|
locktimeToggleGroup.selectToggle(locktimeAbsoluteType);
|
||||||
}
|
}
|
||||||
|
@ -348,7 +352,9 @@ public class InputController extends TransactionFormController implements Initia
|
||||||
locktimeAbsoluteField.setDisable(true);
|
locktimeAbsoluteField.setDisable(true);
|
||||||
txInput.setSequenceNumber(TransactionInput.SEQUENCE_LOCKTIME_DISABLED);
|
txInput.setSequenceNumber(TransactionInput.SEQUENCE_LOCKTIME_DISABLED);
|
||||||
rbf.setSelected(false);
|
rbf.setSelected(false);
|
||||||
EventManager.get().post(new TransactionChangedEvent(transaction));
|
if(old_toggle != null) {
|
||||||
|
EventManager.get().post(new TransactionChangedEvent(transaction));
|
||||||
|
}
|
||||||
} else if(selection.equals("absolute")) {
|
} else if(selection.equals("absolute")) {
|
||||||
locktimeFieldset.getChildren().removeAll(locktimeRelativeField, locktimeAbsoluteField);
|
locktimeFieldset.getChildren().removeAll(locktimeRelativeField, locktimeAbsoluteField);
|
||||||
locktimeFieldset.getChildren().add(locktimeAbsoluteField);
|
locktimeFieldset.getChildren().add(locktimeAbsoluteField);
|
||||||
|
@ -359,14 +365,16 @@ public class InputController extends TransactionFormController implements Initia
|
||||||
} else {
|
} else {
|
||||||
txInput.setSequenceNumber(TransactionInput.SEQUENCE_LOCKTIME_DISABLED - 1);
|
txInput.setSequenceNumber(TransactionInput.SEQUENCE_LOCKTIME_DISABLED - 1);
|
||||||
}
|
}
|
||||||
EventManager.get().post(new TransactionChangedEvent(transaction));
|
if(old_toggle != null) {
|
||||||
|
EventManager.get().post(new TransactionChangedEvent(transaction));
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
locktimeFieldset.getChildren().removeAll(locktimeRelativeField, locktimeAbsoluteField);
|
locktimeFieldset.getChildren().removeAll(locktimeRelativeField, locktimeAbsoluteField);
|
||||||
locktimeFieldset.getChildren().add(locktimeRelativeField);
|
locktimeFieldset.getChildren().add(locktimeRelativeField);
|
||||||
if(locktimeRelativeCombo.getValue() == null) {
|
if(locktimeRelativeCombo.getValue() == null) {
|
||||||
locktimeRelativeCombo.getSelectionModel().select(0);
|
locktimeRelativeCombo.getSelectionModel().select(0);
|
||||||
} else {
|
} else {
|
||||||
setRelativeLocktime(txInput, transaction);
|
setRelativeLocktime(txInput, transaction, old_toggle != null);
|
||||||
}
|
}
|
||||||
rbf.setSelected(true);
|
rbf.setSelected(true);
|
||||||
}
|
}
|
||||||
|
@ -380,7 +388,7 @@ public class InputController extends TransactionFormController implements Initia
|
||||||
boolean blocks = locktimeRelativeCombo.getValue().equals("blocks");
|
boolean blocks = locktimeRelativeCombo.getValue().equals("blocks");
|
||||||
locktimeRelativeSeconds.setVisible(!blocks);
|
locktimeRelativeSeconds.setVisible(!blocks);
|
||||||
locktimeRelativeBlocks.setVisible(blocks);
|
locktimeRelativeBlocks.setVisible(blocks);
|
||||||
setRelativeLocktime(txInput, transaction);
|
setRelativeLocktime(txInput, transaction, old_toggle != null);
|
||||||
});
|
});
|
||||||
|
|
||||||
locktimeRelativeType.setDisable(!transaction.isRelativeLocktimeAllowed());
|
locktimeRelativeType.setDisable(!transaction.isRelativeLocktimeAllowed());
|
||||||
|
@ -400,10 +408,10 @@ public class InputController extends TransactionFormController implements Initia
|
||||||
}
|
}
|
||||||
|
|
||||||
locktimeRelativeBlocks.valueProperty().addListener((obs, oldValue, newValue) -> {
|
locktimeRelativeBlocks.valueProperty().addListener((obs, oldValue, newValue) -> {
|
||||||
setRelativeLocktime(txInput, transaction);
|
setRelativeLocktime(txInput, transaction, oldValue != null);
|
||||||
});
|
});
|
||||||
locktimeRelativeSeconds.valueProperty().addListener((obs, oldValue, newValue) -> {
|
locktimeRelativeSeconds.valueProperty().addListener((obs, oldValue, newValue) -> {
|
||||||
setRelativeLocktime(txInput, transaction);
|
setRelativeLocktime(txInput, transaction, oldValue != null);
|
||||||
});
|
});
|
||||||
|
|
||||||
locktimeNoneType.setDisable(!inputForm.isEditable());
|
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();
|
String relativeSelection = locktimeRelativeCombo.getValue();
|
||||||
if(relativeSelection.equals("blocks")) {
|
if(relativeSelection.equals("blocks")) {
|
||||||
Integer value = locktimeRelativeBlocks.getValue();
|
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;
|
long value = locktimeRelativeSeconds.getValue().toSeconds() / TransactionInput.RELATIVE_TIMELOCK_SECONDS_INCREMENT;
|
||||||
txInput.setSequenceNumber((value & TransactionInput.RELATIVE_TIMELOCK_VALUE_MASK) | TransactionInput.RELATIVE_TIMELOCK_TYPE_FLAG);
|
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) {
|
public void setModel(InputForm form) {
|
||||||
|
|
|
@ -125,7 +125,8 @@ 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) {
|
||||||
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());
|
TransactionOutput output = inputTx.getTransaction().getOutputs().get((int)input.getOutpoint().getIndex());
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
package com.sparrowwallet.sparrow.transaction;
|
package com.sparrowwallet.sparrow.transaction;
|
||||||
|
|
||||||
import com.google.common.eventbus.Subscribe;
|
import com.google.common.eventbus.Subscribe;
|
||||||
import com.sparrowwallet.drongo.Utils;
|
|
||||||
import com.sparrowwallet.drongo.protocol.*;
|
import com.sparrowwallet.drongo.protocol.*;
|
||||||
import com.sparrowwallet.drongo.psbt.PSBT;
|
import com.sparrowwallet.drongo.psbt.PSBT;
|
||||||
import com.sparrowwallet.drongo.psbt.PSBTInput;
|
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.drongo.wallet.BlockTransaction;
|
||||||
import com.sparrowwallet.sparrow.AppController;
|
import com.sparrowwallet.sparrow.AppController;
|
||||||
import com.sparrowwallet.sparrow.EventManager;
|
import com.sparrowwallet.sparrow.EventManager;
|
||||||
|
import com.sparrowwallet.sparrow.control.TransactionHexArea;
|
||||||
import com.sparrowwallet.sparrow.event.*;
|
import com.sparrowwallet.sparrow.event.*;
|
||||||
import com.sparrowwallet.sparrow.io.ElectrumServer;
|
import com.sparrowwallet.sparrow.io.ElectrumServer;
|
||||||
import javafx.application.Platform;
|
import javafx.application.Platform;
|
||||||
|
@ -22,9 +22,7 @@ import javafx.scene.control.cell.TextFieldTreeCell;
|
||||||
import javafx.scene.layout.Pane;
|
import javafx.scene.layout.Pane;
|
||||||
import javafx.util.StringConverter;
|
import javafx.util.StringConverter;
|
||||||
import org.controlsfx.control.MasterDetailPane;
|
import org.controlsfx.control.MasterDetailPane;
|
||||||
import org.fxmisc.richtext.CodeArea;
|
|
||||||
|
|
||||||
import java.io.ByteArrayOutputStream;
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.net.URL;
|
import java.net.URL;
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
|
@ -44,7 +42,7 @@ public class TransactionController implements Initializable {
|
||||||
private Pane txpane;
|
private Pane txpane;
|
||||||
|
|
||||||
@FXML
|
@FXML
|
||||||
private CodeArea txhex;
|
private TransactionHexArea txhex;
|
||||||
|
|
||||||
private TransactionData txdata;
|
private TransactionData txdata;
|
||||||
|
|
||||||
|
@ -54,23 +52,18 @@ public class TransactionController implements Initializable {
|
||||||
private int selectedInputIndex = -1;
|
private int selectedInputIndex = -1;
|
||||||
private int selectedOutputIndex = -1;
|
private int selectedOutputIndex = -1;
|
||||||
|
|
||||||
private int highestInputIndex;
|
|
||||||
private int highestOutputIndex;
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void initialize(URL location, ResourceBundle resources) {
|
public void initialize(URL location, ResourceBundle resources) {
|
||||||
EventManager.get().register(this);
|
EventManager.get().register(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void initializeView() {
|
public void initializeView() {
|
||||||
highestInputIndex = Math.min(getTransaction().getInputs().size(), PageForm.PAGE_SIZE);
|
|
||||||
highestOutputIndex = Math.min(getTransaction().getOutputs().size(), PageForm.PAGE_SIZE);
|
|
||||||
|
|
||||||
initializeTxTree();
|
initializeTxTree();
|
||||||
transactionMasterDetail.setShowDetailNode(AppController.showTxHexProperty);
|
transactionMasterDetail.setShowDetailNode(AppController.showTxHexProperty);
|
||||||
refreshTxHex();
|
txhex.setTransaction(getTransaction());
|
||||||
fetchThisAndInputBlockTransactions(0, highestInputIndex);
|
highlightTxHex();
|
||||||
fetchOutputBlockTransactions(0, highestOutputIndex);
|
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) {
|
if(TransactionView.INPUT.equals(initialView) && initialIndex >= PageForm.PAGE_SIZE) {
|
||||||
fetchThisAndInputBlockTransactions(initialIndex, initialIndex + 1);
|
fetchThisAndInputBlockTransactions(initialIndex, initialIndex + 1);
|
||||||
|
@ -186,15 +179,12 @@ public class TransactionController implements Initializable {
|
||||||
}
|
}
|
||||||
|
|
||||||
if(pageForm.getView().equals(TransactionView.INPUT)) {
|
if(pageForm.getView().equals(TransactionView.INPUT)) {
|
||||||
highestInputIndex = Math.min(max, pageForm.getPageEnd());
|
|
||||||
fetchThisAndInputBlockTransactions(pageForm.getPageStart(), Math.min(max, pageForm.getPageEnd()));
|
fetchThisAndInputBlockTransactions(pageForm.getPageStart(), Math.min(max, pageForm.getPageEnd()));
|
||||||
} else {
|
} else {
|
||||||
highestOutputIndex = Math.min(max, pageForm.getPageEnd());
|
|
||||||
fetchOutputBlockTransactions(pageForm.getPageStart(), Math.min(max, pageForm.getPageEnd()));
|
fetchOutputBlockTransactions(pageForm.getPageStart(), Math.min(max, pageForm.getPageEnd()));
|
||||||
}
|
}
|
||||||
|
|
||||||
setTreeSelection(pageForm.getView(), pageForm.getPageStart());
|
setTreeSelection(pageForm.getView(), pageForm.getPageStart());
|
||||||
Platform.runLater(this::refreshTxHex);
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
Node detailPane = null;
|
Node detailPane = null;
|
||||||
|
@ -233,7 +223,7 @@ public class TransactionController implements Initializable {
|
||||||
selectedOutputIndex = outputForm.getTransactionOutput().getIndex();
|
selectedOutputIndex = outputForm.getTransactionOutput().getIndex();
|
||||||
}
|
}
|
||||||
|
|
||||||
Platform.runLater(this::refreshTxHex);
|
Platform.runLater(this::highlightTxHex);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -297,96 +287,8 @@ public class TransactionController implements Initializable {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
void refreshTxHex() {
|
void highlightTxHex() {
|
||||||
//TODO: Handle large transactions like efd513fffbbc2977c2d3933dfaab590b5cab5841ee791b3116e531ac9f8034ed better by not replacing text
|
txhex.applyHighlighting(getTransaction(), selectedInputIndex, selectedOutputIndex);
|
||||||
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());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void fetchThisAndInputBlockTransactions(int indexStart, int indexEnd) {
|
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() {
|
public Transaction getTransaction() {
|
||||||
return txdata.getTransaction();
|
return txdata.getTransaction();
|
||||||
}
|
}
|
||||||
|
@ -546,15 +428,12 @@ public class TransactionController implements Initializable {
|
||||||
}
|
}
|
||||||
|
|
||||||
if(event.getInitialView().equals(TransactionView.INPUT)) {
|
if(event.getInitialView().equals(TransactionView.INPUT)) {
|
||||||
highestInputIndex = Math.min(max, event.getInitialIndex());
|
|
||||||
fetchThisAndInputBlockTransactions(event.getInitialIndex(), event.getInitialIndex() + 1);
|
fetchThisAndInputBlockTransactions(event.getInitialIndex(), event.getInitialIndex() + 1);
|
||||||
} else {
|
} else {
|
||||||
highestOutputIndex = Math.min(max, event.getInitialIndex());
|
|
||||||
fetchOutputBlockTransactions(event.getInitialIndex(), event.getInitialIndex() + 1);
|
fetchOutputBlockTransactions(event.getInitialIndex(), event.getInitialIndex() + 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
setTreeSelection(event.getInitialView(), event.getInitialIndex());
|
setTreeSelection(event.getInitialView(), event.getInitialIndex());
|
||||||
Platform.runLater(this::refreshTxHex);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -562,7 +441,8 @@ public class TransactionController implements Initializable {
|
||||||
@Subscribe
|
@Subscribe
|
||||||
public void transactionChanged(TransactionChangedEvent event) {
|
public void transactionChanged(TransactionChangedEvent event) {
|
||||||
if(event.getTransaction().equals(getTransaction())) {
|
if(event.getTransaction().equals(getTransaction())) {
|
||||||
refreshTxHex();
|
txhex.setTransaction(getTransaction());
|
||||||
|
highlightTxHex();
|
||||||
txtree.refresh();
|
txtree.refresh();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,9 +2,9 @@
|
||||||
|
|
||||||
<?import javafx.scene.control.*?>
|
<?import javafx.scene.control.*?>
|
||||||
<?import javafx.scene.layout.*?>
|
<?import javafx.scene.layout.*?>
|
||||||
<?import org.fxmisc.richtext.*?>
|
|
||||||
<?import org.fxmisc.flowless.VirtualizedScrollPane?>
|
<?import org.fxmisc.flowless.VirtualizedScrollPane?>
|
||||||
<?import org.controlsfx.control.MasterDetailPane?>
|
<?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">
|
<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>
|
<items>
|
||||||
|
@ -22,7 +22,7 @@
|
||||||
<detailNode>
|
<detailNode>
|
||||||
<VirtualizedScrollPane>
|
<VirtualizedScrollPane>
|
||||||
<content>
|
<content>
|
||||||
<CodeArea fx:id="txhex" editable="false" wrapText="true" />
|
<TransactionHexArea fx:id="txhex" editable="false" wrapText="true" />
|
||||||
</content>
|
</content>
|
||||||
</VirtualizedScrollPane>
|
</VirtualizedScrollPane>
|
||||||
</detailNode>
|
</detailNode>
|
||||||
|
|
Loading…
Reference in a new issue