Skip to content

Commit cdcceba

Browse files
committed
feat(net): add inbound size hard-cap and lazy sanitize fast-path
- BlockMessage strips Block and BlockHeader outer unknown fields and enforces the existing BLOCK_SIZE + ONE_THOUSAND cap before parsing. - TransactionMessage strips top-level transaction unknown fields and rejects oversized transactions with Manager.validateCommon-equivalent size checks after parsing. - TransactionsMessage strips wrapper and nested transaction top-level unknown fields and applies the same per-transaction size check to each entry.
1 parent 0d97d4c commit cdcceba

6 files changed

Lines changed: 276 additions & 36 deletions

File tree

framework/src/main/java/org/tron/core/Wallet.java

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -177,6 +177,7 @@
177177
import org.tron.core.exception.ItemNotFoundException;
178178
import org.tron.core.exception.MaintenanceUnavailableException;
179179
import org.tron.core.exception.NonUniqueObjectException;
180+
import org.tron.core.exception.P2pException;
180181
import org.tron.core.exception.PermissionException;
181182
import org.tron.core.exception.SignatureFormatException;
182183
import org.tron.core.exception.StoreException;
@@ -606,6 +607,18 @@ public GrpcAPI.Return broadcastTransaction(Transaction signedTransaction) {
606607
return builder.setResult(false).setCode(response_code.TRANSACTION_EXPIRATION_ERROR)
607608
.setMessage(ByteString.copyFromUtf8("Transaction expired"))
608609
.build();
610+
} catch (P2pException e) {
611+
logger.info(BROADCAST_TRANS_FAILED, txID, e.getMessage());
612+
if (e.getType() == P2pException.TypeEnum.BAD_MESSAGE) {
613+
// Size cap check in new TransactionMessage(byte[]) above. Map back to
614+
// the historical TOO_BIG_TRANSACTION_ERROR response code so callers
615+
// that branch on it keep working after the check moved up from
616+
// Manager.validateCommon.
617+
return builder.setResult(false).setCode(response_code.TOO_BIG_TRANSACTION_ERROR)
618+
.setMessage(ByteString.copyFromUtf8(e.getMessage())).build();
619+
}
620+
return builder.setResult(false).setCode(response_code.OTHER_ERROR)
621+
.setMessage(ByteString.copyFromUtf8("Error: " + e.getMessage())).build();
609622
} catch (Exception e) {
610623
logger.warn("Broadcast transaction {} failed", txID, e);
611624
return builder.setResult(false).setCode(response_code.OTHER_ERROR)

framework/src/main/java/org/tron/core/net/message/adv/BlockMessage.java

Lines changed: 21 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,44 @@
11
package org.tron.core.net.message.adv;
22

3+
import static org.tron.core.config.Parameter.ChainConstant.BLOCK_SIZE;
4+
35
import com.google.protobuf.UnknownFieldSet;
46
import org.tron.common.overlay.message.Message;
57
import org.tron.common.utils.Sha256Hash;
8+
import org.tron.core.Constant;
69
import org.tron.core.capsule.BlockCapsule;
710
import org.tron.core.capsule.BlockCapsule.BlockId;
811
import org.tron.core.capsule.TransactionCapsule;
12+
import org.tron.core.exception.P2pException;
13+
import org.tron.core.exception.P2pException.TypeEnum;
914
import org.tron.core.net.message.MessageTypes;
1015
import org.tron.core.net.message.TronMessage;
1116
import org.tron.protos.Protocol.Block;
1217

1318
public class BlockMessage extends TronMessage {
1419

20+
// Raw byte limit on the inbound wire frame. Matches the post-parse cap
21+
// previously enforced in BlockMsgHandler (BLOCK_SIZE + ONE_THOUSAND).
22+
static final int MAX_RAW_SIZE = BLOCK_SIZE + Constant.ONE_THOUSAND;
23+
1524
private BlockCapsule block;
1625

1726
public BlockMessage(byte[] data) throws Exception {
27+
if (data.length > MAX_RAW_SIZE) {
28+
throw new P2pException(TypeEnum.BAD_MESSAGE, "block size over limit");
29+
}
1830
this.type = MessageTypes.BLOCK.asByte();
1931
Block parsed = Block.parseFrom(getCodedInputStream(data));
20-
this.block = new BlockCapsule(sanitize(parsed));
21-
this.data = this.block.getData();
32+
Block sanitized = sanitize(parsed);
33+
this.block = new BlockCapsule(sanitized);
34+
// Reuse the input bytes only when no canonicalization happened. parseFrom
35+
// may strip unknown fields when shouldDiscardUnknownFields is set (via
36+
// Message.isFilter()), so check that the parsed size still equals the
37+
// raw input length.
38+
this.data = (sanitized == parsed && parsed.getSerializedSize() == data.length)
39+
? data : this.block.getData();
2240
if (Message.isFilter()) {
23-
Message.compareBytes(data, block.getInstance().toByteArray());
41+
Message.compareBytes(data, this.data);
2442
TransactionCapsule.validContractProto(block.getInstance().getTransactionsList());
2543
}
2644
}

framework/src/main/java/org/tron/core/net/message/adv/TransactionMessage.java

Lines changed: 36 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,10 @@
33
import com.google.protobuf.UnknownFieldSet;
44
import org.tron.common.overlay.message.Message;
55
import org.tron.common.utils.Sha256Hash;
6+
import org.tron.core.Constant;
67
import org.tron.core.capsule.TransactionCapsule;
8+
import org.tron.core.exception.P2pException;
9+
import org.tron.core.exception.P2pException.TypeEnum;
710
import org.tron.core.net.message.MessageTypes;
811
import org.tron.core.net.message.TronMessage;
912
import org.tron.protos.Protocol.Transaction;
@@ -14,13 +17,19 @@ public class TransactionMessage extends TronMessage {
1417

1518
public TransactionMessage(byte[] data) throws Exception {
1619
Transaction parsed = Transaction.parseFrom(getCodedInputStream(data));
20+
checkSize(parsed);
1721
Transaction sanitized = sanitize(parsed);
1822
this.transactionCapsule = new TransactionCapsule(sanitized);
19-
this.data = this.transactionCapsule.getData();
23+
// Reuse the input bytes only when no canonicalization happened. parseFrom
24+
// may also strip unknown fields when shouldDiscardUnknownFields is set
25+
// (via Message.isFilter()), so check that the parsed size still equals
26+
// the raw input length.
27+
this.data = (sanitized == parsed && parsed.getSerializedSize() == data.length)
28+
? data : this.transactionCapsule.getData();
2029
this.type = MessageTypes.TRX.asByte();
2130
if (Message.isFilter()) {
22-
compareBytes(data, transactionCapsule.getInstance().toByteArray());
23-
transactionCapsule
31+
compareBytes(data, this.data);
32+
TransactionCapsule
2433
.validContractProto(transactionCapsule.getInstance().getRawData().getContract(0));
2534
}
2635
}
@@ -31,10 +40,34 @@ public TransactionMessage(Transaction trx) {
3140
this.data = trx.toByteArray();
3241
}
3342

43+
/**
44+
* Mirror Manager.validateCommon's size check. Static helper, also invoked
45+
* from {@link TransactionsMessage} for each tx in a batch.
46+
*/
47+
static void checkSize(Transaction transaction) throws P2pException {
48+
TransactionCapsule capsule = new TransactionCapsule(transaction);
49+
capsule.removeRedundantRet();
50+
long generalBytesSize = capsule.getInstance().toBuilder().clearRet().build().getSerializedSize()
51+
+ Constant.MAX_RESULT_SIZE_IN_TX + Constant.MAX_RESULT_SIZE_IN_TX;
52+
if (generalBytesSize > Constant.TRANSACTION_MAX_BYTE_SIZE
53+
|| capsule.getData().length > Constant.TRANSACTION_MAX_BYTE_SIZE) {
54+
throw new P2pException(TypeEnum.BAD_MESSAGE, "transaction size over limit");
55+
}
56+
}
57+
3458
static Transaction sanitize(Transaction transaction) {
3559
if (transaction.getUnknownFields().asMap().isEmpty()) {
3660
return transaction;
3761
}
62+
return doSanitize(transaction);
63+
}
64+
65+
/**
66+
* Unconditional sanitize — strips Transaction top-level unknown fields.
67+
* Callers that have already checked emptiness (e.g. {@link TransactionsMessage})
68+
* skip {@link #sanitize}'s fast-path check and invoke this directly.
69+
*/
70+
static Transaction doSanitize(Transaction transaction) {
3871
return transaction.toBuilder()
3972
.setUnknownFields(UnknownFieldSet.getDefaultInstance())
4073
.build();

framework/src/main/java/org/tron/core/net/message/adv/TransactionsMessage.java

Lines changed: 23 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
import com.google.protobuf.UnknownFieldSet;
44
import java.util.List;
55
import org.tron.core.capsule.TransactionCapsule;
6+
import org.tron.core.exception.P2pException;
67
import org.tron.core.net.message.MessageTypes;
78
import org.tron.core.net.message.TronMessage;
89
import org.tron.protos.Protocol;
@@ -24,36 +25,37 @@ public TransactionsMessage(byte[] data) throws Exception {
2425
this.type = MessageTypes.TRXS.asByte();
2526
Protocol.Transactions parsed = Protocol.Transactions.parseFrom(getCodedInputStream(data));
2627
this.transactions = sanitize(parsed);
27-
this.data = this.transactions.toByteArray();
28+
// Reuse the input bytes only when no canonicalization happened. parseFrom
29+
// may strip unknown fields when shouldDiscardUnknownFields is set (via
30+
// Message.isFilter()), so check that the parsed size still equals the
31+
// raw input length.
32+
this.data = (this.transactions == parsed && parsed.getSerializedSize() == data.length)
33+
? data : this.transactions.toByteArray();
2834
if (isFilter()) {
29-
compareBytes(data, transactions.toByteArray());
35+
compareBytes(data, this.data);
3036
TransactionCapsule.validContractProto(transactions.getTransactionsList());
3137
}
3238
}
3339

34-
private static Protocol.Transactions sanitize(Protocol.Transactions transactions) {
35-
boolean wrapperHasUnknown = !transactions.getUnknownFields().asMap().isEmpty();
36-
boolean anyTxHasUnknown = false;
37-
for (Transaction tx : transactions.getTransactionsList()) {
38-
if (!tx.getUnknownFields().asMap().isEmpty()) {
39-
anyTxHasUnknown = true;
40-
break;
41-
}
42-
}
43-
if (!wrapperHasUnknown && !anyTxHasUnknown) {
44-
return transactions;
45-
}
46-
Protocol.Transactions.Builder builder = transactions.toBuilder();
47-
if (wrapperHasUnknown) {
40+
private static Protocol.Transactions sanitize(Protocol.Transactions raw)
41+
throws P2pException {
42+
Protocol.Transactions.Builder builder = null;
43+
if (!raw.getUnknownFields().asMap().isEmpty()) {
44+
builder = raw.toBuilder();
4845
builder.setUnknownFields(UnknownFieldSet.getDefaultInstance());
4946
}
50-
if (anyTxHasUnknown) {
51-
builder.clearTransactions();
52-
for (Transaction tx : transactions.getTransactionsList()) {
53-
builder.addTransactions(TransactionMessage.sanitize(tx));
47+
48+
for (int i = 0; i < raw.getTransactionsCount(); i++) {
49+
Transaction tx = raw.getTransactions(i);
50+
TransactionMessage.checkSize(tx);
51+
if (!tx.getUnknownFields().asMap().isEmpty()) {
52+
if (builder == null) {
53+
builder = raw.toBuilder();
54+
}
55+
builder.setTransactions(i, TransactionMessage.doSanitize(tx));
5456
}
5557
}
56-
return builder.build();
58+
return builder == null ? raw : builder.build();
5759
}
5860

5961
public Protocol.Transactions getTransactions() {

framework/src/main/java/org/tron/core/net/messagehandler/BlockMsgHandler.java

Lines changed: 0 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,13 @@
11
package org.tron.core.net.messagehandler;
22

33
import static org.tron.core.config.Parameter.ChainConstant.BLOCK_PRODUCED_INTERVAL;
4-
import static org.tron.core.config.Parameter.ChainConstant.BLOCK_SIZE;
54

65
import lombok.extern.slf4j.Slf4j;
76
import org.bouncycastle.util.encoders.Hex;
87
import org.springframework.beans.factory.annotation.Autowired;
98
import org.springframework.stereotype.Component;
109
import org.tron.common.prometheus.MetricKeys;
1110
import org.tron.common.prometheus.Metrics;
12-
import org.tron.core.Constant;
1311
import org.tron.core.capsule.BlockCapsule;
1412
import org.tron.core.capsule.BlockCapsule.BlockId;
1513
import org.tron.core.config.args.Args;
@@ -51,8 +49,6 @@ public class BlockMsgHandler implements TronMsgHandler {
5149
@Autowired
5250
private WitnessProductBlockService witnessProductBlockService;
5351

54-
private int maxBlockSize = BLOCK_SIZE + Constant.ONE_THOUSAND;
55-
5652
private boolean fastForward = Args.getInstance().isFastForward();
5753

5854
@Override
@@ -62,11 +58,6 @@ public void processMessage(PeerConnection peer, TronMessage msg) throws P2pExcep
6258
BlockId blockId = blockMessage.getBlockId();
6359

6460
BlockCapsule blockCapsule = blockMessage.getBlockCapsule();
65-
if (blockCapsule.getInstance().getSerializedSize() > maxBlockSize) {
66-
logger.error("Receive bad block {} from peer {}, block size over limit",
67-
blockMessage.getBlockId(), peer.getInetSocketAddress());
68-
throw new P2pException(TypeEnum.BAD_MESSAGE, "block size over limit");
69-
}
7061
long gap = blockCapsule.getTimeStamp() - System.currentTimeMillis();
7162
if (gap >= BLOCK_PRODUCED_INTERVAL) {
7263
logger.error("Receive bad block {} from peer {}, block time error",

0 commit comments

Comments
 (0)