ListMessage, InventoryMessage, GetDataMessage: make immutable (after deprecations removed)

Make `ListMessage` and its subclasses "almost" immutable. When the deprecated
`addItem()`, `removeItem()`, etc. methods are removed and the constructors are
changed to create an ummodifiable `List`, they will be immutable.
This commit is contained in:
Sean Gilligan 2023-08-23 00:33:21 -07:00 committed by Andreas Schildbach
parent 9d78d2bd9e
commit 111a8b8a37
11 changed files with 167 additions and 121 deletions

View File

@ -21,13 +21,14 @@ import org.bitcoinj.base.Sha256Hash;
import java.nio.BufferUnderflowException;
import java.nio.ByteBuffer;
import java.util.Collections;
import java.util.List;
/**
* <p>Represents the "getdata" P2P network message, which requests the contents of blocks or transactions given their
* hashes.</p>
*
* <p>Instances of this class are not safe for use by multiple threads.</p>
*
* <p>Instances of this class -- that use deprecated methods -- are not safe for use by multiple threads.</p>
*/
public class GetDataMessage extends ListMessage {
/**
@ -41,23 +42,43 @@ public class GetDataMessage extends ListMessage {
return new GetDataMessage(readItems(payload));
}
@Deprecated
public GetDataMessage() {
super();
}
private GetDataMessage(List<InventoryItem> items) {
GetDataMessage(List<InventoryItem> items) {
super(items);
}
public static GetDataMessage ofBlock(Sha256Hash blockHash, boolean includeWitness) {
return new GetDataMessage(Collections.singletonList(
new InventoryItem(includeWitness
? InventoryItem.Type.WITNESS_BLOCK
: InventoryItem.Type.BLOCK,
blockHash)));
}
public static GetDataMessage ofTransaction(Sha256Hash txId, boolean includeWitness) {
return new GetDataMessage(Collections.singletonList(
new InventoryItem(includeWitness
? InventoryItem.Type.WITNESS_TRANSACTION
: InventoryItem.Type.TRANSACTION,
txId)));
}
@Deprecated
public void addTransaction(Sha256Hash hash, boolean includeWitness) {
addItem(new InventoryItem(
includeWitness ? InventoryItem.Type.WITNESS_TRANSACTION : InventoryItem.Type.TRANSACTION, hash));
}
@Deprecated
public void addBlock(Sha256Hash hash, boolean includeWitness) {
addItem(new InventoryItem(includeWitness ? InventoryItem.Type.WITNESS_BLOCK : InventoryItem.Type.BLOCK, hash));
}
@Deprecated
public void addFilteredBlock(Sha256Hash hash) {
addItem(new InventoryItem(InventoryItem.Type.FILTERED_BLOCK, hash));
}

View File

@ -57,6 +57,16 @@ public class InventoryItem {
this.hash = hash;
}
public InventoryItem(Block block) {
this.type = Type.BLOCK;
this.hash = block.getHash();
}
public InventoryItem(Transaction tx) {
this.type = Type.TRANSACTION;
this.hash = tx.getTxId();
}
@Override
public String toString() {
return type + ": " + hash;

View File

@ -18,7 +18,10 @@ package org.bitcoinj.core;
import java.nio.BufferUnderflowException;
import java.nio.ByteBuffer;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.stream.Collectors;
import static org.bitcoinj.base.internal.Preconditions.checkArgument;
@ -27,8 +30,8 @@ import static org.bitcoinj.base.internal.Preconditions.checkArgument;
* a bandwidth optimization - on receiving some data, a (fully validating) peer sends every connected peer an inv
* containing the hash of what it saw. It'll only transmit the full thing if a peer asks for it with a
* {@link GetDataMessage}.</p>
*
* <p>Instances of this class are not safe for use by multiple threads.</p>
*
* <p>Instances of this class -- that use deprecated methods -- are not safe for use by multiple threads.</p>
*/
public class InventoryMessage extends ListMessage {
@ -46,7 +49,8 @@ public class InventoryMessage extends ListMessage {
return new InventoryMessage(readItems(payload));
}
public InventoryMessage() {
@Deprecated
protected InventoryMessage() {
super();
}
@ -54,20 +58,50 @@ public class InventoryMessage extends ListMessage {
super(items);
}
public static InventoryMessage ofBlocks(List<Block> blocks) {
checkArgument(!blocks.isEmpty());
return new InventoryMessage(blocks.stream()
.map(InventoryItem::new)
.collect(Collectors.toList()));
}
public static InventoryMessage ofBlocks(Block ...blocks) {
return ofBlocks(Arrays.asList(blocks));
}
public static InventoryMessage ofTransactions(List<Transaction> transactions) {
checkArgument(!transactions.isEmpty());
return new InventoryMessage(transactions.stream()
.map(InventoryItem::new)
.collect(Collectors.toList()));
}
public static InventoryMessage ofTransactions(Transaction ...transactions) {
return ofTransactions(Arrays.asList(transactions));
}
/**
* @deprecated Use a constructor or factoring
*/
@Deprecated
public void addBlock(Block block) {
addItem(new InventoryItem(InventoryItem.Type.BLOCK, block.getHash()));
addItem(new InventoryItem(block));
}
/**
* @deprecated Use a constructor or factoring
*/
@Deprecated
public void addTransaction(Transaction tx) {
addItem(new InventoryItem(InventoryItem.Type.TRANSACTION, tx.getTxId()));
addItem(new InventoryItem(tx));
}
/** Creates a new inv message for the given transactions. */
/**
* Creates a new inv message for the given transactions.
* @deprecated Use {@link #ofTransactions(Transaction...)}
*/
@Deprecated
public static InventoryMessage with(Transaction... txns) {
checkArgument(txns.length > 0);
InventoryMessage result = new InventoryMessage();
for (Transaction tx : txns)
result.addTransaction(tx);
return result;
return ofTransactions(txns);
}
}

View File

@ -35,12 +35,12 @@ import static org.bitcoinj.base.internal.Preconditions.check;
/**
* <p>Abstract superclass of classes with list based payload, ie InventoryMessage and GetDataMessage.</p>
*
* <p>Instances of this class are not safe for use by multiple threads.</p>
* <p>Instances of this class -- that use deprecated methods -- are not safe for use by multiple threads.</p>
*/
public abstract class ListMessage extends BaseMessage {
// For some reason the compiler complains if this is inside InventoryItem
protected List<InventoryItem> items;
protected final List<InventoryItem> items;
public static final int MAX_INVENTORY_ITEMS = 50000;
@ -68,23 +68,26 @@ public abstract class ListMessage extends BaseMessage {
return items;
}
@Deprecated
public ListMessage() {
super();
items = new ArrayList<>();
items = new ArrayList<>(); // TODO: unmodifiable empty list
}
protected ListMessage(List<InventoryItem> items) {
this.items = items;
this.items = items; // TODO: unmodifiable defensive copy
}
public List<InventoryItem> getItems() {
return Collections.unmodifiableList(items);
}
@Deprecated
public void addItem(InventoryItem item) {
items.add(item);
}
@Deprecated
public void removeItem(int index) {
items.remove(index);
}

View File

@ -24,8 +24,8 @@ import java.util.List;
/**
* <p>Sent by a peer when a getdata request doesn't find the requested data in the mempool. It has the same format
* as an inventory message and lists the hashes of the missing items.</p>
*
* <p>Instances of this class are not safe for use by multiple threads.</p>
*
* <p>Instances of this class -- that use deprecated methods -- are not safe for use by multiple threads.</p>
*/
public class NotFoundMessage extends InventoryMessage {
public static int MIN_PROTOCOL_VERSION = 70001;
@ -41,6 +41,7 @@ public class NotFoundMessage extends InventoryMessage {
return new NotFoundMessage(readItems(payload));
}
@Deprecated
public NotFoundMessage() {
super();
}

View File

@ -910,10 +910,13 @@ public class Peer extends PeerSocketHandler {
* @return A GetDataMessage that will query those IDs
*/
private GetDataMessage buildMultiTransactionDataMessage(Set<Sha256Hash> txIds) {
GetDataMessage getdata = new GetDataMessage();
txIds.forEach(txId ->
getdata.addTransaction(txId, vPeerVersionMessage.services().has(Services.NODE_WITNESS)));
return getdata;
InventoryItem.Type itemType = vPeerVersionMessage.services().has(Services.NODE_WITNESS)
? InventoryItem.Type.WITNESS_TRANSACTION
: InventoryItem.Type.TRANSACTION;
List<InventoryItem> items = txIds.stream()
.map(hash -> new InventoryItem(itemType, hash))
.collect(Collectors.toList());
return new GetDataMessage(items);
}
/**
@ -1129,34 +1132,35 @@ public class Peer extends PeerSocketHandler {
List<InventoryItem> items = inv.getItems();
// Separate out the blocks and transactions, we'll handle them differently
List<InventoryItem> transactions = new LinkedList<>();
List<InventoryItem> blocks = new LinkedList<>();
List<Sha256Hash> transactions = new LinkedList<>();
List<Sha256Hash> blocks = new LinkedList<>();
for (InventoryItem item : items) {
switch (item.type) {
case TRANSACTION:
transactions.add(item);
transactions.add(item.hash);
break;
case BLOCK:
blocks.add(item);
blocks.add(item.hash);
break;
default:
throw new IllegalStateException("Not implemented: " + item.type);
}
}
if (log.isDebugEnabled())
log.debug("{}: processing 'inv' with {} items: {} blocks, {} txns", this, items.size(), blocks.size(),
transactions.size());
final boolean downloadData = this.vDownloadData;
if (transactions.size() == 0 && blocks.size() == 1) {
if (transactions.isEmpty() && blocks.size() == 1) {
// Single block announcement. If we're downloading the chain this is just a tickle to make us continue
// (the block chain download protocol is very implicit and not well thought out). If we're not downloading
// the chain then this probably means a new block was solved and the peer believes it connects to the best
// chain, so count it. This way getBestChainHeight() can be accurate.
if (downloadData && blockChain != null) {
if (!blockChain.isOrphan(blocks.get(0).hash)) {
if (!blockChain.isOrphan(blocks.get(0))) {
blocksAnnounced.incrementAndGet();
}
} else {
@ -1164,11 +1168,14 @@ public class Peer extends PeerSocketHandler {
}
}
GetDataMessage getdata = new GetDataMessage();
InventoryItem.Type txItemType = vPeerVersionMessage.services().has(Services.NODE_WITNESS)
? InventoryItem.Type.WITNESS_TRANSACTION
: InventoryItem.Type.TRANSACTION;
List<InventoryItem> getDataItems = new ArrayList<>();
Iterator<InventoryItem> it = transactions.iterator();
Iterator<Sha256Hash> it = transactions.iterator();
while (it.hasNext()) {
InventoryItem item = it.next();
Sha256Hash item = it.next();
// Only download the transaction if we are the first peer that saw it be advertised. Other peers will also
// see it be advertised in inv packets asynchronously, they co-ordinate via the memory pool. We could
// potentially download transactions faster by always asking every peer for a tx when advertised, as remote
@ -1177,7 +1184,7 @@ public class Peer extends PeerSocketHandler {
// sending us the transaction: currently we'll never try to re-fetch after a timeout.
//
// The line below can trigger confidence listeners.
TransactionConfidence conf = context.getConfidenceTable().seen(item.hash, this.getAddress());
TransactionConfidence conf = context.getConfidenceTable().seen(item, this.getAddress());
if (conf.numBroadcastPeers() > 1) {
// Some other peer already announced this so don't download.
it.remove();
@ -1186,8 +1193,8 @@ public class Peer extends PeerSocketHandler {
it.remove();
} else {
if (log.isDebugEnabled())
log.debug("{}: getdata on tx {}", getAddress(), item.hash);
getdata.addTransaction(item.hash, vPeerVersionMessage.services().has(Services.NODE_WITNESS));
log.debug("{}: getdata on tx {}", getAddress(), item);
getDataItems.add(new InventoryItem(txItemType, item));
if (pendingTxDownloads.size() > PENDING_TX_DOWNLOADS_LIMIT) {
log.info("{}: Too many pending transactions, disconnecting", this);
close();
@ -1208,11 +1215,11 @@ public class Peer extends PeerSocketHandler {
// Ideally, we'd only ask for the data here if we actually needed it. However that can imply a lot of
// disk IO to figure out what we've got. Normally peers will not send us inv for things we already have
// so we just re-request it here, and if we get duplicates the block chain / wallet will filter them out.
for (InventoryItem item : blocks) {
if (blockChain.isOrphan(item.hash) && downloadBlockBodies) {
for (Sha256Hash item : blocks) {
if (blockChain.isOrphan(item) && downloadBlockBodies) {
// If an orphan was re-advertised, ask for more blocks unless we are not currently downloading
// full block data because we have a getheaders outstanding.
final Block orphanRoot = Objects.requireNonNull(blockChain.getOrphanRoot(item.hash));
final Block orphanRoot = Objects.requireNonNull(blockChain.getOrphanRoot(item));
blockChainDownloadLocked(orphanRoot.getHash());
} else {
// Don't re-request blocks we already requested. Normally this should not happen. However there is
@ -1227,14 +1234,14 @@ public class Peer extends PeerSocketHandler {
// part of chain download with newly announced blocks, so it should always be taken care of by
// the duplicate check in blockChainDownloadLocked(). But Bitcoin Core may change in future so
// it's better to be safe here.
if (!pendingBlockDownloads.contains(item.hash)) {
if (!pendingBlockDownloads.contains(item)) {
if (isBloomFilteringSupported(vPeerVersionMessage) && useFilteredBlocks) {
getdata.addFilteredBlock(item.hash);
getDataItems.add(new InventoryItem(InventoryItem.Type.FILTERED_BLOCK, item));
pingAfterGetData = true;
} else {
getdata.addBlock(item.hash, vPeerVersionMessage.services().has(Services.NODE_WITNESS));
getDataItems.add(new InventoryItem(InventoryItem.Type.BLOCK, item));
}
pendingBlockDownloads.add(item.hash);
pendingBlockDownloads.add(item);
}
}
}
@ -1248,8 +1255,9 @@ public class Peer extends PeerSocketHandler {
lock.unlock();
}
if (!getdata.getItems().isEmpty()) {
if (!getDataItems.isEmpty()) {
// This will cause us to receive a bunch of block or tx messages.
GetDataMessage getdata = new GetDataMessage(getDataItems);
sendMessage(getdata);
}
@ -1269,8 +1277,7 @@ public class Peer extends PeerSocketHandler {
public ListenableCompletableFuture<Block> getBlock(Sha256Hash blockHash) {
// This does not need to be locked.
log.info("Request to fetch block {}", blockHash);
GetDataMessage getdata = new GetDataMessage();
getdata.addBlock(blockHash, true);
GetDataMessage getdata = GetDataMessage.ofBlock(blockHash, true);
return ListenableCompletableFuture.of(sendSingleGetData(getdata));
}
@ -1287,8 +1294,7 @@ public class Peer extends PeerSocketHandler {
// This does not need to be locked.
// TODO: Unit test this method.
log.info("Request to fetch peer mempool tx {}", hash);
GetDataMessage getdata = new GetDataMessage();
getdata.addTransaction(hash, vPeerVersionMessage.services().has(Services.NODE_WITNESS));
GetDataMessage getdata = GetDataMessage.ofTransaction(hash, vPeerVersionMessage.services().has(Services.NODE_WITNESS));
return ListenableCompletableFuture.of(sendSingleGetData(getdata));
}
@ -1760,9 +1766,10 @@ public class Peer extends PeerSocketHandler {
sendPing().thenRunAsync(() -> {
lock.lock();
Objects.requireNonNull(awaitingFreshFilter);
GetDataMessage getdata = new GetDataMessage();
for (Sha256Hash hash : awaitingFreshFilter)
getdata.addFilteredBlock(hash);
List<InventoryItem> items = awaitingFreshFilter.stream()
.map(hash -> new InventoryItem(InventoryItem.Type.FILTERED_BLOCK, hash))
.collect(Collectors.toList());
GetDataMessage getdata = new GetDataMessage(items);
awaitingFreshFilter = null;
lock.unlock();

View File

@ -188,9 +188,7 @@ public class BitcoindComparisonTool {
if (!found)
sendHeaders = headers;
bitcoind.sendMessage(new HeadersMessage(sendHeaders));
InventoryMessage i = new InventoryMessage();
for (Block b : sendHeaders)
i.addBlock(b);
InventoryMessage i = InventoryMessage.ofBlocks(sendHeaders);
bitcoind.sendMessage(i);
} catch (Exception e) {
throw new RuntimeException(e);
@ -271,8 +269,7 @@ public class BitcoindComparisonTool {
boolean shouldntRequest = blocksRequested.contains(nextBlock.getHash());
if (shouldntRequest)
blocksRequested.remove(nextBlock.getHash());
InventoryMessage message = new InventoryMessage();
message.addBlock(nextBlock);
InventoryMessage message = InventoryMessage.ofBlocks(nextBlock);
bitcoind.sendMessage(message);
log.info("Sent inv with block " + nextBlock.getHashAsString());
if (blocksPendingSend.contains(nextBlock.getHash())) {

View File

@ -188,8 +188,7 @@ public class FilteredBlockAndPartialMerkleTreeTest extends TestWithPeerGroup {
InboundMessageQueuer p1 = connectPeer(1);
assertEquals(1, peerGroup.numConnectedPeers());
// Send an inv for block 100001
InventoryMessage inv = new InventoryMessage();
inv.addBlock(block);
InventoryMessage inv = InventoryMessage.ofBlocks(block);
inbound(p1, inv);
// Check that we properly requested the correct FilteredBlock

View File

@ -247,8 +247,7 @@ public class PeerGroupTest extends TestWithPeerGroup {
Coin value = COIN;
Transaction t1 = FakeTxBuilder.createFakeTx(UNITTEST.network(), value, address);
InventoryMessage inv = new InventoryMessage();
inv.addTransaction(t1);
InventoryMessage inv = InventoryMessage.ofTransactions(t1);
// Note: we start with p2 here to verify that transactions are downloaded from whichever peer announces first
// which does not have to be the same as the download peer (which is really the "block download peer").
@ -287,8 +286,7 @@ public class PeerGroupTest extends TestWithPeerGroup {
Coin value = COIN;
Transaction t1 = FakeTxBuilder.createFakeTx(UNITTEST.network(), value, address2);
InventoryMessage inv = new InventoryMessage();
inv.addTransaction(t1);
InventoryMessage inv = InventoryMessage.ofTransactions(t1);
inbound(p1, inv);
assertTrue(outbound(p1) instanceof GetDataMessage);
@ -319,8 +317,7 @@ public class PeerGroupTest extends TestWithPeerGroup {
Block b3 = FakeTxBuilder.makeSolvedTestBlock(b2);
// Peer 1 and 2 receives an inv advertising a newly solved block.
InventoryMessage inv = new InventoryMessage();
inv.addBlock(b3);
InventoryMessage inv = InventoryMessage.ofBlocks(b3);
// Only peer 1 tries to download it.
inbound(p1, inv);
pingAndWait(p1);
@ -358,11 +355,8 @@ public class PeerGroupTest extends TestWithPeerGroup {
GetBlocksMessage getblocks = (GetBlocksMessage) outbound(p1);
assertEquals(Sha256Hash.ZERO_HASH, getblocks.getStopHash());
// We give back an inv with some blocks in it.
InventoryMessage inv = new InventoryMessage();
inv.addBlock(b1);
inv.addBlock(b2);
inv.addBlock(b3);
InventoryMessage inv = InventoryMessage.ofBlocks(b1, b2, b3);
inbound(p1, inv);
assertTrue(outbound(p1) instanceof GetDataMessage);
// We hand back the first block.
@ -388,8 +382,7 @@ public class PeerGroupTest extends TestWithPeerGroup {
InboundMessageQueuer p3 = connectPeer(3);
Transaction tx = FakeTxBuilder.createFakeTx(UNITTEST.network(), valueOf(20, 0), address);
InventoryMessage inv = new InventoryMessage();
inv.addTransaction(tx);
InventoryMessage inv = InventoryMessage.ofTransactions(tx);
assertEquals(0, tx.getConfidence().numBroadcastPeers());
assertFalse(tx.getConfidence().lastBroadcastTime().isPresent());

View File

@ -30,6 +30,7 @@ import org.bitcoinj.testing.InboundMessageQueuer;
import org.bitcoinj.testing.TestWithNetworkConnections;
import org.bitcoinj.utils.Threading;
import org.bitcoinj.wallet.Wallet;
import org.checkerframework.checker.units.qual.A;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
@ -46,8 +47,10 @@ import java.net.SocketException;
import java.nio.channels.CancelledKeyException;
import java.nio.charset.StandardCharsets;
import java.time.Duration;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
@ -140,9 +143,7 @@ public class PeerTest extends TestWithNetworkConnections {
assertEquals(blockStore.getChainHead().getHeader().getHash(), getblocks.getLocator().get(0));
assertEquals(Sha256Hash.ZERO_HASH, getblocks.getStopHash());
// Remote peer sends us an inv with some blocks.
InventoryMessage inv = new InventoryMessage();
inv.addBlock(b2);
inv.addBlock(b3);
InventoryMessage inv = InventoryMessage.ofBlocks(b2, b3);
// We do a getdata on them.
inbound(writeTarget, inv);
GetDataMessage getdata = (GetDataMessage)outbound(writeTarget);
@ -154,8 +155,7 @@ public class PeerTest extends TestWithNetworkConnections {
inbound(writeTarget, b2);
inbound(writeTarget, b3);
inv = new InventoryMessage();
inv.addBlock(b5);
inv = InventoryMessage.ofBlocks(b5);
// We request the head block.
inbound(writeTarget, inv);
getdata = (GetDataMessage)outbound(writeTarget);
@ -173,8 +173,7 @@ public class PeerTest extends TestWithNetworkConnections {
// because we walk backwards down the orphan chain and then discover we already asked for those blocks, so
// nothing is done.
Block b6 = makeSolvedTestBlock(b5);
inv = new InventoryMessage();
inv.addBlock(b6);
inv = InventoryMessage.ofBlocks(b6);
inbound(writeTarget, inv);
getdata = (GetDataMessage)outbound(writeTarget);
assertEquals(1, getdata.getItems().size());
@ -182,9 +181,7 @@ public class PeerTest extends TestWithNetworkConnections {
inbound(writeTarget, b6);
assertNull(outbound(writeTarget)); // Nothing is sent at this point.
// We're still waiting for the response to the getblocks (b3,b5) sent above.
inv = new InventoryMessage();
inv.addBlock(b4);
inv.addBlock(b5);
inv = InventoryMessage.ofBlocks(b4, b5);
inbound(writeTarget, inv);
getdata = (GetDataMessage)outbound(writeTarget);
assertEquals(1, getdata.getItems().size());
@ -208,9 +205,7 @@ public class PeerTest extends TestWithNetworkConnections {
Block b2 = makeSolvedTestBlock(b1);
Block b3 = makeSolvedTestBlock(b2);
inbound(writeTarget, b3);
InventoryMessage inv = new InventoryMessage();
InventoryItem item = new InventoryItem(InventoryItem.Type.BLOCK, b3.getHash());
inv.addItem(item);
InventoryMessage inv = InventoryMessage.ofBlocks(b3);
inbound(writeTarget, inv);
GetBlocksMessage getblocks = (GetBlocksMessage)outbound(writeTarget);
@ -237,9 +232,7 @@ public class PeerTest extends TestWithNetworkConnections {
Block b2 = makeSolvedTestBlock(b1);
// Receive an inv.
InventoryMessage inv = new InventoryMessage();
InventoryItem item = new InventoryItem(InventoryItem.Type.BLOCK, b2.getHash());
inv.addItem(item);
InventoryMessage inv = InventoryMessage.ofBlocks(b2);
inbound(writeTarget, inv);
// Peer does nothing with it.
@ -254,9 +247,7 @@ public class PeerTest extends TestWithNetworkConnections {
// Make a transaction and tell the peer we have it.
Coin value = COIN;
Transaction tx = createFakeTx(TESTNET.network(), value, address);
InventoryMessage inv = new InventoryMessage();
InventoryItem item = new InventoryItem(InventoryItem.Type.TRANSACTION, tx.getTxId());
inv.addItem(item);
InventoryMessage inv = InventoryMessage.ofTransactions(tx);
inbound(writeTarget, inv);
// Peer hasn't seen it before, so will ask for it.
GetDataMessage getdata = (GetDataMessage) outbound(writeTarget);
@ -287,9 +278,7 @@ public class PeerTest extends TestWithNetworkConnections {
// Make a tx and advertise it to one of the peers.
Coin value = COIN;
Transaction tx = createFakeTx(TESTNET.network(), value, this.address);
InventoryMessage inv = new InventoryMessage();
InventoryItem item = new InventoryItem(InventoryItem.Type.TRANSACTION, tx.getTxId());
inv.addItem(item);
InventoryMessage inv = InventoryMessage.ofTransactions(tx);
inbound(writeTarget, inv);
@ -313,9 +302,7 @@ public class PeerTest extends TestWithNetworkConnections {
blockChain.add(b1);
final Block b2 = makeSolvedTestBlock(b1);
// Receive notification of a new block.
final InventoryMessage inv = new InventoryMessage();
InventoryItem item = new InventoryItem(InventoryItem.Type.BLOCK, b2.getHash());
inv.addItem(item);
final InventoryMessage inv = InventoryMessage.ofBlocks(b2);
final AtomicInteger newBlockMessagesReceived = new AtomicInteger(0);
@ -489,8 +476,7 @@ public class PeerTest extends TestWithNetworkConnections {
assertEquals(expectedLocator, getblocks.getLocator());
assertEquals(Sha256Hash.ZERO_HASH, getblocks.getStopHash());
// We're supposed to get an inv here.
InventoryMessage inv = new InventoryMessage();
inv.addItem(new InventoryItem(InventoryItem.Type.BLOCK, b3.getHash()));
InventoryMessage inv = InventoryMessage.ofBlocks(b3);
inbound(writeTarget, inv);
GetDataMessage getdata = (GetDataMessage) outbound(writeTarget);
assertEquals(b3.getHash(), getdata.getItems().get(0).hash);
@ -583,8 +569,7 @@ public class PeerTest extends TestWithNetworkConnections {
t4 = roundTripTransaction(t4);
// Announce the first one. Wait for it to be downloaded.
InventoryMessage inv = new InventoryMessage();
inv.addTransaction(t1);
InventoryMessage inv = InventoryMessage.ofTransactions(t1);
inbound(writeTarget, inv);
GetDataMessage getdata = (GetDataMessage) outbound(writeTarget);
Threading.waitForUserCode();
@ -605,17 +590,17 @@ public class PeerTest extends TestWithNetworkConnections {
// Deliver the requested transactions.
inbound(writeTarget, t2);
inbound(writeTarget, t3);
NotFoundMessage notFound = new NotFoundMessage();
notFound.addItem(new InventoryItem(InventoryItem.Type.TRANSACTION, t7hash));
notFound.addItem(new InventoryItem(InventoryItem.Type.TRANSACTION, t8hash));
List<InventoryItem> notFoundList = new ArrayList<>();
notFoundList.add(new InventoryItem(InventoryItem.Type.TRANSACTION, t7hash));
notFoundList.add(new InventoryItem(InventoryItem.Type.TRANSACTION, t8hash));
NotFoundMessage notFound = new NotFoundMessage(notFoundList);
inbound(writeTarget, notFound);
assertFalse(futures.isDone());
// It will recursively ask for the dependencies of t2: t5 and t4, but not t3 because it already found t4.
getdata = (GetDataMessage) outbound(writeTarget);
assertEquals(getdata.getItems().get(0).hash, t2.getInput(0).getOutpoint().hash());
// t5 isn't found and t4 is.
notFound = new NotFoundMessage();
notFound.addItem(new InventoryItem(InventoryItem.Type.TRANSACTION, t5hash));
notFound = new NotFoundMessage(Collections.singletonList(new InventoryItem(InventoryItem.Type.TRANSACTION, t5hash)));
inbound(writeTarget, notFound);
assertFalse(futures.isDone());
// Request t4 ...
@ -625,8 +610,7 @@ public class PeerTest extends TestWithNetworkConnections {
// Continue to explore the t4 branch and ask for t6, which is in the chain.
getdata = (GetDataMessage) outbound(writeTarget);
assertEquals(t6hash, getdata.getItems().get(0).hash);
notFound = new NotFoundMessage();
notFound.addItem(new InventoryItem(InventoryItem.Type.TRANSACTION, t6hash));
notFound = new NotFoundMessage(Collections.singletonList(new InventoryItem(InventoryItem.Type.TRANSACTION, t6hash)));
inbound(writeTarget, notFound);
pingAndWait(writeTarget);
// That's it, we explored the entire tree.
@ -661,8 +645,7 @@ public class PeerTest extends TestWithNetworkConnections {
t1 = roundTripTransaction(t1);
// Announce the first one. Wait for it to be downloaded.
InventoryMessage inv = new InventoryMessage();
inv.addTransaction(t1);
InventoryMessage inv = InventoryMessage.ofTransactions(t1);
inbound(writeTarget, inv);
GetDataMessage getdata = (GetDataMessage) outbound(writeTarget);
Threading.waitForUserCode();
@ -758,8 +741,7 @@ public class PeerTest extends TestWithNetworkConnections {
t1.addInput(t2.getOutput(0));
t1.addOutput(COIN, key); // Make it relevant.
// Announce t1.
InventoryMessage inv = new InventoryMessage();
inv.addTransaction(t1);
InventoryMessage inv = InventoryMessage.ofTransactions(t1);
inbound(writeTarget, inv);
// Send it.
GetDataMessage getdata = (GetDataMessage) outbound(writeTarget);
@ -775,8 +757,7 @@ public class PeerTest extends TestWithNetworkConnections {
getdata = (GetDataMessage) outbound(writeTarget);
assertEquals(t3, getdata.getItems().get(0).hash);
// Can't find it: bottom of tree.
NotFoundMessage notFound = new NotFoundMessage();
notFound.addItem(new InventoryItem(InventoryItem.Type.TRANSACTION, t3));
NotFoundMessage notFound = new NotFoundMessage(Collections.singletonList(new InventoryItem(InventoryItem.Type.TRANSACTION, t3)));
inbound(writeTarget, notFound);
pingAndWait(writeTarget);
Threading.waitForUserCode();
@ -850,13 +831,14 @@ public class PeerTest extends TestWithNetworkConnections {
MessageSerializer serializer = TESTNET.getDefaultSerializer();
// Now write some bogus truncated message.
ByteArrayOutputStream out = new ByteArrayOutputStream();
serializer.serialize("inv", new InventoryMessage() {
List<InventoryItem> items = new ArrayList<>();
items.add(new InventoryItem(InventoryItem.Type.TRANSACTION, Sha256Hash.of(new byte[] { 1 })));
items.add(new InventoryItem(InventoryItem.Type.TRANSACTION, Sha256Hash.of(new byte[] { 2 })));
items.add(new InventoryItem(InventoryItem.Type.TRANSACTION, Sha256Hash.of(new byte[] { 3 })));
serializer.serialize("inv", new InventoryMessage(items) {
@Override
public void bitcoinSerializeToStream(OutputStream stream) throws IOException {
// Add some hashes.
addItem(new InventoryItem(InventoryItem.Type.TRANSACTION, Sha256Hash.of(new byte[] { 1 })));
addItem(new InventoryItem(InventoryItem.Type.TRANSACTION, Sha256Hash.of(new byte[] { 2 })));
addItem(new InventoryItem(InventoryItem.Type.TRANSACTION, Sha256Hash.of(new byte[] { 3 })));
// Write out a copy that's truncated in the middle.
ByteArrayOutputStream bos = new ByteArrayOutputStream();

View File

@ -110,7 +110,7 @@ public class TransactionBroadcastTest extends TestWithPeerGroup {
Threading.waitForUserCode();
assertFalse(future.isDone());
assertEquals(0.0, lastProgress.get(), 0.0);
inbound(channels[1], InventoryMessage.with(tx));
inbound(channels[1], InventoryMessage.ofTransactions(tx));
future.get();
Threading.waitForUserCode();
assertEquals(1.0, lastProgress.get(), 0.0);
@ -127,7 +127,7 @@ public class TransactionBroadcastTest extends TestWithPeerGroup {
Transaction tx = FakeTxBuilder.createFakeTx(TESTNET.network(), CENT, address);
tx.getConfidence().setSource(TransactionConfidence.Source.SELF);
TransactionBroadcast broadcast = peerGroup.broadcastTransaction(tx);
inbound(channels[1], InventoryMessage.with(tx));
inbound(channels[1], InventoryMessage.ofTransactions(tx));
pingAndWait(channels[1]);
final AtomicDouble p = new AtomicDouble();
broadcast.setProgressCallback(p::set, Threading.SAME_THREAD);
@ -234,8 +234,7 @@ public class TransactionBroadcastTest extends TestWithPeerGroup {
// 49 BTC in change.
assertEquals(valueOf(49, 0), t1.getValueSentToMe(wallet));
// The future won't complete until it's heard back from the network on p2.
InventoryMessage inv = new InventoryMessage();
inv.addTransaction(t1);
InventoryMessage inv = InventoryMessage.ofTransactions(t1);
inbound(p2, inv);
pingAndWait(p2);
Threading.waitForUserCode();