/* * 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 org.bitcoinj.core; import java.io.IOException; import java.io.OutputStream; import java.util.ArrayList; import java.util.Arrays; 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; } }