From 56fdb2676275eac00ba736eae28c4e3fa86a0b10 Mon Sep 17 00:00:00 2001 From: federico Date: Thu, 17 Sep 2026 14:34:55 +0800 Subject: [PATCH] fix(framework): handle shielded admission and validate note logs --- .../src/main/java/org/tron/core/Wallet.java | 160 +++++++++--------- .../org/tron/core/net/TronNetDelegate.java | 4 +- .../TransactionsMsgHandler.java | 8 +- .../tron/core/ShieldedTRC20BuilderTest.java | 108 +++++++++--- .../java/org/tron/core/WalletMockTest.java | 83 +++++++-- .../tron/core/net/TronNetDelegateTest.java | 14 ++ .../TransactionsMsgHandlerTest.java | 8 + 7 files changed, 260 insertions(+), 125 deletions(-) diff --git a/framework/src/main/java/org/tron/core/Wallet.java b/framework/src/main/java/org/tron/core/Wallet.java index ac54cb2b7ff..f13974ce902 100755 --- a/framework/src/main/java/org/tron/core/Wallet.java +++ b/framework/src/main/java/org/tron/core/Wallet.java @@ -264,6 +264,8 @@ public class Wallet { private static final String SHIELDED_TRANSACTION_SCAN_RANGE = "request requires start_block_index >= 0 && end_block_index > " + "start_block_index && end_block_index - start_block_index <= 1000"; + // ABI: pos(32) + cm(32) + cv(32) + epk(32) + ciphertext bytes32[21](672) + private static final int SHIELDED_TRC20_NOTE_LOG_SIZE = 800; private static String addressPreFixString = Constant.ADD_PRE_FIX_STRING_MAINNET;//default testnet private static final byte[] SHIELDED_TRC20_LOG_TOPICS_MINT = Hash.sha3(ByteArray.fromString( "MintNewLeaf(uint256,bytes32,bytes32,bytes32,bytes32[21])")); @@ -572,7 +574,13 @@ public GrpcAPI.Return broadcastTransaction(Transaction signedTransaction) { throw new ContractValidateException(ActuatorConstant.CONTRACT_NOT_EXIST); } trx.checkExpiration(chainBaseManager.getNextBlockSlotTime()); - dbManager.pushTransaction(trx); + if (!dbManager.pushTransaction(trx)) { + logger.info("Broadcast transaction {} has failed, local admission rejected.", txID); + return builder.setResult(false).setCode(response_code.SERVER_BUSY) + .setMessage(ByteString.copyFromUtf8( + "Transaction was not admitted to the pending pool.")) + .build(); + } TransactionMessage message = new TransactionMessage(trx.getInstance().toByteArray()); int num = tronNetService.fastBroadcastTransaction(message); if (num == 0 && minEffectiveConnection != 0) { @@ -3882,7 +3890,7 @@ private Optional getNoteTxFromLogListByIvk( int logType) throws ZksnarkException, ContractExeException { byte[] logData = log.getData().toByteArray(); - if (!ArrayUtils.isEmpty(logData) && logType > 0 && logType < 4) { + if (logData.length == SHIELDED_TRC20_NOTE_LOG_SIZE && logType > 0 && logType < 4) { // Data = pos(32) + cm(32) + cv(32) + epk(32) + c_enc(580) + c_out(80) long pos = ByteArray.toLong(ByteArray.subArray(logData, 0, 32)); byte[] cm = ByteArray.subArray(logData, 32, 64); @@ -4039,84 +4047,82 @@ private Optional getNoteTxFromLogListByOvk( TransactionInfo.Log log, byte[] ovk, int logType, byte[] pendingNf) throws ZksnarkException { byte[] logData = log.getData().toByteArray(); - if (!ArrayUtils.isEmpty(logData)) { - if (logType > 0 && logType < 4) { - //Data = pos(32) + cm(32) + cv(32) + epk(32) + c_enc(580) + c_out(80) - byte[] cm = ByteArray.subArray(logData, 32, 64); - byte[] cv = ByteArray.subArray(logData, 64, 96); - byte[] epk = ByteArray.subArray(logData, 96, 128); - byte[] cenc = ByteArray.subArray(logData, 128, 708); - byte[] coutText = ByteArray.subArray(logData, 708, 788); - Encryption.OutCiphertext cout = new Encryption.OutCiphertext(); - cout.setData(coutText); - Optional notePlaintext = OutgoingPlaintext.decrypt(cout,//ciphertext - ovk, cv, cm, epk); - if (notePlaintext.isPresent()) { - OutgoingPlaintext decryptedOutCtUnwrapped = notePlaintext.get(); - //decode c_enc with pkd、esk - Encryption.EncCiphertext ciphertext = new Encryption.EncCiphertext(); - ciphertext.setData(cenc); - Optional foo = Note.decrypt(ciphertext, - epk, - decryptedOutCtUnwrapped.getEsk(), - decryptedOutCtUnwrapped.getPkD(), - cm); - if (foo.isPresent()) { - Note bar = foo.get(); - String paymentAddress = KeyIo.encodePaymentAddress( - new PaymentAddress(bar.getD(), decryptedOutCtUnwrapped.getPkD())); - GrpcAPI.Note note = GrpcAPI.Note.newBuilder() - .setPaymentAddress(paymentAddress) - .setValue(bar.getValue()) - .setRcm(ByteString.copyFrom(bar.getRcm())) - .setMemo(ByteString.copyFrom(stripRightZero(bar.getMemo()))) - .build(); - builder.setNote(note); - return Optional.of(builder.build()); - } + if (logData.length == SHIELDED_TRC20_NOTE_LOG_SIZE && logType > 0 && logType < 4) { + // Data = pos(32) + cm(32) + cv(32) + epk(32) + c_enc(580) + c_out(80) + byte[] cm = ByteArray.subArray(logData, 32, 64); + byte[] cv = ByteArray.subArray(logData, 64, 96); + byte[] epk = ByteArray.subArray(logData, 96, 128); + byte[] cenc = ByteArray.subArray(logData, 128, 708); + byte[] coutText = ByteArray.subArray(logData, 708, 788); + Encryption.OutCiphertext cout = new Encryption.OutCiphertext(); + cout.setData(coutText); + Optional notePlaintext = OutgoingPlaintext.decrypt(cout,//ciphertext + ovk, cv, cm, epk); + if (notePlaintext.isPresent()) { + OutgoingPlaintext decryptedOutCtUnwrapped = notePlaintext.get(); + //decode c_enc with pkd、esk + Encryption.EncCiphertext ciphertext = new Encryption.EncCiphertext(); + ciphertext.setData(cenc); + Optional foo = Note.decrypt(ciphertext, + epk, + decryptedOutCtUnwrapped.getEsk(), + decryptedOutCtUnwrapped.getPkD(), + cm); + if (foo.isPresent()) { + Note bar = foo.get(); + String paymentAddress = KeyIo.encodePaymentAddress( + new PaymentAddress(bar.getD(), decryptedOutCtUnwrapped.getPkD())); + GrpcAPI.Note note = GrpcAPI.Note.newBuilder() + .setPaymentAddress(paymentAddress) + .setValue(bar.getValue()) + .setRcm(ByteString.copyFrom(bar.getRcm())) + .setMemo(ByteString.copyFrom(stripRightZero(bar.getMemo()))) + .build(); + builder.setNote(note); + return Optional.of(builder.build()); } - } else if (logType == 4) { - // Data = toAddress(32) + value(32) + cipher(80) + nonce(12) + reserved/version(4) - if (logData.length < 64 + NoteEncryption.Encryption.BURN_CIPHER_RECORD_SIZE) { + } + } else if (logType == 4) { + // Data = toAddress(32) + value(32) + cipher(80) + nonce(12) + reserved/version(4) + if (logData.length < 64 + NoteEncryption.Encryption.BURN_CIPHER_RECORD_SIZE) { + return Optional.empty(); + } + byte[] logToAddress = ByteArray.subArray(logData, 12, 32); + byte[] logAmountArray = ByteArray.subArray(logData, 32, 64); + byte[] cipher = ByteArray.subArray(logData, 64, 144); + byte[] nonceFromLog = ByteArray.subArray(logData, 144, + 144 + NoteEncryption.Encryption.BURN_NONCE_LEN); + byte[] reservedFromLog = ByteArray.subArray(logData, + 144 + NoteEncryption.Encryption.BURN_NONCE_LEN, + 144 + NoteEncryption.Encryption.BURN_NONCE_LEN + + NoteEncryption.Encryption.BURN_RESERVED_LEN); + BigInteger logAmount = ByteUtil.bytesToBigInteger(logAmountArray); + byte[] plaintext; + byte[] amountArray = new byte[32]; + byte[] decryptedAddress = new byte[20]; + + byte[] addr21FromLog = new byte[21]; + addr21FromLog[0] = Wallet.getAddressPreFixByte(); + System.arraycopy(logToAddress, 0, addr21FromLog, 1, 20); + Optional decryptedText = NoteEncryption.Encryption + .decryptBurnMessageByOvk(ovk, cipher, nonceFromLog, reservedFromLog, pendingNf, + logAmountArray, addr21FromLog); + + if (decryptedText.isPresent()) { + plaintext = decryptedText.get(); + if (plaintext[32] != Wallet.getAddressPreFixByte()) { return Optional.empty(); } - byte[] logToAddress = ByteArray.subArray(logData, 12, 32); - byte[] logAmountArray = ByteArray.subArray(logData, 32, 64); - byte[] cipher = ByteArray.subArray(logData, 64, 144); - byte[] nonceFromLog = ByteArray.subArray(logData, 144, - 144 + NoteEncryption.Encryption.BURN_NONCE_LEN); - byte[] reservedFromLog = ByteArray.subArray(logData, - 144 + NoteEncryption.Encryption.BURN_NONCE_LEN, - 144 + NoteEncryption.Encryption.BURN_NONCE_LEN - + NoteEncryption.Encryption.BURN_RESERVED_LEN); - BigInteger logAmount = ByteUtil.bytesToBigInteger(logAmountArray); - byte[] plaintext; - byte[] amountArray = new byte[32]; - byte[] decryptedAddress = new byte[20]; - - byte[] addr21FromLog = new byte[21]; - addr21FromLog[0] = Wallet.getAddressPreFixByte(); - System.arraycopy(logToAddress, 0, addr21FromLog, 1, 20); - Optional decryptedText = NoteEncryption.Encryption - .decryptBurnMessageByOvk(ovk, cipher, nonceFromLog, reservedFromLog, pendingNf, - logAmountArray, addr21FromLog); - - if (decryptedText.isPresent()) { - plaintext = decryptedText.get(); - if (plaintext[32] != Wallet.getAddressPreFixByte()) { - return Optional.empty(); - } - System.arraycopy(plaintext, 0, amountArray, 0, 32); - System.arraycopy(plaintext, 33, decryptedAddress, 0, 20); - BigInteger decryptedAmount = ByteUtil.bytesToBigInteger(amountArray); - if (logAmount.equals(decryptedAmount) && Hex.toHexString(logToAddress) - .equals(Hex.toHexString(decryptedAddress))) { - byte[] addressWithPrefix = new byte[21]; - System.arraycopy(plaintext, 32, addressWithPrefix, 0, 21); - builder.setToAmount(logAmount.toString(10)) - .setTransparentToAddress(ByteString.copyFrom(addressWithPrefix)); - return Optional.of(builder.build()); - } + System.arraycopy(plaintext, 0, amountArray, 0, 32); + System.arraycopy(plaintext, 33, decryptedAddress, 0, 20); + BigInteger decryptedAmount = ByteUtil.bytesToBigInteger(amountArray); + if (logAmount.equals(decryptedAmount) && Hex.toHexString(logToAddress) + .equals(Hex.toHexString(decryptedAddress))) { + byte[] addressWithPrefix = new byte[21]; + System.arraycopy(plaintext, 32, addressWithPrefix, 0, 21); + builder.setToAmount(logAmount.toString(10)) + .setTransparentToAddress(ByteString.copyFrom(addressWithPrefix)); + return Optional.of(builder.build()); } } } diff --git a/framework/src/main/java/org/tron/core/net/TronNetDelegate.java b/framework/src/main/java/org/tron/core/net/TronNetDelegate.java index 23050f5218d..9d15886f3ab 100644 --- a/framework/src/main/java/org/tron/core/net/TronNetDelegate.java +++ b/framework/src/main/java/org/tron/core/net/TronNetDelegate.java @@ -322,10 +322,10 @@ public void processBlock(BlockCapsule block, boolean isSync) throws P2pException } } - public void pushTransaction(TransactionCapsule trx) throws P2pException { + public boolean pushTransaction(TransactionCapsule trx) throws P2pException { try { trx.setTime(System.currentTimeMillis()); - dbManager.pushTransaction(trx); + return dbManager.pushTransaction(trx); } catch (ContractSizeNotEqualToOneException | VMIllegalException e) { throw new P2pException(TypeEnum.BAD_TRX, e); 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..b2eb111f3a7 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 @@ -183,7 +183,11 @@ private void handleTransaction(PeerConnection peer, TransactionMessage trx) { try { trx.getTransactionCapsule().checkExpiration(chainBaseManager.getNextBlockSlotTime()); - tronNetDelegate.pushTransaction(trx.getTransactionCapsule()); + if (!tronNetDelegate.pushTransaction(trx.getTransactionCapsule())) { + logger.debug("Drop trx {} from {}, local admission rejected", + trx.getMessageId(), peer.getInetAddress()); + return; + } advService.broadcast(trx); } catch (P2pException e) { logger.warn("Trx {} from peer {} process failed. type: {}, reason: {}", @@ -216,4 +220,4 @@ public TrxEvent(PeerConnection peer, TransactionMessage msg) { this.time = System.currentTimeMillis(); } } -} \ No newline at end of file +} diff --git a/framework/src/test/java/org/tron/core/ShieldedTRC20BuilderTest.java b/framework/src/test/java/org/tron/core/ShieldedTRC20BuilderTest.java index 00be867fd59..02cb80cd954 100644 --- a/framework/src/test/java/org/tron/core/ShieldedTRC20BuilderTest.java +++ b/framework/src/test/java/org/tron/core/ShieldedTRC20BuilderTest.java @@ -1,5 +1,8 @@ package org.tron.core; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.spy; + import com.google.protobuf.ByteString; import java.math.BigInteger; import java.util.Arrays; @@ -11,9 +14,10 @@ import org.bouncycastle.util.encoders.Hex; import org.junit.AfterClass; import org.junit.Assert; +import org.junit.Before; import org.junit.BeforeClass; -import org.junit.Ignore; import org.junit.Test; +import org.springframework.test.util.AopTestUtils; import org.tron.api.GrpcAPI; import org.tron.api.GrpcAPI.BytesMessage; import org.tron.api.GrpcAPI.PrivateShieldedTRC20Parameters; @@ -23,6 +27,7 @@ import org.tron.api.GrpcAPI.SpendAuthSigParameters; import org.tron.common.BaseTest; import org.tron.common.TestConstants; +import org.tron.common.crypto.Hash; import org.tron.common.utils.ByteArray; import org.tron.common.utils.ByteUtil; import org.tron.common.utils.PublicMethod; @@ -33,6 +38,7 @@ import org.tron.common.zksnark.LibrustzcashParam; import org.tron.core.capsule.IncrementalMerkleTreeCapsule; import org.tron.core.capsule.PedersenHashCapsule; +import org.tron.core.capsule.TransactionCapsule; import org.tron.core.config.args.Args; import org.tron.core.exception.ContractExeException; import org.tron.core.exception.ContractValidateException; @@ -49,6 +55,9 @@ import org.tron.core.zen.address.PaymentAddress; import org.tron.core.zen.address.SpendingKey; import org.tron.core.zen.note.Note; +import org.tron.protos.Protocol.Block; +import org.tron.protos.Protocol.Transaction; +import org.tron.protos.Protocol.TransactionInfo; import org.tron.protos.contract.ShieldContract; import org.tron.protos.contract.ShieldContract.SpendDescription; @@ -84,12 +93,19 @@ public static void initZksnarkParams() { Args.getInstance().allowShieldedTransactionApi = true; } + @Before + public void mockContractScalingFactor() throws ContractExeException { + // The test database has no deployed shielded TRC-20 contract. + wallet = spy(AopTestUtils.getTargetObject(wallet)); + doReturn(ByteUtil.longTo32Bytes(1)).when(wallet) + .getShieldedContractScalingFactor(SHIELDED_CONTRACT_ADDRESS); + } + @AfterClass public static void restoreShieldedApi() { Args.getInstance().allowShieldedTransactionApi = origShieldedApi; } - @Ignore @Test public void createShieldedContractParametersForMint() throws ZksnarkException, ContractValidateException, ContractExeException { @@ -127,7 +143,6 @@ public void createShieldedContractParametersForMint() /* * With 1 mint, 1 spendNote, 1 receiveNote * */ - @Ignore @Test public void createShieldedContractParametersForTransfer1to1() throws ZksnarkException, ContractValidateException, ContractExeException { @@ -258,7 +273,6 @@ public void createShieldedContractParametersForTransfer1to1() /* * With 1 mint, 1 spendNote, 2 receiveNote * */ - @Ignore @Test public void createShieldedContractParametersForTransfer1to2() throws ZksnarkException, ContractValidateException, ContractExeException { @@ -408,7 +422,6 @@ public void createShieldedContractParametersForTransfer1to2() /* * With 2 mint, 2 spendNote, 1 receiveNote * */ - @Ignore @Test public void createShieldedContractParametersForTransfer2to1() throws ZksnarkException, ContractValidateException, ContractExeException { @@ -596,7 +609,6 @@ public void createShieldedContractParametersForTransfer2to1() /* * With 2 mint, 2 spendNote, 2 receiveNote * */ - @Ignore @Test public void createShieldedContractParametersForTransfer2to2() throws ZksnarkException, ContractValidateException, ContractExeException { @@ -800,7 +812,6 @@ public void createShieldedContractParametersForTransfer2to2() /* * With 1 spendNote */ - @Ignore @Test public void createShieldedContractParametersForBurn1() throws ZksnarkException, ContractValidateException, ContractExeException { @@ -878,6 +889,7 @@ public void createShieldedContractParametersForBurn1() ExpandedSpendingKey expsk = senderSk.expandedSpendingKey(); privateTRC20Builder.setAsk(ByteString.copyFrom(expsk.getAsk())); privateTRC20Builder.setNsk(ByteString.copyFrom(expsk.getNsk())); + privateTRC20Builder.setOvk(ByteString.copyFrom(expsk.getOvk())); privateTRC20Builder.setToAmount(BigInteger.valueOf(value).toString()); privateTRC20Builder.setTransparentToAddress(ByteString.copyFrom(PUBLIC_TO_ADDRESS)); privateTRC20Builder @@ -896,7 +908,6 @@ public void createShieldedContractParametersForBurn1() /* * With 1 spendNote, 1 receiveNote */ - @Ignore @Test public void createShieldedContractParametersForBurn1to1() throws ZksnarkException, ContractValidateException, ContractExeException { @@ -974,6 +985,7 @@ public void createShieldedContractParametersForBurn1to1() ExpandedSpendingKey expsk = senderSk.expandedSpendingKey(); privateTRC20Builder.setAsk(ByteString.copyFrom(expsk.getAsk())); privateTRC20Builder.setNsk(ByteString.copyFrom(expsk.getNsk())); + privateTRC20Builder.setOvk(ByteString.copyFrom(expsk.getOvk())); privateTRC20Builder.setToAmount(BigInteger.valueOf(60).toString()); privateTRC20Builder.setTransparentToAddress(ByteString.copyFrom(PUBLIC_TO_ADDRESS)); privateTRC20Builder @@ -995,8 +1007,8 @@ public void createShieldedContractParametersForBurn1to1() GrpcAPI.ShieldedTRC20Parameters burnParam = wallet .createShieldedContractParameters(privateTRC20Builder.build()); - byte[] inputData = abiEncodeForBurn(burnParam, value); - Pair contractResult = burnContract.execute(inputData); + byte[] inputData = abiEncodeForTransfer(burnParam, frontier, leafCount, 60); + Pair contractResult = verifyTransfer(inputData); byte[] result = contractResult.getRight(); Assert.assertEquals(1, result[31]); @@ -1004,7 +1016,8 @@ public void createShieldedContractParametersForBurn1to1() //if slot == 0, frontier[0:31]=noteCommitment int slot = result[63]; if (slot == 0) { - System.arraycopy(result, 0, frontier, 0, 32); + System.arraycopy(burnParam.getReceiveDescription(0).getNoteCommitment().toByteArray(), + 0, frontier, 0, 32); } else { int srcPos = (slot + 1) * 32; int destPos = slot * 32; @@ -1018,7 +1031,6 @@ public void createShieldedContractParametersForBurn1to1() /* * With 1 mint, 1 spendNote, 1 receiveNote */ - @Ignore @Test public void createShieldedContractParametersWithoutAskForTransfer1to1() throws Exception { @@ -1191,7 +1203,6 @@ public void createShieldedContractParametersWithoutAskForTransfer1to1() /* * With 1 mint, 1 spendNote, 2 receiveNote */ - @Ignore @Test public void createShieldedContractParametersWithoutAskForTransfer1to2() throws Exception { @@ -1380,7 +1391,6 @@ public void createShieldedContractParametersWithoutAskForTransfer1to2() /* * With 2 mint, 2 spendNote, 1 receiveNote */ - @Ignore @Test public void createShieldedContractParametersWithoutAskForTransfer2to1() throws Exception { @@ -1623,7 +1633,6 @@ public void createShieldedContractParametersWithoutAskForTransfer2to1() /* * With 2 mint, 2 spendNote, 2 receiveNote */ - @Ignore @Test public void createShieldedContractParametersWithoutAskForTransfer2to2() throws Exception { @@ -1884,7 +1893,6 @@ public void createShieldedContractParametersWithoutAskForTransfer2to2() * With 1 mint, 1 spendNote * Burn to Transparent address */ - @Ignore @Test public void createShieldedContractParametersWithoutAskForBurn1to1() throws Exception { @@ -1962,6 +1970,7 @@ public void createShieldedContractParametersWithoutAskForBurn1to1() ExpandedSpendingKey expsk = senderSk.expandedSpendingKey(); privateTRC20Builder.setAk(ByteString.copyFrom(senderFvk.getAk())); privateTRC20Builder.setNsk(ByteString.copyFrom(expsk.getNsk())); + privateTRC20Builder.setOvk(ByteString.copyFrom(expsk.getOvk())); privateTRC20Builder.setToAmount(BigInteger.valueOf(value).toString()); privateTRC20Builder.setTransparentToAddress(ByteString.copyFrom(PUBLIC_TO_ADDRESS)); privateTRC20Builder @@ -2020,7 +2029,6 @@ public void createShieldedContractParametersWithoutAskForBurn1to1() * With 1 mint, 1 spendNote, 1 receiveNote * Burn to Transparent address and A change z-address */ - @Ignore @Test public void createShieldedContractParametersWithoutAskForBurn1to2() throws Exception { @@ -2098,6 +2106,7 @@ public void createShieldedContractParametersWithoutAskForBurn1to2() ExpandedSpendingKey expsk = senderSk.expandedSpendingKey(); privateTRC20Builder.setAk(ByteString.copyFrom(senderFvk.getAk())); privateTRC20Builder.setNsk(ByteString.copyFrom(expsk.getNsk())); + privateTRC20Builder.setOvk(ByteString.copyFrom(expsk.getOvk())); privateTRC20Builder.setToAmount(BigInteger.valueOf(60).toString()); privateTRC20Builder.setTransparentToAddress(ByteString.copyFrom(PUBLIC_TO_ADDRESS)); privateTRC20Builder @@ -2135,7 +2144,7 @@ public void createShieldedContractParametersWithoutAskForBurn1to2() .newBuilder(); triggerParam.setShieldedTRC20Parameters(burnParam); triggerParam.addSpendAuthoritySignature(signMsg1); - triggerParam.setAmount(BigInteger.valueOf(value).toString()); + triggerParam.setAmount(privateTRC20Builder.getToAmount()); triggerParam.setTransparentToAddress(ByteString.copyFrom(PUBLIC_TO_ADDRESS)); BytesMessage triggerInput = wallet .getTriggerInputForShieldedTRC20Contract(triggerParam.build()); @@ -2153,13 +2162,14 @@ public void createShieldedContractParametersWithoutAskForBurn1to2() ShieldedTRC20Parameters.Builder bindingSigBuilder = ShieldedTRC20Parameters.newBuilder(); bindingSigBuilder.addSpendDescription(spendDesBuilder1.build()); + bindingSigBuilder.addReceiveDescription(burnParam.getReceiveDescription(0)); bindingSigBuilder.setMessageHash(burnParam.getMessageHash()); bindingSigBuilder.setBindingSignature(burnParam.getBindingSignature()); bindingSigBuilder.setParameterType(burnParam.getParameterType()); burnParam = bindingSigBuilder.build(); - byte[] inputData = abiEncodeForBurn(burnParam, value); - Pair contractResult = burnContract.execute(inputData); + byte[] inputData = abiEncodeForTransfer(burnParam, frontier, leafCount, 60); + Pair contractResult = verifyTransfer(inputData); byte[] result = contractResult.getRight(); Assert.assertEquals(1, result[31]); @@ -2167,7 +2177,8 @@ public void createShieldedContractParametersWithoutAskForBurn1to2() //if slot == 0, frontier[0:31]=noteCommitment int slot = result[63]; if (slot == 0) { - System.arraycopy(result, 0, frontier, 0, 32); + System.arraycopy(burnParam.getReceiveDescription(0).getNoteCommitment().toByteArray(), + 0, frontier, 0, 32); } else { int srcPos = (slot + 1) * 32; int destPos = slot * 32; @@ -2178,7 +2189,6 @@ public void createShieldedContractParametersWithoutAskForBurn1to2() } } - @Ignore @Test public void getTriggerInputForForMint() throws Exception { SpendingKey sk = SpendingKey.random(); @@ -2276,6 +2286,46 @@ public void testscanShieldedTRC20NotesByOvk() throws Exception { } } + @Test + public void testScanShieldedTRC20NotesSkipsInvalidLogLengths() throws Exception { + PrivateShieldedTRC20Parameters params = mintParams( + priKey, 100, SHIELDED_CONTRACT_ADDRESS_STR, null); + ShieldContract.ReceiveDescription receive = wallet.createShieldedContractParameters(params) + .getReceiveDescription(0); + byte[] data = ByteUtil.merge(ByteUtil.longTo32Bytes(7), + receive.getNoteCommitment().toByteArray(), receive.getValueCommitment().toByteArray(), + receive.getEpk().toByteArray(), receive.getCEnc().toByteArray(), + receive.getCOut().toByteArray()); + String[] events = {"MintNewLeaf", "TransferNewLeaf", "BurnNewLeaf"}; + TransactionInfo.Builder info = TransactionInfo.newBuilder(); + for (String event : events) { + TransactionInfo.Log.Builder log = TransactionInfo.Log.newBuilder() + .setAddress(ByteString.copyFrom(Arrays.copyOfRange(SHIELDED_CONTRACT_ADDRESS, 1, 21))) + .addTopics(ByteString.copyFrom(Hash.sha3(ByteArray.fromString( + event + "(uint256,bytes32,bytes32,bytes32,bytes32[21])")))); + // Only the complete 800-byte ABI encoding should produce a note. + for (int length : new int[] {0, 788, 799, 800, 801, 832}) { + info.addLog(log.setData(ByteString.copyFrom(Arrays.copyOf(data, length)))); + } + } + + Transaction transaction = Transaction.getDefaultInstance(); + ByteString txid = new TransactionCapsule(transaction).getTransactionId().getByteString(); + GrpcAPI.BlockList blocks = GrpcAPI.BlockList.newBuilder() + .addBlock(Block.newBuilder().addTransactions(transaction)).build(); + // Supply stored logs while retaining the real scanner and note decryption. + doReturn(blocks).when(wallet).getBlocksByLimitNext(0, 1); + doReturn(info.build()).when(wallet).getTransactionInfoById(txid); + + FullViewingKey fvk = SpendingKey.decode(priKey).fullViewingKey(); + Assert.assertEquals("IVK should accept only 800-byte logs", events.length, + wallet.scanShieldedTRC20NotesByIvk(0, 1, SHIELDED_CONTRACT_ADDRESS, + fvk.inViewingKey().value, new byte[0], new byte[0]).getNoteTxsCount()); + Assert.assertEquals("OVK should accept only 800-byte logs", events.length, + wallet.scanShieldedTRC20NotesByOvk(0, 1, fvk.getOvk(), SHIELDED_CONTRACT_ADDRESS) + .getNoteTxsCount()); + } + @Test(expected = ZksnarkException.class) public void isShieldedTRC20ContractNoteSpent() throws Exception { int statNum = 9200; @@ -2418,6 +2468,11 @@ private GrpcAPI.Note getNote(long value, String paymentAddress, byte[] rcm, byte private byte[] abiEncodeForTransfer(ShieldedTRC20Parameters params, byte[] frontier, long leafCount) { + return abiEncodeForTransfer(params, frontier, leafCount, 0); + } + + private byte[] abiEncodeForTransfer(ShieldedTRC20Parameters params, byte[] frontier, + long leafCount, long valueBalance) { byte[] input = new byte[0]; byte[] spendAuthSig = new byte[0]; byte[] output = new byte[0]; @@ -2434,10 +2489,10 @@ private byte[] abiEncodeForTransfer(ShieldedTRC20Parameters params, byte[] front spendAuthSig = ByteUtil.merge( spendAuthSig, spendDesc.getSpendAuthoritySignature().toByteArray()); } - byte[] inputOffsetbytes = longTo32Bytes(1280); + byte[] inputOffsetbytes = longTo32Bytes(1312); long spendCount = spendDescs.size(); byte[] spendCountBytes = longTo32Bytes(spendCount); - byte[] authOffsetBytes = longTo32Bytes(1280 + 32 + 320 * spendCount); + byte[] authOffsetBytes = longTo32Bytes(1312 + 32 + 320 * spendCount); List recvDescs = params.getReceiveDescriptionList(); for (ShieldContract.ReceiveDescription recvDesc : recvDescs) { output = ByteUtil.merge(output, @@ -2449,12 +2504,13 @@ private byte[] abiEncodeForTransfer(ShieldedTRC20Parameters params, byte[] front } long recvCount = recvDescs.size(); byte[] recvCountBytes = longTo32Bytes(recvCount); - byte[] outputOffsetbytes = longTo32Bytes(1280 + 32 + 320 * spendCount + 32 + 64 * spendCount); + byte[] outputOffsetbytes = longTo32Bytes(1312 + 32 + 320 * spendCount + 32 + 64 * spendCount); mergedBytes = ByteUtil.merge(inputOffsetbytes, authOffsetBytes, outputOffsetbytes, params.getBindingSignature().toByteArray(), params.getMessageHash().toByteArray(), + longTo32Bytes(valueBalance), frontier, longTo32Bytes(leafCount), spendCountBytes, @@ -2472,6 +2528,4 @@ private byte[] longTo32Bytes(long value) { byte[] zeroBytes = new byte[24]; return ByteUtil.merge(zeroBytes, longBytes); } - - } diff --git a/framework/src/test/java/org/tron/core/WalletMockTest.java b/framework/src/test/java/org/tron/core/WalletMockTest.java index 2f4c08d8f9f..3ba34073b92 100644 --- a/framework/src/test/java/org/tron/core/WalletMockTest.java +++ b/framework/src/test/java/org/tron/core/WalletMockTest.java @@ -65,6 +65,7 @@ import org.tron.core.exception.ValidateSignatureException; import org.tron.core.exception.ZksnarkException; import org.tron.core.net.TronNetDelegate; +import org.tron.core.net.TronNetService; import org.tron.core.net.message.adv.TransactionMessage; import org.tron.core.net.peer.PeerConnection; import org.tron.core.store.AbiStore; @@ -309,6 +310,58 @@ public void testBroadcastTransactionTooManyPending() throws Exception { assertEquals(GrpcAPI.Return.response_code.SERVER_BUSY, ret.getCode()); } + @Test + public void testBroadcastTransactionShieldedPendingPoolFull() throws Exception { + long now = System.currentTimeMillis(); + BalanceContract.TransferContract transferContract = + BalanceContract.TransferContract.newBuilder() + .setAmount(10) + .setOwnerAddress(ByteString.copyFromUtf8("aaa")) + .setToAddress(ByteString.copyFromUtf8("bbb")) + .build(); + Protocol.Transaction transaction = Protocol.Transaction.newBuilder() + .setRawData(Protocol.Transaction.raw.newBuilder() + .setExpiration(now + 60_000) + .addContract(Protocol.Transaction.Contract.newBuilder() + .setParameter(Any.pack(transferContract)) + .setType(Protocol.Transaction.Contract.ContractType.TransferContract))) + .build(); + Sha256Hash txId = new TransactionCapsule(transaction).getTransactionId(); + + Wallet wallet = new Wallet(); + TronNetDelegate tronNetDelegate = mock(TronNetDelegate.class); + TronNetService tronNetService = mock(TronNetService.class); + Manager manager = mock(Manager.class); + ChainBaseManager chainBaseManager = mock(ChainBaseManager.class); + DynamicPropertiesStore dynamicPropertiesStore = mock(DynamicPropertiesStore.class); + Cache transactionIdCache = CacheBuilder.newBuilder().build(); + + when(tronNetDelegate.isBlockUnsolidified()).thenReturn(false); + when(manager.isTooManyPending()).thenReturn(false); + when(manager.getTransactionIdCache()).thenReturn(transactionIdCache); + when(manager.pushTransaction(any())).thenReturn(false); + when(chainBaseManager.getDynamicPropertiesStore()).thenReturn(dynamicPropertiesStore); + when(chainBaseManager.getNextBlockSlotTime()).thenReturn(now); + when(dynamicPropertiesStore.supportVM()).thenReturn(false); + + setField(wallet, "tronNetDelegate", tronNetDelegate); + setField(wallet, "tronNetService", tronNetService); + setField(wallet, "dbManager", manager); + setField(wallet, "chainBaseManager", chainBaseManager); + setField(wallet, "trxCacheEnable", true); + + GrpcAPI.Return result = wallet.broadcastTransaction(transaction); + + assertEquals(GrpcAPI.Return.response_code.SERVER_BUSY, result.getCode()); + assertEquals("Transaction was not admitted to the pending pool.", + result.getMessage().toStringUtf8()); + assertEquals(Boolean.TRUE, transactionIdCache.getIfPresent(txId)); + assertEquals(GrpcAPI.Return.response_code.DUP_TRANSACTION_ERROR, + wallet.broadcastTransaction(transaction).getCode()); + Mockito.verify(manager, Mockito.times(1)).pushTransaction(any()); + Mockito.verify(tronNetService, Mockito.never()).fastBroadcastTransaction(any()); + } + @Test public void testBroadcastTransactionAlreadyExists() throws Exception { Wallet wallet = new Wallet(); @@ -397,6 +450,7 @@ public void testBroadcastTransactionOtherException() throws Exception { = mock(DynamicPropertiesStore.class); when(tronNetDelegateMock.isBlockUnsolidified()).thenReturn(false); when(managerMock.isTooManyPending()).thenReturn(false); + when(managerMock.pushTransaction(any())).thenReturn(true); when(chainBaseManagerMock.getDynamicPropertiesStore()) .thenReturn(dynamicPropertiesStoreMock); when(dynamicPropertiesStoreMock.supportVM()).thenReturn(false); @@ -443,6 +497,12 @@ private Protocol.Transaction getExampleTrans() { .build(); } + private void setField(Object target, String fieldName, Object value) throws Exception { + Field field = target.getClass().getDeclaredField(fieldName); + field.setAccessible(true); + field.set(target, value); + } + private void mockEnv(Wallet wallet, TronException tronException) throws Exception { TronNetDelegate tronNetDelegateMock = mock(TronNetDelegate.class); Manager managerMock = mock(Manager.class); @@ -459,21 +519,10 @@ private void mockEnv(Wallet wallet, TronException tronException) throws Exceptio doThrow(tronException).when(managerMock).pushTransaction(any()); - Field field = wallet.getClass().getDeclaredField("tronNetDelegate"); - field.setAccessible(true); - field.set(wallet, tronNetDelegateMock); - - Field field2 = wallet.getClass().getDeclaredField("dbManager"); - field2.setAccessible(true); - field2.set(wallet, managerMock); - - Field field4 = wallet.getClass().getDeclaredField("chainBaseManager"); - field4.setAccessible(true); - field4.set(wallet, chainBaseManagerMock); - - Field field3 = wallet.getClass().getDeclaredField("trxCacheEnable"); - field3.setAccessible(true); - field3.set(wallet, false); + setField(wallet, "tronNetDelegate", tronNetDelegateMock); + setField(wallet, "dbManager", managerMock); + setField(wallet, "chainBaseManager", chainBaseManagerMock); + setField(wallet, "trxCacheEnable", false); } @Test @@ -1228,8 +1277,8 @@ public void scanShieldedTRC20NotesByIvkSkipsNoteSpentIndex() throws Exception { byte[] transferTopic = Hash.sha3(ByteArray.fromString( "TransferNewLeaf(uint256,bytes32,bytes32,bytes32,bytes32[21])")); - // getNoteTxFromLogListByIvk slices bytes 0..708; only `pos` (bytes 0..32) is read here. - byte[] transferData = new byte[708]; + // Supply a complete ABI-encoded note log, including the ciphertext padding. + byte[] transferData = new byte[800]; Protocol.TransactionInfo.Log transferLog = Protocol.TransactionInfo.Log.newBuilder() .setAddress(ByteString.copyFrom(addressWithoutPrefix)) .addTopics(ByteString.copyFrom(transferTopic)) diff --git a/framework/src/test/java/org/tron/core/net/TronNetDelegateTest.java b/framework/src/test/java/org/tron/core/net/TronNetDelegateTest.java index 4c16f28930c..38738f78a6d 100644 --- a/framework/src/test/java/org/tron/core/net/TronNetDelegateTest.java +++ b/framework/src/test/java/org/tron/core/net/TronNetDelegateTest.java @@ -134,6 +134,20 @@ public void testPushVerifiedBlockPushesBlock() throws Exception { Mockito.verify(dbManager, Mockito.times(1)).pushBlock(Mockito.any()); } + @Test + public void testPushTransactionReturnsAdmissionResult() throws Exception { + TronNetDelegate tronNetDelegate = new TronNetDelegate(); + Manager dbManager = Mockito.mock(Manager.class); + TransactionCapsule transaction = new TransactionCapsule( + TransferContract.getDefaultInstance(), ContractType.TransferContract); + setField(tronNetDelegate, "dbManager", dbManager); + + Mockito.when(dbManager.pushTransaction(transaction)).thenReturn(false, true); + + Assert.assertFalse(tronNetDelegate.pushTransaction(transaction)); + Assert.assertTrue(tronNetDelegate.pushTransaction(transaction)); + } + private static void setField(Object obj, String name, Object value) throws Exception { Field f = obj.getClass().getDeclaredField(name); f.setAccessible(true); 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 78af06e64bc..41ab9e77f13 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 @@ -277,9 +277,17 @@ public void testHandleTransaction() throws Exception { // happy path → push and broadcast Mockito.when(chainBaseManager.getNextBlockSlotTime()).thenReturn(now); + Mockito.when(tronNetDelegate.pushTransaction(Mockito.any())).thenReturn(true); handleTx.invoke(handler, peer, trxMsg); Mockito.verify(advService).broadcast(trxMsg); + // local capacity rejection → do not broadcast or penalize the peer + Mockito.when(tronNetDelegate.pushTransaction(Mockito.any())).thenReturn(false); + handleTx.invoke(handler, peer, trxMsg); + Mockito.verify(advService, Mockito.times(1)).broadcast(trxMsg); + Mockito.verify(peer, Mockito.never()).setBadPeer(true); + Mockito.verify(peer, Mockito.never()).disconnect(Mockito.any()); + // P2pException BAD_TRX → disconnect Mockito.doThrow(new P2pException(TypeEnum.BAD_TRX, "bad")) .when(tronNetDelegate).pushTransaction(Mockito.any());