3
0
mirror of https://github.com/Qortal/altcoinj.git synced 2025-01-30 23:02:15 +00:00

Give Blocks/Transactions an idea of their optimally-encoded size.

In the case of Blocks/Transactions which are encoded using VarInts
which are not optimally encoded, we need to compare MAX_BLOCK_SIZE
with the optimally encoded size, not the actually encoded size.
This commit is contained in:
Matt Corallo 2012-12-30 13:50:49 -05:00 committed by Mike Hearn
parent 009939f9be
commit 810b03dd0a
2 changed files with 46 additions and 3 deletions

View File

@ -90,6 +90,11 @@ public class Block extends Message {
private transient boolean headerBytesValid;
private transient boolean transactionBytesValid;
// Blocks can be encoded in a way that will use more bytes than is optimal (due to VarInts having multiple encodings)
// MAX_BLOCK_SIZE must be compared to the optimal encoding, not the actual encoding, so when parsing, we keep track
// of the size of the ideal encoding in addition to the actual message size (which Message needs)
private transient int optimalEncodingMessageSize;
/** Special case constructor, used for the genesis node, cloneAsHeader and unit tests. */
Block(NetworkParameters params) {
@ -167,6 +172,7 @@ public class Block extends Message {
return;
cursor = offset + HEADER_SIZE;
optimalEncodingMessageSize = HEADER_SIZE;
if (bytes.length == cursor) {
// This message is just a header, it has no transactions.
transactionsParsed = true;
@ -175,11 +181,13 @@ public class Block extends Message {
}
int numTransactions = (int) readVarInt();
optimalEncodingMessageSize += VarInt.sizeOf(numTransactions);
transactions = new ArrayList<Transaction>(numTransactions);
for (int i = 0; i < numTransactions; i++) {
Transaction tx = new Transaction(params, bytes, cursor, this, parseLazy, parseRetain, UNKNOWN_LENGTH);
transactions.add(tx);
cursor += tx.getMessageSize();
optimalEncodingMessageSize += tx.getOptimalEncodingMessageSize();
}
// 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.
@ -192,6 +200,16 @@ public class Block extends Message {
parseTransactions();
length = cursor - offset;
}
public int getOptimalEncodingMessageSize() {
if (optimalEncodingMessageSize != 0)
return optimalEncodingMessageSize;
maybeParseTransactions();
if (optimalEncodingMessageSize != 0)
return optimalEncodingMessageSize;
optimalEncodingMessageSize = getMessageSize();
return optimalEncodingMessageSize;
}
protected void parseLite() throws ProtocolException {
// Ignore the header since it has fixed length. If length is not provided we will have to
@ -716,7 +734,7 @@ public class Block extends Message {
if (transactions.isEmpty())
throw new VerificationException("Block had no transactions");
maybeParseTransactions();
if (this.getMessageSize() > MAX_BLOCK_SIZE)
if (this.getOptimalEncodingMessageSize() > MAX_BLOCK_SIZE)
throw new VerificationException("Block larger than MAX_BLOCK_SIZE");
checkTransactions();
checkMerkleRoot();

View File

@ -79,6 +79,13 @@ public class Transaction extends ChildMessage implements Serializable {
//
// If this transaction is not stored in the wallet, appearsInHashes is null.
private Set<Sha256Hash> appearsInHashes;
// Transactions can be encoded in a way that will use more bytes than is optimal
// (due to VarInts having multiple encodings)
// MAX_BLOCK_SIZE must be compared to the optimal encoding, not the actual encoding, so when parsing, we keep track
// of the size of the ideal encoding in addition to the actual message size (which Message needs) so that Blocks
// can properly keep track of optimal encoded size
private transient int optimalEncodingMessageSize;
public Transaction(NetworkParameters params) {
super(params);
@ -478,26 +485,44 @@ public class Transaction extends ChildMessage implements Serializable {
cursor = offset;
version = readUint32();
optimalEncodingMessageSize = 4;
// First come the inputs.
long numInputs = readVarInt();
optimalEncodingMessageSize += VarInt.sizeOf(numInputs);
inputs = new ArrayList<TransactionInput>((int) numInputs);
for (long i = 0; i < numInputs; i++) {
TransactionInput input = new TransactionInput(params, this, bytes, cursor, parseLazy, parseRetain);
inputs.add(input);
cursor += input.getMessageSize();
long scriptLen = readVarInt(TransactionOutPoint.MESSAGE_LENGTH);
optimalEncodingMessageSize += TransactionOutPoint.MESSAGE_LENGTH + VarInt.sizeOf(scriptLen) + scriptLen + 4;
cursor += scriptLen + 4;
}
// Now the outputs
long numOutputs = readVarInt();
optimalEncodingMessageSize += VarInt.sizeOf(numOutputs);
outputs = new ArrayList<TransactionOutput>((int) numOutputs);
for (long i = 0; i < numOutputs; i++) {
TransactionOutput output = new TransactionOutput(params, this, bytes, cursor, parseLazy, parseRetain);
outputs.add(output);
cursor += output.getMessageSize();
long scriptLen = readVarInt(8);
optimalEncodingMessageSize += 8 + VarInt.sizeOf(scriptLen) + scriptLen;
cursor += scriptLen;
}
lockTime = readUint32();
optimalEncodingMessageSize += 4;
length = cursor - offset;
}
public int getOptimalEncodingMessageSize() {
if (optimalEncodingMessageSize != 0)
return optimalEncodingMessageSize;
maybeParse();
if (optimalEncodingMessageSize != 0)
return optimalEncodingMessageSize;
optimalEncodingMessageSize = getMessageSize();
return optimalEncodingMessageSize;
}
/**
* A coinbase transaction is one that creates a new coin. They are the first transaction in each block and their