mirror of
https://github.com/bitcoinj/bitcoinj.git
synced 2025-02-22 14:22:45 +01:00
Transaction: when serializing a transaction re-use buffer as much as possible
This introduces a ByteBuffer-based `write()`, which calls `write()` on TransactionInput, TransactionOutput, etc. Allocation of intermediate buffers is avoided. This is used for getTxId(), getWTxId() for faster id generation. Subclass `TransactionTest.HugeDeclaredSizeTransaction` is also changed to `write()`.
This commit is contained in:
parent
85d9d3e881
commit
e20e5948dd
2 changed files with 97 additions and 49 deletions
|
@ -53,6 +53,7 @@ import java.io.IOException;
|
|||
import java.io.OutputStream;
|
||||
import java.math.BigInteger;
|
||||
import java.math.RoundingMode;
|
||||
import java.nio.BufferOverflowException;
|
||||
import java.nio.BufferUnderflowException;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.time.Instant;
|
||||
|
@ -337,13 +338,9 @@ public class Transaction extends BaseMessage {
|
|||
if (!hasWitnesses() && cachedWTxId != null) {
|
||||
cachedTxId = cachedWTxId;
|
||||
} else {
|
||||
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
||||
try {
|
||||
bitcoinSerializeToStream(baos, false);
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e); // cannot happen
|
||||
}
|
||||
cachedTxId = Sha256Hash.wrapReversed(Sha256Hash.hashTwice(baos.toByteArray()));
|
||||
ByteBuffer buf = ByteBuffer.allocate(messageSize(false));
|
||||
write(buf, false);
|
||||
cachedTxId = Sha256Hash.wrapReversed(Sha256Hash.hashTwice(buf.array()));
|
||||
}
|
||||
}
|
||||
return cachedTxId;
|
||||
|
@ -368,13 +365,10 @@ public class Transaction extends BaseMessage {
|
|||
if (!hasWitnesses() && cachedTxId != null) {
|
||||
cachedWTxId = cachedTxId;
|
||||
} else {
|
||||
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
||||
try {
|
||||
bitcoinSerializeToStream(baos, hasWitnesses());
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e); // cannot happen
|
||||
}
|
||||
cachedWTxId = Sha256Hash.wrapReversed(Sha256Hash.hashTwice(baos.toByteArray()));
|
||||
boolean useWitness = hasWitnesses();
|
||||
ByteBuffer buf = ByteBuffer.allocate(messageSize(useWitness));
|
||||
write(buf, useWitness);
|
||||
cachedWTxId = Sha256Hash.wrapReversed(Sha256Hash.hashTwice(buf.array()));
|
||||
}
|
||||
}
|
||||
return cachedWTxId;
|
||||
|
@ -1479,6 +1473,10 @@ public class Transaction extends BaseMessage {
|
|||
@Override
|
||||
public int messageSize() {
|
||||
boolean useSegwit = hasWitnesses() && allowWitness(protocolVersion);
|
||||
return messageSize(useSegwit);
|
||||
}
|
||||
|
||||
protected int messageSize(boolean useSegwit) {
|
||||
int size = 4; // version
|
||||
if (useSegwit)
|
||||
size += 2; // marker, flag
|
||||
|
@ -1505,30 +1503,43 @@ public class Transaction extends BaseMessage {
|
|||
* Serialize according to <a href="https://github.com/bitcoin/bips/blob/master/bip-0144.mediawiki">BIP144</a> or the
|
||||
* <a href="https://en.bitcoin.it/wiki/Protocol_documentation#tx">classic format</a>, depending on if segwit is
|
||||
* desired.
|
||||
* <p>
|
||||
* Write this transaction into the given buffer.
|
||||
*
|
||||
* @param buf buffer to write into
|
||||
* @return the buffer
|
||||
* @throws BufferOverflowException if the transaction doesn't fit the remaining buffer
|
||||
*/
|
||||
protected void bitcoinSerializeToStream(OutputStream stream, boolean useSegwit) throws IOException {
|
||||
protected ByteBuffer write(ByteBuffer buf, boolean useSegwit) throws BufferOverflowException {
|
||||
// version
|
||||
writeInt32LE(version, stream);
|
||||
ByteUtils.writeInt32LE(version, buf);
|
||||
// marker, flag
|
||||
if (useSegwit) {
|
||||
stream.write(0);
|
||||
stream.write(1);
|
||||
buf.put((byte) 0);
|
||||
buf.put((byte) 1);
|
||||
}
|
||||
// txin_count, txins
|
||||
stream.write(VarInt.of(inputs.size()).serialize());
|
||||
VarInt.of(inputs.size()).write(buf);
|
||||
for (TransactionInput in : inputs)
|
||||
stream.write(in.serialize());
|
||||
in.write(buf);
|
||||
// txout_count, txouts
|
||||
stream.write(VarInt.of(outputs.size()).serialize());
|
||||
VarInt.of(outputs.size()).write(buf);
|
||||
for (TransactionOutput out : outputs)
|
||||
stream.write(out.serialize());
|
||||
out.write(buf);
|
||||
// script_witnisses
|
||||
if (useSegwit) {
|
||||
for (TransactionInput in : inputs)
|
||||
stream.write(in.getWitness().serialize());
|
||||
in.getWitness().write(buf);
|
||||
}
|
||||
// lock_time
|
||||
writeInt32LE(vLockTime.rawValue(), stream);
|
||||
ByteUtils.writeInt32LE(vLockTime.rawValue(), buf);
|
||||
return buf;
|
||||
}
|
||||
|
||||
private void bitcoinSerializeToStream(OutputStream stream, boolean useSegwit) throws IOException {
|
||||
ByteBuffer buf = ByteBuffer.allocate(messageSize(useSegwit));
|
||||
write(buf, useSegwit);
|
||||
stream.write(buf.array());
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -42,6 +42,7 @@ import org.junit.Test;
|
|||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
import java.math.BigInteger;
|
||||
import java.nio.BufferOverflowException;
|
||||
import java.nio.BufferUnderflowException;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.time.Instant;
|
||||
|
@ -725,41 +726,77 @@ public class TransactionTest {
|
|||
}
|
||||
|
||||
@Override
|
||||
protected void bitcoinSerializeToStream(OutputStream stream, boolean useSegwit) throws IOException {
|
||||
// version
|
||||
writeInt32LE(getVersion(), stream);
|
||||
// marker, flag
|
||||
protected int messageSize(boolean useSegwit) {
|
||||
int size = 4; // version
|
||||
if (useSegwit)
|
||||
size += 2; // marker, flag
|
||||
List<TransactionInput> inputs = getInputs();
|
||||
long inputsSize = hackInputsSize ? Integer.MAX_VALUE : inputs.size();
|
||||
size += VarInt.sizeOf(inputsSize);
|
||||
for (TransactionInput in : inputs)
|
||||
size += in.messageSize();
|
||||
List<TransactionOutput> outputs = getOutputs();
|
||||
long outputsSize = hackOutputsSize ? Integer.MAX_VALUE : outputs.size();
|
||||
size += VarInt.sizeOf(outputsSize);
|
||||
for (TransactionOutput out : outputs)
|
||||
size += out.messageSize();
|
||||
if (useSegwit) {
|
||||
stream.write(0);
|
||||
stream.write(1);
|
||||
}
|
||||
// txin_count, txins
|
||||
long inputsSize = hackInputsSize ? Integer.MAX_VALUE : getInputs().size();
|
||||
stream.write(VarInt.of(inputsSize).serialize());
|
||||
for (TransactionInput in : getInputs())
|
||||
stream.write(in.serialize());
|
||||
// txout_count, txouts
|
||||
long outputsSize = hackOutputsSize ? Integer.MAX_VALUE : getOutputs().size();
|
||||
stream.write(VarInt.of(outputsSize).serialize());
|
||||
for (TransactionOutput out : getOutputs())
|
||||
stream.write(out.serialize());
|
||||
// script_witnisses
|
||||
if (useSegwit) {
|
||||
for (TransactionInput in : getInputs()) {
|
||||
for (TransactionInput in : inputs) {
|
||||
TransactionWitness witness = in.getWitness();
|
||||
long pushCount = hackWitnessPushCountSize ? Integer.MAX_VALUE : witness.getPushCount();
|
||||
stream.write(VarInt.of(pushCount).serialize());
|
||||
size += VarInt.sizeOf(pushCount);
|
||||
for (int i = 0; i < witness.getPushCount(); i++) {
|
||||
byte[] push = witness.getPush(i);
|
||||
stream.write(VarInt.of(push.length).serialize());
|
||||
stream.write(push);
|
||||
size += VarInt.sizeOf(push.length);
|
||||
size += push.length;
|
||||
}
|
||||
|
||||
stream.write(in.getWitness().serialize());
|
||||
size += witness.messageSize();
|
||||
}
|
||||
}
|
||||
size += 4; // locktime
|
||||
return size;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected ByteBuffer write(ByteBuffer buf, boolean useSegwit) throws BufferOverflowException {
|
||||
// version
|
||||
ByteUtils.writeInt32LE(getVersion(), buf);
|
||||
// marker, flag
|
||||
if (useSegwit) {
|
||||
buf.put((byte) 0);
|
||||
buf.put((byte) 1);
|
||||
}
|
||||
// txin_count, txins
|
||||
List<TransactionInput> inputs = getInputs();
|
||||
long inputsSize = hackInputsSize ? Integer.MAX_VALUE : inputs.size();
|
||||
VarInt.of(inputsSize).write(buf);
|
||||
for (TransactionInput in : inputs)
|
||||
in.write(buf);
|
||||
// txout_count, txouts
|
||||
List<TransactionOutput> outputs = getOutputs();
|
||||
long outputsSize = hackOutputsSize ? Integer.MAX_VALUE : outputs.size();
|
||||
VarInt.of(outputsSize).write(buf);
|
||||
for (TransactionOutput out : outputs)
|
||||
out.write(buf);
|
||||
// script_witnisses
|
||||
if (useSegwit) {
|
||||
for (TransactionInput in : inputs) {
|
||||
TransactionWitness witness = in.getWitness();
|
||||
long pushCount = hackWitnessPushCountSize ? Integer.MAX_VALUE : witness.getPushCount();
|
||||
VarInt.of(pushCount).write(buf);
|
||||
for (int i = 0; i < witness.getPushCount(); i++) {
|
||||
byte[] push = witness.getPush(i);
|
||||
VarInt.of(push.length).write(buf);
|
||||
buf.put(push);
|
||||
}
|
||||
|
||||
witness.write(buf);
|
||||
}
|
||||
}
|
||||
// lock_time
|
||||
writeInt32LE(lockTime().rawValue(), stream);
|
||||
ByteUtils.writeInt32LE(lockTime().rawValue(), buf);
|
||||
return buf;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
Loading…
Add table
Reference in a new issue