TransactionOutPoint: divorce from Message

It is never sent on its own, so it doesn't need to be a `Message`.

* A static constructur `read()` replaces the native constructor that deserialized
  from a payload.
* A `write()` helper replace `bitcoinSerializeToStream()`.
* A `serialize()` helper replace `bitcoinSerialize()`.
* A `BYTES` constant replaces `getMessageSize()`.

This comes with a test.
This commit is contained in:
Andreas Schildbach 2023-04-03 14:50:31 +02:00
parent 4248804f82
commit fbc5185b5a
7 changed files with 111 additions and 34 deletions

View File

@ -257,7 +257,7 @@ public class BloomFilter extends Message {
/** Inserts the given transaction outpoint. */
public synchronized void insert(TransactionOutPoint outpoint) {
insert(outpoint.bitcoinSerialize());
insert(outpoint.serialize());
}
/**
@ -359,7 +359,7 @@ public class BloomFilter extends Message {
}
if (found) return true;
for (TransactionInput input : tx.getInputs()) {
if (contains(input.getOutpoint().bitcoinSerialize())) {
if (contains(input.getOutpoint().serialize())) {
return true;
}
for (ScriptChunk chunk : input.getScriptSig().getChunks()) {

View File

@ -682,7 +682,7 @@ public class Transaction extends Message {
TransactionInput input = new TransactionInput(this, payload.slice());
inputs.add(input);
// intentionally read again, due to the slice above
Buffers.skipBytes(payload, TransactionOutPoint.MESSAGE_LENGTH);
Buffers.skipBytes(payload, TransactionOutPoint.BYTES);
VarInt scriptLenVarInt = VarInt.read(payload);
int scriptLen = scriptLenVarInt.intValue();
Buffers.skipBytes(payload, scriptLen + 4);

View File

@ -159,14 +159,14 @@ public class TransactionInput extends Message {
@Override
protected void parse(ByteBuffer payload) throws BufferUnderflowException, ProtocolException {
outpoint = new TransactionOutPoint(payload);
outpoint = TransactionOutPoint.read(payload);
scriptBytes = Buffers.readLengthPrefixedBytes(payload);
sequence = ByteUtils.readUint32(payload);
}
@Override
public int getMessageSize() {
int size = outpoint.getMessageSize();
int size = TransactionOutPoint.BYTES;
size += VarInt.sizeOf(scriptBytes.length) + scriptBytes.length;
size += 4; // sequence
return size;
@ -174,7 +174,7 @@ public class TransactionInput extends Message {
@Override
protected void bitcoinSerializeToStream(OutputStream stream) throws IOException {
outpoint.bitcoinSerializeToStream(stream);
stream.write(outpoint.serialize());
stream.write(VarInt.of(scriptBytes.length).serialize());
stream.write(scriptBytes);
ByteUtils.writeInt32LE(sequence, stream);

View File

@ -29,8 +29,8 @@ import org.bitcoinj.wallet.KeyBag;
import org.bitcoinj.wallet.RedeemData;
import javax.annotation.Nullable;
import java.io.IOException;
import java.io.OutputStream;
import java.nio.Buffer;
import java.nio.BufferOverflowException;
import java.nio.BufferUnderflowException;
import java.nio.ByteBuffer;
import java.util.Objects;
@ -43,9 +43,8 @@ import static org.bitcoinj.base.internal.Preconditions.checkState;
*
* <p>Instances of this class are not safe for use by multiple threads.</p>
*/
public class TransactionOutPoint extends Message {
static final int MESSAGE_LENGTH = 36;
public class TransactionOutPoint {
public static final int BYTES = 36;
/** Special outpoint that normally marks a coinbase input. It's also used as a test dummy. */
public static final TransactionOutPoint UNCONNECTED =
@ -62,6 +61,19 @@ public class TransactionOutPoint extends Message {
// The connected output.
TransactionOutput connectedOutput;
/**
* Deserialize this transaction outpoint from a given payload.
*
* @param payload payload to deserialize from
* @return read transaction outpoint
* @throws BufferUnderflowException if the read message extends beyond the remaining bytes of the payload
*/
public static TransactionOutPoint read(ByteBuffer payload) throws BufferUnderflowException, ProtocolException {
Sha256Hash hash = Sha256Hash.read(payload);
long index = ByteUtils.readUint32(payload);
return new TransactionOutPoint(index, hash);
}
public TransactionOutPoint(long index, Transaction fromTx) {
super();
checkArgument(index >= 0 && index <= ByteUtils.MAX_UNSIGNED_INTEGER, () ->
@ -85,28 +97,37 @@ public class TransactionOutPoint extends Message {
}
/**
* Deserializes the message. This is usually part of a transaction message.
* @throws ProtocolException
* Write this transaction outpoint into the given buffer.
*
* @param buf buffer to write into
* @return the buffer
* @throws BufferOverflowException if the outpoint doesn't fit the remaining buffer
*/
public TransactionOutPoint(ByteBuffer payload) throws ProtocolException {
super(payload);
public ByteBuffer write(ByteBuffer buf) throws BufferOverflowException {
buf.put(hash.serialize());
ByteUtils.writeInt32LE(index, buf);
return buf;
}
@Override
protected void parse(ByteBuffer payload) throws BufferUnderflowException, ProtocolException {
hash = Sha256Hash.read(payload);
index = ByteUtils.readUint32(payload);
/**
* Allocates a byte array and writes this transaction outpoint into it.
*
* @return byte array containing the transaction outpoint
*/
public byte[] serialize() {
return write(ByteBuffer.allocate(BYTES)).array();
}
@Override
/** @deprecated use {@link #serialize()} */
@Deprecated
public byte[] bitcoinSerialize() {
return serialize();
}
/** @deprecated use {@link #BYTES} */
@Deprecated
public int getMessageSize() {
return MESSAGE_LENGTH;
}
@Override
protected void bitcoinSerializeToStream(OutputStream stream) throws IOException {
stream.write(hash.serialize());
ByteUtils.writeInt32LE(index, stream);
return BYTES;
}
/**

View File

@ -0,0 +1,56 @@
/*
* Copyright by the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.bitcoinj.core;
import junitparams.JUnitParamsRunner;
import junitparams.Parameters;
import org.bitcoinj.base.Sha256Hash;
import org.junit.Test;
import org.junit.runner.RunWith;
import java.nio.Buffer;
import java.nio.ByteBuffer;
import java.util.Iterator;
import java.util.Random;
import java.util.stream.Stream;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
@RunWith(JUnitParamsRunner.class)
public class TransactionOutPointTest {
@Test
@Parameters(method = "randomOutPoints")
public void readAndWrite(TransactionOutPoint outpoint) {
ByteBuffer buf = ByteBuffer.allocate(TransactionOutPoint.BYTES);
outpoint.write(buf);
assertFalse(buf.hasRemaining());
((Buffer) buf).rewind();
TransactionOutPoint outpointCopy = TransactionOutPoint.read(buf);
assertFalse(buf.hasRemaining());
assertEquals(outpoint, outpointCopy);
}
private Iterator<TransactionOutPoint> randomOutPoints() {
Random random = new Random();
return Stream.generate(() -> {
byte[] randomBytes = new byte[Sha256Hash.LENGTH];
random.nextBytes(randomBytes);
return new TransactionOutPoint(Integer.toUnsignedLong(random.nextInt()), Sha256Hash.wrap(randomBytes));
}).limit(10).iterator();
}
}

View File

@ -1680,10 +1680,10 @@ public class WalletTest extends TestWithWallet {
wallet.addWatchedAddress(watchedAddress);
// Note that this has a 1e-12 chance of failing this unit test due to a false positive
assertFalse(wallet.getBloomFilter(1e-12).contains(outPoint.bitcoinSerialize()));
assertFalse(wallet.getBloomFilter(1e-12).contains(outPoint.serialize()));
sendMoneyToWallet(BlockChain.NewBlockType.BEST_CHAIN, t1);
assertTrue(wallet.getBloomFilter(1e-12).contains(outPoint.bitcoinSerialize()));
assertTrue(wallet.getBloomFilter(1e-12).contains(outPoint.serialize()));
}
@Test
@ -1733,10 +1733,10 @@ public class WalletTest extends TestWithWallet {
TransactionOutPoint outPoint = new TransactionOutPoint(0, t1);
// Note that this has a 1e-12 chance of failing this unit test due to a false positive
assertFalse(wallet.getBloomFilter(1e-12).contains(outPoint.bitcoinSerialize()));
assertFalse(wallet.getBloomFilter(1e-12).contains(outPoint.serialize()));
sendMoneyToWallet(BlockChain.NewBlockType.BEST_CHAIN, t1);
assertFalse(wallet.getBloomFilter(1e-12).contains(outPoint.bitcoinSerialize()));
assertFalse(wallet.getBloomFilter(1e-12).contains(outPoint.serialize()));
}
}
@ -1751,10 +1751,10 @@ public class WalletTest extends TestWithWallet {
Transaction t1 = createFakeTx(TESTNET, CENT, address);
TransactionOutPoint outPoint = new TransactionOutPoint(0, t1);
assertFalse(wallet.getBloomFilter(falsePositiveRate).contains(outPoint.bitcoinSerialize()));
assertFalse(wallet.getBloomFilter(falsePositiveRate).contains(outPoint.serialize()));
sendMoneyToWallet(BlockChain.NewBlockType.BEST_CHAIN, t1);
assertTrue(wallet.getBloomFilter(falsePositiveRate).contains(outPoint.bitcoinSerialize()));
assertTrue(wallet.getBloomFilter(falsePositiveRate).contains(outPoint.serialize()));
}
@Test

View File

@ -653,7 +653,7 @@ public class PeerGroupTest extends TestWithPeerGroup {
InboundMessageQueuer p3 = connectPeer(3);
assertTrue(p3.lastReceivedFilter.contains(key.getPubKey()));
assertTrue(p3.lastReceivedFilter.contains(key.getPubKeyHash()));
assertTrue(p3.lastReceivedFilter.contains(outpoint.bitcoinSerialize()));
assertTrue(p3.lastReceivedFilter.contains(outpoint.serialize()));
}
@Test