From 96e4774e79f242c1cdd16baad87bd2251e9b1b7f Mon Sep 17 00:00:00 2001 From: Mike Hearn Date: Wed, 23 Jul 2014 15:53:34 +0200 Subject: [PATCH] Add support for fetching UTXOs from a peer using the getutxo message. --- .../bitcoin/core/BitcoinSerializer.java | 6 + .../google/bitcoin/core/GetUTXOSMessage.java | 96 +++++++++++ .../java/com/google/bitcoin/core/Peer.java | 36 +++- .../com/google/bitcoin/core/UTXOSMessage.java | 162 ++++++++++++++++++ .../java/com/google/bitcoin/core/Utils.java | 4 +- .../google/bitcoin/core/VersionMessage.java | 14 +- .../bitcoin/core/BitcoindComparisonTool.java | 17 +- .../bitcoin/core/FullBlockTestGenerator.java | 135 ++++++++++----- 8 files changed, 417 insertions(+), 53 deletions(-) create mode 100644 core/src/main/java/com/google/bitcoin/core/GetUTXOSMessage.java create mode 100644 core/src/main/java/com/google/bitcoin/core/UTXOSMessage.java diff --git a/core/src/main/java/com/google/bitcoin/core/BitcoinSerializer.java b/core/src/main/java/com/google/bitcoin/core/BitcoinSerializer.java index 2976d7e4..c2bc993f 100644 --- a/core/src/main/java/com/google/bitcoin/core/BitcoinSerializer.java +++ b/core/src/main/java/com/google/bitcoin/core/BitcoinSerializer.java @@ -71,6 +71,8 @@ public class BitcoinSerializer { names.put(NotFoundMessage.class, "notfound"); names.put(MemoryPoolMessage.class, "mempool"); names.put(RejectMessage.class, "reject"); + names.put(GetUTXOSMessage.class, "getutxos"); + names.put(UTXOSMessage.class, "utxos"); } /** @@ -234,6 +236,10 @@ public class BitcoinSerializer { return new MemoryPoolMessage(); } else if (command.equals("reject")) { return new RejectMessage(params, payloadBytes); + } else if (command.equals("utxos")) { + return new UTXOSMessage(params, payloadBytes); + } else if (command.equals("getutxos")) { + return new GetUTXOSMessage(params, payloadBytes); } else { log.warn("No support for deserializing message with name {}", command); return new UnknownMessage(params, command, payloadBytes); diff --git a/core/src/main/java/com/google/bitcoin/core/GetUTXOSMessage.java b/core/src/main/java/com/google/bitcoin/core/GetUTXOSMessage.java new file mode 100644 index 00000000..8c103f73 --- /dev/null +++ b/core/src/main/java/com/google/bitcoin/core/GetUTXOSMessage.java @@ -0,0 +1,96 @@ +/* + * Copyright 2014 the bitcoinj 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 com.google.bitcoin.core; + +import com.google.common.collect.ImmutableList; + +import java.io.IOException; +import java.io.OutputStream; +import java.util.List; + +public class GetUTXOSMessage extends Message { + public static final int MIN_PROTOCOL_VERSION = 70003; + + private boolean includeMempool; + private ImmutableList outPoints; + + public GetUTXOSMessage(NetworkParameters params, List outPoints, boolean includeMempool) { + super(params); + this.outPoints = ImmutableList.copyOf(outPoints); + this.includeMempool = includeMempool; + } + + public GetUTXOSMessage(NetworkParameters params, byte[] payloadBytes) { + super(params, payloadBytes, 0); + } + + @Override + protected void parse() throws ProtocolException { + includeMempool = readBytes(1)[0] == 1; + long numOutpoints = readVarInt(); + ImmutableList.Builder list = ImmutableList.builder(); + for (int i = 0; i < numOutpoints; i++) { + TransactionOutPoint outPoint = new TransactionOutPoint(params, payload, cursor); + list.add(outPoint); + cursor += outPoint.getMessageSize(); + } + outPoints = list.build(); + length = cursor; + } + + public boolean getIncludeMempool() { + return includeMempool; + } + + public ImmutableList getOutPoints() { + return outPoints; + } + + @Override + protected void parseLite() throws ProtocolException { + // Not needed. + } + + @Override + void bitcoinSerializeToStream(OutputStream stream) throws IOException { + stream.write(new byte[]{1}); // include mempool. + stream.write(new VarInt(outPoints.size()).encode()); + for (TransactionOutPoint outPoint : outPoints) { + outPoint.bitcoinSerializeToStream(stream); + } + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + GetUTXOSMessage that = (GetUTXOSMessage) o; + + if (includeMempool != that.includeMempool) return false; + if (!outPoints.equals(that.outPoints)) return false; + + return true; + } + + @Override + public int hashCode() { + int result = (includeMempool ? 1 : 0); + result = 31 * result + outPoints.hashCode(); + return result; + } +} diff --git a/core/src/main/java/com/google/bitcoin/core/Peer.java b/core/src/main/java/com/google/bitcoin/core/Peer.java index 9a262084..2d25f81f 100644 --- a/core/src/main/java/com/google/bitcoin/core/Peer.java +++ b/core/src/main/java/com/google/bitcoin/core/Peer.java @@ -143,6 +143,8 @@ public class Peer extends PeerSocketHandler { // A settable future which completes (with this) when the connection is open private final SettableFuture connectionOpenFuture = SettableFuture.create(); + // A future representing the results of doing a getUTXOs call. + @Nullable private SettableFuture utxosFuture; /** *

Construct a peer that reads/writes from the given block chain.

@@ -324,7 +326,12 @@ public class Peer extends PeerSocketHandler { currentFilteredBlock = null; } - if (m instanceof NotFoundMessage) { + if (m instanceof Ping) { + if (((Ping) m).hasNonce()) + sendMessage(new Pong(((Ping) m).getNonce())); + } else if (m instanceof Pong) { + processPong((Pong) m); + } else if (m instanceof NotFoundMessage) { // This is sent to us when we did a getdata on some transactions that aren't in the peers memory pool. // Because NotFoundMessage is a subclass of InventoryMessage, the test for it must come before the next. processNotFoundMessage((NotFoundMessage) m); @@ -373,11 +380,12 @@ public class Peer extends PeerSocketHandler { vPeerVersionMessage.clientVersion, version); close(); } - } else if (m instanceof Ping) { - if (((Ping) m).hasNonce()) - sendMessage(new Pong(((Ping) m).getNonce())); - } else if (m instanceof Pong) { - processPong((Pong)m); + } else if (m instanceof UTXOSMessage) { + if (utxosFuture != null) { + SettableFuture future = utxosFuture; + utxosFuture = null; + future.set((UTXOSMessage)m); + } } else { log.warn("Received unhandled message: {}", m); } @@ -1526,4 +1534,20 @@ public class Peer extends PeerSocketHandler { public BloomFilter getBloomFilter() { return vBloomFilter; } + + /** + * Sends a query to the remote peer asking for the unspent transaction outputs (UTXOs) for the given outpoints, + * with the memory pool included. The result should be treated only as a hint: it's possible for the returned + * outputs to be fictional and not exist in any transaction, and it's possible for them to be spent the moment + * after the query returns. + */ + public ListenableFuture getUTXOs(List outPoints) { + if (utxosFuture != null) + throw new IllegalStateException("Already fetching UTXOs, wait for previous query to complete first."); + if (getPeerVersionMessage().clientVersion < GetUTXOSMessage.MIN_PROTOCOL_VERSION) + throw new IllegalStateException("Peer does not support getutxos protocol version"); + utxosFuture = SettableFuture.create(); + sendMessage(new GetUTXOSMessage(params, outPoints, true)); + return utxosFuture; + } } diff --git a/core/src/main/java/com/google/bitcoin/core/UTXOSMessage.java b/core/src/main/java/com/google/bitcoin/core/UTXOSMessage.java new file mode 100644 index 00000000..ad4f06bc --- /dev/null +++ b/core/src/main/java/com/google/bitcoin/core/UTXOSMessage.java @@ -0,0 +1,162 @@ +/* + * Copyright 2014 the bitcoinj 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 com.google.bitcoin.core; + +import java.io.IOException; +import java.io.OutputStream; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.BitSet; +import java.util.List; + +/** Message representing a list of unspent transaction outputs, returned in response to sending a GetUTXOSMessage. */ +public class UTXOSMessage extends Message { + private long height; + private Sha256Hash chainHead; + private byte[] hits; // little-endian bitset indicating whether an output was found or not. + + private List outputs; + private long[] heights; + + /** This is a special sentinel value that can appear in the heights field if the given tx is in the mempool. */ + public static long MEMPOOL_HEIGHT = 0x7FFFFFFFL; + + public UTXOSMessage(NetworkParameters params, byte[] payloadBytes) { + super(params, payloadBytes, 0); + } + + /** + * Provide an array of output objects, with nulls indicating that the output was missing. The bitset will + * be calculated from this. + */ + public UTXOSMessage(NetworkParameters params, List outputs, long[] heights, Sha256Hash chainHead, long height) { + super(params); + hits = new byte[(int) Math.ceil(outputs.size() / 8.0)]; + for (int i = 0; i < outputs.size(); i++) { + if (outputs.get(i) != null) + Utils.setBitLE(hits, i); + } + this.outputs = new ArrayList(outputs.size()); + for (TransactionOutput output : outputs) { + if (output != null) this.outputs.add(output); + } + this.chainHead = chainHead; + this.height = height; + this.heights = Arrays.copyOf(heights, heights.length); + } + + @Override + void bitcoinSerializeToStream(OutputStream stream) throws IOException { + Utils.uint32ToByteStreamLE(height, stream); + stream.write(chainHead.getBytes()); + stream.write(new VarInt(hits.length).encode()); + stream.write(hits); + stream.write(new VarInt(outputs.size()).encode()); + for (TransactionOutput output : outputs) { + // TODO: Allow these to be specified, if one day we care about sending this message ourselves + // (currently it's just used for unit testing). + Utils.uint32ToByteStreamLE(0L, stream); // Version + Utils.uint32ToByteStreamLE(0L, stream); // Height + output.bitcoinSerializeToStream(stream); + } + } + + @Override + void parse() throws ProtocolException { + // Format is: + // uint32 chainHeight + // uint256 chainHeadHash + // vector hitsBitmap; + // vector outs; + // + // A CCoin is { int nVersion, int nHeight, CTxOut output } + // The bitmap indicates which of the requested TXOs were found in the UTXO set. + height = readUint32(); + chainHead = readHash(); + int numBytes = (int) readVarInt(); + if (numBytes < 0 || numBytes > InventoryMessage.MAX_INVENTORY_ITEMS / 8) + throw new ProtocolException("hitsBitmap out of range: " + numBytes); + hits = readBytes(numBytes); + int numOuts = (int) readVarInt(); + if (numOuts < 0 || numOuts > InventoryMessage.MAX_INVENTORY_ITEMS) + throw new ProtocolException("numOuts out of range: " + numOuts); + outputs = new ArrayList(numOuts); + heights = new long[numOuts]; + for (int i = 0; i < numOuts; i++) { + long version = readUint32(); + long height = readUint32(); + if (version > 1) + throw new ProtocolException("Unknown tx version in getutxo output: " + version); + TransactionOutput output = new TransactionOutput(params, null, payload, cursor); + outputs.add(output); + heights[i] = height; + cursor += output.length; + } + length = cursor; + } + + @Override + protected void parseLite() throws ProtocolException { + // Not used. + } + + public byte[] getHitMap() { + return Arrays.copyOf(hits, hits.length); + } + + public List getOutputs() { + return new ArrayList(outputs); + } + + public long[] getHeights() { return Arrays.copyOf(heights, heights.length); } + + @Override + public String toString() { + return "UTXOSMessage{" + + "height=" + height + + ", chainHead=" + chainHead + + ", hitMap=" + Arrays.toString(hits) + + ", outputs=" + outputs + + ", heights=" + Arrays.toString(heights) + + '}'; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + UTXOSMessage message = (UTXOSMessage) o; + + if (height != message.height) return false; + if (!chainHead.equals(message.chainHead)) return false; + if (!Arrays.equals(heights, message.heights)) return false; + if (!Arrays.equals(hits, message.hits)) return false; + if (!outputs.equals(message.outputs)) return false; + + return true; + } + + @Override + public int hashCode() { + int result = (int) (height ^ (height >>> 32)); + result = 31 * result + chainHead.hashCode(); + result = 31 * result + Arrays.hashCode(hits); + result = 31 * result + outputs.hashCode(); + result = 31 * result + Arrays.hashCode(heights); + return result; + } +} diff --git a/core/src/main/java/com/google/bitcoin/core/Utils.java b/core/src/main/java/com/google/bitcoin/core/Utils.java index 4e759629..48241120 100644 --- a/core/src/main/java/com/google/bitcoin/core/Utils.java +++ b/core/src/main/java/com/google/bitcoin/core/Utils.java @@ -500,12 +500,12 @@ public class Utils { // 00000001, 00000010, 00000100, 00001000, ... private static final int bitMask[] = {0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80}; - // Checks if the given bit is set in data + /** Checks if the given bit is set in data, using little endian (not the same as Java native big endian) */ public static boolean checkBitLE(byte[] data, int index) { return (data[index >>> 3] & bitMask[7 & index]) != 0; } - // Sets the given bit in data to one + /** Sets the given bit in data to one, using little endian (not the same as Java native big endian) */ public static void setBitLE(byte[] data, int index) { data[index >>> 3] |= bitMask[7 & index]; } diff --git a/core/src/main/java/com/google/bitcoin/core/VersionMessage.java b/core/src/main/java/com/google/bitcoin/core/VersionMessage.java index 5d488ae3..2298e4c9 100644 --- a/core/src/main/java/com/google/bitcoin/core/VersionMessage.java +++ b/core/src/main/java/com/google/bitcoin/core/VersionMessage.java @@ -34,17 +34,17 @@ import java.net.UnknownHostException; public class VersionMessage extends Message { private static final long serialVersionUID = 7313594258967483180L; - /** - * A services flag that denotes whether the peer has a copy of the block chain or not. - */ + /** A services flag that denotes whether the peer has a copy of the block chain or not. */ public static final int NODE_NETWORK = 1; + /** A flag that denotes whether the peer supports the getutxos message or not. */ + public static final int NODE_GETUTXOS = 2; /** * The version number of the protocol spoken. */ public int clientVersion; /** - * Flags defining what is supported. Right now {@link #NODE_NETWORK} is the only flag defined. + * Flags defining what optional services are supported. */ public long localServices; /** @@ -309,4 +309,10 @@ public class VersionMessage extends Message { public boolean isBloomFilteringSupported() { return clientVersion >= FilteredBlock.MIN_PROTOCOL_VERSION; } + + /** Returns true if the protocol version and service bits both indicate support for the getutxos message. */ + public boolean isGetUTXOsSupported() { + return clientVersion >= GetUTXOSMessage.MIN_PROTOCOL_VERSION && + (localServices & NODE_GETUTXOS) == NODE_GETUTXOS; + } } diff --git a/core/src/test/java/com/google/bitcoin/core/BitcoindComparisonTool.java b/core/src/test/java/com/google/bitcoin/core/BitcoindComparisonTool.java index eca8935e..4eddffc3 100644 --- a/core/src/test/java/com/google/bitcoin/core/BitcoindComparisonTool.java +++ b/core/src/test/java/com/google/bitcoin/core/BitcoindComparisonTool.java @@ -165,6 +165,7 @@ public class BitcoindComparisonTool { int differingBlocks = 0; int invalidBlocks = 0; int mempoolRulesFailed = 0; + int utxoRulesFailed = 0; for (Rule rule : blockList.list) { if (rule instanceof BlockAndValidity) { BlockAndValidity block = (BlockAndValidity) rule; @@ -245,8 +246,19 @@ public class BitcoindComparisonTool { mempoolRulesFailed++; } mostRecentInv = null; + } else if (rule instanceof UTXORule) { + UTXORule r = (UTXORule) rule; + UTXOSMessage result = bitcoind.getUTXOs(r.query).get(); + if (!result.equals(r.result)) { + log.error("utxo result was not what we expected."); + log.error("Wanted {}", r.result); + log.error("but got {}", result); + utxoRulesFailed++; + } else { + log.info("Successful utxo query {}: {}", r.ruleName, result); + } } else { - log.error("Unknown rule"); + throw new RuntimeException("Unknown rule"); } } @@ -254,7 +266,8 @@ public class BitcoindComparisonTool { "Blocks which were not handled the same between bitcoind/bitcoinj: " + differingBlocks + "\n" + "Blocks which should/should not have been accepted but weren't/were: " + invalidBlocks + "\n" + "Transactions which were/weren't in memory pool but shouldn't/should have been: " + mempoolRulesFailed + "\n" + + "UTXO query mismatches: " + utxoRulesFailed + "\n" + "Unexpected inv messages: " + unexpectedInvs.get()); - System.exit(differingBlocks > 0 || invalidBlocks > 0 || mempoolRulesFailed > 0 || unexpectedInvs.get() > 0 ? 1 : 0); + System.exit(differingBlocks > 0 || invalidBlocks > 0 || mempoolRulesFailed > 0 || utxoRulesFailed > 0 || unexpectedInvs.get() > 0 ? 1 : 0); } } diff --git a/core/src/test/java/com/google/bitcoin/core/FullBlockTestGenerator.java b/core/src/test/java/com/google/bitcoin/core/FullBlockTestGenerator.java index 03d4f689..c6b17660 100644 --- a/core/src/test/java/com/google/bitcoin/core/FullBlockTestGenerator.java +++ b/core/src/test/java/com/google/bitcoin/core/FullBlockTestGenerator.java @@ -1,10 +1,14 @@ package com.google.bitcoin.core; import com.google.bitcoin.core.Transaction.SigHash; +import com.google.bitcoin.crypto.TransactionSignature; import com.google.bitcoin.script.Script; import com.google.bitcoin.script.ScriptBuilder; import com.google.common.base.Preconditions; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.Lists; +import javax.annotation.Nullable; import java.io.ByteArrayOutputStream; import java.io.File; import java.io.FileOutputStream; @@ -16,6 +20,32 @@ import static com.google.bitcoin.core.Coin.*; import static com.google.bitcoin.script.ScriptOpCodes.*; import static com.google.common.base.Preconditions.checkNotNull; import static com.google.common.base.Preconditions.checkState; +import static java.util.Collections.singletonList; + +class TransactionOutPointWithValue { + public TransactionOutPoint outpoint; + public Coin value; + public Script scriptPubKey; + + public TransactionOutPointWithValue(TransactionOutPoint outpoint, Coin value, Script scriptPubKey) { + this.outpoint = outpoint; + this.value = value; + this.scriptPubKey = scriptPubKey; + } + + public TransactionOutPointWithValue(Transaction tx, int output) { + this(new TransactionOutPoint(tx.getParams(), output, tx.getHash()), + tx.getOutput(output).getValue(), tx.getOutput(output).getScriptPubKey()); + } +} + +/** An arbitrary rule which the testing client must match */ +class Rule { + String ruleName; + Rule(String ruleName) { + this.ruleName = ruleName; + } +} /** * Represents a block which is sent to the tested application and which the application must either reject or accept, @@ -58,22 +88,20 @@ class MemoryPoolState extends Rule { } } -/** An arbitrary rule which the testing client must match */ -class Rule { - String ruleName; - Rule(String ruleName) { - this.ruleName = ruleName; - } -} +class UTXORule extends Rule { + List query; + UTXOSMessage result; -class TransactionOutPointWithValue { - public TransactionOutPoint outpoint; - public Coin value; - Script scriptPubKey; - public TransactionOutPointWithValue(TransactionOutPoint outpoint, Coin value, Script scriptPubKey) { - this.outpoint = outpoint; - this.value = value; - this.scriptPubKey = scriptPubKey; + public UTXORule(String ruleName, TransactionOutPoint query, UTXOSMessage result) { + super(ruleName); + this.query = singletonList(query); + this.result = result; + } + + public UTXORule(String ruleName, List query, UTXOSMessage result) { + super(ruleName); + this.query = query; + this.result = result; } } @@ -108,6 +136,7 @@ public class FullBlockTestGenerator { final Script OP_TRUE_SCRIPT = new ScriptBuilder().op(OP_TRUE).build(); final Script OP_NOP_SCRIPT = new ScriptBuilder().op(OP_NOP).build(); + // TODO: Rename this variable. List blocks = new LinkedList() { @Override public boolean add(Rule element) { @@ -164,8 +193,8 @@ public class FullBlockTestGenerator { blocks.add(new BlockAndValidity(blockToHeightMap, b2, true, false, b2.getHash(), chainHeadHeight + 2, "b2")); spendableOutputs.offer(new TransactionOutPointWithValue( new TransactionOutPoint(params, 0, b2.getTransactions().get(0).getHash()), - b2.getTransactions().get(0).getOutputs().get(0).getValue(), - b2.getTransactions().get(0).getOutputs().get(0).getScriptPubKey())); + b2.getTransactions().get(0).getOutput(0).getValue(), + b2.getTransactions().get(0).getOutput(0).getScriptPubKey())); // We now have the following chain (which output is spent is in parentheses): // genesis -> b1 (0) -> b2 (1) // @@ -179,15 +208,39 @@ public class FullBlockTestGenerator { blocks.add(new BlockAndValidity(blockToHeightMap, b3, true, false, b2.getHash(), chainHeadHeight + 2, "b3")); // Make sure nothing breaks if we add b3 twice blocks.add(new BlockAndValidity(blockToHeightMap, b3, true, false, b2.getHash(), chainHeadHeight + 2, "b3")); - // Now we add another block to make the alternative chain longer. - TransactionOutPointWithValue out2 = spendableOutputs.poll(); checkState(out2 != null); - Block b4 = createNextBlock(b3, chainHeadHeight + 3, out2, null); - blocks.add(new BlockAndValidity(blockToHeightMap, b4, true, false, b4.getHash(), chainHeadHeight + 3, "b4")); + // Do a simple UTXO query. + UTXORule utxo1; + { + Transaction coinbase = b2.getTransactions().get(0); + TransactionOutPoint outpoint = new TransactionOutPoint(params, 0, coinbase.getHash()); + long[] heights = new long[] {chainHeadHeight + 2}; + UTXOSMessage result = new UTXOSMessage(params, ImmutableList.of(coinbase.getOutput(0)), heights, b2.getHash(), chainHeadHeight + 2); + utxo1 = new UTXORule("utxo1", outpoint, result); + blocks.add(utxo1); + } + + // Now we add another block to make the alternative chain longer. // // genesis -> b1 (0) -> b2 (1) // \-> b3 (1) -> b4 (2) // + TransactionOutPointWithValue out2 = checkNotNull(spendableOutputs.poll()); + Block b4 = createNextBlock(b3, chainHeadHeight + 3, out2, null); + blocks.add(new BlockAndValidity(blockToHeightMap, b4, true, false, b4.getHash(), chainHeadHeight + 3, "b4")); + + // Check that the old coinbase is no longer in the UTXO set and the new one is. + { + Transaction coinbase = b4.getTransactions().get(0); + TransactionOutPoint outpoint = new TransactionOutPoint(params, 0, coinbase.getHash()); + List queries = ImmutableList.of(utxo1.query.get(0), outpoint); + List results = Lists.asList(null, coinbase.getOutput(0), new TransactionOutput[] {}); + long[] heights = new long[] {chainHeadHeight + 3}; + UTXOSMessage result = new UTXOSMessage(params, results, heights, b4.getHash(), chainHeadHeight + 3); + UTXORule utxo2 = new UTXORule("utxo2", queries, result); + blocks.add(utxo2); + } + // ... and back to the first chain. Block b5 = createNextBlock(b2, chainHeadHeight + 3, out2, null); blocks.add(new BlockAndValidity(blockToHeightMap, b5, true, false, b4.getHash(), chainHeadHeight + 3, "b5")); @@ -1514,6 +1567,19 @@ public class FullBlockTestGenerator { post82Mempool.add(new InventoryItem(InventoryItem.Type.Transaction, b79tx.getHash())); blocks.add(new MemoryPoolState(post82Mempool, "post-b82 tx resurrection")); + System.err.println("b78tx: " + b78tx); + System.err.println("b79tx: " + b79tx); + System.err.flush(); + + // Check the UTXO query takes mempool into account. + { + TransactionOutPoint outpoint = new TransactionOutPoint(params, 0, b79tx.getHash()); + long[] heights = new long[] { UTXOSMessage.MEMPOOL_HEIGHT }; + UTXOSMessage result = new UTXOSMessage(params, ImmutableList.of(b79tx.getOutput(0)), heights, b82.getHash(), chainHeadHeight + 28); + UTXORule utxo3 = new UTXORule("utxo3", outpoint, result); + blocks.add(utxo3); + } + // The remaining tests arent designed to fit in the standard flow, and thus must always come last // Add new tests here. //TODO: Explicitly address MoneyRange() checks @@ -1527,10 +1593,6 @@ public class FullBlockTestGenerator { Block b1001 = createNextBlock(b82, chainHeadHeight + 29, out28, null); blocks.add(new BlockAndValidity(blockToHeightMap, b1001, true, false, b1001.getHash(), chainHeadHeight + 29, "b1001")); - spendableOutputs.offer(new TransactionOutPointWithValue( - new TransactionOutPoint(params, 0, b1001.getTransactions().get(0).getHash()), - b1001.getTransactions().get(0).getOutput(0).getValue(), - b1001.getTransactions().get(0).getOutput(0).getScriptPubKey())); int nextHeight = chainHeadHeight + 30; if (runLargeReorgs) { @@ -1623,7 +1685,7 @@ public class FullBlockTestGenerator { } private byte uniquenessCounter = 0; - private Block createNextBlock(Block baseBlock, int nextBlockHeight, TransactionOutPointWithValue prevOut, + private Block createNextBlock(Block baseBlock, int nextBlockHeight, @Nullable TransactionOutPointWithValue prevOut, Coin additionalCoinbaseValue) throws ScriptException { Integer height = blockToHeightMap.get(baseBlock.getHash()); if (height != null) @@ -1656,20 +1718,15 @@ public class FullBlockTestGenerator { input.setSequenceNumber(sequence); t.addInput(input); - byte[] connectedPubKeyScript = prevOut.scriptPubKey.getProgram(); - Sha256Hash hash = t.hashForSignature(0, connectedPubKeyScript, SigHash.ALL, false); - - // Sign input - try { - ByteArrayOutputStream bos = new UnsafeByteArrayOutputStream(73); - bos.write(coinbaseOutKey.sign(hash).encodeToDER()); - bos.write(SigHash.ALL.ordinal() + 1); - byte[] signature = bos.toByteArray(); - + if (prevOut.scriptPubKey.getChunks().get(0).equalsOpCode(OP_TRUE)) { + input.setScriptSig(new ScriptBuilder().op(OP_1).build()); + } else { + // Sign input checkState(prevOut.scriptPubKey.isSentToRawPubKey()); - input.setScriptBytes(Script.createInputScript(signature)); - } catch (IOException e) { - throw new RuntimeException(e); // Cannot happen. + Sha256Hash hash = t.hashForSignature(0, prevOut.scriptPubKey, SigHash.ALL, false); + input.setScriptSig(ScriptBuilder.createInputScript( + new TransactionSignature(coinbaseOutKey.sign(hash), SigHash.ALL, false)) + ); } } }