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 headerBytesValid;
private transient boolean transactionBytesValid; 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. */ /** Special case constructor, used for the genesis node, cloneAsHeader and unit tests. */
Block(NetworkParameters params) { Block(NetworkParameters params) {
@ -167,6 +172,7 @@ public class Block extends Message {
return; return;
cursor = offset + HEADER_SIZE; cursor = offset + HEADER_SIZE;
optimalEncodingMessageSize = HEADER_SIZE;
if (bytes.length == cursor) { if (bytes.length == cursor) {
// This message is just a header, it has no transactions. // This message is just a header, it has no transactions.
transactionsParsed = true; transactionsParsed = true;
@ -175,11 +181,13 @@ public class Block extends Message {
} }
int numTransactions = (int) readVarInt(); int numTransactions = (int) readVarInt();
optimalEncodingMessageSize += VarInt.sizeOf(numTransactions);
transactions = new ArrayList<Transaction>(numTransactions); transactions = new ArrayList<Transaction>(numTransactions);
for (int i = 0; i < numTransactions; i++) { for (int i = 0; i < numTransactions; i++) {
Transaction tx = new Transaction(params, bytes, cursor, this, parseLazy, parseRetain, UNKNOWN_LENGTH); Transaction tx = new Transaction(params, bytes, cursor, this, parseLazy, parseRetain, UNKNOWN_LENGTH);
transactions.add(tx); transactions.add(tx);
cursor += tx.getMessageSize(); 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(). // 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. // 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(); parseTransactions();
length = cursor - offset; 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 { protected void parseLite() throws ProtocolException {
// Ignore the header since it has fixed length. If length is not provided we will have to // 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()) if (transactions.isEmpty())
throw new VerificationException("Block had no transactions"); throw new VerificationException("Block had no transactions");
maybeParseTransactions(); maybeParseTransactions();
if (this.getMessageSize() > MAX_BLOCK_SIZE) if (this.getOptimalEncodingMessageSize() > MAX_BLOCK_SIZE)
throw new VerificationException("Block larger than MAX_BLOCK_SIZE"); throw new VerificationException("Block larger than MAX_BLOCK_SIZE");
checkTransactions(); checkTransactions();
checkMerkleRoot(); 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. // If this transaction is not stored in the wallet, appearsInHashes is null.
private Set<Sha256Hash> appearsInHashes; 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) { public Transaction(NetworkParameters params) {
super(params); super(params);
@ -478,26 +485,44 @@ public class Transaction extends ChildMessage implements Serializable {
cursor = offset; cursor = offset;
version = readUint32(); version = readUint32();
optimalEncodingMessageSize = 4;
// First come the inputs. // First come the inputs.
long numInputs = readVarInt(); long numInputs = readVarInt();
optimalEncodingMessageSize += VarInt.sizeOf(numInputs);
inputs = new ArrayList<TransactionInput>((int) numInputs); inputs = new ArrayList<TransactionInput>((int) numInputs);
for (long i = 0; i < numInputs; i++) { for (long i = 0; i < numInputs; i++) {
TransactionInput input = new TransactionInput(params, this, bytes, cursor, parseLazy, parseRetain); TransactionInput input = new TransactionInput(params, this, bytes, cursor, parseLazy, parseRetain);
inputs.add(input); 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 // Now the outputs
long numOutputs = readVarInt(); long numOutputs = readVarInt();
optimalEncodingMessageSize += VarInt.sizeOf(numOutputs);
outputs = new ArrayList<TransactionOutput>((int) numOutputs); outputs = new ArrayList<TransactionOutput>((int) numOutputs);
for (long i = 0; i < numOutputs; i++) { for (long i = 0; i < numOutputs; i++) {
TransactionOutput output = new TransactionOutput(params, this, bytes, cursor, parseLazy, parseRetain); TransactionOutput output = new TransactionOutput(params, this, bytes, cursor, parseLazy, parseRetain);
outputs.add(output); outputs.add(output);
cursor += output.getMessageSize(); long scriptLen = readVarInt(8);
optimalEncodingMessageSize += 8 + VarInt.sizeOf(scriptLen) + scriptLen;
cursor += scriptLen;
} }
lockTime = readUint32(); lockTime = readUint32();
optimalEncodingMessageSize += 4;
length = cursor - offset; 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 * A coinbase transaction is one that creates a new coin. They are the first transaction in each block and their