mirror of
https://github.com/Qortal/altcoinj.git
synced 2025-02-11 17:55:53 +00:00
Second part of Steves lazy parsing patchset:
1) Added getters and setters to many objects that lacked them. 2) Introduce a parseLite method that is called even in "lazy parse" mode. This calculates the length of the message so children can be skipped when parsing a container object. 3) Full serialization for AddressMessage 4) Added a (huge, standalone) SpeedTest. 5) Add unit tests for the matrix of lazy parsing modes. A bunch of review comments are added to the TODO list for completion after the patch series is checked in. This is to avoid large numbers of merge conflicts as later parts of the patch-series are committed.
This commit is contained in:
parent
34fea86708
commit
afef6bc029
24
TODO
24
TODO
@ -35,3 +35,27 @@ Impacts from Steves changes:
|
||||
- Remove superfluous empty lines in ListMessage.java
|
||||
- Remove VersionMessage check in Message serialization roundtrip checks
|
||||
- Delete dead code in Message.checkParsed
|
||||
- LazyParseByteCacheTest:
|
||||
- copyright/comments/unused things
|
||||
- debug prints
|
||||
- some redundant asserts
|
||||
- huge functions
|
||||
- dead code
|
||||
- Magic number 36 in ListMessage.parseLite
|
||||
- Manipulator.java needs docs
|
||||
- Assert in Message c'tor needs an English version.
|
||||
- Spelling in PeerAddress.bitcoinSerializeToStream
|
||||
- SpeedTest has a lot of unused fields, large functions. See if it can be simplified/densified.
|
||||
- Caching immutable lists?
|
||||
- Dead code in Transaction.parse
|
||||
- javadocs on Transaction{Input,Output,} getters
|
||||
|
||||
|
||||
Block.java:
|
||||
- Reformat
|
||||
- Replace magic number 80 with HEADER_SIZE_BYTES in Block.parseTransactions
|
||||
- The parseLight stuff isn't clear without referring to superclass.
|
||||
- Delete dead code in writeTransactions
|
||||
- checkParse{Header,Transactions} should be maybeParse...
|
||||
- immutableTransactions is misleading, it allows you to mutate the contents.
|
||||
- Unit-test only getters should be package private.
|
@ -1,34 +1,46 @@
|
||||
package com.google.bitcoin.core;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
public class AddressMessage extends Message {
|
||||
private static final long serialVersionUID = 8058283864924679460L;
|
||||
private static final long MAX_ADDRESSES = 1024;
|
||||
List<PeerAddress> addresses;
|
||||
private List<PeerAddress> addresses;
|
||||
private transient long numAddresses = -1;
|
||||
|
||||
AddressMessage(NetworkParameters params, byte[] payload, int offset, boolean parseLazy, boolean parseRetain) throws ProtocolException {
|
||||
super(params, payload, offset, parseLazy, parseRetain);
|
||||
AddressMessage(NetworkParameters params, byte[] payload, int offset, boolean parseLazy, boolean parseRetain, int length) throws ProtocolException {
|
||||
super(params, payload, offset, parseLazy, parseRetain, length);
|
||||
}
|
||||
|
||||
AddressMessage(NetworkParameters params, byte[] payload, boolean parseLazy, boolean parseRetain) throws ProtocolException {
|
||||
super(params, payload, 0, parseLazy, parseRetain);
|
||||
AddressMessage(NetworkParameters params, byte[] payload, boolean parseLazy, boolean parseRetain, int length) throws ProtocolException {
|
||||
super(params, payload, 0, parseLazy, parseRetain, length);
|
||||
}
|
||||
|
||||
AddressMessage(NetworkParameters params, byte[] payload, int offset) throws ProtocolException {
|
||||
super(params, payload, offset, false, false);
|
||||
super(params, payload, offset, false, false, UNKNOWN_LENGTH);
|
||||
}
|
||||
|
||||
AddressMessage(NetworkParameters params, byte[] payload) throws ProtocolException {
|
||||
super(params, payload, 0, false, false);
|
||||
super(params, payload, 0, false, false, UNKNOWN_LENGTH);
|
||||
}
|
||||
|
||||
/* (non-Javadoc)
|
||||
* @see com.google.bitcoin.core.Message#parseLite()
|
||||
*/
|
||||
@Override
|
||||
protected void parseLite() throws ProtocolException {
|
||||
//nop this should never be taken off the wire without having a length provided.
|
||||
}
|
||||
|
||||
@Override
|
||||
@Override
|
||||
void parse() throws ProtocolException {
|
||||
long numAddresses = readVarInt();
|
||||
numAddresses = readVarInt();
|
||||
// Guard against ultra large messages that will crash us.
|
||||
if (numAddresses > MAX_ADDRESSES)
|
||||
if (numAddresses > MAX_ADDRESSES)
|
||||
throw new ProtocolException("Address message too large.");
|
||||
addresses = new ArrayList<PeerAddress>((int)numAddresses);
|
||||
for (int i = 0; i < numAddresses; i++) {
|
||||
@ -37,8 +49,44 @@ public class AddressMessage extends Message {
|
||||
cursor += addr.getMessageSize();
|
||||
}
|
||||
}
|
||||
|
||||
/* (non-Javadoc)
|
||||
* @see com.google.bitcoin.core.Message#bitcoinSerializeToStream(java.io.OutputStream)
|
||||
*/
|
||||
@Override
|
||||
void bitcoinSerializeToStream(OutputStream stream) throws IOException {
|
||||
if (addresses == null)
|
||||
return;
|
||||
stream.write(new VarInt(addresses.size()).encode());
|
||||
for (PeerAddress addr: addresses) {
|
||||
addr.bitcoinSerialize(stream);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
/**
|
||||
* @return An unmodifiableList view of the backing List of addresses. Addresses contained within the list may be safely modified.
|
||||
*/
|
||||
public List<PeerAddress> getAddresses() {
|
||||
checkParse();
|
||||
return Collections.unmodifiableList(addresses);
|
||||
}
|
||||
|
||||
public void addAddress(PeerAddress address) {
|
||||
unCache();
|
||||
checkParse();
|
||||
address.setParent(this);
|
||||
addresses.add(address);
|
||||
}
|
||||
|
||||
public void removeAddress(int index) {
|
||||
unCache();
|
||||
PeerAddress address = addresses.remove(index);
|
||||
if (address != null)
|
||||
address.setParent(null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
StringBuilder builder = new StringBuilder();
|
||||
builder.append("addr: ");
|
||||
|
@ -51,6 +51,8 @@ public class BitcoinSerializer {
|
||||
|
||||
private NetworkParameters params;
|
||||
private boolean usesChecksumming;
|
||||
private boolean parseLazy = false;
|
||||
private boolean parseRetain = false;
|
||||
|
||||
private static Map<Class<? extends Message>, String> names = new HashMap<Class<? extends Message>,String>();
|
||||
|
||||
@ -96,9 +98,25 @@ public class BitcoinSerializer {
|
||||
*/
|
||||
public BitcoinSerializer(NetworkParameters params, boolean usesChecksumming,
|
||||
LinkedHashMap<Sha256Hash, Integer> dedupeList) {
|
||||
this(params, usesChecksumming, false, false, dedupeList);
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a BitcoinSerializer with the given behavior.
|
||||
*
|
||||
* @param params networkParams used to create Messages instances and termining packetMagic
|
||||
* @param usesChecksumming set to true if checkums should be included and expected in headers
|
||||
* @param parseLazy deserialize messages in lazy mode.
|
||||
* @param parseRetain retain the backing byte array of a message for fast reserialization.
|
||||
* @param dedupeList possibly shared list of previously received messages used to avoid parsing duplicates.
|
||||
*/
|
||||
public BitcoinSerializer(NetworkParameters params, boolean usesChecksumming, boolean parseLazy, boolean parseRetain,
|
||||
LinkedHashMap<Sha256Hash, Integer> dedupeList) {
|
||||
this.params = params;
|
||||
this.usesChecksumming = usesChecksumming;
|
||||
this.dedupeList = dedupeList;
|
||||
this.parseLazy = parseLazy;
|
||||
this.parseRetain = parseRetain;
|
||||
}
|
||||
|
||||
public void setUseChecksumming(boolean usesChecksumming) {
|
||||
@ -250,26 +268,26 @@ public class BitcoinSerializer {
|
||||
}
|
||||
|
||||
try {
|
||||
return makeMessage(header.command, payloadBytes);
|
||||
return makeMessage(header.command, header.size, payloadBytes);
|
||||
} catch (Exception e) {
|
||||
throw new ProtocolException("Error deserializing message " + Utils.bytesToHexString(payloadBytes) + "\n", e);
|
||||
}
|
||||
}
|
||||
|
||||
private Message makeMessage(String command, byte[] payloadBytes) throws ProtocolException {
|
||||
private Message makeMessage(String command, int length, byte[] payloadBytes) throws ProtocolException {
|
||||
// We use an if ladder rather than reflection because reflection is very slow on Android.
|
||||
if (command.equals("version")) {
|
||||
return new VersionMessage(params, payloadBytes);
|
||||
} else if (command.equals("inv")) {
|
||||
return new InventoryMessage(params, payloadBytes);
|
||||
return new InventoryMessage(params, payloadBytes, parseLazy, parseRetain, length);
|
||||
} else if (command.equals("block")) {
|
||||
return new Block(params, payloadBytes);
|
||||
return new Block(params, payloadBytes, parseLazy, parseRetain, length);
|
||||
} else if (command.equals("getdata")) {
|
||||
return new GetDataMessage(params, payloadBytes);
|
||||
return new GetDataMessage(params, payloadBytes, parseLazy, parseRetain, length);
|
||||
} else if (command.equals("tx")) {
|
||||
return new Transaction(params, payloadBytes);
|
||||
return new Transaction(params, payloadBytes, null, parseLazy, parseRetain, length);
|
||||
} else if (command.equals("addr")) {
|
||||
return new AddressMessage(params, payloadBytes);
|
||||
return new AddressMessage(params, payloadBytes, parseLazy, parseRetain, length);
|
||||
} else if (command.equals("ping")) {
|
||||
return new Ping();
|
||||
} else if (command.equals("verack")) {
|
||||
@ -303,8 +321,24 @@ public class BitcoinSerializer {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether the serializer will produce lazy parse mode Messages
|
||||
*/
|
||||
public boolean isParseLazyMode() {
|
||||
return parseLazy;
|
||||
}
|
||||
|
||||
public class BitcoinPacketHeader {
|
||||
/**
|
||||
* Whether the serializer will produce cached mode Messages
|
||||
*/
|
||||
public boolean isParseRetainMode() {
|
||||
return parseRetain;
|
||||
}
|
||||
|
||||
|
||||
|
||||
public class BitcoinPacketHeader {
|
||||
final byte[] header;
|
||||
final String command;
|
||||
final int size;
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -428,7 +428,7 @@ public class BlockChain {
|
||||
try {
|
||||
for (Wallet wallet : wallets) {
|
||||
boolean shouldReceive = false;
|
||||
for (TransactionOutput output : tx.outputs) {
|
||||
for (TransactionOutput output : tx.getOutputs()) {
|
||||
// TODO: Handle more types of outputs, not just regular to address outputs.
|
||||
if (output.getScriptPubKey().isSentToIP()) continue;
|
||||
// This is not thread safe as a key could be removed between the call to isMine and receive.
|
||||
@ -440,7 +440,7 @@ public class BlockChain {
|
||||
|
||||
// Coinbase transactions don't have anything useful in their inputs (as they create coins out of thin air).
|
||||
if (!shouldReceive && !tx.isCoinBase()) {
|
||||
for (TransactionInput i : tx.inputs) {
|
||||
for (TransactionInput i : tx.getInputs()) {
|
||||
byte[] pubkey = i.getScriptSig().getPubKey();
|
||||
// This is not thread safe as a key could be removed between the call to isPubKeyMine and receive.
|
||||
if (wallet.isPubKeyMine(pubkey)) {
|
||||
|
@ -23,8 +23,8 @@ public abstract class ChildMessage extends Message {
|
||||
}
|
||||
|
||||
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);
|
||||
boolean parseRetain, int length) throws ProtocolException {
|
||||
super(params, msg, offset, protocolVersion, parseLazy, parseRetain, length);
|
||||
this.parent = parent;
|
||||
}
|
||||
|
||||
@ -32,9 +32,9 @@ public abstract class ChildMessage extends Message {
|
||||
super(params, msg, offset);
|
||||
}
|
||||
|
||||
public ChildMessage(NetworkParameters params, byte[] msg, int offset, Message parent, boolean parseLazy, boolean parseRetain)
|
||||
public ChildMessage(NetworkParameters params, byte[] msg, int offset, Message parent, boolean parseLazy, boolean parseRetain, int length)
|
||||
throws ProtocolException {
|
||||
super(params, msg, offset, parseLazy, parseRetain);
|
||||
super(params, msg, offset, parseLazy, parseRetain, length);
|
||||
this.parent = parent;
|
||||
}
|
||||
|
||||
|
@ -26,4 +26,42 @@ public abstract class EmptyMessage extends Message {
|
||||
final protected void bitcoinSerializeToStream(OutputStream stream) throws IOException {
|
||||
}
|
||||
|
||||
@Override
|
||||
int getMessageSize() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* (non-Javadoc)
|
||||
* @see com.google.bitcoin.core.Message#parse()
|
||||
*/
|
||||
@Override
|
||||
void parse() throws ProtocolException {
|
||||
}
|
||||
|
||||
/* (non-Javadoc)
|
||||
* @see com.google.bitcoin.core.Message#parseLite()
|
||||
*/
|
||||
@Override
|
||||
protected void parseLite() throws ProtocolException {
|
||||
length = 0;
|
||||
}
|
||||
|
||||
/* (non-Javadoc)
|
||||
* @see com.google.bitcoin.core.Message#ensureParsed()
|
||||
*/
|
||||
@Override
|
||||
public void ensureParsed() throws ProtocolException {
|
||||
parsed = true;
|
||||
}
|
||||
|
||||
/* (non-Javadoc)
|
||||
* @see com.google.bitcoin.core.Message#bitcoinSerialize()
|
||||
*/
|
||||
@Override
|
||||
public byte[] bitcoinSerialize() {
|
||||
return new byte[0];
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
@ -23,9 +23,9 @@ public class GetDataMessage extends ListMessage {
|
||||
super(params, payloadBytes);
|
||||
}
|
||||
|
||||
public GetDataMessage(NetworkParameters params, byte[] msg, boolean parseLazy, boolean parseRetain)
|
||||
public GetDataMessage(NetworkParameters params, byte[] msg, boolean parseLazy, boolean parseRetain, int length)
|
||||
throws ProtocolException {
|
||||
super(params, msg, parseLazy, parseRetain);
|
||||
super(params, msg, parseLazy, parseRetain, length);
|
||||
}
|
||||
|
||||
public GetDataMessage(NetworkParameters params) {
|
||||
|
@ -23,9 +23,9 @@ public class InventoryMessage extends ListMessage {
|
||||
super(params, bytes);
|
||||
}
|
||||
|
||||
public InventoryMessage(NetworkParameters params, byte[] msg, boolean parseLazy, boolean parseRetain)
|
||||
public InventoryMessage(NetworkParameters params, byte[] msg, boolean parseLazy, boolean parseRetain, int length)
|
||||
throws ProtocolException {
|
||||
super(params, msg, parseLazy, parseRetain);
|
||||
super(params, msg, parseLazy, parseRetain, length);
|
||||
}
|
||||
|
||||
public InventoryMessage(NetworkParameters params) {
|
||||
|
@ -19,6 +19,7 @@ package com.google.bitcoin.core;
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
@ -26,7 +27,8 @@ import java.util.List;
|
||||
*/
|
||||
public abstract class ListMessage extends Message
|
||||
{
|
||||
// For some reason the compiler complains if this is inside InventoryItem
|
||||
private long arrayLen;
|
||||
// For some reason the compiler complains if this is inside InventoryItem
|
||||
private List<InventoryItem> items;
|
||||
|
||||
private static final long MAX_INVENTORY_ITEMS = 50000;
|
||||
@ -38,9 +40,9 @@ public abstract class ListMessage extends Message
|
||||
|
||||
|
||||
|
||||
public ListMessage(NetworkParameters params, byte[] msg, boolean parseLazy, boolean parseRetain)
|
||||
public ListMessage(NetworkParameters params, byte[] msg, boolean parseLazy, boolean parseRetain, int length)
|
||||
throws ProtocolException {
|
||||
super(params, msg, 0, parseLazy, parseRetain);
|
||||
super(params, msg, 0, parseLazy, parseRetain, length);
|
||||
}
|
||||
|
||||
|
||||
@ -52,21 +54,34 @@ public abstract class ListMessage extends Message
|
||||
|
||||
public List<InventoryItem> getItems()
|
||||
{
|
||||
return items;
|
||||
checkParse();
|
||||
return Collections.unmodifiableList(items);
|
||||
}
|
||||
|
||||
public void addItem(InventoryItem item)
|
||||
{
|
||||
items.add(item);
|
||||
unCache();
|
||||
items.add(item);
|
||||
}
|
||||
|
||||
public void removeItem(int index)
|
||||
{
|
||||
unCache();
|
||||
items.remove(index);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void parseLite() throws ProtocolException {
|
||||
arrayLen = readVarInt();
|
||||
if (arrayLen > MAX_INVENTORY_ITEMS)
|
||||
throw new ProtocolException("Too many items in INV message: " + arrayLen);
|
||||
length = (int) (cursor - offset + (arrayLen * 36));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void parse() throws ProtocolException {
|
||||
// An inv is vector<CInv> where CInv is int+hash. The int is either 1 or 2 for tx or block.
|
||||
long arrayLen = readVarInt();
|
||||
if (arrayLen > MAX_INVENTORY_ITEMS)
|
||||
throw new ProtocolException("Too many items in INV message: " + arrayLen);
|
||||
items = new ArrayList<InventoryItem>((int)arrayLen);
|
||||
items = new ArrayList<InventoryItem>((int)arrayLen);
|
||||
for (int i = 0; i < arrayLen; i++) {
|
||||
if (cursor + 4 + 32 > bytes.length) {
|
||||
throw new ProtocolException("Ran off the end of the INV");
|
||||
|
@ -35,6 +35,8 @@ public abstract class Message implements Serializable {
|
||||
private static final long serialVersionUID = -3561053461717079135L;
|
||||
|
||||
public static final int MAX_SIZE = 0x02000000;
|
||||
|
||||
public static final int UNKNOWN_LENGTH = -1;
|
||||
|
||||
// Useful to ensure serialize/deserialize are consistent with each other.
|
||||
private static final boolean SELF_CHECK = false;
|
||||
@ -44,11 +46,13 @@ public abstract class Message implements Serializable {
|
||||
// The cursor keeps track of where we are in the byte array as we parse it.
|
||||
// Note that it's relative to the start of the array NOT the start of the message.
|
||||
protected transient int cursor;
|
||||
|
||||
protected transient int length = UNKNOWN_LENGTH;
|
||||
|
||||
// The raw message bytes themselves.
|
||||
protected transient byte[] bytes;
|
||||
|
||||
private transient boolean parsed = false;
|
||||
protected transient boolean parsed = false;
|
||||
protected transient final boolean parseLazy;
|
||||
protected transient final boolean parseRetain;
|
||||
|
||||
@ -72,21 +76,31 @@ public abstract class Message implements Serializable {
|
||||
}
|
||||
|
||||
Message(NetworkParameters params, byte[] msg, int offset, int protocolVersion) throws ProtocolException {
|
||||
this(params, msg, offset, protocolVersion, false, false);
|
||||
this(params, msg, offset, protocolVersion, false, false, UNKNOWN_LENGTH);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
Message(NetworkParameters params, byte[] msg, int offset, int protocolVersion, final boolean parseLazy, final boolean parseRetain) throws ProtocolException {
|
||||
Message(NetworkParameters params, byte[] msg, int offset, int protocolVersion, final boolean parseLazy, final boolean parseRetain, int length) throws ProtocolException {
|
||||
this.parseLazy = parseLazy;
|
||||
this.parseRetain = parseRetain;
|
||||
this.protocolVersion = protocolVersion;
|
||||
this.params = params;
|
||||
this.bytes = msg;
|
||||
this.cursor = this.offset = offset;
|
||||
if (!parseLazy) {
|
||||
this.length = length;
|
||||
if (parseLazy) {
|
||||
parseLite();
|
||||
} else {
|
||||
parseLite();
|
||||
parse();
|
||||
parsed = true;
|
||||
}
|
||||
|
||||
assert (parseLazy ? !parsed : parsed && (parseRetain ? bytes != null : bytes == null))
|
||||
: "parseLazy : " + parseLazy + " parsed: " + parsed
|
||||
+ " parseRetain:" + parseRetain
|
||||
+ " bytes == null" + bytes == null;
|
||||
|
||||
if (SELF_CHECK && !this.getClass().getSimpleName().equals("VersionMessage")) {
|
||||
checkParse();
|
||||
byte[] msgbytes = new byte[cursor - offset];
|
||||
@ -103,11 +117,11 @@ public abstract class Message implements Serializable {
|
||||
}
|
||||
|
||||
Message(NetworkParameters params, byte[] msg, int offset) throws ProtocolException {
|
||||
this(params, msg, offset, NetworkParameters.PROTOCOL_VERSION, false, false);
|
||||
this(params, msg, offset, NetworkParameters.PROTOCOL_VERSION, false, false, UNKNOWN_LENGTH);
|
||||
}
|
||||
|
||||
Message(NetworkParameters params, byte[] msg, int offset, final boolean parseLazy, final boolean parseRetain) throws ProtocolException {
|
||||
this(params, msg, offset, NetworkParameters.PROTOCOL_VERSION, parseLazy, parseRetain);
|
||||
Message(NetworkParameters params, byte[] msg, int offset, final boolean parseLazy, final boolean parseRetain, int length) throws ProtocolException {
|
||||
this(params, msg, offset, NetworkParameters.PROTOCOL_VERSION, parseLazy, parseRetain, length);
|
||||
}
|
||||
|
||||
// These methods handle the serialization/deserialization using the custom BitCoin protocol.
|
||||
@ -116,6 +130,19 @@ public abstract class Message implements Serializable {
|
||||
// are serialized to the wallet.
|
||||
abstract void parse() throws ProtocolException;
|
||||
|
||||
/**
|
||||
* Perform the most minimal parse possible to calculate the length of the message.
|
||||
* This is only required for subclasses of ChildClass as root level messages will have their length passed
|
||||
* into the constructor.
|
||||
*
|
||||
* It is expected that the length field will be set before this method returns.
|
||||
* @return
|
||||
* @throws ProtocolException
|
||||
*/
|
||||
protected void parseLite() throws ProtocolException {
|
||||
length = getMessageSize();
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.
|
||||
@ -126,13 +153,32 @@ public abstract class Message implements Serializable {
|
||||
try {
|
||||
parse();
|
||||
parsed = true;
|
||||
//if (!parseRetain)
|
||||
//bytes = null;
|
||||
if (!parseRetain)
|
||||
bytes = null;
|
||||
} catch (ProtocolException e) {
|
||||
throw new RuntimeException("Lazy parsing of message failed", e);
|
||||
throw new LazyParseException("ProtocolException caught during lazy parse. For safe access to fields call ensureParsed before attempting read or write access", e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* In lazy parsing mode access to getters and setters may throw an unchecked LazyParseException. If guaranteed safe access is required
|
||||
* this method will force parsing to occur immediately thus ensuring LazyParseExeption will never be thrown from this Message.
|
||||
* If the Message contains child messages (e.g. a Block containing Transaction messages) this will not force child messages to parse.
|
||||
*
|
||||
* This could be overidden for Transaction and it's child classes to ensure the entire tree of Message objects is parsed.
|
||||
*
|
||||
* @throws ProtocolException
|
||||
*/
|
||||
public void ensureParsed() throws ProtocolException {
|
||||
try {
|
||||
checkParse();
|
||||
} catch (LazyParseException e) {
|
||||
if (e.getCause() instanceof ProtocolException)
|
||||
throw (ProtocolException) e.getCause();
|
||||
throw new ProtocolException(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.
|
||||
@ -158,10 +204,25 @@ public abstract class Message implements Serializable {
|
||||
*/
|
||||
|
||||
|
||||
//if (!parseRetain)
|
||||
// return;
|
||||
//checkParse();
|
||||
//bytes = null;
|
||||
if (parseRetain) {
|
||||
checkParse();
|
||||
bytes = null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* used for unit testing
|
||||
*/
|
||||
public boolean isParsed() {
|
||||
return parsed;
|
||||
}
|
||||
|
||||
/**
|
||||
* used for unit testing
|
||||
*/
|
||||
public boolean isCached() {
|
||||
//return parseLazy ? parsed && bytes != null : bytes != null;
|
||||
return bytes != null;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -180,28 +241,30 @@ public abstract class Message implements Serializable {
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
final public byte[] bitcoinSerialize() {
|
||||
public byte[] bitcoinSerialize() {
|
||||
|
||||
//1st attempt to use a cached array
|
||||
if (bytes != null) {
|
||||
if (offset == 0 && cursor == bytes.length) {
|
||||
if (offset == 0 && length == 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;
|
||||
}
|
||||
|
||||
assert bytes == null : "cached bytes present but failed to use them for serialization";
|
||||
|
||||
//no cached array available so serialize parts by stream.
|
||||
ByteArrayOutputStream stream = new ByteArrayOutputStream();
|
||||
try {
|
||||
bitcoinSerializeToStream(stream);
|
||||
} catch (IOException e) {
|
||||
// Cannot happen, we are serializing to a memory stream.
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
return stream.toByteArray();
|
||||
}
|
||||
@ -213,10 +276,11 @@ public abstract class Message implements Serializable {
|
||||
*/
|
||||
final public void bitcoinSerialize(OutputStream stream) throws IOException {
|
||||
//1st check for cached bytes
|
||||
if (bytes != null) {
|
||||
stream.write(bytes, offset, cursor - offset);
|
||||
if (bytes != null && length != UNKNOWN_LENGTH) {
|
||||
stream.write(bytes, offset, length);
|
||||
return;
|
||||
}
|
||||
|
||||
bitcoinSerializeToStream(stream);
|
||||
}
|
||||
|
||||
@ -227,8 +291,19 @@ public abstract class Message implements Serializable {
|
||||
log.debug("Warning: {} class has not implemented bitcoinSerializeToStream method. Generating message with no payload", getClass());
|
||||
}
|
||||
|
||||
/**
|
||||
* This should be overidden to extract correct message size in the case of lazy parsing. Until this method is
|
||||
* implemented in a subclass of ChildMessage lazy parsing will have no effect.
|
||||
*
|
||||
* This default implementation is a safe fall back that will ensure it returns a correct value.
|
||||
* @return
|
||||
*/
|
||||
int getMessageSize() {
|
||||
return cursor - offset;
|
||||
if (length != UNKNOWN_LENGTH)
|
||||
return length;
|
||||
checkParse();
|
||||
length = cursor - offset;
|
||||
return length;
|
||||
}
|
||||
|
||||
long readUint32() {
|
||||
@ -262,6 +337,12 @@ public abstract class Message implements Serializable {
|
||||
cursor += varint.getSizeInBytes();
|
||||
return varint.value;
|
||||
}
|
||||
|
||||
long readVarInt(int offset) {
|
||||
VarInt varint = new VarInt(bytes, cursor + offset);
|
||||
cursor += offset + varint.getSizeInBytes();
|
||||
return varint.value;
|
||||
}
|
||||
|
||||
|
||||
byte[] readBytes(int length) {
|
||||
@ -287,4 +368,16 @@ public abstract class Message implements Serializable {
|
||||
throw new RuntimeException(e); // Cannot happen, UTF-8 is always supported.
|
||||
}
|
||||
}
|
||||
|
||||
public class LazyParseException extends RuntimeException {
|
||||
|
||||
public LazyParseException(String message, Throwable cause) {
|
||||
super(message, cause);
|
||||
}
|
||||
|
||||
public LazyParseException(String message) {
|
||||
super(message);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
@ -74,9 +74,9 @@ public class NetworkConnection {
|
||||
int bestHeight, int connectTimeout, boolean dedupe)
|
||||
throws IOException, ProtocolException {
|
||||
this.params = params;
|
||||
this.remoteIp = peerAddress.addr;
|
||||
this.remoteIp = peerAddress.getAddr();
|
||||
|
||||
int port = (peerAddress.port > 0) ? peerAddress.port : params.port;
|
||||
int port = (peerAddress.getPort() > 0) ? peerAddress.getPort() : params.port;
|
||||
|
||||
InetSocketAddress address = new InetSocketAddress(remoteIp, port);
|
||||
socket = new Socket();
|
||||
|
@ -78,12 +78,12 @@ public class NetworkParameters implements Serializable {
|
||||
// "The Times 03/Jan/2009 Chancellor on brink of second bailout for banks"
|
||||
byte[] bytes = Hex.decode
|
||||
("04ffff001d0104455468652054696d65732030332f4a616e2f32303039204368616e63656c6c6f72206f6e206272696e6b206f66207365636f6e64206261696c6f757420666f722062616e6b73");
|
||||
t.inputs.add(new TransactionInput(n, t, bytes));
|
||||
t.addInput(new TransactionInput(n, t, bytes));
|
||||
ByteArrayOutputStream scriptPubKeyBytes = new ByteArrayOutputStream();
|
||||
Script.writeBytes(scriptPubKeyBytes, Hex.decode
|
||||
("04678afdb0fe5548271967f1a67130b7105cd6a828e03909a67962e0ea1f61deb649f6bc3f4cef38c4f35504e51ec112de5c384df7ba0b8d578a4c702b6bf11d5f"));
|
||||
scriptPubKeyBytes.write(Script.OP_CHECKSIG);
|
||||
t.outputs.add(new TransactionOutput(n, t, scriptPubKeyBytes.toByteArray()));
|
||||
t.addOutput(new TransactionOutput(n, t, scriptPubKeyBytes.toByteArray()));
|
||||
} catch (Exception e) {
|
||||
// Cannot happen.
|
||||
throw new RuntimeException(e);
|
||||
|
@ -93,7 +93,7 @@ public class Peer {
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "Peer(" + address.addr + ":" + address.port + ")";
|
||||
return "Peer(" + address.getAddr() + ":" + address.getPort() + ")";
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -33,11 +33,12 @@ import static com.google.bitcoin.core.Utils.uint64ToByteStreamLE;
|
||||
*/
|
||||
public class PeerAddress extends ChildMessage {
|
||||
private static final long serialVersionUID = 7501293709324197411L;
|
||||
private static final int MESSAGE_SIZE = 30;
|
||||
|
||||
InetAddress addr;
|
||||
int port;
|
||||
BigInteger services;
|
||||
long time;
|
||||
private InetAddress addr;
|
||||
private int port;
|
||||
private BigInteger services;
|
||||
private long time;
|
||||
|
||||
/**
|
||||
* Construct a peer address from a serialized payload.
|
||||
@ -52,7 +53,9 @@ public class PeerAddress extends ChildMessage {
|
||||
*/
|
||||
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);
|
||||
super(params, msg, offset, protocolVersion, parent, parseLazy, parseRetain, UNKNOWN_LENGTH);
|
||||
//Message length is calculated in parseLite which is guaranteed to be called before it is ever read.
|
||||
//Safer to leave it there as it will be set regardless of which constructor was used.
|
||||
}
|
||||
|
||||
|
||||
@ -82,7 +85,10 @@ public class PeerAddress extends ChildMessage {
|
||||
@Override
|
||||
protected void bitcoinSerializeToStream(OutputStream stream) throws IOException {
|
||||
if (protocolVersion >= 31402) {
|
||||
int secs = (int)(Utils.now().getTime() / 1000);
|
||||
//TODO this appears to be dynamic because the client only ever sends out it's own address
|
||||
//so assumes itself to be up. For a fuller implementation this needs to be dynamic only if
|
||||
//the address refers to this clinet.
|
||||
int secs = (int)(Utils.now().getTime() / 1000);
|
||||
uint32ToByteStreamLE(secs, stream);
|
||||
}
|
||||
uint64ToByteStreamLE(services, stream); // nServices.
|
||||
@ -101,6 +107,10 @@ public class PeerAddress extends ChildMessage {
|
||||
stream.write((byte) (0xFF & port));
|
||||
}
|
||||
|
||||
protected void parseLite() {
|
||||
length = protocolVersion > 31402 ? MESSAGE_SIZE : MESSAGE_SIZE - 4;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void parse() {
|
||||
// Format of a serialized address:
|
||||
@ -121,8 +131,88 @@ public class PeerAddress extends ChildMessage {
|
||||
}
|
||||
port = ((0xFF & bytes[cursor++]) << 8) | (0xFF & bytes[cursor++]);
|
||||
}
|
||||
|
||||
/* (non-Javadoc)
|
||||
* @see com.google.bitcoin.core.Message#getMessageSize()
|
||||
*/
|
||||
@Override
|
||||
int getMessageSize() {
|
||||
return length;
|
||||
}
|
||||
|
||||
@Override
|
||||
/**
|
||||
* @return the addr
|
||||
*/
|
||||
public InetAddress getAddr() {
|
||||
checkParse();
|
||||
return addr;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param addr the addr to set
|
||||
*/
|
||||
public void setAddr(InetAddress addr) {
|
||||
unCache();
|
||||
this.addr = addr;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @return the port
|
||||
*/
|
||||
public int getPort() {
|
||||
checkParse();
|
||||
return port;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param port the port to set
|
||||
*/
|
||||
public void setPort(int port) {
|
||||
unCache();
|
||||
this.port = port;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @return the services
|
||||
*/
|
||||
public BigInteger getServices() {
|
||||
checkParse();
|
||||
return services;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param services the services to set
|
||||
*/
|
||||
public void setServices(BigInteger services) {
|
||||
unCache();
|
||||
this.services = services;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @return the time
|
||||
*/
|
||||
public long getTime() {
|
||||
checkParse();
|
||||
return time;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param time the time to set
|
||||
*/
|
||||
public void setTime(long time) {
|
||||
unCache();
|
||||
this.time = time;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "[" + addr.getHostAddress() + "]:" + port;
|
||||
}
|
||||
|
@ -43,10 +43,16 @@ public class Transaction extends ChildMessage implements Serializable {
|
||||
private static final long serialVersionUID = -8567546957352643140L;
|
||||
|
||||
// These are serialized in both bitcoin and java serialization.
|
||||
long version;
|
||||
ArrayList<TransactionInput> inputs;
|
||||
ArrayList<TransactionOutput> outputs;
|
||||
long lockTime;
|
||||
private long version;
|
||||
private ArrayList<TransactionInput> inputs;
|
||||
//a cached copy to prevent constantly rewrapping
|
||||
private transient List<TransactionInput> immutableInputs;
|
||||
|
||||
private ArrayList<TransactionOutput> outputs;
|
||||
//a cached copy to prevent constantly rewrapping
|
||||
private transient List<TransactionOutput> immutableOutputs;
|
||||
|
||||
private long lockTime;
|
||||
|
||||
// This is only stored in Java serialization. It records which blocks (and their height + work) the transaction
|
||||
// has been included in. For most transactions this set will have a single member. In the case of a chain split a
|
||||
@ -93,26 +99,19 @@ public class Transaction extends ChildMessage implements Serializable {
|
||||
/**
|
||||
* 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)
|
||||
public Transaction(NetworkParameters params, byte[] msg, int offset, Message parent, boolean parseLazy, boolean parseRetain, int length)
|
||||
throws ProtocolException {
|
||||
super(params, msg, offset, parent, parseLazy, parseRetain);
|
||||
super(params, msg, offset, parent, parseLazy, parseRetain, length);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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)
|
||||
public Transaction(NetworkParameters params, byte[] msg, Message parent, boolean parseLazy, boolean parseRetain, int length)
|
||||
throws ProtocolException {
|
||||
super(params, msg, 0, parent, parseLazy, parseRetain);
|
||||
super(params, msg, 0, parent, parseLazy, parseRetain, length);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a read-only list of the inputs of this transaction.
|
||||
*/
|
||||
public List<TransactionInput> getInputs() {
|
||||
return Collections.unmodifiableList(inputs);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the transaction hash as you see them in the block explorer.
|
||||
*/
|
||||
@ -283,9 +282,35 @@ public class Transaction extends ChildMessage implements Serializable {
|
||||
SINGLE, // 3
|
||||
}
|
||||
|
||||
protected void parseLite() throws ProtocolException {
|
||||
|
||||
//skip this if the length has been provided i.e. the tx is not part of a block
|
||||
if (parseLazy && length == UNKNOWN_LENGTH) {
|
||||
//If length hasn't been provided this tx is probably contained within a block.
|
||||
//In parseRetain mode the block needs to know how long the transaction is
|
||||
//unfortunately this requires a fairly deep (though not total) parse.
|
||||
//This is due to the fact that transactions in the block's list do not include a
|
||||
//size header and inputs/outputs are also variable length due the contained
|
||||
//script so each must be instantiated so the scriptlength varint can be read
|
||||
//to calculate total length of the transaction.
|
||||
//We will still persist will this semi-light parsing because getting the lengths
|
||||
//of the various components gains us the ability to cache the backing bytearrays
|
||||
//so that only those subcomponents that have changed will need to be reserialized.
|
||||
|
||||
parse();
|
||||
parsed = true;
|
||||
}
|
||||
}
|
||||
|
||||
void parse() throws ProtocolException {
|
||||
version = readUint32();
|
||||
// First come the inputs.
|
||||
|
||||
if (parsed)
|
||||
return;
|
||||
|
||||
version = readUint32();
|
||||
int marker = cursor;
|
||||
|
||||
// First come the inputs.
|
||||
long numInputs = readVarInt();
|
||||
inputs = new ArrayList<TransactionInput>((int)numInputs);
|
||||
for (long i = 0; i < numInputs; i++) {
|
||||
@ -302,6 +327,7 @@ public class Transaction extends ChildMessage implements Serializable {
|
||||
cursor += output.getMessageSize();
|
||||
}
|
||||
lockTime = readUint32();
|
||||
length = cursor - offset;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -375,6 +401,9 @@ public class Transaction extends ChildMessage implements Serializable {
|
||||
|
||||
/** Adds an input directly, with no checking that it's valid. */
|
||||
public void addInput(TransactionInput input) {
|
||||
unCache();
|
||||
input.setParent(this);
|
||||
immutableInputs = null;
|
||||
inputs.add(input);
|
||||
}
|
||||
|
||||
@ -382,7 +411,13 @@ public class Transaction extends ChildMessage implements Serializable {
|
||||
* Adds the given output to this transaction. The output must be completely initialized.
|
||||
*/
|
||||
public void addOutput(TransactionOutput to) {
|
||||
unCache();
|
||||
|
||||
//these could be merged into one but would need parentTransaction to be cast whenever it was accessed.
|
||||
to.setParent(this);
|
||||
to.parentTransaction = this;
|
||||
|
||||
immutableOutputs = null;
|
||||
outputs.add(to);
|
||||
}
|
||||
|
||||
@ -415,11 +450,11 @@ public class Transaction extends ChildMessage implements Serializable {
|
||||
ECKey[] signingKeys = new ECKey[inputs.size()];
|
||||
for (int i = 0; i < inputs.size(); i++) {
|
||||
TransactionInput input = inputs.get(i);
|
||||
assert input.scriptBytes.length == 0 : "Attempting to sign a non-fresh transaction";
|
||||
assert input.getScriptBytes().length == 0 : "Attempting to sign a non-fresh transaction";
|
||||
// Set the input to the script of its output.
|
||||
input.scriptBytes = input.outpoint.getConnectedPubKeyScript();
|
||||
input.setScriptBytes(input.getOutpoint().getConnectedPubKeyScript());
|
||||
// Find the signing key we'll need to use.
|
||||
byte[] connectedPubKeyHash = input.outpoint.getConnectedPubKeyHash();
|
||||
byte[] connectedPubKeyHash = input.getOutpoint().getConnectedPubKeyHash();
|
||||
ECKey key = wallet.findKeyFromPubHash(connectedPubKeyHash);
|
||||
// This assert should never fire. If it does, it means the wallet is inconsistent.
|
||||
assert key != null : "Transaction exists in wallet that we cannot redeem: " + Utils.bytesToHexString(connectedPubKeyHash);
|
||||
@ -429,7 +464,7 @@ public class Transaction extends ChildMessage implements Serializable {
|
||||
boolean anyoneCanPay = false;
|
||||
byte[] hash = hashTransactionForSignature(hashType, anyoneCanPay);
|
||||
// Set the script to empty again for the next input.
|
||||
input.scriptBytes = TransactionInput.EMPTY_ARRAY;
|
||||
input.setScriptBytes(TransactionInput.EMPTY_ARRAY);
|
||||
|
||||
// Now sign for the output so we can redeem it. We use the keypair to sign the hash,
|
||||
// and then put the resulting signature in the script along with the public key (below).
|
||||
@ -448,9 +483,9 @@ public class Transaction extends ChildMessage implements Serializable {
|
||||
// output.
|
||||
for (int i = 0; i < inputs.size(); i++) {
|
||||
TransactionInput input = inputs.get(i);
|
||||
assert input.scriptBytes.length == 0;
|
||||
assert input.getScriptBytes().length == 0;
|
||||
ECKey key = signingKeys[i];
|
||||
input.scriptBytes = Script.createInputScript(signatures[i], key.getPubKey());
|
||||
input.setScriptBytes(Script.createInputScript(signatures[i], key.getPubKey()));
|
||||
}
|
||||
|
||||
// Every input is now complete.
|
||||
@ -484,8 +519,56 @@ public class Transaction extends ChildMessage implements Serializable {
|
||||
out.bitcoinSerialize(stream);
|
||||
uint32ToByteStreamLE(lockTime, stream);
|
||||
}
|
||||
|
||||
|
||||
|
||||
@Override
|
||||
/**
|
||||
* @return the lockTime
|
||||
*/
|
||||
public long getLockTime() {
|
||||
checkParse();
|
||||
return lockTime;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param lockTime the lockTime to set
|
||||
*/
|
||||
public void setLockTime(long lockTime) {
|
||||
unCache();
|
||||
this.lockTime = lockTime;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the version
|
||||
*/
|
||||
public long getVersion() {
|
||||
checkParse();
|
||||
return version;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return a read-only list of the inputs of this transaction.
|
||||
*/
|
||||
public List<TransactionInput> getInputs() {
|
||||
if (immutableInputs == null) {
|
||||
checkParse();
|
||||
immutableInputs = Collections.unmodifiableList(inputs);
|
||||
}
|
||||
return immutableInputs;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return a read-only list of the outputs of this transaction.
|
||||
*/
|
||||
public List<TransactionOutput> getOutputs() {
|
||||
if (immutableOutputs == null) {
|
||||
checkParse();
|
||||
immutableOutputs = Collections.unmodifiableList(outputs);
|
||||
}
|
||||
return immutableOutputs;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object other) {
|
||||
if (!(other instanceof Transaction)) return false;
|
||||
Transaction t = (Transaction) other;
|
||||
|
@ -35,18 +35,18 @@ public class TransactionInput extends ChildMessage implements Serializable {
|
||||
// Allows for altering transactions after they were broadcast. Tx replacement is currently disabled in the C++
|
||||
// client so this is always the UINT_MAX.
|
||||
// TODO: Document this in more detail and build features that use it.
|
||||
long sequence;
|
||||
private long sequence;
|
||||
// Data needed to connect to the output of the transaction we're gathering coins from.
|
||||
TransactionOutPoint outpoint;
|
||||
private TransactionOutPoint outpoint;
|
||||
// The "script bytes" might not actually be a script. In coinbase transactions where new coins are minted there
|
||||
// is no input transaction, so instead the scriptBytes contains some extra stuff (like a rollover nonce) that we
|
||||
// don't care about much. The bytes are turned into a Script object (cached below) on demand via a getter.
|
||||
byte[] scriptBytes;
|
||||
private byte[] scriptBytes;
|
||||
// The Script object obtained from parsing scriptBytes. Only filled in on demand and if the transaction is not
|
||||
// coinbase.
|
||||
transient private Script scriptSig;
|
||||
// A pointer to the transaction that owns this input.
|
||||
Transaction parentTransaction;
|
||||
private Transaction parentTransaction;
|
||||
|
||||
/** Used only in creation of the genesis block. */
|
||||
TransactionInput(NetworkParameters params, Transaction parentTransaction, byte[] scriptBytes) {
|
||||
@ -77,10 +77,17 @@ public class TransactionInput extends ChildMessage implements Serializable {
|
||||
/** 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);
|
||||
super(params, msg, offset, parentTransaction, parseLazy, parseRetain, UNKNOWN_LENGTH);
|
||||
this.parentTransaction = parentTransaction;
|
||||
}
|
||||
|
||||
protected void parseLite() {
|
||||
int curs = cursor;
|
||||
int scriptLen = (int) readVarInt(36);
|
||||
length = cursor - offset + scriptLen + 4;
|
||||
cursor = curs;
|
||||
}
|
||||
|
||||
void parse() throws ProtocolException {
|
||||
outpoint = new TransactionOutPoint(params, bytes, cursor, this, parseLazy, parseRetain);
|
||||
cursor += outpoint.getMessageSize();
|
||||
@ -101,7 +108,7 @@ public class TransactionInput extends ChildMessage implements Serializable {
|
||||
* Coinbase transactions have special inputs with hashes of zero. If this is such an input, returns true.
|
||||
*/
|
||||
public boolean isCoinBase() {
|
||||
return outpoint.hash.equals(Sha256Hash.ZERO_HASH);
|
||||
return outpoint.getHash().equals(Sha256Hash.ZERO_HASH);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -111,7 +118,8 @@ public class TransactionInput extends ChildMessage implements Serializable {
|
||||
// Transactions that generate new coins don't actually have a script. Instead this
|
||||
// parameter is overloaded to be something totally different.
|
||||
if (scriptSig == null) {
|
||||
assert scriptBytes != null;
|
||||
checkParse();
|
||||
assert scriptBytes != null;
|
||||
scriptSig = new Script(params, scriptBytes, 0, scriptBytes.length);
|
||||
}
|
||||
return scriptSig;
|
||||
@ -125,9 +133,55 @@ public class TransactionInput extends ChildMessage implements Serializable {
|
||||
assert !isCoinBase();
|
||||
return getScriptSig().getFromAddress();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the sequence
|
||||
*/
|
||||
public long getSequence() {
|
||||
checkParse();
|
||||
return sequence;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param sequence the sequence to set
|
||||
*/
|
||||
public void setSequence(long sequence) {
|
||||
unCache();
|
||||
this.sequence = sequence;
|
||||
}
|
||||
|
||||
/** Returns a human readable debug string. */
|
||||
/**
|
||||
* @return the outpoint
|
||||
*/
|
||||
public TransactionOutPoint getOutpoint() {
|
||||
checkParse();
|
||||
return outpoint;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the scriptBytes
|
||||
*/
|
||||
public byte[] getScriptBytes() {
|
||||
checkParse();
|
||||
return scriptBytes;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param scriptBytes the scriptBytes to set
|
||||
*/
|
||||
void setScriptBytes(byte[] scriptBytes) {
|
||||
unCache();
|
||||
this.scriptBytes = scriptBytes;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the parentTransaction
|
||||
*/
|
||||
public Transaction getParentTransaction() {
|
||||
return parentTransaction;
|
||||
}
|
||||
|
||||
/** Returns a human readable debug string. */
|
||||
public String toString() {
|
||||
if (isCoinBase())
|
||||
return "TxIn: COINBASE";
|
||||
@ -153,10 +207,10 @@ public class TransactionInput extends ChildMessage implements Serializable {
|
||||
* @return The TransactionOutput or null if the transactions map doesn't contain the referenced tx.
|
||||
*/
|
||||
TransactionOutput getConnectedOutput(Map<Sha256Hash, Transaction> transactions) {
|
||||
Transaction tx = transactions.get(outpoint.hash);
|
||||
Transaction tx = transactions.get(outpoint.getHash());
|
||||
if (tx == null)
|
||||
return null;
|
||||
TransactionOutput out = tx.outputs.get((int)outpoint.index);
|
||||
TransactionOutput out = tx.getOutputs().get((int)outpoint.getIndex());
|
||||
return out;
|
||||
}
|
||||
|
||||
@ -169,10 +223,10 @@ public class TransactionInput extends ChildMessage implements Serializable {
|
||||
* @return true if connection took place, false if the referenced transaction was not in the list.
|
||||
*/
|
||||
ConnectionResult connect(Map<Sha256Hash, Transaction> transactions, boolean disconnect) {
|
||||
Transaction tx = transactions.get(outpoint.hash);
|
||||
Transaction tx = transactions.get(outpoint.getHash());
|
||||
if (tx == null)
|
||||
return TransactionInput.ConnectionResult.NO_SUCH_TX;
|
||||
TransactionOutput out = tx.outputs.get((int)outpoint.index);
|
||||
TransactionOutput out = tx.getOutputs().get((int)outpoint.getIndex());
|
||||
if (!out.isAvailableForSpending()) {
|
||||
if (disconnect)
|
||||
out.markAsUnspent();
|
||||
@ -191,7 +245,7 @@ public class TransactionInput extends ChildMessage implements Serializable {
|
||||
*/
|
||||
boolean disconnect() {
|
||||
if (outpoint.fromTx == null) return false;
|
||||
outpoint.fromTx.outputs.get((int)outpoint.index).markAsUnspent();
|
||||
outpoint.fromTx.getOutputs().get((int)outpoint.getIndex()).markAsUnspent();
|
||||
outpoint.fromTx = null;
|
||||
return true;
|
||||
}
|
||||
|
@ -28,11 +28,13 @@ import java.io.Serializable;
|
||||
*/
|
||||
public class TransactionOutPoint extends ChildMessage implements Serializable {
|
||||
private static final long serialVersionUID = -6320880638344662579L;
|
||||
|
||||
private static final int MESSAGE_LENGTH = 36;
|
||||
|
||||
/** Hash of the transaction to which we refer. */
|
||||
Sha256Hash hash;
|
||||
private Sha256Hash hash;
|
||||
/** Which output of that transaction we are talking about. */
|
||||
long index;
|
||||
private long index;
|
||||
|
||||
// This is not part of Bitcoin serialization. It's included in Java serialization.
|
||||
// It points to the connected transaction.
|
||||
@ -57,7 +59,7 @@ public class TransactionOutPoint extends ChildMessage implements Serializable {
|
||||
|
||||
/** 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);
|
||||
super(params, payload, offset, parent, parseLazy, parseRetain, MESSAGE_LENGTH);
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -65,8 +67,16 @@ public class TransactionOutPoint extends ChildMessage implements Serializable {
|
||||
hash = readHash();
|
||||
index = readUint32();
|
||||
}
|
||||
|
||||
/* (non-Javadoc)
|
||||
* @see com.google.bitcoin.core.Message#getMessageSize()
|
||||
*/
|
||||
@Override
|
||||
int getMessageSize() {
|
||||
return MESSAGE_LENGTH;
|
||||
}
|
||||
|
||||
@Override
|
||||
@Override
|
||||
protected void bitcoinSerializeToStream(OutputStream stream) throws IOException {
|
||||
stream.write(Utils.reverseBytes(hash.getBytes()));
|
||||
Utils.uint32ToByteStreamLE(index, stream);
|
||||
@ -78,7 +88,7 @@ public class TransactionOutPoint extends ChildMessage implements Serializable {
|
||||
*/
|
||||
TransactionOutput getConnectedOutput() {
|
||||
if (fromTx == null) return null;
|
||||
return fromTx.outputs.get((int)index);
|
||||
return fromTx.getOutputs().get((int)index);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -103,7 +113,40 @@ public class TransactionOutPoint extends ChildMessage implements Serializable {
|
||||
return "outpoint " + index + ":" + hash.toString();
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* @return the hash
|
||||
*/
|
||||
public Sha256Hash getHash() {
|
||||
checkParse();
|
||||
return hash;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param hash the hash to set
|
||||
*/
|
||||
void setHash(Sha256Hash hash) {
|
||||
this.hash = hash;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the index
|
||||
*/
|
||||
public long getIndex() {
|
||||
checkParse();
|
||||
return index;
|
||||
}
|
||||
|
||||
// /**
|
||||
// * @param index the index to set
|
||||
// */
|
||||
// public void setIndex(long index) {
|
||||
// unCache();
|
||||
// this.index = index;
|
||||
// }
|
||||
|
||||
/**
|
||||
* 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.
|
||||
|
@ -50,6 +50,7 @@ public class TransactionOutput extends ChildMessage implements Serializable {
|
||||
|
||||
// A reference to the transaction which holds this output.
|
||||
Transaction parentTransaction;
|
||||
private transient int scriptLen;
|
||||
|
||||
/** Deserializes a transaction output message. This is usually part of a transaction message. */
|
||||
public TransactionOutput(NetworkParameters params, Transaction parent, byte[] payload,
|
||||
@ -62,7 +63,7 @@ public class TransactionOutput extends ChildMessage implements Serializable {
|
||||
/** 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);
|
||||
super(params, msg, offset, parent, parseLazy, parseRetain, UNKNOWN_LENGTH);
|
||||
parentTransaction = parent;
|
||||
availableForSpending = true;
|
||||
}
|
||||
@ -85,14 +86,20 @@ public class TransactionOutput extends ChildMessage implements Serializable {
|
||||
}
|
||||
|
||||
public Script getScriptPubKey() throws ScriptException {
|
||||
if (scriptPubKey == null)
|
||||
scriptPubKey = new Script(params, scriptBytes, 0, scriptBytes.length);
|
||||
if (scriptPubKey == null) {
|
||||
checkParse();
|
||||
scriptPubKey = new Script(params, scriptBytes, 0, scriptBytes.length);
|
||||
}
|
||||
return scriptPubKey;
|
||||
}
|
||||
|
||||
protected void parseLite() {
|
||||
value = readUint64();
|
||||
scriptLen = (int) readVarInt();
|
||||
length = cursor - offset + scriptLen;
|
||||
}
|
||||
|
||||
void parse() throws ProtocolException {
|
||||
value = readUint64();
|
||||
int scriptLen = (int) readVarInt();
|
||||
scriptBytes = readBytes(scriptLen);
|
||||
}
|
||||
|
||||
@ -110,13 +117,14 @@ public class TransactionOutput extends ChildMessage implements Serializable {
|
||||
* receives.
|
||||
*/
|
||||
public BigInteger getValue() {
|
||||
return value;
|
||||
checkParse();
|
||||
return value;
|
||||
}
|
||||
|
||||
int getIndex() {
|
||||
assert parentTransaction != null;
|
||||
for (int i = 0; i < parentTransaction.outputs.size(); i++) {
|
||||
if (parentTransaction.outputs.get(i) == this)
|
||||
for (int i = 0; i < parentTransaction.getOutputs().size(); i++) {
|
||||
if (parentTransaction.getOutputs().get(i) == this)
|
||||
return i;
|
||||
}
|
||||
// Should never happen.
|
||||
@ -143,7 +151,8 @@ public class TransactionOutput extends ChildMessage implements Serializable {
|
||||
}
|
||||
|
||||
public byte[] getScriptBytes() {
|
||||
return scriptBytes;
|
||||
checkParse();
|
||||
return scriptBytes;
|
||||
}
|
||||
|
||||
/** Returns true if this output is to an address we have the keys for in the wallet. */
|
||||
|
@ -34,4 +34,6 @@ public class UnknownMessage extends EmptyMessage {
|
||||
public void parse() throws ProtocolException {
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
@ -79,7 +79,11 @@ public class VersionMessage extends Message {
|
||||
|
||||
@Override
|
||||
public void parse() throws ProtocolException {
|
||||
clientVersion = (int) readUint32();
|
||||
if (parsed)
|
||||
return;
|
||||
parsed = true;
|
||||
|
||||
clientVersion = (int) readUint32();
|
||||
localServices = readUint64().longValue();
|
||||
time = readUint64().longValue();
|
||||
myAddr = new PeerAddress(params, bytes, cursor, 0);
|
||||
|
@ -333,7 +333,7 @@ public class Wallet implements Serializable {
|
||||
*/
|
||||
private void updateForSpends(Transaction tx) throws VerificationException {
|
||||
// tx is on the best chain by this point.
|
||||
for (TransactionInput input : tx.inputs) {
|
||||
for (TransactionInput input : tx.getInputs()) {
|
||||
TransactionInput.ConnectionResult result = input.connect(unspent, false);
|
||||
if (result == TransactionInput.ConnectionResult.NO_SUCH_TX) {
|
||||
// Not found in the unspent map. Try again with the spent map.
|
||||
@ -352,13 +352,13 @@ public class Wallet implements Serializable {
|
||||
//
|
||||
// A -> spent by B [pending]
|
||||
// \-> spent by C [chain]
|
||||
Transaction doubleSpent = input.outpoint.fromTx; // == A
|
||||
Transaction doubleSpent = input.getOutpoint().fromTx; // == A
|
||||
assert doubleSpent != null;
|
||||
int index = (int) input.outpoint.index;
|
||||
TransactionOutput output = doubleSpent.outputs.get(index);
|
||||
int index = (int) input.getOutpoint().getIndex();
|
||||
TransactionOutput output = doubleSpent.getOutputs().get(index);
|
||||
TransactionInput spentBy = output.getSpentBy();
|
||||
assert spentBy != null;
|
||||
Transaction connected = spentBy.parentTransaction;
|
||||
Transaction connected = spentBy.getParentTransaction();
|
||||
assert connected != null;
|
||||
if (pending.containsKey(connected.getHash())) {
|
||||
log.info("Saw double spend from chain override pending tx {}", connected.getHashAsString());
|
||||
@ -378,7 +378,7 @@ public class Wallet implements Serializable {
|
||||
// Otherwise we saw a transaction spend our coins, but we didn't try and spend them ourselves yet.
|
||||
// The outputs are already marked as spent by the connect call above, so check if there are any more for
|
||||
// us to use. Move if not.
|
||||
Transaction connected = input.outpoint.fromTx;
|
||||
Transaction connected = input.getOutpoint().fromTx;
|
||||
maybeMoveTxToSpent(connected, "prevtx");
|
||||
}
|
||||
}
|
||||
@ -425,8 +425,8 @@ public class Wallet implements Serializable {
|
||||
assert !pending.containsKey(tx.getHash()) : "confirmSend called on the same transaction twice";
|
||||
log.info("confirmSend of {}", tx.getHashAsString());
|
||||
// Mark the outputs of the used transcations as spent, so we don't try and spend it again.
|
||||
for (TransactionInput input : tx.inputs) {
|
||||
TransactionOutput connectedOutput = input.outpoint.getConnectedOutput();
|
||||
for (TransactionInput input : tx.getInputs()) {
|
||||
TransactionOutput connectedOutput = input.getOutpoint().getConnectedOutput();
|
||||
Transaction connectedTx = connectedOutput.parentTransaction;
|
||||
connectedOutput.markAsSpent(input);
|
||||
maybeMoveTxToSpent(connectedTx, "spent tx");
|
||||
@ -590,7 +590,7 @@ public class Wallet implements Serializable {
|
||||
BigInteger valueGathered = BigInteger.ZERO;
|
||||
List<TransactionOutput> gathered = new LinkedList<TransactionOutput>();
|
||||
for (Transaction tx : unspent.values()) {
|
||||
for (TransactionOutput output : tx.outputs) {
|
||||
for (TransactionOutput output : tx.getOutputs()) {
|
||||
if (!output.isAvailableForSpending()) continue;
|
||||
if (!output.isMine(this)) continue;
|
||||
gathered.add(output);
|
||||
@ -714,7 +714,7 @@ public class Wallet implements Serializable {
|
||||
public synchronized BigInteger getBalance(BalanceType balanceType) {
|
||||
BigInteger available = BigInteger.ZERO;
|
||||
for (Transaction tx : unspent.values()) {
|
||||
for (TransactionOutput output : tx.outputs) {
|
||||
for (TransactionOutput output : tx.getOutputs()) {
|
||||
if (!output.isMine(this)) continue;
|
||||
if (!output.isAvailableForSpending()) continue;
|
||||
available = available.add(output.getValue());
|
||||
@ -726,7 +726,7 @@ public class Wallet implements Serializable {
|
||||
// Now add back all the pending outputs to assume the transaction goes through.
|
||||
BigInteger estimated = available;
|
||||
for (Transaction tx : pending.values()) {
|
||||
for (TransactionOutput output : tx.outputs) {
|
||||
for (TransactionOutput output : tx.getOutputs()) {
|
||||
if (!output.isMine(this)) continue;
|
||||
estimated = estimated.add(output.getValue());
|
||||
}
|
||||
@ -871,7 +871,7 @@ public class Wallet implements Serializable {
|
||||
inactive.clear();
|
||||
for (Transaction tx : commonChainTransactions.values()) {
|
||||
int unspentOutputs = 0;
|
||||
for (TransactionOutput output : tx.outputs) {
|
||||
for (TransactionOutput output : tx.getOutputs()) {
|
||||
if (output.isAvailableForSpending()) unspentOutputs++;
|
||||
}
|
||||
if (unspentOutputs > 0) {
|
||||
@ -949,11 +949,11 @@ public class Wallet implements Serializable {
|
||||
|
||||
private void reprocessTxAfterReorg(Map<Sha256Hash, Transaction> pool, Transaction tx) {
|
||||
log.info(" TX {}", tx.getHashAsString());
|
||||
int numInputs = tx.inputs.size();
|
||||
int numInputs = tx.getInputs().size();
|
||||
int noSuchTx = 0;
|
||||
int success = 0;
|
||||
boolean isDead = false;
|
||||
for (TransactionInput input : tx.inputs) {
|
||||
for (TransactionInput input : tx.getInputs()) {
|
||||
if (input.isCoinBase()) {
|
||||
// Input is not in our wallet so there is "no such input tx", bit of an abuse.
|
||||
noSuchTx++;
|
||||
@ -970,7 +970,7 @@ public class Wallet implements Serializable {
|
||||
// your own transaction? I hope not!!
|
||||
log.info(" ->dead, will not confirm now unless there's another re-org", tx.getHashAsString());
|
||||
TransactionOutput doubleSpent = input.getConnectedOutput(pool);
|
||||
Transaction replacement = doubleSpent.getSpentBy().parentTransaction;
|
||||
Transaction replacement = doubleSpent.getSpentBy().getParentTransaction();
|
||||
dead.put(tx.getHash(), tx);
|
||||
pending.remove(tx.getHash());
|
||||
// Inform the event listeners of the newly dead tx.
|
||||
|
@ -21,11 +21,14 @@ import org.bouncycastle.util.encoders.Hex;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.util.Arrays;
|
||||
import java.util.LinkedHashMap;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertNotNull;
|
||||
import static org.junit.Assert.assertNull;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
public class BitcoinSerializerTest {
|
||||
private final byte[] addrMessage = Hex.decode("f9beb4d96164647200000000000000001f000000" +
|
||||
@ -53,7 +56,7 @@ public class BitcoinSerializerTest {
|
||||
|
||||
@Test
|
||||
public void testVersion() throws Exception {
|
||||
BitcoinSerializer bs = new BitcoinSerializer(NetworkParameters.prodNet(), false, null);
|
||||
BitcoinSerializer bs = new BitcoinSerializer(NetworkParameters.prodNet(), false, null);
|
||||
// the actual data from https://en.bitcoin.it/wiki/Protocol_specification#version
|
||||
ByteArrayInputStream bais = new ByteArrayInputStream(Hex.decode("f9beb4d976657273696f6e0000000000550000009" +
|
||||
"c7c00000100000000000000e615104d00000000010000000000000000000000000000000000ffff0a000001daf6010000" +
|
||||
@ -82,14 +85,19 @@ public class BitcoinSerializerTest {
|
||||
|
||||
@Test
|
||||
public void testAddr() throws Exception {
|
||||
BitcoinSerializer bs = new BitcoinSerializer(NetworkParameters.prodNet(), true, null);
|
||||
BitcoinSerializer bs = new BitcoinSerializer(NetworkParameters.prodNet(), true, null);
|
||||
// the actual data from https://en.bitcoin.it/wiki/Protocol_specification#addr
|
||||
ByteArrayInputStream bais = new ByteArrayInputStream(addrMessage);
|
||||
AddressMessage a = (AddressMessage)bs.deserialize(bais);
|
||||
assertEquals(1, a.addresses.size());
|
||||
PeerAddress pa = a.addresses.get(0);
|
||||
assertEquals(8333, pa.port);
|
||||
assertEquals("10.0.0.1", pa.addr.getHostAddress());
|
||||
assertEquals(1, a.getAddresses().size());
|
||||
PeerAddress pa = a.getAddresses().get(0);
|
||||
assertEquals(8333, pa.getPort());
|
||||
assertEquals("10.0.0.1", pa.getAddr().getHostAddress());
|
||||
ByteArrayOutputStream bos = new ByteArrayOutputStream(addrMessage.length);
|
||||
bs.serialize(a, bos);
|
||||
|
||||
//this wont be true due to dynamic timestamps.
|
||||
//assertTrue(LazyParseByteCacheTest.arrayContains(bos.toByteArray(), addrMessage));
|
||||
}
|
||||
|
||||
@Test
|
||||
@ -103,4 +111,93 @@ public class BitcoinSerializerTest {
|
||||
tx = (Transaction)bs.deserialize(bais);
|
||||
assertNull(tx);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testLazyParsing() throws Exception {
|
||||
BitcoinSerializer bs = new BitcoinSerializer(NetworkParameters.prodNet(), true, true, false, null);
|
||||
|
||||
ByteArrayInputStream bais = new ByteArrayInputStream(txMessage);
|
||||
Transaction tx = (Transaction)bs.deserialize(bais);
|
||||
assertNotNull(tx);
|
||||
assertEquals(false, tx.isParsed());
|
||||
assertEquals(true, tx.isCached());
|
||||
tx.getInputs();
|
||||
assertEquals(true, tx.isParsed());
|
||||
|
||||
ByteArrayOutputStream bos = new ByteArrayOutputStream();
|
||||
bs.serialize(tx, bos);
|
||||
System.out.println(Utils.bytesToHexString(txMessage));
|
||||
System.out.println(Utils.bytesToHexString(bos.toByteArray()));
|
||||
assertEquals(true, Arrays.equals(txMessage, bos.toByteArray()));
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCachedParsing() throws Exception {
|
||||
testCachedParsing(true);
|
||||
testCachedParsing(false);
|
||||
}
|
||||
|
||||
private void testCachedParsing(boolean lazy) throws Exception {
|
||||
BitcoinSerializer bs = new BitcoinSerializer(NetworkParameters.prodNet(), true, lazy, true, null);
|
||||
|
||||
//first try writing to a fields to ensure uncaching and children are not affected
|
||||
ByteArrayInputStream bais = new ByteArrayInputStream(txMessage);
|
||||
Transaction tx = (Transaction)bs.deserialize(bais);
|
||||
assertNotNull(tx);
|
||||
assertEquals(!lazy, tx.isParsed());
|
||||
assertEquals(true, tx.isCached());
|
||||
|
||||
tx.setLockTime(1);
|
||||
//parent should have been uncached
|
||||
assertEquals(false, tx.isCached());
|
||||
//child should remain cached.
|
||||
assertEquals(true, tx.getInputs().get(0).isCached());
|
||||
|
||||
ByteArrayOutputStream bos = new ByteArrayOutputStream();
|
||||
bs.serialize(tx, bos);
|
||||
assertEquals(true, !Arrays.equals(txMessage, bos.toByteArray()));
|
||||
|
||||
//now try writing to a child to ensure uncaching is propagated up to parent but not to siblings
|
||||
bais = new ByteArrayInputStream(txMessage);
|
||||
tx = (Transaction)bs.deserialize(bais);
|
||||
assertNotNull(tx);
|
||||
assertEquals(!lazy, tx.isParsed());
|
||||
assertEquals(true, tx.isCached());
|
||||
|
||||
tx.getInputs().get(0).setSequence(1);
|
||||
//parent should have been uncached
|
||||
assertEquals(false, tx.isCached());
|
||||
//so should child
|
||||
assertEquals(false, tx.getInputs().get(0).isCached());
|
||||
|
||||
bos = new ByteArrayOutputStream();
|
||||
bs.serialize(tx, bos);
|
||||
assertEquals(true, !Arrays.equals(txMessage, bos.toByteArray()));
|
||||
|
||||
//deserialize/reserialize to check for equals.
|
||||
bais = new ByteArrayInputStream(txMessage);
|
||||
tx = (Transaction)bs.deserialize(bais);
|
||||
assertNotNull(tx);
|
||||
assertEquals(!lazy, tx.isParsed());
|
||||
assertEquals(true, tx.isCached());
|
||||
bos = new ByteArrayOutputStream();
|
||||
bs.serialize(tx, bos);
|
||||
assertEquals(true, Arrays.equals(txMessage, bos.toByteArray()));
|
||||
|
||||
//deserialize/reserialize to check for equals. Set a field to it's existing value to trigger uncache
|
||||
bais = new ByteArrayInputStream(txMessage);
|
||||
tx = (Transaction)bs.deserialize(bais);
|
||||
assertNotNull(tx);
|
||||
assertEquals(!lazy, tx.isParsed());
|
||||
assertEquals(true, tx.isCached());
|
||||
|
||||
tx.getInputs().get(0).setSequence(tx.getInputs().get(0).getSequence());
|
||||
|
||||
bos = new ByteArrayOutputStream();
|
||||
bs.serialize(tx, bos);
|
||||
assertEquals(true, Arrays.equals(txMessage, bos.toByteArray()));
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
559
tests/com/google/bitcoin/core/LazyParseByteCacheTest.java
Normal file
559
tests/com/google/bitcoin/core/LazyParseByteCacheTest.java
Normal file
@ -0,0 +1,559 @@
|
||||
package com.google.bitcoin.core;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.util.Arrays;
|
||||
|
||||
import org.bouncycastle.util.encoders.Hex;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
|
||||
import static com.google.bitcoin.core.TestUtils.createFakeBlock;
|
||||
import static com.google.bitcoin.core.TestUtils.createFakeTx;
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
import com.google.bitcoin.store.BlockStore;
|
||||
import com.google.bitcoin.store.MemoryBlockStore;
|
||||
|
||||
public class LazyParseByteCacheTest {
|
||||
|
||||
private final byte[] addrMessage = Hex.decode("f9beb4d96164647200000000000000001f000000" +
|
||||
"ed52399b01e215104d010000000000000000000000000000000000ffff0a000001208d");
|
||||
|
||||
private final byte[] txMessage = Hex.decode(
|
||||
"F9 BE B4 D9 74 78 00 00 00 00 00 00 00 00 00 00" +
|
||||
"02 01 00 00 E2 93 CD BE 01 00 00 00 01 6D BD DB" +
|
||||
"08 5B 1D 8A F7 51 84 F0 BC 01 FA D5 8D 12 66 E9" +
|
||||
"B6 3B 50 88 19 90 E4 B4 0D 6A EE 36 29 00 00 00" +
|
||||
"00 8B 48 30 45 02 21 00 F3 58 1E 19 72 AE 8A C7" +
|
||||
"C7 36 7A 7A 25 3B C1 13 52 23 AD B9 A4 68 BB 3A" +
|
||||
"59 23 3F 45 BC 57 83 80 02 20 59 AF 01 CA 17 D0" +
|
||||
"0E 41 83 7A 1D 58 E9 7A A3 1B AE 58 4E DE C2 8D" +
|
||||
"35 BD 96 92 36 90 91 3B AE 9A 01 41 04 9C 02 BF" +
|
||||
"C9 7E F2 36 CE 6D 8F E5 D9 40 13 C7 21 E9 15 98" +
|
||||
"2A CD 2B 12 B6 5D 9B 7D 59 E2 0A 84 20 05 F8 FC" +
|
||||
"4E 02 53 2E 87 3D 37 B9 6F 09 D6 D4 51 1A DA 8F" +
|
||||
"14 04 2F 46 61 4A 4C 70 C0 F1 4B EF F5 FF FF FF" +
|
||||
"FF 02 40 4B 4C 00 00 00 00 00 19 76 A9 14 1A A0" +
|
||||
"CD 1C BE A6 E7 45 8A 7A BA D5 12 A9 D9 EA 1A FB" +
|
||||
"22 5E 88 AC 80 FA E9 C7 00 00 00 00 19 76 A9 14" +
|
||||
"0E AB 5B EA 43 6A 04 84 CF AB 12 48 5E FD A0 B7" +
|
||||
"8B 4E CC 52 88 AC 00 00 00 00");
|
||||
|
||||
private final byte[] txMessagePart = Hex.decode(
|
||||
"08 5B 1D 8A F7 51 84 F0 BC 01 FA D5 8D 12 66 E9" +
|
||||
"B6 3B 50 88 19 90 E4 B4 0D 6A EE 36 29 00 00 00" +
|
||||
"00 8B 48 30 45 02 21 00 F3 58 1E 19 72 AE 8A C7" +
|
||||
"C7 36 7A 7A 25 3B C1 13 52 23 AD B9 A4 68 BB 3A");
|
||||
|
||||
private static final NetworkParameters testNet = NetworkParameters.testNet();
|
||||
private BlockChain testNetChain;
|
||||
|
||||
private Wallet wallet;
|
||||
private BlockChain chain;
|
||||
private BlockStore blockStore;
|
||||
private Address coinbaseTo;
|
||||
private NetworkParameters unitTestParams;
|
||||
|
||||
private byte[] b1Bytes;
|
||||
private byte[] b1BytesWithHeader;
|
||||
|
||||
private byte[] tx1Bytes;
|
||||
private byte[] tx1BytesWithHeader;
|
||||
|
||||
private byte[] tx2Bytes;
|
||||
private byte[] tx2BytesWithHeader;
|
||||
|
||||
private void resetBlockStore() {
|
||||
blockStore = new MemoryBlockStore(unitTestParams);
|
||||
}
|
||||
|
||||
@Before
|
||||
public void setUp() throws Exception {
|
||||
testNetChain = new BlockChain(testNet, new Wallet(testNet), new MemoryBlockStore(testNet));
|
||||
unitTestParams = NetworkParameters.unitTests();
|
||||
wallet = new Wallet(unitTestParams);
|
||||
wallet.addKey(new ECKey());
|
||||
|
||||
resetBlockStore();
|
||||
chain = new BlockChain(unitTestParams, wallet, blockStore);
|
||||
|
||||
coinbaseTo = wallet.keychain.get(0).toAddress(unitTestParams);
|
||||
|
||||
Transaction tx1 = createFakeTx(unitTestParams,
|
||||
Utils.toNanoCoins(2, 0),
|
||||
wallet.keychain.get(0).toAddress(unitTestParams));
|
||||
|
||||
//add a second input so can test granularity of byte cache.
|
||||
Transaction prevTx = new Transaction(unitTestParams);
|
||||
TransactionOutput prevOut = new TransactionOutput(unitTestParams, prevTx, Utils.toNanoCoins(1, 0), wallet.keychain.get(0).toAddress(unitTestParams));
|
||||
prevTx.addOutput(prevOut);
|
||||
// Connect it.
|
||||
tx1.addInput(prevOut);
|
||||
|
||||
Transaction tx2 = createFakeTx(unitTestParams, Utils.toNanoCoins(1, 0),
|
||||
new ECKey().toAddress(unitTestParams));
|
||||
|
||||
Block b1 = createFakeBlock(unitTestParams, blockStore, tx1, tx2).block;
|
||||
|
||||
BitcoinSerializer bs = new BitcoinSerializer(unitTestParams, true, null);
|
||||
|
||||
ByteArrayOutputStream bos = new ByteArrayOutputStream();
|
||||
bs.serialize(tx1, bos);
|
||||
tx1BytesWithHeader = bos.toByteArray();
|
||||
tx1Bytes = tx1.bitcoinSerialize();
|
||||
|
||||
bos.reset();
|
||||
bs.serialize(tx2, bos);
|
||||
tx2BytesWithHeader = bos.toByteArray();
|
||||
tx2Bytes = tx2.bitcoinSerialize();
|
||||
|
||||
bos.reset();
|
||||
bs.serialize(b1, bos);
|
||||
b1BytesWithHeader = bos.toByteArray();
|
||||
b1Bytes = b1.bitcoinSerialize();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void validateSetup() {
|
||||
byte[] b1 = new byte[] {1, 1, 1, 2, 3, 4, 5, 6, 7};
|
||||
byte[] b2 = new byte[] {1, 2, 3};
|
||||
assertTrue(arrayContains(b1, b2));
|
||||
assertTrue(arrayContains(txMessage, txMessagePart));
|
||||
assertTrue(arrayContains(tx1BytesWithHeader, tx1Bytes));
|
||||
assertTrue(arrayContains(tx2BytesWithHeader, tx2Bytes));
|
||||
assertTrue(arrayContains(b1BytesWithHeader, b1Bytes));
|
||||
assertTrue(arrayContains(b1BytesWithHeader, tx1Bytes));
|
||||
assertTrue(arrayContains(b1BytesWithHeader, tx2Bytes));
|
||||
assertFalse(arrayContains(tx1BytesWithHeader, b1Bytes));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testTransactionsLazyRetain() throws Exception {
|
||||
testTransaction(NetworkParameters.prodNet(), txMessage, false, true, true);
|
||||
testTransaction(unitTestParams, tx1BytesWithHeader, false, true, true);
|
||||
testTransaction(unitTestParams, tx2BytesWithHeader, false, true, true);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testTransactionsLazyNoRetain() throws Exception {
|
||||
testTransaction(NetworkParameters.prodNet(), txMessage, false, true, false);
|
||||
testTransaction(unitTestParams, tx1BytesWithHeader, false, true, false);
|
||||
testTransaction(unitTestParams, tx2BytesWithHeader, false, true, false);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testTransactionsNoLazyNoRetain() throws Exception {
|
||||
testTransaction(NetworkParameters.prodNet(), txMessage, false, false, false);
|
||||
testTransaction(unitTestParams, tx1BytesWithHeader, false, false, false);
|
||||
testTransaction(unitTestParams, tx2BytesWithHeader, false, false, false);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testTransactionsNoLazyRetain() throws Exception {
|
||||
testTransaction(NetworkParameters.prodNet(), txMessage, false, false, true);
|
||||
testTransaction(unitTestParams, tx1BytesWithHeader, false, false, true);
|
||||
testTransaction(unitTestParams, tx2BytesWithHeader, false, false, true);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testBlockAll() throws Exception {
|
||||
testBlock(b1BytesWithHeader, false, false, false);
|
||||
testBlock(b1BytesWithHeader, false, true, true);
|
||||
testBlock(b1BytesWithHeader, false, true, false);
|
||||
testBlock(b1BytesWithHeader, false, false, true);
|
||||
}
|
||||
|
||||
|
||||
public void testBlock(byte[] blockBytes, boolean isChild, boolean lazy, boolean retain) throws Exception {
|
||||
//reference serializer to produce comparison serialization output after changes to
|
||||
//message structure.
|
||||
BitcoinSerializer bsRef = new BitcoinSerializer(unitTestParams, true, false, false, null);
|
||||
ByteArrayOutputStream bos = new ByteArrayOutputStream();
|
||||
|
||||
BitcoinSerializer bs = new BitcoinSerializer(unitTestParams, true, lazy, retain, null);
|
||||
Block b1;
|
||||
Block bRef;
|
||||
if (lazy && !retain)
|
||||
System.out.print("");
|
||||
b1 = (Block) bs.deserialize(new ByteArrayInputStream(blockBytes));
|
||||
bRef = (Block) bsRef.deserialize(new ByteArrayInputStream(blockBytes));
|
||||
|
||||
//verify our reference BitcoinSerializer produces matching byte array.
|
||||
bos.reset();
|
||||
bsRef.serialize(bRef, bos);
|
||||
assertTrue(Arrays.equals(bos.toByteArray(), blockBytes));
|
||||
|
||||
//check lazy and retain status survive both before and after a serialization
|
||||
assertEquals(!lazy, b1.isParsedTransactions());
|
||||
assertEquals(!lazy, b1.isParsedHeader());
|
||||
if (b1.isParsedHeader())
|
||||
assertEquals(retain, b1.isHeaderBytesValid());
|
||||
if (b1.isParsedTransactions())
|
||||
assertEquals(retain, b1.isTransactionBytesValid());
|
||||
|
||||
if (lazy && !retain)
|
||||
System.out.print("");
|
||||
serDeser(bs, b1, blockBytes, null, null);
|
||||
|
||||
assertEquals(!lazy, b1.isParsedTransactions());
|
||||
if (lazy == b1.isParsedHeader())
|
||||
System.out.print("");
|
||||
assertEquals(!lazy, b1.isParsedHeader());
|
||||
if (b1.isParsedHeader())
|
||||
assertEquals(retain, b1.isHeaderBytesValid());
|
||||
if (b1.isParsedTransactions())
|
||||
assertEquals(retain, b1.isTransactionBytesValid());
|
||||
|
||||
//compare to ref block
|
||||
bos.reset();
|
||||
bsRef.serialize(bRef, bos);
|
||||
serDeser(bs, b1, bos.toByteArray(), null, null);
|
||||
|
||||
//retrieve a value from a child
|
||||
if (lazy && retain)
|
||||
System.out.print("");
|
||||
b1.getTransactions();
|
||||
assertTrue(b1.isParsedTransactions());
|
||||
if (b1.getTransactions().size() > 0) {
|
||||
assertTrue(b1.isParsedTransactions());
|
||||
Transaction tx1 = b1.getTransactions().get(0);
|
||||
if (lazy == tx1.isParsed())
|
||||
System.out.print("");
|
||||
//this will always be true for all children of a block once they are retrieved.
|
||||
//the tx child inputs/outputs may not be parsed however.
|
||||
assertEquals(true, tx1.isParsed());
|
||||
assertEquals(retain, tx1.isCached());
|
||||
|
||||
//does it still match ref block?
|
||||
serDeser(bs, b1, bos.toByteArray(), null, null);
|
||||
}
|
||||
|
||||
//refresh block
|
||||
b1 = (Block) bs.deserialize(new ByteArrayInputStream(blockBytes));
|
||||
bRef = (Block) bsRef.deserialize(new ByteArrayInputStream(blockBytes));
|
||||
|
||||
//retrieve a value from header
|
||||
if (lazy && !retain)
|
||||
System.out.print("");
|
||||
b1.getDifficultyTarget();
|
||||
assertTrue(b1.isParsedHeader());
|
||||
assertEquals(lazy, !b1.isParsedTransactions());
|
||||
|
||||
//does it still match ref block?
|
||||
serDeser(bs, b1, bos.toByteArray(), null, null);
|
||||
|
||||
|
||||
//refresh block
|
||||
b1 = (Block) bs.deserialize(new ByteArrayInputStream(blockBytes));
|
||||
bRef = (Block) bsRef.deserialize(new ByteArrayInputStream(blockBytes));
|
||||
|
||||
//retrieve a value from a child and header
|
||||
b1.getDifficultyTarget();
|
||||
assertTrue(b1.isParsedHeader());
|
||||
assertEquals(lazy, !b1.isParsedTransactions());
|
||||
|
||||
b1.getTransactions();
|
||||
assertTrue(b1.isParsedTransactions());
|
||||
if (b1.getTransactions().size() > 0) {
|
||||
assertTrue(b1.isParsedTransactions());
|
||||
Transaction tx1 = b1.getTransactions().get(0);
|
||||
assertEquals(true, tx1.isParsed());
|
||||
assertEquals(retain, tx1.isCached());
|
||||
}
|
||||
//does it still match ref block?
|
||||
serDeser(bs, b1, bos.toByteArray(), null, null);
|
||||
|
||||
//refresh block
|
||||
b1 = (Block) bs.deserialize(new ByteArrayInputStream(blockBytes));
|
||||
bRef = (Block) bsRef.deserialize(new ByteArrayInputStream(blockBytes));
|
||||
|
||||
//change a value in header
|
||||
if (lazy && !retain)
|
||||
System.out.println("");
|
||||
b1.setNonce(23);
|
||||
bRef.setNonce(23);
|
||||
assertTrue(b1.isParsedHeader());
|
||||
assertEquals(lazy, !b1.isParsedTransactions());
|
||||
assertFalse(b1.isHeaderBytesValid());
|
||||
if (b1.isParsedTransactions())
|
||||
assertEquals(retain , b1.isTransactionBytesValid());
|
||||
else
|
||||
assertEquals(true, b1.isTransactionBytesValid());
|
||||
//does it still match ref block?
|
||||
bos.reset();
|
||||
bsRef.serialize(bRef, bos);
|
||||
serDeser(bs, b1, bos.toByteArray(), null, null);
|
||||
|
||||
//refresh block
|
||||
b1 = (Block) bs.deserialize(new ByteArrayInputStream(blockBytes));
|
||||
bRef = (Block) bsRef.deserialize(new ByteArrayInputStream(blockBytes));
|
||||
|
||||
//retrieve a value from a child of a child
|
||||
b1.getTransactions();
|
||||
if (b1.getTransactions().size() > 0) {
|
||||
Transaction tx1 = b1.getTransactions().get(0);
|
||||
|
||||
TransactionInput tin = tx1.getInputs().get(0);
|
||||
|
||||
assertTrue(tx1.isParsed());
|
||||
assertTrue(b1.isParsedTransactions());
|
||||
assertEquals(!lazy, b1.isParsedHeader());
|
||||
|
||||
assertEquals(!lazy, tin.isParsed());
|
||||
assertEquals(tin.isParsed() ? retain : true, tin.isCached());
|
||||
|
||||
//does it still match ref tx?
|
||||
bos.reset();
|
||||
bsRef.serialize(bRef, bos);
|
||||
serDeser(bs, b1, bos.toByteArray(), null, null);
|
||||
}
|
||||
|
||||
//refresh block
|
||||
b1 = (Block) bs.deserialize(new ByteArrayInputStream(blockBytes));
|
||||
bRef = (Block) bsRef.deserialize(new ByteArrayInputStream(blockBytes));
|
||||
|
||||
//add an input
|
||||
b1.getTransactions();
|
||||
if (b1.getTransactions().size() > 0) {
|
||||
Transaction tx1 = b1.getTransactions().get(0);
|
||||
|
||||
if (tx1.getInputs().size() > 0) {
|
||||
tx1.addInput(tx1.getInputs().get(0));
|
||||
//replicate on reference tx
|
||||
bRef.getTransactions().get(0).addInput(bRef.getTransactions().get(0).getInputs().get(0));
|
||||
|
||||
assertFalse(tx1.isCached());
|
||||
assertTrue(tx1.isParsed());
|
||||
assertFalse(b1.isTransactionBytesValid());
|
||||
assertTrue(b1.isParsedHeader());
|
||||
|
||||
//confirm sibling cache status was unaffected
|
||||
if (tx1.getInputs().size() > 1) {
|
||||
boolean parsed = tx1.getInputs().get(1).isParsed();
|
||||
assertEquals(parsed ? retain : true, tx1.getInputs().get(1).isCached());
|
||||
assertEquals(!lazy, parsed);
|
||||
}
|
||||
|
||||
//this has to be false. Altering a tx invalidates the merkle root.
|
||||
//when we have seperate merkle caching then the entire won't need to be
|
||||
//invalidated.
|
||||
assertFalse(b1.isHeaderBytesValid());
|
||||
|
||||
bos.reset();
|
||||
bsRef.serialize(bRef, bos);
|
||||
byte[] source = bos.toByteArray();
|
||||
//confirm we still match the reference tx.
|
||||
serDeser(bs, b1, source, null, null);
|
||||
}
|
||||
|
||||
//does it still match ref tx?
|
||||
bos.reset();
|
||||
bsRef.serialize(bRef, bos);
|
||||
serDeser(bs, b1, bos.toByteArray(), null, null);
|
||||
}
|
||||
|
||||
//refresh block
|
||||
b1 = (Block) bs.deserialize(new ByteArrayInputStream(blockBytes));
|
||||
Block b2 = (Block) bs.deserialize(new ByteArrayInputStream(blockBytes));
|
||||
bRef = (Block) bsRef.deserialize(new ByteArrayInputStream(blockBytes));
|
||||
Block bRef2 = (Block) bsRef.deserialize(new ByteArrayInputStream(blockBytes));
|
||||
|
||||
//reparent an input
|
||||
b1.getTransactions();
|
||||
if (b1.getTransactions().size() > 0) {
|
||||
Transaction tx1 = b1.getTransactions().get(0);
|
||||
Transaction tx2 = b2.getTransactions().get(0);
|
||||
|
||||
if (tx1.getInputs().size() > 0) {
|
||||
if (lazy && retain)
|
||||
System.out.print("");
|
||||
TransactionInput fromTx1 = tx1.getInputs().get(0);
|
||||
tx2.addInput(fromTx1);
|
||||
|
||||
//replicate on reference tx
|
||||
TransactionInput fromTxRef = bRef.getTransactions().get(0).getInputs().get(0);
|
||||
bRef2.getTransactions().get(0).addInput(fromTxRef);
|
||||
|
||||
//b1 hasn't changed but it's no longer in the parent
|
||||
//chain of fromTx1 so has to have been uncached since it won't be
|
||||
//notified of changes throught the parent chain anymore.
|
||||
if (b1.isTransactionBytesValid())
|
||||
System.out.print("");
|
||||
assertFalse(b1.isTransactionBytesValid());
|
||||
|
||||
//b2 should have it's cache invalidated because it has changed.
|
||||
assertFalse(b2.isTransactionBytesValid());
|
||||
|
||||
bos.reset();
|
||||
bsRef.serialize(bRef2, bos);
|
||||
byte[] source = bos.toByteArray();
|
||||
//confirm altered block matches altered ref block.
|
||||
serDeser(bs, b2, source, null, null);
|
||||
}
|
||||
|
||||
//does unaltered block still match ref block?
|
||||
bos.reset();
|
||||
bsRef.serialize(bRef, bos);
|
||||
serDeser(bs, b1, bos.toByteArray(), null, null);
|
||||
|
||||
//how about if we refresh it?
|
||||
bRef = (Block) bsRef.deserialize(new ByteArrayInputStream(blockBytes));
|
||||
bos.reset();
|
||||
bsRef.serialize(bRef, bos);
|
||||
serDeser(bs, b1, bos.toByteArray(), null, null);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public void testTransaction(NetworkParameters params, byte[] txBytes, boolean isChild, boolean lazy, boolean retain) throws Exception {
|
||||
|
||||
//reference serializer to produce comparison serialization output after changes to
|
||||
//message structure.
|
||||
BitcoinSerializer bsRef = new BitcoinSerializer(params, true, false, false, null);
|
||||
ByteArrayOutputStream bos = new ByteArrayOutputStream();
|
||||
|
||||
BitcoinSerializer bs = new BitcoinSerializer(params, true, lazy, retain, null);
|
||||
Transaction t1;
|
||||
Transaction tRef;
|
||||
t1 = (Transaction) bs.deserialize(new ByteArrayInputStream(txBytes));
|
||||
tRef = (Transaction) bsRef.deserialize(new ByteArrayInputStream(txBytes));
|
||||
|
||||
//verify our reference BitcoinSerializer produces matching byte array.
|
||||
bos.reset();
|
||||
bsRef.serialize(tRef, bos);
|
||||
assertTrue(Arrays.equals(bos.toByteArray(), txBytes));
|
||||
|
||||
//check lazy and retain status survive both before and after a serialization
|
||||
assertEquals(!lazy, t1.isParsed());
|
||||
if (retain != t1.isCached())
|
||||
System.out.print("");
|
||||
if (t1.isParsed())
|
||||
assertEquals(retain, t1.isCached());
|
||||
|
||||
serDeser(bs, t1, txBytes, null, null);
|
||||
|
||||
assertEquals(lazy, !t1.isParsed());
|
||||
if (t1.isParsed())
|
||||
assertEquals(retain, t1.isCached());
|
||||
|
||||
//compare to ref tx
|
||||
bos.reset();
|
||||
bsRef.serialize(tRef, bos);
|
||||
serDeser(bs, t1, bos.toByteArray(), null, null);
|
||||
|
||||
//retrieve a value from a child
|
||||
t1.getInputs();
|
||||
assertTrue(t1.isParsed());
|
||||
if (t1.getInputs().size() > 0) {
|
||||
assertTrue(t1.isParsed());
|
||||
TransactionInput tin = t1.getInputs().get(0);
|
||||
assertEquals(!lazy, tin.isParsed());
|
||||
if (tin.isParsed())
|
||||
assertEquals(retain, tin.isCached());
|
||||
|
||||
//does it still match ref tx?
|
||||
serDeser(bs, t1, bos.toByteArray(), null, null);
|
||||
}
|
||||
|
||||
//refresh tx
|
||||
t1 = (Transaction) bs.deserialize(new ByteArrayInputStream(txBytes));
|
||||
tRef = (Transaction) bsRef.deserialize(new ByteArrayInputStream(txBytes));
|
||||
|
||||
//add an input
|
||||
if (t1.getInputs().size() > 0) {
|
||||
if (lazy && !retain)
|
||||
System.out.print("");
|
||||
|
||||
t1.addInput(t1.getInputs().get(0));
|
||||
|
||||
//replicate on reference tx
|
||||
tRef.addInput(tRef.getInputs().get(0));
|
||||
|
||||
if (t1.isCached())
|
||||
System.out.print("BUGGER!");
|
||||
assertFalse(t1.isCached());
|
||||
assertTrue(t1.isParsed());
|
||||
|
||||
bos.reset();
|
||||
bsRef.serialize(tRef, bos);
|
||||
byte[] source = bos.toByteArray();
|
||||
//confirm we still match the reference tx.
|
||||
serDeser(bs, t1, source, null, null);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private void serDeser(BitcoinSerializer bs, Message message, byte[] sourceBytes, byte[] containedBytes, byte[] containingBytes) throws Exception {
|
||||
ByteArrayOutputStream bos = new ByteArrayOutputStream();
|
||||
bs.serialize(message, bos);
|
||||
byte[] b1 = bos.toByteArray();
|
||||
|
||||
Message m2 = bs.deserialize(new ByteArrayInputStream(b1));
|
||||
|
||||
if (!message.equals(m2)) {
|
||||
System.out.print("");
|
||||
}
|
||||
assertEquals(message, m2);
|
||||
|
||||
bos.reset();
|
||||
bs.serialize(m2, bos);
|
||||
byte[] b2 = bos.toByteArray();
|
||||
assertTrue(Arrays.equals(b1, b2));
|
||||
|
||||
if (sourceBytes != null) {
|
||||
if (!arrayContains(sourceBytes, b1)) {
|
||||
System.out.print("");
|
||||
}
|
||||
assertTrue(arrayContains(sourceBytes, b1));
|
||||
|
||||
assertTrue(arrayContains(sourceBytes, b2));
|
||||
}
|
||||
|
||||
if (containedBytes != null) {
|
||||
assertTrue(arrayContains(b1, containedBytes));
|
||||
}
|
||||
if (containingBytes != null) {
|
||||
assertTrue(arrayContains(containingBytes, b1));
|
||||
}
|
||||
}
|
||||
|
||||
public static boolean arrayContains(byte[] sup, byte[] sub) {
|
||||
if (sup.length < sub.length)
|
||||
return false;
|
||||
|
||||
String superstring = Utils.bytesToHexString(sup);
|
||||
String substring = Utils.bytesToHexString(sub);
|
||||
|
||||
int ind = superstring.indexOf(substring);
|
||||
|
||||
StringBuilder sb = new StringBuilder();
|
||||
int len = superstring.length() - substring.length();
|
||||
if (ind > -1)
|
||||
len = ind;
|
||||
for (int i = 0; i < superstring.indexOf(substring); i++)
|
||||
sb.append(" ");
|
||||
|
||||
System.out.println(superstring);
|
||||
System.out.println(sb.append(substring).toString());
|
||||
System.out.println();
|
||||
return ind > -1;
|
||||
|
||||
// if (sup.length < sub.length)
|
||||
// return false;
|
||||
// for (int i = 0; i < sup.length - sub.length; i++) {
|
||||
// boolean bad = false;
|
||||
// for (int j = 0; j < sub.length && !bad; j++) {
|
||||
// if (sup[i + j] == sub[j]) {
|
||||
// if (j == sub.length - 1)
|
||||
// return true;
|
||||
// } else {
|
||||
// bad = true;
|
||||
// }
|
||||
//
|
||||
// }
|
||||
// }
|
||||
// return false;
|
||||
}
|
||||
}
|
14
tests/com/google/bitcoin/core/Manipulator.java
Normal file
14
tests/com/google/bitcoin/core/Manipulator.java
Normal file
@ -0,0 +1,14 @@
|
||||
package com.google.bitcoin.core;
|
||||
|
||||
public abstract class Manipulator<M extends Message> {
|
||||
|
||||
public abstract void manipulate(BitcoinSerializer bs, M message) throws Exception;
|
||||
|
||||
public abstract void manipulate(BitcoinSerializer bs, byte[] bytes) throws Exception;
|
||||
|
||||
public abstract String getDescription();
|
||||
|
||||
public void beforeTest() {}
|
||||
|
||||
public void afterTest() {}
|
||||
}
|
799
tests/com/google/bitcoin/core/SpeedTest.java
Normal file
799
tests/com/google/bitcoin/core/SpeedTest.java
Normal file
@ -0,0 +1,799 @@
|
||||
package com.google.bitcoin.core;
|
||||
|
||||
import static com.google.bitcoin.core.TestUtils.createFakeBlock;
|
||||
import static com.google.bitcoin.core.TestUtils.createFakeTx;
|
||||
|
||||
import java.io.BufferedInputStream;
|
||||
import java.io.BufferedReader;
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStreamReader;
|
||||
import java.net.InetAddress;
|
||||
import java.text.NumberFormat;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
import org.bouncycastle.util.encoders.Hex;
|
||||
|
||||
import com.google.bitcoin.core.Address;
|
||||
import com.google.bitcoin.core.BitcoinSerializer;
|
||||
import com.google.bitcoin.core.Block;
|
||||
import com.google.bitcoin.core.BlockChain;
|
||||
import com.google.bitcoin.core.ECKey;
|
||||
import com.google.bitcoin.core.NetworkParameters;
|
||||
import com.google.bitcoin.core.Transaction;
|
||||
import com.google.bitcoin.core.TransactionOutput;
|
||||
import com.google.bitcoin.core.Utils;
|
||||
import com.google.bitcoin.core.Wallet;
|
||||
import com.google.bitcoin.store.BlockStore;
|
||||
import com.google.bitcoin.store.MemoryBlockStore;
|
||||
|
||||
public class SpeedTest {
|
||||
private final byte[] addrMessage = Hex.decode("fabfb5da6164647200000000000000001f000000"
|
||||
+ "ed52399b01e215104d010000000000000000000000000000000000ffff0a000001208d");
|
||||
|
||||
private final byte[] txMessage = Hex.decode("F9 BE B4 D9 74 78 00 00 00 00 00 00 00 00 00 00"
|
||||
+ "02 01 00 00 E2 93 CD BE 01 00 00 00 01 6D BD DB" + "08 5B 1D 8A F7 51 84 F0 BC 01 FA D5 8D 12 66 E9"
|
||||
+ "B6 3B 50 88 19 90 E4 B4 0D 6A EE 36 29 00 00 00" + "00 8B 48 30 45 02 21 00 F3 58 1E 19 72 AE 8A C7"
|
||||
+ "C7 36 7A 7A 25 3B C1 13 52 23 AD B9 A4 68 BB 3A" + "59 23 3F 45 BC 57 83 80 02 20 59 AF 01 CA 17 D0"
|
||||
+ "0E 41 83 7A 1D 58 E9 7A A3 1B AE 58 4E DE C2 8D" + "35 BD 96 92 36 90 91 3B AE 9A 01 41 04 9C 02 BF"
|
||||
+ "C9 7E F2 36 CE 6D 8F E5 D9 40 13 C7 21 E9 15 98" + "2A CD 2B 12 B6 5D 9B 7D 59 E2 0A 84 20 05 F8 FC"
|
||||
+ "4E 02 53 2E 87 3D 37 B9 6F 09 D6 D4 51 1A DA 8F" + "14 04 2F 46 61 4A 4C 70 C0 F1 4B EF F5 FF FF FF"
|
||||
+ "FF 02 40 4B 4C 00 00 00 00 00 19 76 A9 14 1A A0" + "CD 1C BE A6 E7 45 8A 7A BA D5 12 A9 D9 EA 1A FB"
|
||||
+ "22 5E 88 AC 80 FA E9 C7 00 00 00 00 19 76 A9 14" + "0E AB 5B EA 43 6A 04 84 CF AB 12 48 5E FD A0 B7"
|
||||
+ "8B 4E CC 52 88 AC 00 00 00 00");
|
||||
|
||||
private final byte[] txMessagePart = Hex.decode("08 5B 1D 8A F7 51 84 F0 BC 01 FA D5 8D 12 66 E9"
|
||||
+ "B6 3B 50 88 19 90 E4 B4 0D 6A EE 36 29 00 00 00" + "00 8B 48 30 45 02 21 00 F3 58 1E 19 72 AE 8A C7"
|
||||
+ "C7 36 7A 7A 25 3B C1 13 52 23 AD B9 A4 68 BB 3A");
|
||||
|
||||
private static final NetworkParameters testNet = NetworkParameters.testNet();
|
||||
private BlockChain testNetChain;
|
||||
|
||||
private Wallet wallet;
|
||||
private BlockChain chain;
|
||||
private BlockStore blockStore;
|
||||
private Address coinbaseTo;
|
||||
private NetworkParameters unitTestParams;
|
||||
|
||||
AddressMessage addr1;
|
||||
byte[] addr1BytesWithHeader;
|
||||
|
||||
Block b1;
|
||||
private byte[] b1Bytes;
|
||||
private byte[] b1BytesWithHeader;
|
||||
|
||||
Transaction tx1;
|
||||
private byte[] tx1Bytes;
|
||||
private byte[] tx1BytesWithHeader;
|
||||
|
||||
private byte[] tx2Bytes;
|
||||
private byte[] tx2BytesWithHeader;
|
||||
|
||||
List<SerializerEntry> bss;
|
||||
List<Manipulator<Transaction>> txMans = new ArrayList();
|
||||
List<Manipulator<Block>> blockMans = new ArrayList();
|
||||
List<Manipulator<AddressMessage>> addrMans = new ArrayList();
|
||||
|
||||
public static void main(String[] args) throws Exception {
|
||||
SpeedTest test = new SpeedTest();
|
||||
test.setUp();
|
||||
test.start(10000, 10000, false);
|
||||
}
|
||||
|
||||
public void start(int warmupIterations, int iterations, boolean pauseForKeyPress) {
|
||||
|
||||
if (pauseForKeyPress) {
|
||||
System.out.println("Attach profiler or whatever and press enter to start test");
|
||||
InputStreamReader r = new InputStreamReader(System.in);
|
||||
BufferedReader reader = new BufferedReader(r);
|
||||
try {
|
||||
reader.readLine();
|
||||
} catch (IOException e) {
|
||||
// TODO Auto-generated catch block
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
// allocate some arrays to prefill memory and hopefully push up the head
|
||||
// size.
|
||||
System.out
|
||||
.println("Filling memory to 80% of maximum mb: " + (Runtime.getRuntime().maxMemory() / (1024 * 1024)));
|
||||
List junk = new ArrayList();
|
||||
while (Runtime.getRuntime().totalMemory() - Runtime.getRuntime().freeMemory() < Runtime.getRuntime()
|
||||
.maxMemory() * 0.8) {
|
||||
junk.add(new byte[10000]);
|
||||
if (junk.size() % 10000 == 0)
|
||||
System.out.println("totalMemory: "
|
||||
+ ((Runtime.getRuntime().totalMemory() - Runtime.getRuntime().freeMemory()) / (1024 * 1024))
|
||||
+ "mb");
|
||||
}
|
||||
|
||||
junk = null;
|
||||
|
||||
System.out.println("******************************");
|
||||
System.out.println("*** WARMUP PHASE ***");
|
||||
System.out.println("******************************");
|
||||
for (Manipulator<AddressMessage> man : addrMans) {
|
||||
testManipulator(man, "warmup", warmupIterations, bss, addr1, addr1BytesWithHeader);
|
||||
}
|
||||
for (Manipulator<Transaction> man : txMans) {
|
||||
testManipulator(man, "warmup", warmupIterations, bss, tx1, tx1BytesWithHeader);
|
||||
}
|
||||
for (Manipulator<Block> man : blockMans) {
|
||||
testManipulator(man, "warmup", warmupIterations, bss, b1, b1BytesWithHeader);
|
||||
}
|
||||
|
||||
System.out.println("******************************");
|
||||
System.out.println("*** TEST PHASE ***");
|
||||
System.out.println("******************************");
|
||||
for (Manipulator<AddressMessage> man : addrMans) {
|
||||
testManipulator(man, "main test", iterations, bss, addr1, addr1BytesWithHeader);
|
||||
}
|
||||
for (Manipulator<Transaction> man : txMans) {
|
||||
testManipulator(man, "main test", iterations, bss, tx1, tx1BytesWithHeader);
|
||||
}
|
||||
for (Manipulator<Block> man : blockMans) {
|
||||
testManipulator(man, "main test", iterations, bss, b1, b1BytesWithHeader);
|
||||
}
|
||||
}
|
||||
|
||||
private void resetBlockStore() {
|
||||
blockStore = new MemoryBlockStore(unitTestParams);
|
||||
}
|
||||
|
||||
public void setUp() throws Exception {
|
||||
testNetChain = new BlockChain(testNet, new Wallet(testNet), new MemoryBlockStore(testNet));
|
||||
unitTestParams = NetworkParameters.unitTests();
|
||||
wallet = new Wallet(unitTestParams);
|
||||
wallet.addKey(new ECKey());
|
||||
|
||||
resetBlockStore();
|
||||
chain = new BlockChain(unitTestParams, wallet, blockStore);
|
||||
|
||||
coinbaseTo = wallet.keychain.get(0).toAddress(unitTestParams);
|
||||
|
||||
tx1 = createFakeTx(unitTestParams, Utils.toNanoCoins(2, 0), wallet.keychain.get(0).toAddress(unitTestParams));
|
||||
|
||||
// add a second input so can test granularity of byte cache.
|
||||
Transaction prevTx = new Transaction(unitTestParams);
|
||||
TransactionOutput prevOut = new TransactionOutput(unitTestParams, prevTx, Utils.toNanoCoins(1, 0),
|
||||
wallet.keychain.get(0).toAddress(unitTestParams));
|
||||
prevTx.addOutput(prevOut);
|
||||
// Connect it.
|
||||
tx1.addInput(prevOut);
|
||||
|
||||
Transaction tx2 = createFakeTx(unitTestParams, Utils.toNanoCoins(1, 0), new ECKey().toAddress(unitTestParams));
|
||||
|
||||
b1 = createFakeBlock(unitTestParams, blockStore, tx1, tx2).block;
|
||||
|
||||
BitcoinSerializer bs = new BitcoinSerializer(unitTestParams, true, null);
|
||||
|
||||
ByteArrayOutputStream bos = new ByteArrayOutputStream();
|
||||
addr1 = (AddressMessage) bs.deserialize(new ByteArrayInputStream(addrMessage));
|
||||
bs.serialize(addr1, bos);
|
||||
addr1BytesWithHeader = bos.toByteArray();
|
||||
|
||||
bos.reset();
|
||||
bs.serialize(tx1, bos);
|
||||
tx1BytesWithHeader = bos.toByteArray();
|
||||
tx1Bytes = tx1.bitcoinSerialize();
|
||||
|
||||
bos.reset();
|
||||
bs.serialize(tx2, bos);
|
||||
tx2BytesWithHeader = bos.toByteArray();
|
||||
tx2Bytes = tx2.bitcoinSerialize();
|
||||
|
||||
bos.reset();
|
||||
bs.serialize(b1, bos);
|
||||
b1BytesWithHeader = bos.toByteArray();
|
||||
b1Bytes = b1.bitcoinSerialize();
|
||||
|
||||
bss = new ArrayList();
|
||||
bss.add(new SerializerEntry(bs, "Standard (Non-lazy, No cached)"));
|
||||
// add 2 because when profiling the first seems to take a lot longer
|
||||
// than usual.
|
||||
// bss.add(new SerializerEntry(bs, "Standard (Non-lazy, No cached)"));
|
||||
|
||||
bss.add(new SerializerEntry(new BitcoinSerializer(unitTestParams, true, true, true, null), "Lazy, Cached"));
|
||||
bss.add(new SerializerEntry(new BitcoinSerializer(unitTestParams, true, true, false, null), "Lazy, No cache"));
|
||||
bss.add(new SerializerEntry(new BitcoinSerializer(unitTestParams, true, true, true, null), "Non-Lazy, Cached"));
|
||||
buildManipulators();
|
||||
}
|
||||
|
||||
private void buildManipulators() {
|
||||
|
||||
Manipulator seralizeAddr = new Manipulator<AddressMessage>() {
|
||||
|
||||
ByteArrayOutputStream bos = new ByteArrayOutputStream();
|
||||
|
||||
@Override
|
||||
public void manipulate(BitcoinSerializer bs, AddressMessage message) throws Exception {
|
||||
bos.reset();
|
||||
bs.serialize(message, bos);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void manipulate(BitcoinSerializer bs, byte[] bytes) throws Exception {
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDescription() {
|
||||
return "Serialize Address";
|
||||
}
|
||||
|
||||
};
|
||||
addrMans.add(seralizeAddr);
|
||||
|
||||
Manipulator deseralizeAddr = new Manipulator<AddressMessage>() {
|
||||
|
||||
ByteArrayOutputStream bos = new ByteArrayOutputStream();
|
||||
|
||||
@Override
|
||||
public void manipulate(BitcoinSerializer bs, AddressMessage message) throws Exception {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void manipulate(BitcoinSerializer bs, byte[] bytes) throws Exception {
|
||||
AddressMessage addr = (AddressMessage) bs.deserialize(new ByteArrayInputStream(bytes));
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDescription() {
|
||||
return "Deserialize Address";
|
||||
}
|
||||
|
||||
};
|
||||
addrMans.add(deseralizeAddr);
|
||||
|
||||
Manipulator seralizeAddr_1 = new Manipulator<AddressMessage>() {
|
||||
|
||||
AddressMessage addr;
|
||||
ByteArrayOutputStream bos = new ByteArrayOutputStream();
|
||||
|
||||
@Override
|
||||
public void manipulate(BitcoinSerializer bs, AddressMessage message) throws Exception {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void manipulate(BitcoinSerializer bs, byte[] bytes) throws Exception {
|
||||
if (addr == null) {
|
||||
addr = (AddressMessage) bs.deserialize(new ByteArrayInputStream(bytes));
|
||||
}
|
||||
bos.reset();
|
||||
bs.serialize(addr, bos);
|
||||
}
|
||||
|
||||
public void beforeTest() {
|
||||
addr = null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDescription() {
|
||||
return "Serialize cached Address";
|
||||
}
|
||||
|
||||
};
|
||||
addrMans.add(seralizeAddr_1);
|
||||
|
||||
Manipulator deserSerAddr1 = new Manipulator<AddressMessage>() {
|
||||
|
||||
ByteArrayOutputStream bos = new ByteArrayOutputStream();
|
||||
|
||||
@Override
|
||||
public void manipulate(BitcoinSerializer bs, AddressMessage message) throws Exception {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void manipulate(BitcoinSerializer bs, byte[] bytes) throws Exception {
|
||||
AddressMessage addr = (AddressMessage) bs.deserialize(new ByteArrayInputStream(bytes));
|
||||
addr.getAddresses().get(0).getAddr();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDescription() {
|
||||
return "Deserialize Address, read field";
|
||||
}
|
||||
|
||||
};
|
||||
addrMans.add(deserSerAddr1);
|
||||
|
||||
Manipulator deserSerAddr2 = new Manipulator<AddressMessage>() {
|
||||
|
||||
ByteArrayOutputStream bos = new ByteArrayOutputStream();
|
||||
PeerAddress peer;
|
||||
|
||||
@Override
|
||||
public void manipulate(BitcoinSerializer bs, AddressMessage message) throws Exception {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void manipulate(BitcoinSerializer bs, byte[] bytes) throws Exception {
|
||||
if (peer == null) {
|
||||
peer = new PeerAddress(InetAddress.getLocalHost(), 8332);
|
||||
}
|
||||
AddressMessage addr = (AddressMessage) bs.deserialize(new ByteArrayInputStream(bytes));
|
||||
addr.addAddress(peer);
|
||||
bos.reset();
|
||||
bs.serialize(addr, bos);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDescription() {
|
||||
return "Deserialize Address, Add Address, Serialize";
|
||||
}
|
||||
|
||||
};
|
||||
addrMans.add(deserSerAddr2);
|
||||
|
||||
Manipulator seralizeTx = new Manipulator<Transaction>() {
|
||||
|
||||
ByteArrayOutputStream bos = new ByteArrayOutputStream();
|
||||
|
||||
@Override
|
||||
public void manipulate(BitcoinSerializer bs, Transaction message) throws Exception {
|
||||
bos.reset();
|
||||
bs.serialize(message, bos);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void manipulate(BitcoinSerializer bs, byte[] bytes) throws Exception {
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDescription() {
|
||||
return "Serialize Transaction";
|
||||
}
|
||||
|
||||
};
|
||||
txMans.add(seralizeTx);
|
||||
|
||||
Manipulator deSeralizeTx = new Manipulator<Transaction>() {
|
||||
|
||||
@Override
|
||||
public void manipulate(BitcoinSerializer bs, Transaction message) throws Exception {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void manipulate(BitcoinSerializer bs, byte[] bytes) throws Exception {
|
||||
ByteArrayInputStream bis = new ByteArrayInputStream(bytes);
|
||||
Transaction tx = (Transaction) bs.deserialize(bis);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDescription() {
|
||||
return "Deserialize Transaction";
|
||||
}
|
||||
|
||||
};
|
||||
txMans.add(deSeralizeTx);
|
||||
|
||||
Manipulator deSeralizeTx_1 = new Manipulator<Transaction>() {
|
||||
|
||||
Transaction tx;
|
||||
ByteArrayOutputStream bos = new ByteArrayOutputStream();
|
||||
|
||||
@Override
|
||||
public void manipulate(BitcoinSerializer bs, Transaction message) throws Exception {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void manipulate(BitcoinSerializer bs, byte[] bytes) throws Exception {
|
||||
if (tx == null) {
|
||||
ByteArrayInputStream bis = new ByteArrayInputStream(bytes);
|
||||
tx = (Transaction) bs.deserialize(bis);
|
||||
}
|
||||
bos.reset();
|
||||
bs.serialize(tx, bos);
|
||||
}
|
||||
|
||||
public void beforeTest() {
|
||||
tx = null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDescription() {
|
||||
return "Serialize cached Transaction";
|
||||
}
|
||||
|
||||
};
|
||||
txMans.add(deSeralizeTx_1);
|
||||
|
||||
Manipulator seralizeBlock = new Manipulator<Block>() {
|
||||
|
||||
ByteArrayOutputStream bos = new ByteArrayOutputStream();
|
||||
|
||||
@Override
|
||||
public void manipulate(BitcoinSerializer bs, Block message) throws Exception {
|
||||
bos.reset();
|
||||
bs.serialize(message, bos);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void manipulate(BitcoinSerializer bs, byte[] bytes) throws Exception {
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDescription() {
|
||||
return "Serialize Block";
|
||||
}
|
||||
|
||||
};
|
||||
blockMans.add(seralizeBlock);
|
||||
|
||||
Manipulator deSeralizeBlock = new Manipulator<Block>() {
|
||||
|
||||
@Override
|
||||
public void manipulate(BitcoinSerializer bs, Block message) throws Exception {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void manipulate(BitcoinSerializer bs, byte[] bytes) throws Exception {
|
||||
ByteArrayInputStream bis = new ByteArrayInputStream(bytes);
|
||||
Block block = (Block) bs.deserialize(bis);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDescription() {
|
||||
return "Deserialize Block";
|
||||
}
|
||||
|
||||
};
|
||||
blockMans.add(deSeralizeBlock);
|
||||
|
||||
Manipulator deSeralizeBlock_1 = new Manipulator<Block>() {
|
||||
|
||||
Block block;
|
||||
ByteArrayOutputStream bos = new ByteArrayOutputStream();
|
||||
|
||||
@Override
|
||||
public void manipulate(BitcoinSerializer bs, Block message) throws Exception {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void manipulate(BitcoinSerializer bs, byte[] bytes) throws Exception {
|
||||
if (block == null) {
|
||||
ByteArrayInputStream bis = new ByteArrayInputStream(bytes);
|
||||
block = (Block) bs.deserialize(bis);
|
||||
}
|
||||
bos.reset();
|
||||
bs.serialize(block, bos);
|
||||
}
|
||||
|
||||
public void beforeTest() {
|
||||
block = null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDescription() {
|
||||
return "Serialize cached Block";
|
||||
}
|
||||
|
||||
};
|
||||
blockMans.add(deSeralizeBlock_1);
|
||||
|
||||
Manipulator deSerReadReser1 = new Manipulator<Block>() {
|
||||
|
||||
ByteArrayOutputStream bos = new ByteArrayOutputStream();
|
||||
|
||||
@Override
|
||||
public void manipulate(BitcoinSerializer bs, Block message) throws Exception {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void manipulate(BitcoinSerializer bs, byte[] bytes) throws Exception {
|
||||
ByteArrayInputStream bis = new ByteArrayInputStream(bytes);
|
||||
Block block = (Block) bs.deserialize(bis);
|
||||
block.getNonce();
|
||||
bos.reset();
|
||||
bs.serialize(block, bos);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDescription() {
|
||||
return "Deserialize Block, Read nonce header, Serialize";
|
||||
}
|
||||
|
||||
};
|
||||
blockMans.add(deSerReadReser1);
|
||||
|
||||
Manipulator deSerReadReser1_1 = new Manipulator<Block>() {
|
||||
|
||||
ByteArrayOutputStream bos = new ByteArrayOutputStream();
|
||||
|
||||
@Override
|
||||
public void manipulate(BitcoinSerializer bs, Block message) throws Exception {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void manipulate(BitcoinSerializer bs, byte[] bytes) throws Exception {
|
||||
ByteArrayInputStream bis = new ByteArrayInputStream(bytes);
|
||||
Block block = (Block) bs.deserialize(bis);
|
||||
block.getHash();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDescription() {
|
||||
return "Deserialize Block, Calculate hash";
|
||||
}
|
||||
|
||||
};
|
||||
blockMans.add(deSerReadReser1_1);
|
||||
|
||||
Manipulator deSerReadReser1_2 = new Manipulator<Block>() {
|
||||
|
||||
ByteArrayOutputStream bos = new ByteArrayOutputStream();
|
||||
|
||||
@Override
|
||||
public void manipulate(BitcoinSerializer bs, Block message) throws Exception {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void manipulate(BitcoinSerializer bs, byte[] bytes) throws Exception {
|
||||
ByteArrayInputStream bis = new ByteArrayInputStream(bytes);
|
||||
Block block = (Block) bs.deserialize(bis);
|
||||
block.getHash();
|
||||
bos.reset();
|
||||
bs.serialize(block, bos);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDescription() {
|
||||
return "Deserialize Block, Calculate hash, Serialize";
|
||||
}
|
||||
|
||||
};
|
||||
blockMans.add(deSerReadReser1_2);
|
||||
|
||||
Manipulator deSerReadReser2 = new Manipulator<Block>() {
|
||||
|
||||
ByteArrayOutputStream bos = new ByteArrayOutputStream();
|
||||
|
||||
@Override
|
||||
public void manipulate(BitcoinSerializer bs, Block message) throws Exception {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void manipulate(BitcoinSerializer bs, byte[] bytes) throws Exception {
|
||||
ByteArrayInputStream bis = new ByteArrayInputStream(bytes);
|
||||
Block block = (Block) bs.deserialize(bis);
|
||||
block.getTransactions().get(0).getInputs().get(0).getFromAddress();
|
||||
bos.reset();
|
||||
bs.serialize(block, bos);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDescription() {
|
||||
return "Deserialize Block, Read tx input address, Serialize";
|
||||
}
|
||||
|
||||
};
|
||||
blockMans.add(deSerReadReser2);
|
||||
|
||||
Manipulator deSerReadReser2_1 = new Manipulator<Block>() {
|
||||
|
||||
ByteArrayOutputStream bos = new ByteArrayOutputStream();
|
||||
|
||||
@Override
|
||||
public void manipulate(BitcoinSerializer bs, Block message) throws Exception {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void manipulate(BitcoinSerializer bs, byte[] bytes) throws Exception {
|
||||
ByteArrayInputStream bis = new ByteArrayInputStream(bytes);
|
||||
Block block = (Block) bs.deserialize(bis);
|
||||
for (Transaction tx : block.getTransactions()) {
|
||||
tx.getLockTime();
|
||||
tx.getVersion();
|
||||
for (TransactionInput in : tx.getInputs()) {
|
||||
in.getScriptBytes();
|
||||
in.getOutpoint().getIndex();
|
||||
}
|
||||
for (TransactionOutput out : tx.getOutputs()) {
|
||||
out.getScriptBytes();
|
||||
out.getValue();
|
||||
}
|
||||
}
|
||||
|
||||
bos.reset();
|
||||
bs.serialize(block, bos);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDescription() {
|
||||
return "Deserialize Block, read all tx fields, Serialize";
|
||||
}
|
||||
|
||||
};
|
||||
blockMans.add(deSerReadReser2_1);
|
||||
|
||||
Manipulator deSerReadReser3 = new Manipulator<Block>() {
|
||||
|
||||
ByteArrayOutputStream bos = new ByteArrayOutputStream();
|
||||
|
||||
@Override
|
||||
public void manipulate(BitcoinSerializer bs, Block message) throws Exception {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void manipulate(BitcoinSerializer bs, byte[] bytes) throws Exception {
|
||||
ByteArrayInputStream bis = new ByteArrayInputStream(bytes);
|
||||
Block block = (Block) bs.deserialize(bis);
|
||||
block.setNonce(55);
|
||||
bos.reset();
|
||||
bs.serialize(block, bos);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDescription() {
|
||||
return "Deserialize Block, Write nonce, Serialize";
|
||||
}
|
||||
|
||||
};
|
||||
blockMans.add(deSerReadReser3);
|
||||
|
||||
Manipulator deSerReadReser4 = new Manipulator<Block>() {
|
||||
|
||||
ByteArrayOutputStream bos = new ByteArrayOutputStream();
|
||||
Transaction tx;
|
||||
|
||||
@Override
|
||||
public void manipulate(BitcoinSerializer bs, Block message) throws Exception {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void manipulate(BitcoinSerializer bs, byte[] bytes) throws Exception {
|
||||
if (tx == null) {
|
||||
tx = (Transaction) bs.deserialize(new ByteArrayInputStream(tx1BytesWithHeader));
|
||||
tx.ensureParsed();
|
||||
for (TransactionInput input : tx.getInputs()) {
|
||||
input.ensureParsed();
|
||||
}
|
||||
for (TransactionOutput output : tx.getOutputs()) {
|
||||
output.ensureParsed();
|
||||
}
|
||||
}
|
||||
ByteArrayInputStream bis = new ByteArrayInputStream(bytes);
|
||||
Block block = (Block) bs.deserialize(bis);
|
||||
block.addTransaction(tx);
|
||||
bos.reset();
|
||||
bs.serialize(block, bos);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDescription() {
|
||||
return "Deserialize Block, add tx, Serialize";
|
||||
}
|
||||
|
||||
};
|
||||
blockMans.add(deSerReadReser4);
|
||||
|
||||
Manipulator deSerReadReser5 = new Manipulator<Block>() {
|
||||
|
||||
ByteArrayOutputStream bos = new ByteArrayOutputStream();
|
||||
|
||||
@Override
|
||||
public void manipulate(BitcoinSerializer bs, Block message) throws Exception {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void manipulate(BitcoinSerializer bs, byte[] bytes) throws Exception {
|
||||
ByteArrayInputStream bis = new ByteArrayInputStream(bytes);
|
||||
Block block = (Block) bs.deserialize(bis);
|
||||
for (Transaction tx : block.getTransactions()) {
|
||||
tx.getHash();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDescription() {
|
||||
return "Deserialize Block, scan tx hashes";
|
||||
}
|
||||
|
||||
};
|
||||
blockMans.add(deSerReadReser5);
|
||||
|
||||
Manipulator deSerReadReser6 = new Manipulator<Block>() {
|
||||
|
||||
ByteArrayOutputStream bos = new ByteArrayOutputStream();
|
||||
|
||||
@Override
|
||||
public void manipulate(BitcoinSerializer bs, Block message) throws Exception {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void manipulate(BitcoinSerializer bs, byte[] bytes) throws Exception {
|
||||
ByteArrayInputStream bis = new ByteArrayInputStream(bytes);
|
||||
Block block = (Block) bs.deserialize(bis);
|
||||
for (Transaction tx : block.getTransactions()) {
|
||||
tx.getHash();
|
||||
}
|
||||
bos.reset();
|
||||
bs.serialize(block, bos);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDescription() {
|
||||
return "Deserialize Block, scan tx hashes, Serialize";
|
||||
}
|
||||
|
||||
};
|
||||
blockMans.add(deSerReadReser6);
|
||||
|
||||
}
|
||||
|
||||
public <M extends Message> void testManipulator(Manipulator<M> man, String phaseName, int iterations, List<SerializerEntry> bss,
|
||||
M message, byte[] bytes) {
|
||||
long allStart = System.currentTimeMillis();
|
||||
System.out.println("Beginning " + phaseName + " run for manipulator: [" + man.getDescription() + "]");
|
||||
int pause = iterations / 20;
|
||||
pause = pause < 200 ? 200 : pause;
|
||||
pause = pause > 1000 ? 1000 : pause;
|
||||
long bestTime = Long.MAX_VALUE;
|
||||
long worstTime = 0;
|
||||
for (SerializerEntry entry : bss) {
|
||||
System.gc();
|
||||
pause(pause);
|
||||
long start = System.currentTimeMillis();
|
||||
long memStart = Runtime.getRuntime().totalMemory() - Runtime.getRuntime().freeMemory();
|
||||
boolean fail = false;
|
||||
int completed = 0;
|
||||
man.beforeTest();
|
||||
for (int i = 0; i < iterations; i++) {
|
||||
try {
|
||||
man.manipulate(entry.bs, bytes);
|
||||
man.manipulate(entry.bs, message);
|
||||
} catch (Exception e) {
|
||||
completed = i;
|
||||
e.printStackTrace();
|
||||
break;
|
||||
}
|
||||
}
|
||||
man.afterTest();
|
||||
if (fail) {
|
||||
System.out.println("Test failed after " + completed + " iterations");
|
||||
} else {
|
||||
long time = System.currentTimeMillis() - start;
|
||||
if (time < bestTime)
|
||||
bestTime = time;
|
||||
if (time > worstTime)
|
||||
worstTime = time;
|
||||
long mem = (Runtime.getRuntime().totalMemory() - Runtime.getRuntime().freeMemory() - memStart) / (1024);
|
||||
System.out.println("Completed " + iterations + " iterations in " + time + "ms. Consumed memory: "
|
||||
+ mem + "kb. Using Serializer: [" + entry.name + "]");
|
||||
}
|
||||
}
|
||||
long time = System.currentTimeMillis() - allStart;
|
||||
System.out.println("Finished test run for manipulator: " + man.getDescription() + " in " + time + "ms");
|
||||
NumberFormat nf = NumberFormat.getInstance();
|
||||
nf.setMaximumFractionDigits(2);
|
||||
long diff = worstTime - bestTime;
|
||||
float perc = ((float) worstTime / bestTime - 1) * 100;
|
||||
float perc2 = (1 - (float) bestTime / worstTime) * 100;
|
||||
System.out.println("Best/Worst time diff: " + diff + "ms. (" + nf.format(perc2) + "% gain)\n");
|
||||
}
|
||||
|
||||
public static void pause(int millis) {
|
||||
try {
|
||||
Thread.sleep(millis);
|
||||
} catch (InterruptedException e) {
|
||||
// TODO Auto-generated catch block
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
public class SerializerEntry {
|
||||
public BitcoinSerializer bs;
|
||||
public String name;
|
||||
|
||||
public SerializerEntry(BitcoinSerializer bs, String name) {
|
||||
super();
|
||||
this.bs = bs;
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
@ -66,8 +66,8 @@ public class WalletTest {
|
||||
assertEquals(1, wallet.getPoolSize(Wallet.Pool.ALL));
|
||||
|
||||
// Do some basic sanity checks.
|
||||
assertEquals(1, t2.inputs.size());
|
||||
assertEquals(myAddress, t2.inputs.get(0).getScriptSig().getFromAddress());
|
||||
assertEquals(1, t2.getInputs().size());
|
||||
assertEquals(myAddress, t2.getInputs().get(0).getScriptSig().getFromAddress());
|
||||
|
||||
// We have NOT proven that the signature is correct!
|
||||
|
||||
@ -229,7 +229,7 @@ public class WalletTest {
|
||||
// That other guy gives us the coins right back.
|
||||
Transaction inbound2 = new Transaction(params);
|
||||
inbound2.addOutput(new TransactionOutput(params, inbound2, coinHalf, myAddress));
|
||||
inbound2.addInput(outbound1.outputs.get(0));
|
||||
inbound2.addInput(outbound1.getOutputs().get(0));
|
||||
wallet.receive(inbound2, null, BlockChain.NewBlockType.BEST_CHAIN);
|
||||
assertEquals(coin1, wallet.getBalance());
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user