diff --git a/chainbase/src/main/java/org/tron/core/capsule/TransactionCapsule.java b/chainbase/src/main/java/org/tron/core/capsule/TransactionCapsule.java index 8724a688548..30d1f4f5aae 100755 --- a/chainbase/src/main/java/org/tron/core/capsule/TransactionCapsule.java +++ b/chainbase/src/main/java/org/tron/core/capsule/TransactionCapsule.java @@ -18,6 +18,7 @@ import static org.tron.common.utils.StringUtil.encode58Check; import static org.tron.common.utils.WalletUtil.checkPermissionOperations; import static org.tron.core.Constant.MAX_CONTRACT_RESULT_SIZE; +import static org.tron.core.Constant.MAX_PER_SIGN_LENGTH; import static org.tron.core.exception.P2pException.TypeEnum.PROTOBUF_ERROR; import com.google.common.primitives.Bytes; @@ -505,6 +506,30 @@ public boolean sanitize() { return true; } + public boolean sanitizeSignatures() { + List sigs = this.transaction.getSignatureList(); + boolean changed = false; + for (ByteString sig : sigs) { + if (sig.size() > MAX_PER_SIGN_LENGTH) { + changed = true; + break; + } + } + if (!changed) { + return false; + } + Transaction.Builder builder = this.transaction.toBuilder().clearSignature(); + for (ByteString sig : sigs) { + if (sig.size() > MAX_PER_SIGN_LENGTH) { + builder.addSignature(sig.substring(0, MAX_PER_SIGN_LENGTH)); + } else { + builder.addSignature(sig); + } + } + this.transaction = builder.build(); + return true; + } + public void resetResult() { if (this.getInstance().getRetCount() > 0) { this.transaction = this.getInstance().toBuilder().clearRet().build(); @@ -631,7 +656,7 @@ public void addSign(byte[] privateKey, AccountStore accountStore) .signHash(getTransactionId().getBytes()))); this.transaction = this.transaction.toBuilder().addSignature(sig).build(); } - + private static void checkPermission(int permissionId, Permission permission, Transaction.Contract contract) throws PermissionException { if (permissionId != 0) { if (permission.getType() != PermissionType.Active) { @@ -714,7 +739,7 @@ public boolean validateSignature(AccountStore accountStore, } } isVerified = true; - } + } return true; } diff --git a/crypto/src/main/java/org/tron/common/crypto/SignUtils.java b/crypto/src/main/java/org/tron/common/crypto/SignUtils.java index e0e20fb2677..b921d548e8b 100644 --- a/crypto/src/main/java/org/tron/common/crypto/SignUtils.java +++ b/crypto/src/main/java/org/tron/common/crypto/SignUtils.java @@ -1,8 +1,5 @@ package org.tron.common.crypto; -import static org.tron.core.Constant.MAX_PER_SIGN_LENGTH; -import static org.tron.core.Constant.PER_SIGN_LENGTH; - import java.security.SecureRandom; import java.security.SignatureException; import org.tron.common.crypto.ECKey.ECDSASignature; @@ -11,21 +8,6 @@ public class SignUtils { - /** - * Strict signature-length check for admission entry-points (RPC broadcast, - * P2P transaction ingress, peer hello handshake). Accepts only sizes in - * [{@link org.tron.core.Constant#PER_SIGN_LENGTH PER_SIGN_LENGTH}, - * {@link org.tron.core.Constant#MAX_PER_SIGN_LENGTH MAX_PER_SIGN_LENGTH}]. - * - *

Consensus paths (e.g. {@code TransactionCapsule.checkWeight}) intentionally - * keep the looser {@code size < 65} check to remain compatible with historical - * on-chain signatures that carry trailing padding bytes; do not call this - * helper from those paths. - */ - public static boolean isValidLength(int size) { - return size >= PER_SIGN_LENGTH && size <= MAX_PER_SIGN_LENGTH; - } - public static SignInterface getGeneratedRandomSign( SecureRandom secureRandom, boolean isECKeyCryptoEngine) { if (isECKeyCryptoEngine) { diff --git a/framework/src/main/java/org/tron/core/Wallet.java b/framework/src/main/java/org/tron/core/Wallet.java index 72b8d7090d9..bcaecbd18a6 100755 --- a/framework/src/main/java/org/tron/core/Wallet.java +++ b/framework/src/main/java/org/tron/core/Wallet.java @@ -505,15 +505,7 @@ public GrpcAPI.Return broadcastTransaction(Transaction signedTransaction) { trx.setTime(System.currentTimeMillis()); Sha256Hash txID = trx.getTransactionId(); try { - for (ByteString sig : signedTransaction.getSignatureList()) { - if (!SignUtils.isValidLength(sig.size())) { - String info = "Signature size is " + sig.size(); - logger.warn("Broadcast transaction {} has failed, {}.", txID, info); - return builder.setResult(false).setCode(response_code.SIGERROR) - .setMessage(ByteString.copyFromUtf8("Validate signature error: " + info)) - .build(); - } - } + trx.sanitizeSignatures(); if (tronNetDelegate.isBlockUnsolidified()) { logger.warn("Broadcast transaction {} has failed, block unsolidified.", txID); diff --git a/framework/src/main/java/org/tron/core/net/message/adv/TransactionsMessage.java b/framework/src/main/java/org/tron/core/net/message/adv/TransactionsMessage.java index 2193ac6b546..a1293cc35cd 100644 --- a/framework/src/main/java/org/tron/core/net/message/adv/TransactionsMessage.java +++ b/framework/src/main/java/org/tron/core/net/message/adv/TransactionsMessage.java @@ -1,5 +1,6 @@ package org.tron.core.net.message.adv; +import java.util.ArrayList; import java.util.List; import org.tron.core.capsule.TransactionCapsule; import org.tron.core.net.message.MessageTypes; @@ -33,6 +34,29 @@ public Protocol.Transactions getTransactions() { return transactions; } + public boolean sanitizeSignature() { + List list = transactions.getTransactionsList(); + boolean changed = false; + List sanitized = new ArrayList<>(list.size()); + for (Transaction trx : list) { + TransactionCapsule cap = new TransactionCapsule(trx); + if (cap.sanitizeSignatures()) { + changed = true; + sanitized.add(cap.getInstance()); + } else { + sanitized.add(trx); + } + } + if (!changed) { + return false; + } + Protocol.Transactions.Builder builder = Protocol.Transactions.newBuilder(); + sanitized.forEach(builder::addTransactions); + this.transactions = builder.build(); + this.data = this.transactions.toByteArray(); + return true; + } + @Override public String toString() { return new StringBuilder().append(super.toString()).append("trx size: ") diff --git a/framework/src/main/java/org/tron/core/net/messagehandler/TransactionsMsgHandler.java b/framework/src/main/java/org/tron/core/net/messagehandler/TransactionsMsgHandler.java index 52137c5881c..58d35514a37 100644 --- a/framework/src/main/java/org/tron/core/net/messagehandler/TransactionsMsgHandler.java +++ b/framework/src/main/java/org/tron/core/net/messagehandler/TransactionsMsgHandler.java @@ -1,6 +1,5 @@ package org.tron.core.net.messagehandler; -import com.google.protobuf.ByteString; import java.util.HashSet; import java.util.List; import java.util.Set; @@ -14,7 +13,6 @@ import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; -import org.tron.common.crypto.SignUtils; import org.tron.common.es.ExecutorServiceManager; import org.tron.common.utils.Sha256Hash; import org.tron.core.ChainBaseManager; @@ -127,6 +125,7 @@ public void processMessage(PeerConnection peer, TronMessage msg) throws P2pExcep } private void check(PeerConnection peer, TransactionsMessage msg) throws P2pException { + msg.sanitizeSignature(); List list = msg.getTransactions().getTransactionsList(); Set seen = new HashSet<>(list.size() * 2); for (Transaction trx : list) { @@ -144,12 +143,6 @@ private void check(PeerConnection peer, TransactionsMessage msg) throws P2pExcep throw new P2pException(TypeEnum.BAD_TRX, "tx " + item.getHash() + " contract size should be greater than 0"); } - for (ByteString sig : trx.getSignatureList()) { - if (!SignUtils.isValidLength(sig.size())) { - throw new P2pException(TypeEnum.BAD_TRX, - "tx " + item.getHash() + " signature size is " + sig.size()); - } - } } } diff --git a/framework/src/main/java/org/tron/core/net/service/relay/RelayService.java b/framework/src/main/java/org/tron/core/net/service/relay/RelayService.java index d4e010ff21d..f3570a3ed37 100644 --- a/framework/src/main/java/org/tron/core/net/service/relay/RelayService.java +++ b/framework/src/main/java/org/tron/core/net/service/relay/RelayService.java @@ -23,6 +23,7 @@ import org.tron.common.utils.ByteArray; import org.tron.common.utils.Sha256Hash; import org.tron.core.ChainBaseManager; +import org.tron.core.Constant; import org.tron.core.capsule.TransactionCapsule; import org.tron.core.config.args.Args; import org.tron.core.db.Manager; @@ -150,9 +151,10 @@ public boolean checkHelloMessage(HelloMessage message, Channel channel) { return false; } - if (!SignUtils.isValidLength(msg.getSignature().size())) { + int sigSize = msg.getSignature().size(); + if (sigSize < Constant.PER_SIGN_LENGTH || sigSize > Constant.MAX_PER_SIGN_LENGTH) { logger.warn("HelloMessage from {}, signature size is {}.", - channel.getInetAddress(), msg.getSignature().size()); + channel.getInetAddress(), sigSize); return false; } diff --git a/framework/src/test/java/org/tron/core/WalletMockTest.java b/framework/src/test/java/org/tron/core/WalletMockTest.java index c9184bf276d..3fffe7916dd 100644 --- a/framework/src/test/java/org/tron/core/WalletMockTest.java +++ b/framework/src/test/java/org/tron/core/WalletMockTest.java @@ -168,48 +168,21 @@ public void testCreateTransactionCapsuleWithoutValidateWithTimeout() public void testBroadcastTxInvalidSigLength() throws Exception { Wallet wallet = new Wallet(); TronNetDelegate tronNetDelegateMock = mock(TronNetDelegate.class); + when(tronNetDelegateMock.isBlockUnsolidified()).thenReturn(true); Field field = wallet.getClass().getDeclaredField("tronNetDelegate"); field.setAccessible(true); field.set(wallet, tronNetDelegateMock); - // signature shorter than 65 bytes → SIGERROR - Protocol.Transaction shortSig = Protocol.Transaction.newBuilder() - .addSignature(ByteString.copyFrom(new byte[64])) - .build(); - GrpcAPI.Return ret = wallet.broadcastTransaction(shortSig); - assertEquals(GrpcAPI.Return.response_code.SIGERROR, ret.getCode()); - - // signature longer than 68 bytes → SIGERROR - Protocol.Transaction longSig = Protocol.Transaction.newBuilder() - .addSignature(ByteString.copyFrom(new byte[69])) - .build(); - ret = wallet.broadcastTransaction(longSig); - assertEquals(GrpcAPI.Return.response_code.SIGERROR, ret.getCode()); - - // empty signature → SIGERROR - Protocol.Transaction emptySig = Protocol.Transaction.newBuilder() - .addSignature(ByteString.EMPTY) - .build(); - ret = wallet.broadcastTransaction(emptySig); - assertEquals(GrpcAPI.Return.response_code.SIGERROR, ret.getCode()); - - // tronNetDelegate must not be consulted because the request is rejected up front - Mockito.verify(tronNetDelegateMock, Mockito.never()).isBlockUnsolidified(); - - // 65-byte signature passes the length check and proceeds to downstream logic - when(tronNetDelegateMock.isBlockUnsolidified()).thenReturn(true); - Protocol.Transaction validSig = Protocol.Transaction.newBuilder() - .addSignature(ByteString.copyFrom(new byte[65])) - .build(); - ret = wallet.broadcastTransaction(validSig); - assertEquals(GrpcAPI.Return.response_code.BLOCK_UNSOLIDIFIED, ret.getCode()); - - // 68-byte signature (upper bound) also passes the length check - Protocol.Transaction paddedSig = Protocol.Transaction.newBuilder() - .addSignature(ByteString.copyFrom(new byte[68])) - .build(); - ret = wallet.broadcastTransaction(paddedSig); - assertEquals(GrpcAPI.Return.response_code.BLOCK_UNSOLIDIFIED, ret.getCode()); + // Signature length is no longer validated up front — any length proceeds past sanitize and + // reaches downstream logic (here: BLOCK_UNSOLIDIFIED stubbed for short-circuit). + for (int size : new int[] {0, 64, 65, 68, 69, 200}) { + Protocol.Transaction trx = Protocol.Transaction.newBuilder() + .addSignature(ByteString.copyFrom(new byte[size])) + .build(); + GrpcAPI.Return ret = wallet.broadcastTransaction(trx); + assertEquals("sig size=" + size, + GrpcAPI.Return.response_code.BLOCK_UNSOLIDIFIED, ret.getCode()); + } } @Test diff --git a/framework/src/test/java/org/tron/core/net/message/adv/SanitizeSignaturesTest.java b/framework/src/test/java/org/tron/core/net/message/adv/SanitizeSignaturesTest.java new file mode 100644 index 00000000000..c3916018956 --- /dev/null +++ b/framework/src/test/java/org/tron/core/net/message/adv/SanitizeSignaturesTest.java @@ -0,0 +1,145 @@ +package org.tron.core.net.message.adv; + +import static org.junit.Assert.assertArrayEquals; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertSame; +import static org.junit.Assert.assertTrue; + +import com.google.protobuf.ByteString; +import java.util.Arrays; +import org.junit.Test; +import org.tron.core.capsule.TransactionCapsule; +import org.tron.protos.Protocol.Transaction; + +/** + * Verifies that {@link TransactionCapsule#sanitizeSignatures()} and + * {@link TransactionsMessage#sanitizeSignature()} truncate any signature longer than 68 + * bytes to exactly 68 bytes while leaving in-range signatures (and the + * transaction id) untouched. + */ +public class SanitizeSignaturesTest { + + private static Transaction sampleTransaction() { + return Transaction.newBuilder() + .setRawData(Transaction.raw.newBuilder().setTimestamp(123456789L).build()) + .build(); + } + + // ---- TransactionCapsule.sanitizeSignatures ---- + + @Test + public void truncatesOversizedSignatureTo68Bytes() { + byte[] sigBytes = new byte[200]; + Arrays.fill(sigBytes, (byte) 0x7f); + Transaction padded = sampleTransaction().toBuilder() + .addSignature(ByteString.copyFrom(sigBytes)) + .build(); + TransactionCapsule capsule = new TransactionCapsule(padded); + + assertTrue("sanitizeSignatures() should report it mutated the capsule", + capsule.sanitizeSignatures()); + assertEquals(68, capsule.getInstance().getSignature(0).size()); + assertArrayEquals("First 68 bytes must be preserved", + Arrays.copyOf(sigBytes, 68), + capsule.getInstance().getSignature(0).toByteArray()); + } + + @Test + public void leavesSixtyFiveByteSignatureUnchanged() { + Transaction trx = sampleTransaction().toBuilder() + .addSignature(ByteString.copyFrom(new byte[65])) + .build(); + TransactionCapsule capsule = new TransactionCapsule(trx); + Transaction before = capsule.getInstance(); + + assertFalse(capsule.sanitizeSignatures()); + assertSame(before, capsule.getInstance()); + } + + @Test + public void leavesSixtyEightByteSignatureUnchanged() { + Transaction trx = sampleTransaction().toBuilder() + .addSignature(ByteString.copyFrom(new byte[68])) + .build(); + TransactionCapsule capsule = new TransactionCapsule(trx); + Transaction before = capsule.getInstance(); + + assertFalse(capsule.sanitizeSignatures()); + assertSame(before, capsule.getInstance()); + } + + @Test + public void leavesUndersizedSignatureUnchanged() { + Transaction trx = sampleTransaction().toBuilder() + .addSignature(ByteString.copyFrom(new byte[64])) + .build(); + TransactionCapsule capsule = new TransactionCapsule(trx); + Transaction before = capsule.getInstance(); + + assertFalse(capsule.sanitizeSignatures()); + assertSame(before, capsule.getInstance()); + assertEquals(64, capsule.getInstance().getSignature(0).size()); + } + + @Test + public void truncatesOnlyOversizedSignaturesInMixedList() { + Transaction trx = sampleTransaction().toBuilder() + .addSignature(ByteString.copyFrom(new byte[65])) + .addSignature(ByteString.copyFrom(new byte[100])) + .addSignature(ByteString.copyFrom(new byte[68])) + .build(); + TransactionCapsule capsule = new TransactionCapsule(trx); + + assertTrue(capsule.sanitizeSignatures()); + assertEquals(65, capsule.getInstance().getSignature(0).size()); + assertEquals(68, capsule.getInstance().getSignature(1).size()); + assertEquals(68, capsule.getInstance().getSignature(2).size()); + } + + @Test + public void preservesTransactionId() { + Transaction clean = sampleTransaction().toBuilder() + .addSignature(ByteString.copyFrom(new byte[65])) + .build(); + Transaction padded = sampleTransaction().toBuilder() + .addSignature(ByteString.copyFrom(new byte[200])) + .build(); + TransactionCapsule cleanCapsule = new TransactionCapsule(clean); + TransactionCapsule paddedCapsule = new TransactionCapsule(padded); + + paddedCapsule.sanitizeSignatures(); + + assertEquals("Truncating signatures must not change the transaction id", + cleanCapsule.getTransactionId(), paddedCapsule.getTransactionId()); + } + + // ---- TransactionsMessage.sanitizeSignature ---- + + @Test + public void messageSanitizeRewritesAffectedTransactionAndWireBytes() { + Transaction oversized = sampleTransaction().toBuilder() + .addSignature(ByteString.copyFrom(new byte[200])) + .build(); + TransactionsMessage msg = new TransactionsMessage(Arrays.asList(oversized)); + byte[] before = msg.getData(); + + assertTrue(msg.sanitizeSignature()); + assertEquals(68, msg.getTransactions().getTransactions(0).getSignature(0).size()); + assertTrue("Wire data should shrink after truncating", + msg.getData().length < before.length); + assertArrayEquals(msg.getTransactions().toByteArray(), msg.getData()); + } + + @Test + public void messageSanitizeIsNoOpWhenAllSignaturesInRange() { + Transaction inRange = sampleTransaction().toBuilder() + .addSignature(ByteString.copyFrom(new byte[65])) + .build(); + TransactionsMessage msg = new TransactionsMessage(Arrays.asList(inRange)); + byte[] before = msg.getData(); + + assertFalse(msg.sanitizeSignature()); + assertSame(before, msg.getData()); + } +} diff --git a/framework/src/test/java/org/tron/core/net/messagehandler/TransactionsMsgHandlerTest.java b/framework/src/test/java/org/tron/core/net/messagehandler/TransactionsMsgHandlerTest.java index ed2121d360f..9dc34eeefb5 100644 --- a/framework/src/test/java/org/tron/core/net/messagehandler/TransactionsMsgHandlerTest.java +++ b/framework/src/test/java/org/tron/core/net/messagehandler/TransactionsMsgHandlerTest.java @@ -231,7 +231,7 @@ private void stubAdvInvRequest(PeerConnection peer, TransactionsMessage msg) { Protocol.Inventory.InventoryType.TRX); advInvRequest.put(item, 0L); } - Mockito.when(peer.getAdvInvRequest()).thenReturn(advInvRequest); + Mockito.doReturn(advInvRequest).when(peer).getAdvInvRequest(); } @Test @@ -343,6 +343,8 @@ public void testInvalidSigLength() throws Exception { handler.init(); try { PeerConnection peer = Mockito.mock(PeerConnection.class); + // Short-circuit async handleTransaction so it doesn't race with later stubbing. + Mockito.doReturn(true).when(peer).isBadPeer(); BalanceContract.TransferContract transferContract = BalanceContract.TransferContract .newBuilder() @@ -351,42 +353,24 @@ public void testInvalidSigLength() throws Exception { .setToAddress(ByteString.copyFrom(ByteArray.fromHexString("232323a9cf"))) .build(); - // signature shorter than 65 bytes → BAD_TRX - Protocol.Transaction shortSigTrx = Protocol.Transaction.newBuilder() + Protocol.Transaction longSigTrx = Protocol.Transaction.newBuilder() .setRawData(Protocol.Transaction.raw.newBuilder() + .setRefBlockNum(1) .addContract(Protocol.Transaction.Contract.newBuilder() .setType(Protocol.Transaction.Contract.ContractType.TransferContract) .setParameter(Any.pack(transferContract)).build()) .build()) - .addSignature(ByteString.copyFrom(new byte[64])) + .addSignature(ByteString.copyFrom(new byte[69])) .build(); - - List shortList = new ArrayList<>(); - shortList.add(shortSigTrx); - stubAdvInvRequest(peer, new TransactionsMessage(shortList)); - P2pException shortEx = Assert.assertThrows(P2pException.class, - () -> handler.processMessage(peer, new TransactionsMessage(shortList))); - Assert.assertEquals(TypeEnum.BAD_TRX, shortEx.getType()); - - // signature longer than 68 bytes → BAD_TRX - Protocol.Transaction longSigTrx = Protocol.Transaction.newBuilder() + Protocol.Transaction veryLongSigTrx = Protocol.Transaction.newBuilder() .setRawData(Protocol.Transaction.raw.newBuilder() - .setRefBlockNum(1) + .setRefBlockNum(4) .addContract(Protocol.Transaction.Contract.newBuilder() .setType(Protocol.Transaction.Contract.ContractType.TransferContract) .setParameter(Any.pack(transferContract)).build()) .build()) - .addSignature(ByteString.copyFrom(new byte[69])) + .addSignature(ByteString.copyFrom(new byte[200])) .build(); - - List longList = new ArrayList<>(); - longList.add(longSigTrx); - stubAdvInvRequest(peer, new TransactionsMessage(longList)); - P2pException longEx = Assert.assertThrows(P2pException.class, - () -> handler.processMessage(peer, new TransactionsMessage(longList))); - Assert.assertEquals(TypeEnum.BAD_TRX, longEx.getType()); - - // exactly 65 bytes → passes the length check (no P2pException from check) Protocol.Transaction validSigTrx = Protocol.Transaction.newBuilder() .setRawData(Protocol.Transaction.raw.newBuilder() .setRefBlockNum(2) @@ -396,13 +380,6 @@ public void testInvalidSigLength() throws Exception { .build()) .addSignature(ByteString.copyFrom(new byte[65])) .build(); - - List validList = new ArrayList<>(); - validList.add(validSigTrx); - stubAdvInvRequest(peer, new TransactionsMessage(validList)); - handler.processMessage(peer, new TransactionsMessage(validList)); - - // 68 bytes (upper bound) also passes the length check Protocol.Transaction paddedSigTrx = Protocol.Transaction.newBuilder() .setRawData(Protocol.Transaction.raw.newBuilder() .setRefBlockNum(3) @@ -413,10 +390,41 @@ public void testInvalidSigLength() throws Exception { .addSignature(ByteString.copyFrom(new byte[68])) .build(); - List paddedList = new ArrayList<>(); - paddedList.add(paddedSigTrx); - stubAdvInvRequest(peer, new TransactionsMessage(paddedList)); - handler.processMessage(peer, new TransactionsMessage(paddedList)); + // Stub once with a map containing every test case's item; re-populate before each call so + // the in-place removal done by processMessage doesn't affect later cases. + Map advInvRequest = new ConcurrentHashMap<>(); + Mockito.doReturn(advInvRequest).when(peer).getAdvInvRequest(); + java.util.function.Consumer primeInv = trx -> { + Item item = new Item(new TransactionMessage(trx).getMessageId(), + Protocol.Inventory.InventoryType.TRX); + advInvRequest.put(item, 0L); + }; + + // signature longer than 68 bytes is truncated to 68 and accepted + primeInv.accept(longSigTrx); + TransactionsMessage longMsg = new TransactionsMessage( + new ArrayList<>(java.util.Collections.singletonList(longSigTrx))); + handler.processMessage(peer, longMsg); + Assert.assertEquals(68, + longMsg.getTransactions().getTransactions(0).getSignature(0).size()); + + // signature far longer than 68 bytes is also truncated to 68 + primeInv.accept(veryLongSigTrx); + TransactionsMessage veryLongMsg = new TransactionsMessage( + new ArrayList<>(java.util.Collections.singletonList(veryLongSigTrx))); + handler.processMessage(peer, veryLongMsg); + Assert.assertEquals(68, + veryLongMsg.getTransactions().getTransactions(0).getSignature(0).size()); + + // exactly 65 bytes → passes the length check + primeInv.accept(validSigTrx); + handler.processMessage(peer, new TransactionsMessage( + new ArrayList<>(java.util.Collections.singletonList(validSigTrx)))); + + // 68 bytes (upper bound) also passes the length check + primeInv.accept(paddedSigTrx); + handler.processMessage(peer, new TransactionsMessage( + new ArrayList<>(java.util.Collections.singletonList(paddedSigTrx)))); } finally { handler.close(); }