mirror of
https://github.com/Qortal/altcoinj.git
synced 2025-11-01 21:17:13 +00:00
Add support for fetching UTXOs from a peer using the getutxo message.
This commit is contained in:
@@ -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);
|
||||
|
||||
@@ -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<TransactionOutPoint> outPoints;
|
||||
|
||||
public GetUTXOSMessage(NetworkParameters params, List<TransactionOutPoint> 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<TransactionOutPoint> 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<TransactionOutPoint> 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;
|
||||
}
|
||||
}
|
||||
@@ -143,6 +143,8 @@ public class Peer extends PeerSocketHandler {
|
||||
|
||||
// A settable future which completes (with this) when the connection is open
|
||||
private final SettableFuture<Peer> connectionOpenFuture = SettableFuture.create();
|
||||
// A future representing the results of doing a getUTXOs call.
|
||||
@Nullable private SettableFuture<UTXOSMessage> utxosFuture;
|
||||
|
||||
/**
|
||||
* <p>Construct a peer that reads/writes from the given block chain.</p>
|
||||
@@ -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<UTXOSMessage> 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<UTXOSMessage> getUTXOs(List<TransactionOutPoint> 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;
|
||||
}
|
||||
}
|
||||
|
||||
162
core/src/main/java/com/google/bitcoin/core/UTXOSMessage.java
Normal file
162
core/src/main/java/com/google/bitcoin/core/UTXOSMessage.java
Normal file
@@ -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<TransactionOutput> 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<TransactionOutput> 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<TransactionOutput>(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<unsigned char> hitsBitmap;
|
||||
// vector<CCoin> 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<TransactionOutput>(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<TransactionOutput> getOutputs() {
|
||||
return new ArrayList<TransactionOutput>(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;
|
||||
}
|
||||
}
|
||||
@@ -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];
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<TransactionOutPoint> 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<TransactionOutPoint> 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<Rule> blocks = new LinkedList<Rule>() {
|
||||
@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<TransactionOutPoint> queries = ImmutableList.of(utxo1.query.get(0), outpoint);
|
||||
List<TransactionOutput> 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))
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user