3
0
mirror of https://github.com/Qortal/altcoinj.git synced 2025-02-07 14:54:15 +00:00

First part of Steves changes in preparation for a high performance multiplexing proxy:

1) Introduce partial support for caching the underlying byte arrays during message deserialization, so re-serialization can be skipped in the case where a message is not modified.
 
 2) Add c'tors that allow a message to be configured to lazily parse on-demand.
 
 Note that the getters/setters that make lazy parsing transparent are coming in future commits.
This commit is contained in:
Mike Hearn 2011-10-11 13:08:54 +00:00
parent ba2351f5aa
commit 34fea86708
17 changed files with 384 additions and 48 deletions

12
TODO
View File

@ -23,3 +23,15 @@ Cleanup:
- Find a way to avoid some horrid hacks when shutting down the network connection. - Find a way to avoid some horrid hacks when shutting down the network connection.
- Implement a BitCoin class that encapsulates a BigInteger and formatting. - Implement a BitCoin class that encapsulates a BigInteger and formatting.
- Make NetworkParameters use subclasses instead of static methods to construct. - Make NetworkParameters use subclasses instead of static methods to construct.
==========
Impacts from Steves changes:
- Reformat affected files
- JavaDocs for the c'tors that take parseLazy/parseRetain
- Copyright header/correct author for EmptyMessage/ChildMessage
- Remove superfluous empty lines in ListMessage.java
- Remove VersionMessage check in Message serialization roundtrip checks
- Delete dead code in Message.checkParsed

View File

@ -8,12 +8,20 @@ public class AddressMessage extends Message {
private static final long MAX_ADDRESSES = 1024; private static final long MAX_ADDRESSES = 1024;
List<PeerAddress> addresses; List<PeerAddress> addresses;
AddressMessage(NetworkParameters params, byte[] payload, int offset, boolean parseLazy, boolean parseRetain) throws ProtocolException {
super(params, payload, offset, parseLazy, parseRetain);
}
AddressMessage(NetworkParameters params, byte[] payload, boolean parseLazy, boolean parseRetain) throws ProtocolException {
super(params, payload, 0, parseLazy, parseRetain);
}
AddressMessage(NetworkParameters params, byte[] payload, int offset) throws ProtocolException { AddressMessage(NetworkParameters params, byte[] payload, int offset) throws ProtocolException {
super(params, payload, offset); super(params, payload, offset, false, false);
} }
AddressMessage(NetworkParameters params, byte[] payload) throws ProtocolException { AddressMessage(NetworkParameters params, byte[] payload) throws ProtocolException {
super(params, payload, 0); super(params, payload, 0, false, false);
} }
@Override @Override
@ -24,7 +32,7 @@ public class AddressMessage extends Message {
throw new ProtocolException("Address message too large."); throw new ProtocolException("Address message too large.");
addresses = new ArrayList<PeerAddress>((int)numAddresses); addresses = new ArrayList<PeerAddress>((int)numAddresses);
for (int i = 0; i < numAddresses; i++) { for (int i = 0; i < numAddresses; i++) {
PeerAddress addr = new PeerAddress(params, bytes, cursor, protocolVersion); PeerAddress addr = new PeerAddress(params, bytes, cursor, protocolVersion, this, parseLazy, parseRetain);
addresses.add(addr); addresses.add(addr);
cursor += addr.getMessageSize(); cursor += addr.getMessageSize();
} }

View File

@ -79,6 +79,10 @@ public class Block extends Message {
super(params, payloadBytes, 0); super(params, payloadBytes, 0);
} }
public Block(NetworkParameters params, byte[] payloadBytes, boolean parseLazy, boolean parseRetain) throws ProtocolException {
super(params, payloadBytes, 0, parseLazy, parseRetain);
}
private void readObject(ObjectInputStream ois) throws ClassNotFoundException, IOException { private void readObject(ObjectInputStream ois) throws ClassNotFoundException, IOException {
ois.defaultReadObject(); ois.defaultReadObject();
// This code is not actually necessary, as transient fields are initialized to the default value which is in // This code is not actually necessary, as transient fields are initialized to the default value which is in
@ -104,7 +108,7 @@ public class Block extends Message {
int numTransactions = (int) readVarInt(); int numTransactions = (int) readVarInt();
transactions = new ArrayList<Transaction>(numTransactions); transactions = new ArrayList<Transaction>(numTransactions);
for (int i = 0; i < numTransactions; i++) { for (int i = 0; i < numTransactions; i++) {
Transaction tx = new Transaction(params, bytes, cursor); Transaction tx = new Transaction(params, bytes, cursor, this, parseLazy, parseRetain);
transactions.add(tx); transactions.add(tx);
cursor += tx.getMessageSize(); cursor += tx.getMessageSize();
} }
@ -120,13 +124,13 @@ public class Block extends Message {
} }
@Override @Override
void bitcoinSerializeToStream(OutputStream stream) throws IOException { protected void bitcoinSerializeToStream(OutputStream stream) throws IOException {
writeHeader(stream); writeHeader(stream);
// We may only have enough data to write the header. // We may only have enough data to write the header.
if (transactions == null) return; if (transactions == null) return;
stream.write(new VarInt(transactions.size()).encode()); stream.write(new VarInt(transactions.size()).encode());
for (Transaction tx : transactions) { for (Transaction tx : transactions) {
tx.bitcoinSerializeToStream(stream); tx.bitcoinSerialize(stream);
} }
} }

View File

@ -0,0 +1,63 @@
package com.google.bitcoin.core;
/**
* Represents a Message type that can be contained within another Message. ChildMessages that have a cached
* backing byte array need to invalidate their parent's caches as well as their own if they are modified.
*
* @author git
*
*/
public abstract class ChildMessage extends Message {
private Message parent;
protected ChildMessage() {
}
public ChildMessage(NetworkParameters params) {
super(params);
}
public ChildMessage(NetworkParameters params, byte[] msg, int offset, int protocolVersion) throws ProtocolException {
super(params, msg, offset, protocolVersion);
}
public ChildMessage(NetworkParameters params, byte[] msg, int offset, int protocolVersion, Message parent, boolean parseLazy,
boolean parseRetain) throws ProtocolException {
super(params, msg, offset, protocolVersion, parseLazy, parseRetain);
this.parent = parent;
}
public ChildMessage(NetworkParameters params, byte[] msg, int offset) throws ProtocolException {
super(params, msg, offset);
}
public ChildMessage(NetworkParameters params, byte[] msg, int offset, Message parent, boolean parseLazy, boolean parseRetain)
throws ProtocolException {
super(params, msg, offset, parseLazy, parseRetain);
this.parent = parent;
}
public void setParent(Message parent) {
if (this.parent != null && this.parent != parent && parent !=null) {
//after old parent is unlinked it won't be able to receive notice if this ChildMessage
//changes internally. To be safe we invalidate the parent cache to ensure it rebuilds
//manually on serialization.
this.parent.unCache();
}
this.parent = parent;
}
/* (non-Javadoc)
* @see com.google.bitcoin.core.Message#unCache()
*/
@Override
protected void unCache() {
super.unCache();
if (parent != null)
parent.unCache();
}
}

View File

@ -4,7 +4,8 @@ import java.io.IOException;
import java.io.OutputStream; import java.io.OutputStream;
/** /**
* Parent class for header only message that don't have a payload * Parent class for header only messages that don't have a payload.
* Currently this includes getaddr, ping, verack as well as the special bitcoinj class UnknownMessage
* @author git * @author git
* *
*/ */
@ -17,16 +18,12 @@ public abstract class EmptyMessage extends Message {
super(params); super(params);
} }
public EmptyMessage(NetworkParameters params, byte[] msg, int offset, int protocolVersion) throws ProtocolException {
super(params, msg, offset, protocolVersion);
}
public EmptyMessage(NetworkParameters params, byte[] msg, int offset) throws ProtocolException { public EmptyMessage(NetworkParameters params, byte[] msg, int offset) throws ProtocolException {
super(params, msg, offset); super(params, msg, offset);
} }
@Override @Override
final void bitcoinSerializeToStream(OutputStream stream) throws IOException { final protected void bitcoinSerializeToStream(OutputStream stream) throws IOException {
} }
} }

View File

@ -17,6 +17,7 @@
package com.google.bitcoin.core; package com.google.bitcoin.core;
public class GetAddrMessage extends EmptyMessage { public class GetAddrMessage extends EmptyMessage {
public GetAddrMessage(NetworkParameters params) { public GetAddrMessage(NetworkParameters params) {
super(params); super(params);
} }

View File

@ -52,7 +52,7 @@ public class GetBlocksMessage extends Message {
return b.toString(); return b.toString();
} }
void bitcoinSerializeToStream(OutputStream stream) throws IOException { protected void bitcoinSerializeToStream(OutputStream stream) throws IOException {
// Version, for some reason. // Version, for some reason.
Utils.uint32ToByteStreamLE(NetworkParameters.PROTOCOL_VERSION, stream); Utils.uint32ToByteStreamLE(NetworkParameters.PROTOCOL_VERSION, stream);
// Then a vector of block hashes. This is actually a "block locator", a set of block // Then a vector of block hashes. This is actually a "block locator", a set of block

View File

@ -23,6 +23,11 @@ public class GetDataMessage extends ListMessage {
super(params, payloadBytes); super(params, payloadBytes);
} }
public GetDataMessage(NetworkParameters params, byte[] msg, boolean parseLazy, boolean parseRetain)
throws ProtocolException {
super(params, msg, parseLazy, parseRetain);
}
public GetDataMessage(NetworkParameters params) { public GetDataMessage(NetworkParameters params) {
super(params); super(params);
} }

View File

@ -23,6 +23,11 @@ public class InventoryMessage extends ListMessage {
super(params, bytes); super(params, bytes);
} }
public InventoryMessage(NetworkParameters params, byte[] msg, boolean parseLazy, boolean parseRetain)
throws ProtocolException {
super(params, msg, parseLazy, parseRetain);
}
public InventoryMessage(NetworkParameters params) { public InventoryMessage(NetworkParameters params) {
super(params); super(params);
} }

View File

@ -37,6 +37,14 @@ public abstract class ListMessage extends Message
} }
public ListMessage(NetworkParameters params, byte[] msg, boolean parseLazy, boolean parseRetain)
throws ProtocolException {
super(params, msg, 0, parseLazy, parseRetain);
}
public ListMessage(NetworkParameters params) { public ListMessage(NetworkParameters params) {
super(params); super(params);
items = new ArrayList<InventoryItem>(); items = new ArrayList<InventoryItem>();

View File

@ -48,6 +48,10 @@ public abstract class Message implements Serializable {
// The raw message bytes themselves. // The raw message bytes themselves.
protected transient byte[] bytes; protected transient byte[] bytes;
private transient boolean parsed = false;
protected transient final boolean parseLazy;
protected transient final boolean parseRetain;
protected transient int protocolVersion; protected transient int protocolVersion;
// This will be saved by subclasses that implement Serializable. // This will be saved by subclasses that implement Serializable.
@ -55,20 +59,36 @@ public abstract class Message implements Serializable {
/** This exists for the Java serialization framework to use only. */ /** This exists for the Java serialization framework to use only. */
protected Message() { protected Message() {
parsed = true;
parseLazy = false;
parseRetain = false;
} }
Message(NetworkParameters params) { Message(NetworkParameters params) {
this.params = params; this.params = params;
parsed = true;
parseLazy = false;
parseRetain = false;
}
Message(NetworkParameters params, byte[] msg, int offset, int protocolVersion) throws ProtocolException {
this(params, msg, offset, protocolVersion, false, false);
} }
@SuppressWarnings("unused") @SuppressWarnings("unused")
Message(NetworkParameters params, byte[] msg, int offset, int protocolVersion) throws ProtocolException { Message(NetworkParameters params, byte[] msg, int offset, int protocolVersion, final boolean parseLazy, final boolean parseRetain) throws ProtocolException {
this.parseLazy = parseLazy;
this.parseRetain = parseRetain;
this.protocolVersion = protocolVersion; this.protocolVersion = protocolVersion;
this.params = params; this.params = params;
this.bytes = msg; this.bytes = msg;
this.cursor = this.offset = offset; this.cursor = this.offset = offset;
if (!parseLazy) {
parse(); parse();
parsed = true;
}
if (SELF_CHECK && !this.getClass().getSimpleName().equals("VersionMessage")) { if (SELF_CHECK && !this.getClass().getSimpleName().equals("VersionMessage")) {
checkParse();
byte[] msgbytes = new byte[cursor - offset]; byte[] msgbytes = new byte[cursor - offset];
System.arraycopy(msg, offset, msgbytes, 0, cursor - offset); System.arraycopy(msg, offset, msgbytes, 0, cursor - offset);
byte[] reserialized = bitcoinSerialize(); byte[] reserialized = bitcoinSerialize();
@ -77,11 +97,17 @@ public abstract class Message implements Serializable {
Utils.bytesToHexString(reserialized) + " vs \n" + Utils.bytesToHexString(reserialized) + " vs \n" +
Utils.bytesToHexString(msgbytes)); Utils.bytesToHexString(msgbytes));
} }
if (parseRetain || !parsed)
return;
this.bytes = null; this.bytes = null;
} }
Message(NetworkParameters params, byte[] msg, int offset) throws ProtocolException { Message(NetworkParameters params, byte[] msg, int offset) throws ProtocolException {
this(params, msg, offset, NetworkParameters.PROTOCOL_VERSION); this(params, msg, offset, NetworkParameters.PROTOCOL_VERSION, false, false);
}
Message(NetworkParameters params, byte[] msg, int offset, final boolean parseLazy, final boolean parseRetain) throws ProtocolException {
this(params, msg, offset, NetworkParameters.PROTOCOL_VERSION, parseLazy, parseRetain);
} }
// These methods handle the serialization/deserialization using the custom BitCoin protocol. // These methods handle the serialization/deserialization using the custom BitCoin protocol.
@ -90,7 +116,86 @@ public abstract class Message implements Serializable {
// are serialized to the wallet. // are serialized to the wallet.
abstract void parse() throws ProtocolException; abstract void parse() throws ProtocolException;
/**
* Ensure the object is parsed if needed. This should be called in every getter before returning a value.
* If the lazy parse flag is not set this is a method returns immediately.
*/
protected synchronized void checkParse() {
if (parsed || bytes == null)
return;
try {
parse();
parsed = true;
//if (!parseRetain)
//bytes = null;
} catch (ProtocolException e) {
throw new RuntimeException("Lazy parsing of message failed", e);
}
}
/**
* To be called before any change of internal values including any setters. This ensures any cached byte array is removed after performing
* a lazy parse if necessary to ensure the object is fully populated.
*
* Child messages of this object(e.g. Transactions belonging to a Block) will not have their internal byte caches invalidated unless
* they are also modified internally.
*/
protected void unCache() {
/*
* This is a NOP at the moment. Will complete lazy parsing as a separate patch first.
* safe retention of backing byte array is tricky in cases where a parent Message object
* may have child message objects (e.g. block - tx). There has to be a way to either
* mark the cursor at the end of the parent portion of the array or a way the child can
* invalidate the parent array. This might require a ByteArrayView class which implements List
* and retains a reference to it's parent ByteArrayView so it can invalidate it.
* Alternately the child message can hold a reference to
* it's parent and propagate a call to unCache up the chain to the parent. This way only those children on the
* invalidated branch lose their caching. On the other hand this might introduce more overhead than it's worth
* since this call has to made in every setter.
* Perhaps a simpler approach where in the special cases where a cached array is wanted it is the callers responsibility
* to keep track of whether the cache is valid or not.
*/
//if (!parseRetain)
// return;
//checkParse();
//bytes = null;
}
/**
* Serialize this message to a byte array that conforms to the bitcoin wire protocol.
* <br/>
* This method may return the original byte array used to construct this message if the
* following conditions are met:
* <ol>
* <li>1) The message was parsed from a byte array with parseRetain = true</li>
* <li>2) The message has not been modified</li>
* <li>3) The array had an offset of 0 and no surplus bytes</li>
* </ol>
*
* If condition 3 is not met then an copy of the relevant portion of the array will be returned.
* Otherwise a full serialize will occur.
*
* @return
*/
final public byte[] bitcoinSerialize() { final public byte[] bitcoinSerialize() {
//1st attempt to use a cached array
if (bytes != null) {
if (offset == 0 && cursor == bytes.length) {
//cached byte array is the entire message with no extras
//so we can return as is and avoid an array copy.
return bytes;
}
int len = cursor - offset;
byte[] buf = new byte[len];
System.arraycopy(bytes, offset, buf, 0, len);
return buf;
}
//no cached array available so serialize parts by stream.
ByteArrayOutputStream stream = new ByteArrayOutputStream(); ByteArrayOutputStream stream = new ByteArrayOutputStream();
try { try {
bitcoinSerializeToStream(stream); bitcoinSerializeToStream(stream);
@ -101,6 +206,20 @@ public abstract class Message implements Serializable {
return stream.toByteArray(); return stream.toByteArray();
} }
/**
* Serialize this message to the provided OutputStream using the bitcoin wire format.
* @param stream
* @throws IOException
*/
final public void bitcoinSerialize(OutputStream stream) throws IOException {
//1st check for cached bytes
if (bytes != null) {
stream.write(bytes, offset, cursor - offset);
return;
}
bitcoinSerializeToStream(stream);
}
/** /**
* Serializes this message to the provided stream. If you just want the raw bytes use bitcoinSerialize(). * Serializes this message to the provided stream. If you just want the raw bytes use bitcoinSerialize().
*/ */

View File

@ -31,7 +31,7 @@ import static com.google.bitcoin.core.Utils.uint64ToByteStreamLE;
* A PeerAddress holds an IP address and port number representing the network location of * A PeerAddress holds an IP address and port number representing the network location of
* a peer in the BitCoin P2P network. It exists primarily for serialization purposes. * a peer in the BitCoin P2P network. It exists primarily for serialization purposes.
*/ */
public class PeerAddress extends Message { public class PeerAddress extends ChildMessage {
private static final long serialVersionUID = 7501293709324197411L; private static final long serialVersionUID = 7501293709324197411L;
InetAddress addr; InetAddress addr;
@ -46,6 +46,17 @@ public class PeerAddress extends Message {
super(params, payload, offset, protocolVersion); super(params, payload, offset, protocolVersion);
} }
/**
* Construct a peer address from a serialized payload.
*/
public PeerAddress(NetworkParameters params, byte[] msg, int offset, int protocolVersion, Message parent, boolean parseLazy,
boolean parseRetain) throws ProtocolException {
super(params, msg, offset, protocolVersion, parent, parseLazy, parseRetain);
}
/** /**
* Construct a peer address from a memorized or hardcoded address. * Construct a peer address from a memorized or hardcoded address.
*/ */
@ -69,7 +80,7 @@ public class PeerAddress extends Message {
} }
@Override @Override
public void bitcoinSerializeToStream(OutputStream stream) throws IOException { protected void bitcoinSerializeToStream(OutputStream stream) throws IOException {
if (protocolVersion >= 31402) { if (protocolVersion >= 31402) {
int secs = (int)(Utils.now().getTime() / 1000); int secs = (int)(Utils.now().getTime() / 1000);
uint32ToByteStreamLE(secs, stream); uint32ToByteStreamLE(secs, stream);

View File

@ -18,6 +18,7 @@ package com.google.bitcoin.core;
import java.io.ByteArrayOutputStream; import java.io.ByteArrayOutputStream;
import java.io.IOException; import java.io.IOException;
import java.io.ObjectOutputStream;
import java.io.OutputStream; import java.io.OutputStream;
import java.io.Serializable; import java.io.Serializable;
import java.math.BigInteger; import java.math.BigInteger;
@ -37,7 +38,7 @@ import static com.google.bitcoin.core.Utils.*;
* serialization which is used for the wallet. This allows us to easily add extra fields used for our own accounting * serialization which is used for the wallet. This allows us to easily add extra fields used for our own accounting
* or UI purposes. * or UI purposes.
*/ */
public class Transaction extends Message implements Serializable { public class Transaction extends ChildMessage implements Serializable {
private static final Logger log = LoggerFactory.getLogger(Transaction.class); private static final Logger log = LoggerFactory.getLogger(Transaction.class);
private static final long serialVersionUID = -8567546957352643140L; private static final long serialVersionUID = -8567546957352643140L;
@ -89,6 +90,22 @@ public class Transaction extends Message implements Serializable {
// inputs/outputs will be created in parse() // inputs/outputs will be created in parse()
} }
/**
* Creates a transaction by reading payload starting from offset bytes in. Length of a transaction is fixed.
*/
public Transaction(NetworkParameters params, byte[] msg, int offset, Message parent, boolean parseLazy, boolean parseRetain)
throws ProtocolException {
super(params, msg, offset, parent, parseLazy, parseRetain);
}
/**
* Creates a transaction by reading payload starting from offset bytes in. Length of a transaction is fixed.
*/
public Transaction(NetworkParameters params, byte[] msg, Message parent, boolean parseLazy, boolean parseRetain)
throws ProtocolException {
super(params, msg, 0, parent, parseLazy, parseRetain);
}
/** /**
* Returns a read-only list of the inputs of this transaction. * Returns a read-only list of the inputs of this transaction.
*/ */
@ -272,7 +289,7 @@ public class Transaction extends Message implements Serializable {
long numInputs = readVarInt(); long numInputs = readVarInt();
inputs = new ArrayList<TransactionInput>((int)numInputs); inputs = new ArrayList<TransactionInput>((int)numInputs);
for (long i = 0; i < numInputs; i++) { for (long i = 0; i < numInputs; i++) {
TransactionInput input = new TransactionInput(params, this, bytes, cursor); TransactionInput input = new TransactionInput(params, this, bytes, cursor, parseLazy, parseRetain);
inputs.add(input); inputs.add(input);
cursor += input.getMessageSize(); cursor += input.getMessageSize();
} }
@ -280,7 +297,7 @@ public class Transaction extends Message implements Serializable {
long numOutputs = readVarInt(); long numOutputs = readVarInt();
outputs = new ArrayList<TransactionOutput>((int)numOutputs); outputs = new ArrayList<TransactionOutput>((int)numOutputs);
for (long i = 0; i < numOutputs; i++) { for (long i = 0; i < numOutputs; i++) {
TransactionOutput output = new TransactionOutput(params, this, bytes, cursor); TransactionOutput output = new TransactionOutput(params, this, bytes, cursor, parseLazy, parseRetain);
outputs.add(output); outputs.add(output);
cursor += output.getMessageSize(); cursor += output.getMessageSize();
} }
@ -457,14 +474,14 @@ public class Transaction extends Message implements Serializable {
} }
@Override @Override
public void bitcoinSerializeToStream(OutputStream stream) throws IOException { protected void bitcoinSerializeToStream(OutputStream stream) throws IOException {
uint32ToByteStreamLE(version, stream); uint32ToByteStreamLE(version, stream);
stream.write(new VarInt(inputs.size()).encode()); stream.write(new VarInt(inputs.size()).encode());
for (TransactionInput in : inputs) for (TransactionInput in : inputs)
in.bitcoinSerializeToStream(stream); in.bitcoinSerialize(stream);
stream.write(new VarInt(outputs.size()).encode()); stream.write(new VarInt(outputs.size()).encode());
for (TransactionOutput out : outputs) for (TransactionOutput out : outputs)
out.bitcoinSerializeToStream(stream); out.bitcoinSerialize(stream);
uint32ToByteStreamLE(lockTime, stream); uint32ToByteStreamLE(lockTime, stream);
} }
@ -480,4 +497,15 @@ public class Transaction extends Message implements Serializable {
public int hashCode() { public int hashCode() {
return getHash().hashCode(); return getHash().hashCode();
} }
/**
* Ensure object is fully parsed before invoking java serialization. The backing byte array
* is transient so if the object has parseLazy = true and hasn't invoked checkParse yet
* then data will be lost during serialization.
*/
private void writeObject(ObjectOutputStream out) throws IOException {
checkParse();
out.defaultWriteObject();
}
} }

View File

@ -17,6 +17,7 @@
package com.google.bitcoin.core; package com.google.bitcoin.core;
import java.io.IOException; import java.io.IOException;
import java.io.ObjectOutputStream;
import java.io.OutputStream; import java.io.OutputStream;
import java.io.Serializable; import java.io.Serializable;
import java.util.Map; import java.util.Map;
@ -27,7 +28,7 @@ import java.util.Map;
* transaction as being a module which is wired up to others, the inputs of one have to be wired * transaction as being a module which is wired up to others, the inputs of one have to be wired
* to the outputs of another. The exceptions are coinbase transactions, which create new coins. * to the outputs of another. The exceptions are coinbase transactions, which create new coins.
*/ */
public class TransactionInput extends Message implements Serializable { public class TransactionInput extends ChildMessage implements Serializable {
private static final long serialVersionUID = 2; private static final long serialVersionUID = 2;
public static final byte[] EMPTY_ARRAY = new byte[0]; public static final byte[] EMPTY_ARRAY = new byte[0];
@ -73,8 +74,15 @@ public class TransactionInput extends Message implements Serializable {
this.parentTransaction = parentTransaction; this.parentTransaction = parentTransaction;
} }
/** Deserializes an input message. This is usually part of a transaction message. */
public TransactionInput(NetworkParameters params, Transaction parentTransaction, byte[] msg, int offset, boolean parseLazy, boolean parseRetain)
throws ProtocolException {
super(params, msg, offset, parentTransaction, parseLazy, parseRetain);
this.parentTransaction = parentTransaction;
}
void parse() throws ProtocolException { void parse() throws ProtocolException {
outpoint = new TransactionOutPoint(params, bytes, cursor); outpoint = new TransactionOutPoint(params, bytes, cursor, this, parseLazy, parseRetain);
cursor += outpoint.getMessageSize(); cursor += outpoint.getMessageSize();
int scriptLen = (int) readVarInt(); int scriptLen = (int) readVarInt();
scriptBytes = readBytes(scriptLen); scriptBytes = readBytes(scriptLen);
@ -82,8 +90,8 @@ public class TransactionInput extends Message implements Serializable {
} }
@Override @Override
public void bitcoinSerializeToStream(OutputStream stream) throws IOException { protected void bitcoinSerializeToStream(OutputStream stream) throws IOException {
outpoint.bitcoinSerializeToStream(stream); outpoint.bitcoinSerialize(stream);
stream.write(new VarInt(scriptBytes.length).encode()); stream.write(new VarInt(scriptBytes.length).encode());
stream.write(scriptBytes); stream.write(scriptBytes);
Utils.uint32ToByteStreamLE(sequence, stream); Utils.uint32ToByteStreamLE(sequence, stream);
@ -187,4 +195,14 @@ public class TransactionInput extends Message implements Serializable {
outpoint.fromTx = null; outpoint.fromTx = null;
return true; return true;
} }
/**
* Ensure object is fully parsed before invoking java serialization. The backing byte array
* is transient so if the object has parseLazy = true and hasn't invoked checkParse yet
* then data will be lost during serialization.
*/
private void writeObject(ObjectOutputStream out) throws IOException {
checkParse();
out.defaultWriteObject();
}
} }

View File

@ -17,6 +17,7 @@
package com.google.bitcoin.core; package com.google.bitcoin.core;
import java.io.IOException; import java.io.IOException;
import java.io.ObjectOutputStream;
import java.io.OutputStream; import java.io.OutputStream;
import java.io.Serializable; import java.io.Serializable;
@ -25,7 +26,7 @@ import java.io.Serializable;
/** /**
* This message is a reference or pointer to an output of a different transaction. * This message is a reference or pointer to an output of a different transaction.
*/ */
public class TransactionOutPoint extends Message implements Serializable { public class TransactionOutPoint extends ChildMessage implements Serializable {
private static final long serialVersionUID = -6320880638344662579L; private static final long serialVersionUID = -6320880638344662579L;
/** Hash of the transaction to which we refer. */ /** Hash of the transaction to which we refer. */
@ -54,6 +55,11 @@ public class TransactionOutPoint extends Message implements Serializable {
super(params, payload, offset); super(params, payload, offset);
} }
/** Deserializes the message. This is usually part of a transaction message. */
public TransactionOutPoint(NetworkParameters params, byte[] payload, int offset, Message parent, boolean parseLazy, boolean parseRetain) throws ProtocolException {
super(params, payload, offset, parent, parseLazy, parseRetain);
}
@Override @Override
void parse() throws ProtocolException { void parse() throws ProtocolException {
hash = readHash(); hash = readHash();
@ -61,7 +67,7 @@ public class TransactionOutPoint extends Message implements Serializable {
} }
@Override @Override
public void bitcoinSerializeToStream(OutputStream stream) throws IOException { protected void bitcoinSerializeToStream(OutputStream stream) throws IOException {
stream.write(Utils.reverseBytes(hash.getBytes())); stream.write(Utils.reverseBytes(hash.getBytes()));
Utils.uint32ToByteStreamLE(index, stream); Utils.uint32ToByteStreamLE(index, stream);
} }
@ -96,4 +102,14 @@ public class TransactionOutPoint extends Message implements Serializable {
public String toString() { public String toString() {
return "outpoint " + index + ":" + hash.toString(); return "outpoint " + index + ":" + hash.toString();
} }
/**
* Ensure object is fully parsed before invoking java serialization. The backing byte array
* is transient so if the object has parseLazy = true and hasn't invoked checkParse yet
* then data will be lost during serialization.
*/
private void writeObject(ObjectOutputStream out) throws IOException {
checkParse();
out.defaultWriteObject();
}
} }

View File

@ -20,6 +20,7 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import java.io.IOException; import java.io.IOException;
import java.io.ObjectOutputStream;
import java.io.OutputStream; import java.io.OutputStream;
import java.io.Serializable; import java.io.Serializable;
import java.math.BigInteger; import java.math.BigInteger;
@ -28,7 +29,7 @@ import java.math.BigInteger;
* A TransactionOutput message contains a scriptPubKey that controls who is able to spend its value. It is a sub-part * A TransactionOutput message contains a scriptPubKey that controls who is able to spend its value. It is a sub-part
* of the Transaction message. * of the Transaction message.
*/ */
public class TransactionOutput extends Message implements Serializable { public class TransactionOutput extends ChildMessage implements Serializable {
private static final Logger log = LoggerFactory.getLogger(TransactionOutput.class); private static final Logger log = LoggerFactory.getLogger(TransactionOutput.class);
private static final long serialVersionUID = -590332479859256824L; private static final long serialVersionUID = -590332479859256824L;
@ -58,6 +59,14 @@ public class TransactionOutput extends Message implements Serializable {
availableForSpending = true; availableForSpending = true;
} }
/** Deserializes a transaction output message. This is usually part of a transaction message. */
public TransactionOutput(NetworkParameters params, Transaction parent, byte[] msg, int offset, boolean parseLazy, boolean parseRetain)
throws ProtocolException {
super(params, msg, offset, parent, parseLazy, parseRetain);
parentTransaction = parent;
availableForSpending = true;
}
TransactionOutput(NetworkParameters params, Transaction parent, BigInteger value, Address to) { TransactionOutput(NetworkParameters params, Transaction parent, BigInteger value, Address to) {
super(params); super(params);
this.value = value; this.value = value;
@ -88,7 +97,7 @@ public class TransactionOutput extends Message implements Serializable {
} }
@Override @Override
public void bitcoinSerializeToStream( OutputStream stream) throws IOException { protected void bitcoinSerializeToStream( OutputStream stream) throws IOException {
assert scriptBytes != null; assert scriptBytes != null;
Utils.uint64ToByteStreamLE(getValue(), stream); Utils.uint64ToByteStreamLE(getValue(), stream);
// TODO: Move script serialization into the Script class, where it belongs. // TODO: Move script serialization into the Script class, where it belongs.
@ -162,4 +171,14 @@ public class TransactionOutput extends Message implements Serializable {
TransactionInput getSpentBy() { TransactionInput getSpentBy() {
return spentBy; return spentBy;
} }
/**
* Ensure object is fully parsed before invoking java serialization. The backing byte array
* is transient so if the object has parseLazy = true and hasn't invoked checkParse yet
* then data will be lost during serialization.
*/
private void writeObject(ObjectOutputStream out) throws IOException {
checkParse();
out.defaultWriteObject();
}
} }

View File

@ -51,6 +51,15 @@ public class VersionMessage extends Message {
super(params, msg, 0); super(params, msg, 0);
} }
/**
* It doesn't really make sense to ever lazily parse a version message or to retain the backing bytes.
* If you're receiving this on the wire you need to check the protocol version and it will never need to be sent
* back down the wire.
*/
// public VersionMessage(NetworkParameters params, byte[] msg, boolean parseLazy, boolean parseRetain) throws ProtocolException {
// super(params, msg, 0, parseLazy, parseRetain);
// }
public VersionMessage(NetworkParameters params, int newBestHeight) { public VersionMessage(NetworkParameters params, int newBestHeight) {
super(params); super(params);
clientVersion = NetworkParameters.PROTOCOL_VERSION; clientVersion = NetworkParameters.PROTOCOL_VERSION;
@ -96,9 +105,9 @@ public class VersionMessage extends Message {
Utils.uint32ToByteStreamLE(time >> 32, buf); Utils.uint32ToByteStreamLE(time >> 32, buf);
try { try {
// My address. // My address.
myAddr.bitcoinSerializeToStream(buf); myAddr.bitcoinSerialize(buf);
// Their address. // Their address.
theirAddr.bitcoinSerializeToStream(buf); theirAddr.bitcoinSerialize(buf);
} catch (UnknownHostException e) { } catch (UnknownHostException e) {
throw new RuntimeException(e); // Can't happen. throw new RuntimeException(e); // Can't happen.
} catch (IOException e) { } catch (IOException e) {
@ -143,4 +152,17 @@ public class VersionMessage extends Message {
return (int)bestHeight ^ clientVersion ^ (int)localServices ^ (int)time ^ subVer.hashCode() ^ myAddr.hashCode() return (int)bestHeight ^ clientVersion ^ (int)localServices ^ (int)time ^ subVer.hashCode() ^ myAddr.hashCode()
^ theirAddr.hashCode(); ^ theirAddr.hashCode();
} }
public String toString() {
StringBuilder sb = new StringBuilder();
sb.append("\n");
sb.append("client version: ").append(clientVersion).append("\n");
sb.append("local services: ").append(localServices).append("\n");
sb.append("time: ").append(time).append("\n");
sb.append("my addr: ").append(myAddr).append("\n");
sb.append("their addr: ").append(theirAddr).append("\n");
sb.append("sub version: ").append(subVer).append("\n");
sb.append("best height: ").append(bestHeight).append("\n");
return sb.toString();
}
} }