mirror of
https://github.com/bitcoinj/bitcoinj.git
synced 2025-01-18 21:32:35 +01:00
PartialMerkleTree: divorce from Message
It is never sent on its own, so it doesn't need to be a `Message`. * Static constructor `read()` replaces the native constructor that deserialized from a payload. * `write()` helper replaces `bitcoinSerializeToStream()`. * `serialize()` and `getMessageSize()` helpers replace `bitcoinSerialize()`. Includes a test.
This commit is contained in:
parent
cc5d735eb4
commit
cd75c6ab6b
@ -63,14 +63,14 @@ public class FilteredBlock extends Message {
|
||||
header.bitcoinSerializeToStream(stream);
|
||||
else
|
||||
header.cloneAsHeader().bitcoinSerializeToStream(stream);
|
||||
merkleTree.bitcoinSerializeToStream(stream);
|
||||
stream.write(merkleTree.serialize());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void parse(ByteBuffer payload) throws BufferUnderflowException, ProtocolException {
|
||||
byte[] headerBytes = Buffers.readBytes(payload, Block.HEADER_SIZE);
|
||||
header = new Block(ByteBuffer.wrap(headerBytes));
|
||||
merkleTree = new PartialMerkleTree(payload);
|
||||
merkleTree = PartialMerkleTree.read(payload);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -23,8 +23,7 @@ import org.bitcoinj.base.VarInt;
|
||||
import org.bitcoinj.base.internal.Buffers;
|
||||
import org.bitcoinj.base.internal.ByteUtils;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
import java.nio.BufferOverflowException;
|
||||
import java.nio.BufferUnderflowException;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.ArrayList;
|
||||
@ -66,7 +65,7 @@ import static org.bitcoinj.base.internal.ByteUtils.writeInt32LE;
|
||||
*
|
||||
* <p>Instances of this class are not safe for use by multiple threads.</p>
|
||||
*/
|
||||
public class PartialMerkleTree extends Message {
|
||||
public class PartialMerkleTree {
|
||||
// the total number of transactions in the block
|
||||
private int transactionCount;
|
||||
|
||||
@ -75,9 +74,22 @@ public class PartialMerkleTree extends Message {
|
||||
|
||||
// txids and internal hashes
|
||||
private List<Sha256Hash> hashes;
|
||||
|
||||
public PartialMerkleTree(ByteBuffer payload) throws ProtocolException {
|
||||
super(payload);
|
||||
|
||||
/**
|
||||
* Deserialize a partial merkle tree from a given payload.
|
||||
*
|
||||
* @param payload payload to deserialize from
|
||||
* @return read message
|
||||
* @throws BufferUnderflowException if the read message extends beyond the remaining bytes of the payload
|
||||
*/
|
||||
public static PartialMerkleTree read(ByteBuffer payload) throws BufferUnderflowException, ProtocolException {
|
||||
int transactionCount = (int) ByteUtils.readUint32(payload);
|
||||
int nHashes = VarInt.read(payload).intValue();
|
||||
List<Sha256Hash> hashes = new ArrayList<>(Math.min(nHashes, Utils.MAX_INITIAL_ARRAY_LENGTH));
|
||||
for (int i = 0; i < nHashes; i++)
|
||||
hashes.add(Sha256Hash.read(payload));
|
||||
byte[] matchedChildBits = Buffers.readLengthPrefixedBytes(payload);
|
||||
return new PartialMerkleTree(matchedChildBits, hashes, transactionCount);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -110,27 +122,43 @@ public class PartialMerkleTree extends Message {
|
||||
return new PartialMerkleTree(bits, hashes, allLeafHashes.size());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void bitcoinSerializeToStream(OutputStream stream) throws IOException {
|
||||
writeInt32LE(transactionCount, stream);
|
||||
|
||||
stream.write(VarInt.of(hashes.size()).serialize());
|
||||
/**
|
||||
* Write this partial merkle tree into the given buffer.
|
||||
*
|
||||
* @param buf buffer to write into
|
||||
* @return the buffer
|
||||
* @throws BufferOverflowException if the partial merkle tree doesn't fit the remaining buffer
|
||||
*/
|
||||
public ByteBuffer write(ByteBuffer buf) throws BufferOverflowException {
|
||||
writeInt32LE(transactionCount, buf);
|
||||
VarInt.of(hashes.size()).write(buf);
|
||||
for (Sha256Hash hash : hashes)
|
||||
stream.write(hash.serialize());
|
||||
|
||||
stream.write(VarInt.of(matchedChildBits.length).serialize());
|
||||
stream.write(matchedChildBits);
|
||||
hash.write(buf);
|
||||
Buffers.writeLengthPrefixedBytes(buf, matchedChildBits);
|
||||
return buf;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void parse(ByteBuffer payload) throws BufferUnderflowException, ProtocolException {
|
||||
transactionCount = (int) ByteUtils.readUint32(payload);
|
||||
/**
|
||||
* Allocates a byte array and writes this partial merkle tree into it.
|
||||
*
|
||||
* @return byte array containing the partial merkle tree
|
||||
*/
|
||||
public byte[] serialize() {
|
||||
return write(ByteBuffer.allocate(getMessageSize())).array();
|
||||
}
|
||||
|
||||
int nHashes = VarInt.read(payload).intValue();
|
||||
hashes = new ArrayList<>(Math.min(nHashes, Utils.MAX_INITIAL_ARRAY_LENGTH));
|
||||
for (int i = 0; i < nHashes; i++)
|
||||
hashes.add(Sha256Hash.read(payload));
|
||||
matchedChildBits = Buffers.readLengthPrefixedBytes(payload);
|
||||
/**
|
||||
* Return the size of the serialized message. Note that if the message was deserialized from a payload, this
|
||||
* size can differ from the size of the original payload.
|
||||
*
|
||||
* @return size of the serialized message in bytes
|
||||
*/
|
||||
public int getMessageSize() {
|
||||
int size = Integer.BYTES; // transactionCount
|
||||
size += VarInt.sizeOf(hashes.size());
|
||||
size += hashes.size() * Sha256Hash.LENGTH;
|
||||
size += VarInt.sizeOf(matchedChildBits.length) + matchedChildBits.length;
|
||||
return size;
|
||||
}
|
||||
|
||||
// Based on CPartialMerkleTree::TraverseAndBuild in Bitcoin Core.
|
||||
|
@ -0,0 +1,62 @@
|
||||
/*
|
||||
* 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.List;
|
||||
import java.util.Random;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertFalse;
|
||||
|
||||
@RunWith(JUnitParamsRunner.class)
|
||||
public class PartialMerkleTreeTest {
|
||||
@Test
|
||||
@Parameters(method = "randomPartialMerkleTrees")
|
||||
public void readAndWrite(PartialMerkleTree pmt) {
|
||||
ByteBuffer buf = ByteBuffer.allocate(pmt.getMessageSize());
|
||||
pmt.write(buf);
|
||||
assertFalse(buf.hasRemaining());
|
||||
((Buffer) buf).rewind();
|
||||
PartialMerkleTree pmtCopy = PartialMerkleTree.read(buf);
|
||||
assertFalse(buf.hasRemaining());
|
||||
assertEquals(pmt, pmtCopy);
|
||||
}
|
||||
|
||||
private Iterator<PartialMerkleTree> randomPartialMerkleTrees() {
|
||||
Random random = new Random();
|
||||
return Stream.generate(() -> {
|
||||
byte[] randomBits = new byte[random.nextInt(20)];
|
||||
random.nextBytes(randomBits);
|
||||
List<Sha256Hash> hashes = Stream.generate(() -> {
|
||||
byte[] randomHash = new byte[Sha256Hash.LENGTH];
|
||||
return Sha256Hash.wrap(randomHash);
|
||||
}).limit(random.nextInt(10)).collect(Collectors.toList());
|
||||
return new PartialMerkleTree(randomBits, hashes, random.nextInt(20));
|
||||
}).limit(10).iterator();
|
||||
}
|
||||
}
|
@ -17,13 +17,13 @@
|
||||
|
||||
package org.bitcoinj.core;
|
||||
|
||||
import com.google.common.io.BaseEncoding;
|
||||
import org.bitcoinj.base.BitcoinNetwork;
|
||||
import org.bitcoinj.base.Coin;
|
||||
import org.bitcoinj.base.LegacyAddress;
|
||||
import org.bitcoinj.base.ScriptType;
|
||||
import org.bitcoinj.base.Sha256Hash;
|
||||
import org.bitcoinj.base.VarInt;
|
||||
import org.bitcoinj.base.internal.Buffers;
|
||||
import org.bitcoinj.base.internal.ByteUtils;
|
||||
import org.bitcoinj.core.TransactionConfidence.ConfidenceType;
|
||||
import org.bitcoinj.crypto.ECKey;
|
||||
@ -38,9 +38,9 @@ import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.junit.runners.Parameterized;
|
||||
|
||||
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.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
@ -235,23 +235,26 @@ public class FilteredBlockAndPartialMerkleTreeTest extends TestWithPeerGroup {
|
||||
hashes.add(Sha256Hash.wrap("0000000000000000000000000000000000000000000000000000000000000002"));
|
||||
hashes.add(Sha256Hash.wrap("0000000000000000000000000000000000000000000000000000000000000003"));
|
||||
PartialMerkleTree pmt = new PartialMerkleTree(bits, hashes, 3) {
|
||||
public void bitcoinSerializeToStream(OutputStream stream) throws IOException {
|
||||
writeInt32LE(getTransactionCount(), stream);
|
||||
public ByteBuffer write(ByteBuffer buf) throws BufferOverflowException {
|
||||
writeInt32LE(getTransactionCount(), buf);
|
||||
// Add Integer.MAX_VALUE instead of hashes.size()
|
||||
stream.write(VarInt.of(Integer.MAX_VALUE).serialize());
|
||||
//stream.write(VarInt.of(hashes.size()).encode());
|
||||
VarInt.of(Integer.MAX_VALUE).write(buf);
|
||||
for (Sha256Hash hash : hashes)
|
||||
stream.write(hash.serialize());
|
||||
hash.write(buf);
|
||||
Buffers.writeLengthPrefixedBytes(buf, bits);
|
||||
return buf;
|
||||
}
|
||||
|
||||
stream.write(VarInt.of(bits.length).serialize());
|
||||
stream.write(bits);
|
||||
@Override
|
||||
public int getMessageSize() {
|
||||
return super.getMessageSize() + 4; // adjust for the longer VarInt
|
||||
}
|
||||
};
|
||||
byte[] serializedPmt = pmt.bitcoinSerialize();
|
||||
byte[] serializedPmt = pmt.serialize();
|
||||
try {
|
||||
new PartialMerkleTree(ByteBuffer.wrap(serializedPmt));
|
||||
fail("We expect ProtocolException with the fixed code and OutOfMemoryError with the buggy code, so this is weird");
|
||||
} catch (ProtocolException e) {
|
||||
PartialMerkleTree.read(ByteBuffer.wrap(serializedPmt));
|
||||
fail("We expect BufferUnderflowException with the fixed code and OutOfMemoryError with the buggy code, so this is weird");
|
||||
} catch (BufferUnderflowException e) {
|
||||
//Expected, do nothing
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user