Add support for fetching UTXOs from a peer using the getutxo message.

This commit is contained in:
Mike Hearn
2014-07-23 15:53:34 +02:00
parent bca2f2abac
commit 96e4774e79
8 changed files with 417 additions and 53 deletions

View File

@@ -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);

View File

@@ -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;
}
}

View File

@@ -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;
}
}

View 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;
}
}

View File

@@ -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];
}

View File

@@ -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;
}
}

View File

@@ -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);
}
}

View File

@@ -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))
);
}
}
}