Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,21 +1,24 @@
package org.tron.core.net.message.adv;

import com.google.protobuf.UnknownFieldSet;
import org.tron.common.overlay.message.Message;
import org.tron.common.utils.Sha256Hash;
import org.tron.core.capsule.BlockCapsule;
import org.tron.core.capsule.BlockCapsule.BlockId;
import org.tron.core.capsule.TransactionCapsule;
import org.tron.core.net.message.MessageTypes;
import org.tron.core.net.message.TronMessage;
import org.tron.protos.Protocol.Block;

public class BlockMessage extends TronMessage {

private BlockCapsule block;

public BlockMessage(byte[] data) throws Exception {
super(data);
this.type = MessageTypes.BLOCK.asByte();
this.block = new BlockCapsule(getCodedInputStream(data));
Block parsed = Block.parseFrom(getCodedInputStream(data));
this.block = new BlockCapsule(sanitize(parsed));
this.data = this.block.getData();
if (Message.isFilter()) {
Message.compareBytes(data, block.getInstance().toByteArray());
TransactionCapsule.validContractProto(block.getInstance().getTransactionsList());
Expand All @@ -28,6 +31,25 @@ public BlockMessage(BlockCapsule block) {
this.block = block;
}

private static Block sanitize(Block block) {
boolean blockHasUnknown = !block.getUnknownFields().asMap().isEmpty();
boolean headerHasUnknown = !block.getBlockHeader().getUnknownFields().asMap().isEmpty();
if (!blockHasUnknown && !headerHasUnknown) {
return block;
}
UnknownFieldSet empty = UnknownFieldSet.getDefaultInstance();
Block.Builder builder = block.toBuilder();
if (blockHasUnknown) {
builder.setUnknownFields(empty);
}
if (headerHasUnknown) {
builder.setBlockHeader(block.getBlockHeader().toBuilder()
.setUnknownFields(empty)
.build());
}
return builder.build();
}

public BlockId getBlockId() {
return getBlockCapsule().getBlockId();
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package org.tron.core.net.message.adv;

import com.google.protobuf.UnknownFieldSet;
import org.tron.common.overlay.message.Message;
import org.tron.common.utils.Sha256Hash;
import org.tron.core.capsule.TransactionCapsule;
Expand All @@ -12,8 +13,10 @@ public class TransactionMessage extends TronMessage {
private TransactionCapsule transactionCapsule;

public TransactionMessage(byte[] data) throws Exception {
super(data);
this.transactionCapsule = new TransactionCapsule(getCodedInputStream(data));
Transaction parsed = Transaction.parseFrom(getCodedInputStream(data));
Transaction sanitized = sanitize(parsed);
this.transactionCapsule = new TransactionCapsule(sanitized);
this.data = this.transactionCapsule.getData();
this.type = MessageTypes.TRX.asByte();
if (Message.isFilter()) {
compareBytes(data, transactionCapsule.getInstance().toByteArray());
Expand All @@ -28,6 +31,15 @@ public TransactionMessage(Transaction trx) {
this.data = trx.toByteArray();
}

static Transaction sanitize(Transaction transaction) {
if (transaction.getUnknownFields().asMap().isEmpty()) {
return transaction;
}
return transaction.toBuilder()
.setUnknownFields(UnknownFieldSet.getDefaultInstance())
.build();
}

@Override
public String toString() {
return new StringBuilder().append(super.toString())
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package org.tron.core.net.message.adv;

import com.google.protobuf.UnknownFieldSet;
import java.util.List;
import org.tron.core.capsule.TransactionCapsule;
import org.tron.core.net.message.MessageTypes;
Expand All @@ -20,15 +21,41 @@ public TransactionsMessage(List<Transaction> trxs) {
}

public TransactionsMessage(byte[] data) throws Exception {
super(data);
this.type = MessageTypes.TRXS.asByte();
this.transactions = Protocol.Transactions.parseFrom(getCodedInputStream(data));
Protocol.Transactions parsed = Protocol.Transactions.parseFrom(getCodedInputStream(data));
this.transactions = sanitize(parsed);
this.data = this.transactions.toByteArray();
if (isFilter()) {
compareBytes(data, transactions.toByteArray());
TransactionCapsule.validContractProto(transactions.getTransactionsList());
}
}

private static Protocol.Transactions sanitize(Protocol.Transactions transactions) {
boolean wrapperHasUnknown = !transactions.getUnknownFields().asMap().isEmpty();
boolean anyTxHasUnknown = false;
for (Transaction tx : transactions.getTransactionsList()) {
if (!tx.getUnknownFields().asMap().isEmpty()) {
anyTxHasUnknown = true;
break;
}
}
if (!wrapperHasUnknown && !anyTxHasUnknown) {
return transactions;
}
Protocol.Transactions.Builder builder = transactions.toBuilder();
if (wrapperHasUnknown) {
builder.setUnknownFields(UnknownFieldSet.getDefaultInstance());
}
if (anyTxHasUnknown) {
builder.clearTransactions();
for (Transaction tx : transactions.getTransactionsList()) {
builder.addTransactions(TransactionMessage.sanitize(tx));
}
}
return builder.build();
}

public Protocol.Transactions getTransactions() {
return transactions;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,198 @@
package org.tron.core.net.message.adv;

import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;

import com.google.protobuf.ByteString;
import com.google.protobuf.UnknownFieldSet;
import org.junit.BeforeClass;
import org.junit.Test;
import org.mockito.Mockito;
import org.tron.common.overlay.message.Message;
import org.tron.core.store.DynamicPropertiesStore;
import org.tron.protos.Protocol.Block;
import org.tron.protos.Protocol.BlockHeader;
import org.tron.protos.Protocol.Transaction;
import org.tron.protos.Protocol.Transactions;

/**
* Verifies that the three P2P message constructors that take raw bytes
* (BlockMessage, TransactionMessage, TransactionsMessage) strip unknown
* protobuf fields at positions not covered by any consensus hash, while
* leaving the signed regions byte-identical so transaction id and block hash
* remain stable.
*/
public class SanitizeUnknownFieldsTest {

/**
* 1 KiB length-delimited unknown field at tag 99999, large enough that any
* sanitize-stripped message is noticeably shorter than the padded input.
*/
private static final UnknownFieldSet PADDING = UnknownFieldSet.newBuilder()
.addField(99999, UnknownFieldSet.Field.newBuilder()
.addLengthDelimited(ByteString.copyFrom(new byte[1024]))
.build())
.build();

@BeforeClass
public static void setUp() {
// Mock dynamicPropertiesStore so Message.isFilter() returns false
// (mock's primitive-long getter returns 0L by default).
Message.setDynamicPropertiesStore(Mockito.mock(DynamicPropertiesStore.class));
}

private static BlockHeader.raw sampleRawHeader() {
return BlockHeader.raw.newBuilder()
.setNumber(100)
.setTimestamp(123456789L)
.build();
}

private static Block sampleBlock() {
return Block.newBuilder()
.setBlockHeader(BlockHeader.newBuilder().setRawData(sampleRawHeader()).build())
.build();
}

private static Transaction sampleTransaction() {
return Transaction.newBuilder()
.setRawData(Transaction.raw.newBuilder().setTimestamp(123456789L).build())
.build();
}

// ---- BlockMessage ----

@Test
public void testBlockMessageStripsBlockLevelUnknownFields() throws Exception {
Block padded = sampleBlock().toBuilder().setUnknownFields(PADDING).build();
byte[] paddedBytes = padded.toByteArray();

BlockMessage msg = new BlockMessage(paddedBytes);

assertTrue("Block-level unknown fields should be stripped",
msg.getBlockCapsule().getInstance().getUnknownFields().asMap().isEmpty());
assertTrue("msg.data should be canonical (no padding)",
msg.getData().length < paddedBytes.length);
}

@Test
public void testBlockMessageStripsBlockHeaderOuterUnknownFields() throws Exception {
BlockHeader paddedHeader = BlockHeader.newBuilder()
.setRawData(sampleRawHeader())
.setUnknownFields(PADDING)
.build();
Block padded = Block.newBuilder().setBlockHeader(paddedHeader).build();
byte[] paddedBytes = padded.toByteArray();

BlockMessage msg = new BlockMessage(paddedBytes);

assertTrue("BlockHeader outer unknown fields should be stripped",
msg.getBlockCapsule().getInstance().getBlockHeader()
.getUnknownFields().asMap().isEmpty());
assertTrue(msg.getData().length < paddedBytes.length);
}

@Test
public void testBlockMessagePreservesBlockHeaderRawData() throws Exception {
Block clean = sampleBlock();
Block padded = clean.toBuilder().setUnknownFields(PADDING).build();

BlockMessage msg = new BlockMessage(padded.toByteArray());

assertEquals("BlockHeader.raw_data must be byte-identical so block hash matches",
clean.getBlockHeader().getRawData(),
msg.getBlockCapsule().getInstance().getBlockHeader().getRawData());
}

@Test
public void testBlockMessageCleanBlockPassesThroughUnchanged() throws Exception {
Block clean = sampleBlock();
byte[] cleanBytes = clean.toByteArray();

BlockMessage msg = new BlockMessage(cleanBytes);

assertArrayEquals("Clean block bytes should pass through unchanged",
cleanBytes, msg.getData());
}

// ---- TransactionMessage ----

@Test
public void testTransactionMessageStripsTopLevelUnknownFields() throws Exception {
Transaction padded = sampleTransaction().toBuilder().setUnknownFields(PADDING).build();
byte[] paddedBytes = padded.toByteArray();

TransactionMessage msg = new TransactionMessage(paddedBytes);

assertTrue("Transaction-level unknown fields should be stripped",
msg.getTransactionCapsule().getInstance().getUnknownFields().asMap().isEmpty());
assertTrue(msg.getData().length < paddedBytes.length);
}

@Test
public void testTransactionMessagePreservesTransactionId() throws Exception {
Transaction clean = sampleTransaction();
Transaction padded = clean.toBuilder().setUnknownFields(PADDING).build();

TransactionMessage cleanMsg = new TransactionMessage(clean.toByteArray());
TransactionMessage paddedMsg = new TransactionMessage(padded.toByteArray());

assertEquals("Padding outside raw_data must not change tx id",
cleanMsg.getTransactionCapsule().getTransactionId(),
paddedMsg.getTransactionCapsule().getTransactionId());
}

@Test
public void testTransactionMessageCleanTransactionPassesThroughUnchanged() throws Exception {
Transaction clean = sampleTransaction();
byte[] cleanBytes = clean.toByteArray();

TransactionMessage msg = new TransactionMessage(cleanBytes);

assertArrayEquals(cleanBytes, msg.getData());
}

// ---- TransactionsMessage ----

@Test
public void testTransactionsMessageStripsWrapperUnknownFields() throws Exception {
Transactions padded = Transactions.newBuilder()
.addTransactions(sampleTransaction())
.setUnknownFields(PADDING)
.build();
byte[] paddedBytes = padded.toByteArray();

TransactionsMessage msg = new TransactionsMessage(paddedBytes);

assertTrue("Wrapper unknown fields should be stripped",
msg.getTransactions().getUnknownFields().asMap().isEmpty());
assertTrue(msg.getData().length < paddedBytes.length);
}

@Test
public void testTransactionsMessageStripsNestedTxUnknownFields() throws Exception {
Transaction paddedTx = sampleTransaction().toBuilder().setUnknownFields(PADDING).build();
Transactions wrapper = Transactions.newBuilder().addTransactions(paddedTx).build();
byte[] paddedBytes = wrapper.toByteArray();

TransactionsMessage msg = new TransactionsMessage(paddedBytes);

assertTrue("Nested tx unknown fields should be stripped",
msg.getTransactions().getTransactions(0)
.getUnknownFields().asMap().isEmpty());
assertTrue(msg.getData().length < paddedBytes.length);
}

@Test
public void testTransactionsMessageCleanWrapperPassesThroughUnchanged() throws Exception {
Transactions clean = Transactions.newBuilder()
.addTransactions(sampleTransaction())
.build();
byte[] cleanBytes = clean.toByteArray();

TransactionsMessage msg = new TransactionsMessage(cleanBytes);

assertArrayEquals(cleanBytes, msg.getData());
}
}
Loading