3
0
mirror of https://github.com/Qortal/altcoinj.git synced 2025-02-12 02:05: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:
Mike Hearn 2011-10-11 17:24:50 +00:00
parent 34fea86708
commit afef6bc029
27 changed files with 3026 additions and 605 deletions

24
TODO
View File

@ -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.

View File

@ -1,32 +1,44 @@
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
void parse() throws ProtocolException {
long numAddresses = readVarInt();
numAddresses = readVarInt();
// Guard against ultra large messages that will crash us.
if (numAddresses > MAX_ADDRESSES)
throw new ProtocolException("Address message too large.");
@ -38,6 +50,42 @@ public class AddressMessage extends Message {
}
}
/* (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);
}
}
/**
* @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();

View File

@ -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")) {
@ -304,6 +322,22 @@ public class BitcoinSerializer {
}
}
/**
* Whether the serializer will produce lazy parse mode Messages
*/
public boolean isParseLazyMode() {
return parseLazy;
}
/**
* Whether the serializer will produce cached mode Messages
*/
public boolean isParseRetainMode() {
return parseRetain;
}
public class BitcoinPacketHeader {
final byte[] header;
final String command;

View File

@ -26,15 +26,21 @@ import java.util.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import sun.reflect.generics.reflectiveObjects.NotImplementedException;
import static com.google.bitcoin.core.Utils.*;
/**
* A block is the foundation of the BitCoin system. It records a set of {@link Transaction}s together with
* some data that links it into a place in the global block chain, and proves that a difficult calculation was done
* over its contents. See the BitCoin technical paper for more detail on blocks.<p>
* A block is the foundation of the BitCoin system. It records a set of
* {@link Transaction}s together with some data that links it into a place in
* the global block chain, and proves that a difficult calculation was done over
* its contents. See the BitCoin technical paper for more detail on blocks.
* <p>
*
* To get a block, you can either build one from the raw bytes you can get from another implementation,
* or request one specifically using {@link Peer#getBlock(Sha256Hash)}, or grab one from a downloaded {@link BlockChain}.
* To get a block, you can either build one from the raw bytes you can get from
* another implementation, or request one specifically using
* {@link Peer#getBlock(Sha256Hash)}, or grab one from a downloaded
* {@link BlockChain}.
*/
public class Block extends Message {
private static final Logger log = LoggerFactory.getLogger(Block.class);
@ -43,9 +49,13 @@ public class Block extends Message {
/** How many bytes are required to represent a block header. */
public static final int HEADER_SIZE = 80;
static final long ALLOWED_TIME_DRIFT = 2 * 60 * 60; // Same value as official client.
static final long ALLOWED_TIME_DRIFT = 2 * 60 * 60; // Same value as
// official client.
/** A value for difficultyTarget (nBits) that allows half of all possible hash solutions. Used in unit testing. */
/**
* A value for difficultyTarget (nBits) that allows half of all possible
* hash solutions. Used in unit testing.
*/
static final long EASIEST_DIFFICULTY_TARGET = 0x207fFFFFL;
// For unit testing. If not zero, use this instead of the current time.
@ -61,10 +71,22 @@ public class Block extends Message {
/** If null, it means this object holds only the headers. */
List<Transaction> transactions;
private transient List<Transaction> immutableTransactions;
/** Stores the hash of the block. If null, getHash() will recalculate it. */
private transient Sha256Hash hash;
/** Special case constructor, used for the genesis node, cloneAsHeader and unit tests. */
private transient boolean headerParsed;
private transient boolean transactionsParsed;
private transient boolean headerBytesValid;
private transient boolean transactionBytesValid;
/**
* Special case constructor, used for the genesis node, cloneAsHeader and
* unit tests.
*/
Block(NetworkParameters params) {
super(params);
// Set up a few basic things. We are not complete after this though.
@ -79,18 +101,26 @@ public class Block extends Message {
super(params, payloadBytes, 0);
}
public Block(NetworkParameters params, byte[] payloadBytes, boolean parseLazy, boolean parseRetain) throws ProtocolException {
super(params, payloadBytes, 0, parseLazy, parseRetain);
public Block(NetworkParameters params, byte[] payloadBytes, boolean parseLazy, boolean parseRetain, int length)
throws ProtocolException {
super(params, payloadBytes, 0, parseLazy, parseRetain, length);
}
private void readObject(ObjectInputStream ois) throws ClassNotFoundException, IOException {
ois.defaultReadObject();
// This code is not actually necessary, as transient fields are initialized to the default value which is in
// this case null. However it clears out a FindBugs warning and makes it explicit what we're doing.
// This code is not actually necessary, as transient fields are
// initialized to the default value which is in
// this case null. However it clears out a FindBugs warning and makes it
// explicit what we're doing.
hash = null;
}
void parse() throws ProtocolException {
private void parseHeader() {
if (headerParsed)
return;
cursor = offset;
version = readUint32();
prevBlockHash = readHash();
merkleRoot = readHash();
@ -98,23 +128,180 @@ public class Block extends Message {
difficultyTarget = readUint32();
nonce = readUint32();
hash = new Sha256Hash(Utils.reverseBytes(Utils.doubleDigest(bytes, 0, cursor)));
hash = new Sha256Hash(Utils.reverseBytes(Utils.doubleDigest(bytes, offset, cursor)));
if (cursor == bytes.length) {
headerParsed = true;
headerBytesValid = parseRetain;
}
private void parseTransactions() throws ProtocolException {
if (transactionsParsed)
return;
cursor = offset + 80;
if (bytes.length == cursor) {
// This message is just a header, it has no transactions.
transactionsParsed = true;
transactionBytesValid = false;
return;
}
int numTransactions = (int) readVarInt();
transactions = new ArrayList<Transaction>(numTransactions);
for (int i = 0; i < numTransactions; i++) {
Transaction tx = new Transaction(params, bytes, cursor, this, parseLazy, parseRetain);
Transaction tx = new Transaction(params, bytes, cursor, this, parseLazy, parseRetain, UNKNOWN_LENGTH);
transactions.add(tx);
cursor += tx.getMessageSize();
}
// no need to set length here. If length was not provided then it should
// be set at the end
// of parseLight(). If this is a genuine lazy parse then length must
// have been provided to
// the constructor.
transactionsParsed = true;
transactionBytesValid = parseRetain;
}
void parse() throws ProtocolException {
parseHeader();
parseTransactions();
length = cursor - offset;
}
protected void parseLite() throws ProtocolException {
// we already know parseLazy is true if this method was called so no
// need to check.
// ignore the header since it has fixed length. If length is not
// provided we will have to
// invoke a light parse of transactions to calculate the length.
if (length == UNKNOWN_LENGTH) {
log.warn("Performing lite parse of block transaction as block was initialised from byte array without providing length. This should never need to happen.");
parseTransactions();
length = cursor - offset;
} else {
transactionBytesValid = !transactionsParsed || parseRetain && length > 80;
}
headerBytesValid = !headerParsed || parseRetain && length >= 80;
}
/*
* Block uses some special handling for lazy parsing and retention of cached
* bytes. Parsing and serializing the block header and the transaction list
* are both non-trivial so there are good efficiency gains to be had by
* separating them. There are many cases where a user may need access to
* access or change one or the other but not both.
*
* With this in mind we ignore the inherited checkParse() and unCache()
* methods and implement a separate version of them for both header and tx.
*
* Serializing methods are also handled in their own way. Whilst they deal
* with seperate parts of the block structure there are some
* interdependencies. For example altering a tx requires invalidating the
* Merkle root and therefore the cached header bytes.
*/
private synchronized void checkParseHeader() {
if (headerParsed || bytes == null)
return;
parseHeader();
if (!(headerBytesValid || transactionBytesValid))
bytes = null;
}
private synchronized void checkParseTransactions() {
if (transactionsParsed || bytes == null)
return;
try {
parseTransactions();
if (!parseRetain) {
transactionBytesValid = false;
if (headerParsed)
bytes = null;
}
} catch (ProtocolException e) {
throw new LazyParseException(
"ProtocolException caught during lazy parse. For safe access to fields call ensureParsed before attempting read or write access",
e);
}
}
/**
* Ensure the object is parsed if needed. This should be called in every
* getter before returning a value. If the lazy parse flag is not set this
* is a method returns immediately.
*/
protected synchronized void checkParse() {
throw new LazyParseException(
"checkParse() should never be called on a Block. Instead use checkParseHeader() and checkParseTransactions()");
}
/**
* 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 method ensures parsing of both headers and transactions.
*
* @throws ProtocolException
*/
public void ensureParsed() throws ProtocolException {
try {
checkParseHeader();
checkParseTransactions();
} catch (LazyParseException e) {
if (e.getCause() instanceof ProtocolException)
throw (ProtocolException) e.getCause();
throw new ProtocolException(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 method ensures parsing of headers only.
*
* @throws ProtocolException
*/
public void ensureParsedHeader() throws ProtocolException {
try {
checkParseHeader();
} catch (LazyParseException e) {
if (e.getCause() instanceof ProtocolException)
throw (ProtocolException) e.getCause();
throw new ProtocolException(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 method ensures parsing of transactions only.
*
* @throws ProtocolException
*/
public void ensureParsedTransactions() throws ProtocolException {
try {
checkParseTransactions();
} catch (LazyParseException e) {
if (e.getCause() instanceof ProtocolException)
throw (ProtocolException) e.getCause();
throw new ProtocolException(e);
}
}
private void writeHeader(OutputStream stream) throws IOException {
// try for cached write first
if (headerBytesValid && bytes != null && bytes.length >= offset + 80) {
stream.write(bytes, offset, 80);
return;
}
// fall back to manual write
checkParseHeader();
Utils.uint32ToByteStreamLE(version, stream);
stream.write(Utils.reverseBytes(prevBlockHash.getBytes()));
stream.write(Utils.reverseBytes(getMerkleRoot().getBytes()));
@ -123,19 +310,113 @@ public class Block extends Message {
Utils.uint32ToByteStreamLE(nonce, stream);
}
@Override
protected void bitcoinSerializeToStream(OutputStream stream) throws IOException {
writeHeader(stream);
// We may only have enough data to write the header.
if (transactions == null) return;
private void writeTransactions(OutputStream stream) throws IOException {
// check for no transaction conditions first
// must be a more efficient way to do this but I'm tired atm.
if (transactions == null && transactionsParsed) {
return;
}
// if (transactions == null && !(parseRetain || !transactionsParsed))
// return;
// if (parseRetain && transactions == null && !transactionBytesValid)
// return;
// if (transactionBytesValid && bytes.length <= 80)
// return;
// confirmed we must have transactions either cached or as objects.
if (transactionBytesValid && bytes != null && bytes.length >= offset + length) {
stream.write(bytes, offset + 80, length - 80);
return;
}
// if (transactionBytesValid && bytes != null && bytes.length >= offset
// + length) {
// stream.write(bytes, offset + 80, length - 80);
// return;
// }
if (transactions != null) {
stream.write(new VarInt(transactions.size()).encode());
for (Transaction tx : transactions) {
tx.bitcoinSerialize(stream);
}
}
}
/**
* Calculates the block hash by serializing the block and hashing the resulting bytes.
* Special handling to check if we have a valid byte array for both header
* and transactions
*
* @throws IOException
*/
public byte[] bitcoinSerialize() {
// we have completely cached byte array.
if (headerBytesValid && transactionBytesValid) {
assert bytes != null : "Bytes should never be null if headerBytesValid && transactionBytesValid";
if (length == bytes.length) {
return bytes;
} else {
// byte array is offset so copy out the correct range.
byte[] buf = new byte[length];
System.arraycopy(bytes, offset, buf, 0, length);
return buf;
}
}
// At least one of the two cacheable components is invalid
// so fall back to stream write since we can't be sure of the length.
ByteArrayOutputStream stream = new ByteArrayOutputStream();
try {
writeHeader(stream);
writeTransactions(stream);
} catch (IOException e) {
// Cannot happen, we are serializing to a memory stream.
}
return stream.toByteArray();
}
@Override
protected void bitcoinSerializeToStream(OutputStream stream) throws IOException {
writeHeader(stream);
// We may only have enough data to write the header.
writeTransactions(stream);
}
protected void unCache() {
// Since we have alternate uncache methods to use internally this will
// only ever be called
// by a child transaction so we only need to invalidate that part of the
// cache.
unCacheTransactions();
}
private void unCacheHeader() {
checkParseHeader();
headerBytesValid = false;
if (!transactionBytesValid)
bytes = null;
hash = null;
}
private void unCacheTransactions() {
checkParseTransactions();
transactionBytesValid = false;
if (!headerBytesValid)
bytes = null;
// current implementation has to uncache headers as well as any change
// to a tx
// will alter the merkle root. In future we can go more granular and
// cache merkle root
// seperately to rest of the header does not need to be rewritten
unCacheHeader();
// clear merkleRoot last as it may end up being parsed during
// unCacheHeader().
merkleRoot = null;
}
/**
* Calculates the block hash by serializing the block and hashing the
* resulting bytes.
*/
private Sha256Hash calculateHash() {
try {
@ -148,8 +429,9 @@ public class Block extends Message {
}
/**
* Returns the hash of the block (which for a valid, solved block should be below the target) in the form seen
* on the block explorer. If you call this on block 1 in the production chain, you will get
* Returns the hash of the block (which for a valid, solved block should be
* below the target) in the form seen on the block explorer. If you call
* this on block 1 in the production chain, you will get
* "00000000839a8e6886ab5951d76f411475428afc90947ee320161bbf18eb6048".
*/
public String getHashAsString() {
@ -157,7 +439,8 @@ public class Block extends Message {
}
/**
* Returns the hash of the block (which for a valid, solved block should be below the target). Big endian.
* Returns the hash of the block (which for a valid, solved block should be
* below the target). Big endian.
*/
public Sha256Hash getHash() {
if (hash == null)
@ -165,14 +448,19 @@ public class Block extends Message {
return hash;
}
/** The number that is one greater than the largest representable SHA-256 hash. */
/**
* The number that is one greater than the largest representable SHA-256
* hash.
*/
static private BigInteger LARGEST_HASH = BigInteger.ONE.shiftLeft(256);
/**
* Returns the work represented by this block.<p>
* Returns the work represented by this block.
* <p>
*
* Work is defined as the number of tries needed to solve a block in the average case. Consider a difficulty
* target that covers 5% of all possible hash values. Then the work of the block will be 20. As the target gets
* Work is defined as the number of tries needed to solve a block in the
* average case. Consider a difficulty target that covers 5% of all possible
* hash values. Then the work of the block will be 20. As the target gets
* lower, the amount of work goes up.
*/
public BigInteger getWork() throws VerificationException {
@ -182,6 +470,7 @@ public class Block extends Message {
/** Returns a copy of the block, but without any transactions. */
public Block cloneAsHeader() {
checkParseHeader();
Block block = new Block(params);
block.nonce = nonce;
block.prevBlockHash = prevBlockHash.duplicate();
@ -195,17 +484,15 @@ public class Block extends Message {
}
/**
* Returns a multi-line string containing a description of the contents of the block. Use for debugging purposes
* only.
* Returns a multi-line string containing a description of the contents of
* the block. Use for debugging purposes only.
*/
@Override
public String toString() {
StringBuffer s = new StringBuffer("v" + version + " block: \n" +
" previous block: " + prevBlockHash.toString() + "\n" +
" merkle root: " + getMerkleRoot().toString() + "\n" +
" time: [" + time + "] " + new Date(time * 1000).toString() + "\n" +
" difficulty target (nBits): " + difficultyTarget + "\n" +
" nonce: " + nonce + "\n");
StringBuffer s = new StringBuffer("v" + version + " block: \n" + " previous block: "
+ prevBlockHash.toString() + "\n" + " merkle root: " + getMerkleRoot().toString() + "\n"
+ " time: [" + time + "] " + new Date(time * 1000).toString() + "\n"
+ " difficulty target (nBits): " + difficultyTarget + "\n" + " nonce: " + nonce + "\n");
if (transactions != null && transactions.size() > 0) {
s.append(" with ").append(transactions.size()).append(" transaction(s):\n");
for (Transaction tx : transactions) {
@ -216,17 +503,21 @@ public class Block extends Message {
}
/**
* Finds a value of nonce that makes the blocks hash lower than the difficulty target. This is called mining,
* but solve() is far too slow to do real mining with. It exists only for unit testing purposes and is not a part
* of the public API.
* Finds a value of nonce that makes the blocks hash lower than the
* difficulty target. This is called mining, but solve() is far too slow to
* do real mining with. It exists only for unit testing purposes and is not
* a part of the public API.
*
* This can loop forever if a solution cannot be found solely by incrementing nonce. It doesn't change extraNonce.
* This can loop forever if a solution cannot be found solely by
* incrementing nonce. It doesn't change extraNonce.
*/
void solve() {
checkParseHeader();
while (true) {
try {
// Is our proof of work valid yet?
if (checkProofOfWork(false)) return;
if (checkProofOfWork(false))
return;
// No, so increment the nonce and try again.
setNonce(getNonce() + 1);
} catch (VerificationException e) {
@ -236,35 +527,46 @@ public class Block extends Message {
}
/**
* Returns the difficulty target as a 256 bit value that can be compared to a SHA-256 hash. Inside a block the
* target is represented using a compact form. If this form decodes to a value that is out of bounds,
* an exception is thrown.
* Returns the difficulty target as a 256 bit value that can be compared to
* a SHA-256 hash. Inside a block the target is represented using a compact
* form. If this form decodes to a value that is out of bounds, an exception
* is thrown.
*/
public BigInteger getDifficultyTargetAsInteger() throws VerificationException {
checkParseHeader();
BigInteger target = Utils.decodeCompactBits(difficultyTarget);
if (target.compareTo(BigInteger.valueOf(0)) <= 0 || target.compareTo(params.proofOfWorkLimit) > 0)
throw new VerificationException("Difficulty target is bad: " + target.toString());
return target;
}
/** Returns true if the hash of the block is OK (lower than difficulty target). */
/**
* Returns true if the hash of the block is OK (lower than difficulty
* target).
*/
private boolean checkProofOfWork(boolean throwException) throws VerificationException {
// This part is key - it is what proves the block was as difficult to make as it claims
// to be. Note however that in the context of this function, the block can claim to be
// as difficult as it wants to be .... if somebody was able to take control of our network
// connection and fork us onto a different chain, they could send us valid blocks with
// This part is key - it is what proves the block was as difficult to
// make as it claims
// to be. Note however that in the context of this function, the block
// can claim to be
// as difficult as it wants to be .... if somebody was able to take
// control of our network
// connection and fork us onto a different chain, they could send us
// valid blocks with
// ridiculously easy difficulty and this function would accept them.
//
// To prevent this attack from being possible, elsewhere we check that the difficultyTarget
// field is of the right value. This requires us to have the preceeding blocks.
// To prevent this attack from being possible, elsewhere we check that
// the difficultyTarget
// field is of the right value. This requires us to have the preceeding
// blocks.
BigInteger target = getDifficultyTargetAsInteger();
BigInteger h = getHash().toBigInteger();
if (h.compareTo(target) > 0) {
// Proof of work check failed!
if (throwException)
throw new VerificationException("Hash is higher than target: " + getHashAsString() + " vs " +
target.toString(16));
throw new VerificationException("Hash is higher than target: " + getHashAsString() + " vs "
+ target.toString(16));
else
return false;
}
@ -272,6 +574,7 @@ public class Block extends Message {
}
private void checkTimestamp() throws VerificationException {
checkParseHeader();
// Allow injection of a fake clock to allow unit testing.
long currentTime = fakeClock != 0 ? fakeClock : System.currentTimeMillis() / 1000;
if (time > currentTime + ALLOWED_TIME_DRIFT)
@ -282,8 +585,7 @@ public class Block extends Message {
Sha256Hash calculatedRoot = calculateMerkleRoot();
if (!calculatedRoot.equals(merkleRoot)) {
log.error("Merkle tree did not verify");
throw new VerificationException("Merkle hashes do not match: " +
calculatedRoot + " vs " + merkleRoot);
throw new VerificationException("Merkle hashes do not match: " + calculatedRoot + " vs " + merkleRoot);
}
}
@ -293,7 +595,8 @@ public class Block extends Message {
}
private List<byte[]> buildMerkleTree() {
// The Merkle root is based on a tree of hashes calculated from the transactions:
// The Merkle root is based on a tree of hashes calculated from the
// transactions:
//
// root
// / \
@ -302,18 +605,27 @@ public class Block extends Message {
// / \ / \
// t1 t2 t3 t4
//
// The tree is represented as a list: t1,t2,t3,t4,A,B,root where each entry is a hash.
// The tree is represented as a list: t1,t2,t3,t4,A,B,root where each
// entry is a hash.
//
// The hashing algorithm is double SHA-256. The leaves are a hash of the serialized contents of the
// transaction. The interior nodes are hashes of the concenation of the two child hashes.
// The hashing algorithm is double SHA-256. The leaves are a hash of the
// serialized contents of the
// transaction. The interior nodes are hashes of the concenation of the
// two child hashes.
//
// This structure allows the creation of proof that a transaction was included into a block without having to
// provide the full block contents. Instead, you can provide only a Merkle branch. For example to prove tx2 was
// in a block you can just provide tx2, the hash(tx1) and B. Now the other party has everything they need to
// derive the root, which can be checked against the block header. These proofs aren't used right now but
// will be helpful later when we want to download partial block contents.
// This structure allows the creation of proof that a transaction was
// included into a block without having to
// provide the full block contents. Instead, you can provide only a
// Merkle branch. For example to prove tx2 was
// in a block you can just provide tx2, the hash(tx1) and B. Now the
// other party has everything they need to
// derive the root, which can be checked against the block header. These
// proofs aren't used right now but
// will be helpful later when we want to download partial block
// contents.
//
// Note that if the number of transactions is not even the last tx is repeated to make it so (see
// Note that if the number of transactions is not even the last tx is
// repeated to make it so (see
// tx3 above). A tree with 5 transactions would look like this:
//
// root
@ -323,17 +635,22 @@ public class Block extends Message {
// 2 3 4
// / \ / \ / \
// t1 t2 t3 t4 t5 t5
checkParseTransactions();
ArrayList<byte[]> tree = new ArrayList<byte[]>();
// Start by adding all the hashes of the transactions as leaves of the tree.
// Start by adding all the hashes of the transactions as leaves of the
// tree.
for (Transaction t : transactions) {
tree.add(t.getHash().getBytes());
}
int levelOffset = 0; // Offset in the list where the currently processed level starts.
// Step through each level, stopping when we reach the root (levelSize == 1).
int levelOffset = 0; // Offset in the list where the currently processed
// level starts.
// Step through each level, stopping when we reach the root (levelSize
// == 1).
for (int levelSize = transactions.size(); levelSize > 1; levelSize = (levelSize + 1) / 2) {
// For each pair of nodes on that level:
for (int left = 0; left < levelSize; left += 2) {
// The right hand node can be the same as the left hand, in the case where we don't have enough
// The right hand node can be the same as the left hand, in the
// case where we don't have enough
// transactions.
int right = Math.min(left + 1, levelSize - 1);
byte[] leftBytes = Utils.reverseBytes(tree.get(levelOffset + left));
@ -347,7 +664,8 @@ public class Block extends Message {
}
private void checkTransactions() throws VerificationException {
// The first transaction in a block must always be a coinbase transaction.
// The first transaction in a block must always be a coinbase
// transaction.
if (!transactions.get(0).isCoinBase())
throw new VerificationException("First tx is not coinbase");
// The rest must not be.
@ -358,39 +676,51 @@ public class Block extends Message {
}
/**
* Checks the block data to ensure it follows the rules laid out in the network parameters. Specifically, throws
* an exception if the proof of work is invalid, or if the timestamp is too far from what it should be. This is
* <b>not</b> everything that is required for a block to be valid, only what is checkable independent of the
* chain and without a transaction index.
* Checks the block data to ensure it follows the rules laid out in the
* network parameters. Specifically, throws an exception if the proof of
* work is invalid, or if the timestamp is too far from what it should be.
* This is <b>not</b> everything that is required for a block to be valid,
* only what is checkable independent of the chain and without a transaction
* index.
*
* @throws VerificationException
*/
public void verifyHeader() throws VerificationException {
// Prove that this block is OK. It might seem that we can just ignore most of these checks given that the
// network is also verifying the blocks, but we cannot as it'd open us to a variety of obscure attacks.
// Prove that this block is OK. It might seem that we can just ignore
// most of these checks given that the
// network is also verifying the blocks, but we cannot as it'd open us
// to a variety of obscure attacks.
//
// Firstly we need to ensure this block does in fact represent real work done. If the difficulty is high
// Firstly we need to ensure this block does in fact represent real work
// done. If the difficulty is high
// enough, it's probably been done by the network.
checkParseHeader();
checkProofOfWork(true);
checkTimestamp();
}
/**
* Checks the block contents
*
* @throws VerificationException
*/
public void verifyTransactions() throws VerificationException {
// Now we need to check that the body of the block actually matches the headers. The network won't generate
// an invalid block, but if we didn't validate this then an untrusted man-in-the-middle could obtain the next
// valid block from the network and simply replace the transactions in it with their own fictional
// Now we need to check that the body of the block actually matches the
// headers. The network won't generate
// an invalid block, but if we didn't validate this then an untrusted
// man-in-the-middle could obtain the next
// valid block from the network and simply replace the transactions in
// it with their own fictional
// transactions that reference spent or non-existant inputs.
assert transactions.size() > 0;
checkParseTransactions();
checkTransactions();
checkMerkleRoot();
}
/**
* Verifies both the header and that the transactions hash to the merkle root.
* Verifies both the header and that the transactions hash to the merkle
* root.
*/
public void verify() throws VerificationException {
verifyHeader();
@ -399,7 +729,8 @@ public class Block extends Message {
@Override
public boolean equals(Object o) {
if (!(o instanceof Block)) return false;
if (!(o instanceof Block))
return false;
Block other = (Block) o;
return getHash().equals(other.getHash());
}
@ -409,85 +740,122 @@ public class Block extends Message {
return getHash().hashCode();
}
/** Returns the merkle root in big endian form, calculating it from transactions if necessary. */
/**
* Returns the merkle root in big endian form, calculating it from
* transactions if necessary.
*/
public Sha256Hash getMerkleRoot() {
if (merkleRoot == null)
checkParseHeader();
if (merkleRoot == null) {
unCacheHeader();
merkleRoot = calculateMerkleRoot();
}
return merkleRoot;
}
/** Exists only for unit testing. */
void setMerkleRoot(Sha256Hash value) {
unCacheHeader();
merkleRoot = value;
hash = null;
}
/** Adds a transaction to this block. */
void addTransaction(Transaction t) {
unCacheTransactions();
immutableTransactions = null;
if (transactions == null) {
transactions = new ArrayList<Transaction>();
}
t.setParent(this);
transactions.add(t);
// Force a recalculation next time the values are needed.
merkleRoot = null;
hash = null;
}
/** Returns the version of the block data structure as defined by the BitCoin protocol. */
/**
* Returns the version of the block data structure as defined by the BitCoin
* protocol.
*/
public long getVersion() {
checkParseHeader();
return version;
}
/** Returns the hash of the previous block in the chain, as defined by the block header. */
/**
* Returns the hash of the previous block in the chain, as defined by the
* block header.
*/
public Sha256Hash getPrevBlockHash() {
checkParseHeader();
return prevBlockHash;
}
void setPrevBlockHash(Sha256Hash prevBlockHash) {
unCacheHeader();
this.prevBlockHash = prevBlockHash;
this.hash = null;
}
/**
* Returns the time at which the block was solved and broadcast, according to the clock of the solving node.
* This is measured in seconds since the UNIX epoch (midnight Jan 1st 1970).
* Returns the time at which the block was solved and broadcast, according
* to the clock of the solving node. This is measured in seconds since the
* UNIX epoch (midnight Jan 1st 1970).
*/
public long getTimeSeconds() {
checkParseHeader();
return time;
}
void setTime(long time) {
unCacheHeader();
this.time = time;
this.hash = null;
}
/**
* Returns the difficulty of the proof of work that this block should meet encoded in compact form. The
* {@link BlockChain} verifies that this is not too easy by looking at the length of the chain when the block is
* added. To find the actual value the hash should be compared against, use getDifficultyTargetBI.
* Returns the difficulty of the proof of work that this block should meet
* encoded in compact form. The {@link BlockChain} verifies that this is not
* too easy by looking at the length of the chain when the block is added.
* To find the actual value the hash should be compared against, use
* getDifficultyTargetBI.
*/
public long getDifficultyTarget() {
checkParseHeader();
return difficultyTarget;
}
void setDifficultyTarget(long compactForm) {
unCacheHeader();
this.difficultyTarget = compactForm;
this.hash = null;
}
/**
* Returns the nonce, an arbitrary value that exists only to make the hash of the block header fall below the
* difficulty target.
* Returns the nonce, an arbitrary value that exists only to make the hash
* of the block header fall below the difficulty target.
*/
public long getNonce() {
checkParseHeader();
return nonce;
}
void setNonce(long nonce) {
unCacheHeader();
this.nonce = nonce;
this.hash = null;
}
public List<Transaction> getTransactions() {
if (immutableTransactions == null) {
checkParseTransactions();
if (transactions != null)
immutableTransactions = Collections.unmodifiableList(transactions);
}
return immutableTransactions;
}
// ///////////////////////////////////////////////////////////////////////////////////////////////
// Unit testing related methods.
@ -496,21 +864,28 @@ public class Block extends Message {
/** Adds a coinbase transaction to the block. This exists for unit tests. */
void addCoinbaseTransaction(byte[] pubKeyTo) {
unCacheTransactions();
transactions = new ArrayList<Transaction>();
Transaction coinbase = new Transaction(params);
// A real coinbase transaction has some stuff in the scriptSig like the extraNonce and difficulty. The
// transactions are distinguished by every TX output going to a different key.
// A real coinbase transaction has some stuff in the scriptSig like the
// extraNonce and difficulty. The
// transactions are distinguished by every TX output going to a
// different key.
//
// Here we will do things a bit differently so a new address isn't needed every time. We'll put a simple
// Here we will do things a bit differently so a new address isn't
// needed every time. We'll put a simple
// counter in the scriptSig so every transaction has a different hash.
coinbase.inputs.add(new TransactionInput(params, coinbase, new byte[] { (byte) txCounter++ } ));
coinbase.outputs.add(new TransactionOutput(params, coinbase, Script.createOutputScript(pubKeyTo)));
coinbase.addInput(new TransactionInput(params, coinbase, new byte[] { (byte) txCounter++ }));
coinbase.addOutput(new TransactionOutput(params, coinbase, Script.createOutputScript(pubKeyTo)));
transactions.add(coinbase);
}
static final byte[] EMPTY_BYTES = new byte[32];
/** Returns a solved block that builds on top of this one. This exists for unit tests. */
/**
* Returns a solved block that builds on top of this one. This exists for
* unit tests.
*/
Block createNextBlock(Address to, long time) {
Block b = new Block(params);
b.setDifficultyTarget(difficultyTarget);
@ -519,13 +894,16 @@ public class Block extends Message {
// Add a transaction paying 50 coins to the "to" address.
Transaction t = new Transaction(params);
t.addOutput(new TransactionOutput(params, t, Utils.toNanoCoins(50, 0), to));
// The input does not really need to be a valid signature, as long as it has the right general form.
// The input does not really need to be a valid signature, as long as it
// has the right general form.
TransactionInput input = new TransactionInput(params, t, Script.createInputScript(EMPTY_BYTES, EMPTY_BYTES));
// Importantly the outpoint hash cannot be zero as that's how we detect a coinbase transaction in isolation
// but it must be unique to avoid 'different' transactions looking the same.
// Importantly the outpoint hash cannot be zero as that's how we detect
// a coinbase transaction in isolation
// but it must be unique to avoid 'different' transactions looking the
// same.
byte[] counter = new byte[32];
counter[0] = (byte) txCounter++;
input.outpoint.hash = new Sha256Hash(counter);
input.getOutpoint().setHash(new Sha256Hash(counter));
t.addInput(input);
b.addTransaction(t);
@ -544,4 +922,41 @@ public class Block extends Message {
public Block createNextBlock(Address to) {
return createNextBlock(to, System.currentTimeMillis() / 1000);
}
/**
* Used for unit test
*
* @return the headerParsed
*/
public boolean isParsedHeader() {
return headerParsed;
}
/**
* Used for unit test
*
* @return the transactionsParsed
*/
public boolean isParsedTransactions() {
return transactionsParsed;
}
/**
* Used for unit test
*
* @return the headerBytesValid
*/
public boolean isHeaderBytesValid() {
return headerBytesValid;
}
/**
* Used for unit test
*
* @return the transactionBytesValid
*/
public boolean isTransactionBytesValid() {
return transactionBytesValid;
}
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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,6 +27,7 @@ import java.util.List;
*/
public abstract class ListMessage extends Message
{
private long arrayLen;
// For some reason the compiler complains if this is inside InventoryItem
private List<InventoryItem> items;
@ -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,20 +54,33 @@ public abstract class ListMessage extends Message
public List<InventoryItem> getItems()
{
return items;
checkParse();
return Collections.unmodifiableList(items);
}
public void addItem(InventoryItem 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);
for (int i = 0; i < arrayLen; i++) {
if (cursor + 4 + 32 > bytes.length) {

View File

@ -36,6 +36,8 @@ public abstract class Message implements Serializable {
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;
@ -45,10 +47,12 @@ public abstract class Message implements Serializable {
// 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,10 +153,29 @@ 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);
}
}
@ -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() {
@ -263,6 +338,12 @@ public abstract class Message implements Serializable {
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) {
byte[] b = new byte[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);
}
}
}

View File

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

View File

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

View File

@ -93,7 +93,7 @@ public class Peer {
@Override
public String toString() {
return "Peer(" + address.addr + ":" + address.port + ")";
return "Peer(" + address.getAddr() + ":" + address.getPort() + ")";
}
/**

View File

@ -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,6 +85,9 @@ public class PeerAddress extends ChildMessage {
@Override
protected void bitcoinSerializeToStream(OutputStream stream) throws IOException {
if (protocolVersion >= 31402) {
//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);
}
@ -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:
@ -122,6 +132,86 @@ 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;
}
/**
* @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;

View File

@ -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,24 +99,17 @@ 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);
}
/**
* Returns a read-only list of the inputs of this transaction.
*/
public List<TransactionInput> getInputs() {
return Collections.unmodifiableList(inputs);
super(params, msg, 0, parent, parseLazy, parseRetain, length);
}
/**
@ -283,8 +282,34 @@ 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 {
if (parsed)
return;
version = readUint32();
int marker = cursor;
// First come the inputs.
long numInputs = readVarInt();
inputs = new ArrayList<TransactionInput>((int)numInputs);
@ -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.
@ -485,6 +520,54 @@ public class Transaction extends ChildMessage implements Serializable {
uint32ToByteStreamLE(lockTime, stream);
}
/**
* @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;

View File

@ -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,6 +118,7 @@ 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) {
checkParse();
assert scriptBytes != null;
scriptSig = new Script(params, scriptBytes, 0, scriptBytes.length);
}
@ -126,6 +134,52 @@ public class TransactionInput extends ChildMessage implements Serializable {
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;
}
/**
* @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() {
@ -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;
}

View File

@ -29,10 +29,12 @@ 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
@ -66,6 +68,14 @@ public class TransactionOutPoint extends ChildMessage implements Serializable {
index = readUint32();
}
/* (non-Javadoc)
* @see com.google.bitcoin.core.Message#getMessageSize()
*/
@Override
int getMessageSize() {
return MESSAGE_LENGTH;
}
@Override
protected void bitcoinSerializeToStream(OutputStream stream) throws IOException {
stream.write(Utils.reverseBytes(hash.getBytes()));
@ -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,6 +113,39 @@ 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

View File

@ -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)
if (scriptPubKey == null) {
checkParse();
scriptPubKey = new Script(params, scriptBytes, 0, scriptBytes.length);
}
return scriptPubKey;
}
void parse() throws ProtocolException {
protected void parseLite() {
value = readUint64();
int scriptLen = (int) readVarInt();
scriptLen = (int) readVarInt();
length = cursor - offset + scriptLen;
}
void parse() throws ProtocolException {
scriptBytes = readBytes(scriptLen);
}
@ -110,13 +117,14 @@ public class TransactionOutput extends ChildMessage implements Serializable {
* receives.
*/
public BigInteger getValue() {
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,6 +151,7 @@ public class TransactionOutput extends ChildMessage implements Serializable {
}
public byte[] getScriptBytes() {
checkParse();
return scriptBytes;
}

View File

@ -34,4 +34,6 @@ public class UnknownMessage extends EmptyMessage {
public void parse() throws ProtocolException {
}
}

View File

@ -79,6 +79,10 @@ public class VersionMessage extends Message {
@Override
public void parse() throws ProtocolException {
if (parsed)
return;
parsed = true;
clientVersion = (int) readUint32();
localServices = readUint64().longValue();
time = readUint64().longValue();

View File

@ -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.

View File

@ -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" +
@ -86,10 +89,15 @@ public class BitcoinSerializerTest {
// 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()));
}
}

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

View 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() {}
}

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

View File

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