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
@@ -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);
}
}
}