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 f897e5d848a..c0a0fa4d88c 100755 --- a/chainbase/src/main/java/org/tron/core/capsule/TransactionCapsule.java +++ b/chainbase/src/main/java/org/tron/core/capsule/TransactionCapsule.java @@ -17,6 +17,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.exception.P2pException.TypeEnum.PROTOBUF_ERROR; import com.google.common.primitives.Bytes; @@ -733,6 +734,15 @@ public long getResultSerializedSize() { return size; } + public long getResultSizeWithMaxContractRet() { + long size = 0; + for (Result result : this.transaction.getRetList()) { + size += result.toBuilder().clearContractRet().build().getSerializedSize() + + MAX_CONTRACT_RESULT_SIZE; + } + return size; + } + @Override public Transaction getInstance() { return this.transaction; @@ -845,4 +855,18 @@ public BalanceContract.TransferContract getTransferContract() { return null; } } + + public void removeRedundantRet() { + Transaction tx = this.getInstance(); + List tmpList = new ArrayList<>(tx.getRetList()); + int contractCount = tx.getRawData().getContractCount(); + if (tx.getRetCount() > contractCount && contractCount > 0) { + Transaction.Builder transactionBuilder = tx.toBuilder().clearRet(); + for (int i = 0; i < contractCount; i++) { + Result result = tmpList.get(i); + transactionBuilder.addRet(result); + } + this.transaction = transactionBuilder.build(); + } + } } diff --git a/chainbase/src/main/java/org/tron/core/db/BandwidthProcessor.java b/chainbase/src/main/java/org/tron/core/db/BandwidthProcessor.java index d6da5904aa3..3f0db86c537 100644 --- a/chainbase/src/main/java/org/tron/core/db/BandwidthProcessor.java +++ b/chainbase/src/main/java/org/tron/core/db/BandwidthProcessor.java @@ -98,6 +98,13 @@ public void consume(TransactionCapsule trx, TransactionTrace trace) throws ContractValidateException, AccountResourceInsufficientException, TooBigTransactionResultException, TooBigTransactionException { List contracts = trx.getInstance().getRawData().getContractList(); + long resultSizeWithMaxContractRet = trx.getResultSizeWithMaxContractRet(); + if (!trx.isInBlock() && resultSizeWithMaxContractRet > + Constant.MAX_RESULT_SIZE_IN_TX * contracts.size()) { + throw new TooBigTransactionResultException(String.format( + "Too big transaction result, TxId %s, the result size is %d bytes, maxResultSize %d", + trx.getTransactionId(), resultSizeWithMaxContractRet, Constant.MAX_RESULT_SIZE_IN_TX)); + } if (trx.getResultSerializedSize() > Constant.MAX_RESULT_SIZE_IN_TX * contracts.size()) { throw new TooBigTransactionResultException(); } diff --git a/common/src/main/java/org/tron/core/Constant.java b/common/src/main/java/org/tron/core/Constant.java index 729f59c8317..a89e90efd20 100644 --- a/common/src/main/java/org/tron/core/Constant.java +++ b/common/src/main/java/org/tron/core/Constant.java @@ -34,6 +34,7 @@ public class Constant { public static final long ENERGY_LIMIT_IN_CONSTANT_TX = 3_000_000L; // ref: 1 us = 1 energy public static final long MAX_RESULT_SIZE_IN_TX = 64; // max 8 * 8 items in result public static final long PER_SIGN_LENGTH = 65L; + public static final long MAX_CONTRACT_RESULT_SIZE = 2L; public static final long PB_DEFAULT_ENERGY_LIMIT = 0L; public static final long CREATOR_DEFAULT_ENERGY_LIMIT = 1000 * 10_000L; diff --git a/framework/src/main/java/org/tron/core/Wallet.java b/framework/src/main/java/org/tron/core/Wallet.java index 50dffcf3c83..75ee7d2060a 100755 --- a/framework/src/main/java/org/tron/core/Wallet.java +++ b/framework/src/main/java/org/tron/core/Wallet.java @@ -497,8 +497,6 @@ public GrpcAPI.Return broadcastTransaction(Transaction signedTransaction) { trx.setTime(System.currentTimeMillis()); Sha256Hash txID = trx.getTransactionId(); try { - TransactionMessage message = new TransactionMessage(signedTransaction.toByteArray()); - if (tronNetDelegate.isBlockUnsolidified()) { logger.warn("Broadcast transaction {} has failed, block unsolidified.", txID); return builder.setResult(false).setCode(response_code.BLOCK_UNSOLIDIFIED) @@ -550,6 +548,7 @@ public GrpcAPI.Return broadcastTransaction(Transaction signedTransaction) { if (trx.getInstance().getRawData().getContractCount() == 0) { throw new ContractValidateException(ActuatorConstant.CONTRACT_NOT_EXIST); } + TransactionMessage message = new TransactionMessage(trx.getInstance().toByteArray()); dbManager.pushTransaction(trx); int num = tronNetService.fastBroadcastTransaction(message); if (num == 0 && minEffectiveConnection != 0) { @@ -592,8 +591,7 @@ public GrpcAPI.Return broadcastTransaction(Transaction signedTransaction) { } catch (TooBigTransactionException e) { logger.warn(BROADCAST_TRANS_FAILED, txID, e.getMessage()); return builder.setResult(false).setCode(response_code.TOO_BIG_TRANSACTION_ERROR) - .setMessage(ByteString.copyFromUtf8("Transaction size is too big.")) - .build(); + .setMessage(ByteString.copyFromUtf8(e.getMessage())).build(); } catch (TransactionExpirationException e) { logger.warn(BROADCAST_TRANS_FAILED, txID, e.getMessage()); return builder.setResult(false).setCode(response_code.TRANSACTION_EXPIRATION_ERROR) diff --git a/framework/src/main/java/org/tron/core/db/Manager.java b/framework/src/main/java/org/tron/core/db/Manager.java index 858d965d81f..ef2f5c81124 100644 --- a/framework/src/main/java/org/tron/core/db/Manager.java +++ b/framework/src/main/java/org/tron/core/db/Manager.java @@ -795,6 +795,7 @@ void validateTapos(TransactionCapsule transactionCapsule) throws TaposException void validateCommon(TransactionCapsule transactionCapsule) throws TransactionExpirationException, TooBigTransactionException { if (!transactionCapsule.isInBlock()) { + transactionCapsule.removeRedundantRet(); long generalBytesSize = transactionCapsule.getInstance().toBuilder().clearRet().build().getSerializedSize() + Constant.MAX_RESULT_SIZE_IN_TX + Constant.MAX_RESULT_SIZE_IN_TX; @@ -804,7 +805,6 @@ void validateCommon(TransactionCapsule transactionCapsule) transactionCapsule.getTransactionId(), generalBytesSize, TRANSACTION_MAX_BYTE_SIZE)); } } - if (transactionCapsule.getData().length > Constant.TRANSACTION_MAX_BYTE_SIZE) { throw new TooBigTransactionException(String.format( "Too big transaction, TxId %s, the size is %d bytes, maxTxSize %d", @@ -1425,8 +1425,14 @@ public TransactionInfo processTransaction(final TransactionCapsule trxCap, Block if (trxCap == null) { return null; } - Contract contract = trxCap.getInstance().getRawData().getContract(0); Sha256Hash txId = trxCap.getTransactionId(); + if (trxCap.getInstance().getRawData().getContractList().size() != 1) { + throw new ContractSizeNotEqualToOneException( + String.format( + "tx %s contract size should be exactly 1, this is extend feature ,actual :%d", + txId, trxCap.getInstance().getRawData().getContractList().size())); + } + Contract contract = trxCap.getInstance().getRawData().getContract(0); final Histogram.Timer requestTimer = Metrics.histogramStartTimer( MetricKeys.Histogram.PROCESS_TRANSACTION_LATENCY, Objects.nonNull(blockCap) ? MetricLabels.BLOCK : MetricLabels.TRX, @@ -1442,13 +1448,6 @@ public TransactionInfo processTransaction(final TransactionCapsule trxCap, Block validateTapos(trxCap); validateCommon(trxCap); - if (trxCap.getInstance().getRawData().getContractList().size() != 1) { - throw new ContractSizeNotEqualToOneException( - String.format( - "tx %s contract size should be exactly 1, this is extend feature ,actual :%d", - txId, trxCap.getInstance().getRawData().getContractList().size())); - } - validateDup(trxCap); if (!trxCap.validateSignature(chainBaseManager.getAccountStore(), diff --git a/framework/src/test/java/org/tron/common/runtime/vm/BandWidthRuntimeTest.java b/framework/src/test/java/org/tron/common/runtime/vm/BandWidthRuntimeTest.java index 8b8e3a3c1eb..7debeb12772 100644 --- a/framework/src/test/java/org/tron/common/runtime/vm/BandWidthRuntimeTest.java +++ b/framework/src/test/java/org/tron/common/runtime/vm/BandWidthRuntimeTest.java @@ -44,6 +44,8 @@ import org.tron.protos.Protocol.Transaction; import org.tron.protos.Protocol.Transaction.Contract; import org.tron.protos.Protocol.Transaction.Contract.ContractType; +import org.tron.protos.Protocol.Transaction.Result; +import org.tron.protos.Protocol.Transaction.Result.contractResult; import org.tron.protos.Protocol.Transaction.raw; import org.tron.protos.contract.SmartContractOuterClass.CreateSmartContract; import org.tron.protos.contract.SmartContractOuterClass.TriggerSmartContract; @@ -237,4 +239,17 @@ private byte[] createContract() Assert.assertNull(trace.getRuntimeError()); return trace.getRuntimeResult().getContractAddress(); } + + @Test + public void testMaxContractResultSize() { + int maxSize = 0; + for (contractResult cr : contractResult.values()) { + if (cr.name().equals("UNRECOGNIZED")) { + continue; + } + Result result = Result.newBuilder().setContractRet(cr).build(); + maxSize = Math.max(maxSize, result.getSerializedSize()); + } + Assert.assertEquals(2, maxSize); + } } \ No newline at end of file diff --git a/framework/src/test/java/org/tron/core/BandwidthProcessorTest.java b/framework/src/test/java/org/tron/core/BandwidthProcessorTest.java index 7d13892c635..1faa70d59ee 100755 --- a/framework/src/test/java/org/tron/core/BandwidthProcessorTest.java +++ b/framework/src/test/java/org/tron/core/BandwidthProcessorTest.java @@ -1,7 +1,10 @@ package org.tron.core; +import static org.junit.Assert.assertThrows; + import com.google.protobuf.Any; import com.google.protobuf.ByteString; +import java.nio.charset.StandardCharsets; import lombok.extern.slf4j.Slf4j; import org.joda.time.DateTime; import org.junit.Assert; @@ -24,6 +27,11 @@ import org.tron.core.store.StoreFactory; import org.tron.protos.Protocol; import org.tron.protos.Protocol.AccountType; +import org.tron.protos.Protocol.Transaction; +import org.tron.protos.Protocol.Transaction.Contract; +import org.tron.protos.Protocol.Transaction.Contract.ContractType; +import org.tron.protos.Protocol.Transaction.Result; +import org.tron.protos.Protocol.Transaction.raw; import org.tron.protos.contract.AssetIssueContractOuterClass.AssetIssueContract; import org.tron.protos.contract.AssetIssueContractOuterClass.TransferAssetContract; import org.tron.protos.contract.BalanceContract.TransferContract; @@ -540,6 +548,32 @@ public void testUsingFee() throws Exception { dbManager.consumeBandwidth(trx, trace); } + @Test + public void testConsumeBandwidthTooBigTransactionResultException() { + TransferContract transferContract = + TransferContract.newBuilder() + .setAmount(10) + .setOwnerAddress(ByteString.copyFromUtf8("aaa")) + .setToAddress(ByteString.copyFromUtf8("bbb")) + .build(); + StringBuilder sb = new StringBuilder(); + for (int i = 0; i < 6666; i++) { + sb.append("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"); + } + Transaction transaction = Transaction.newBuilder().setRawData(raw.newBuilder() + .setData(ByteString.copyFrom(sb.toString().getBytes(StandardCharsets.UTF_8))) + .addContract(Contract.newBuilder().setParameter(Any.pack(transferContract)) + .setType(ContractType.TransferContract))) + .addRet(Result.newBuilder().setAssetIssueID(sb.toString()).build()).build(); + TransactionCapsule trx = new TransactionCapsule(transaction); + trx.setInBlock(false); + TransactionTrace trace = new TransactionTrace(trx, StoreFactory + .getInstance(), new RuntimeImpl()); + assertThrows( + "Too big transaction result, TxId %s, the result size is %d bytes, maxResultSize %d", + TooBigTransactionResultException.class, () -> dbManager.consumeBandwidth(trx, trace)); + } + /** * sameTokenName close, consume success assetIssueCapsule.getOwnerAddress() != * fromAccount.getAddress()) contract.getType() = TransferAssetContract diff --git a/framework/src/test/java/org/tron/core/capsule/TransactionCapsuleTest.java b/framework/src/test/java/org/tron/core/capsule/TransactionCapsuleTest.java index fcb2a4fbfd5..17e9d3ac887 100644 --- a/framework/src/test/java/org/tron/core/capsule/TransactionCapsuleTest.java +++ b/framework/src/test/java/org/tron/core/capsule/TransactionCapsuleTest.java @@ -1,5 +1,9 @@ package org.tron.core.capsule; +import static org.tron.protos.Protocol.Transaction.Result.contractResult.BAD_JUMP_DESTINATION; +import static org.tron.protos.Protocol.Transaction.Result.contractResult.PRECOMPILED_CONTRACT; +import static org.tron.protos.Protocol.Transaction.Result.contractResult.SUCCESS; + import com.google.protobuf.ByteString; import lombok.extern.slf4j.Slf4j; import org.junit.Assert; @@ -13,8 +17,10 @@ import org.tron.core.config.args.Args; import org.tron.protos.Protocol.AccountType; import org.tron.protos.Protocol.Transaction; +import org.tron.protos.Protocol.Transaction.Contract.ContractType; import org.tron.protos.Protocol.Transaction.Result; import org.tron.protos.Protocol.Transaction.Result.contractResult; +import org.tron.protos.Protocol.Transaction.raw; @Slf4j public class TransactionCapsuleTest extends BaseTest { @@ -1064,4 +1070,18 @@ public void trxCapsuleClearTest() { Assert.assertEquals(trxCap.getInstance() .getRet(0).getContractRet(), Result.contractResult.OUT_OF_TIME); } + + @Test + public void testRemoveRedundantRet() { + Transaction.Builder transaction = Transaction.newBuilder().setRawData(raw.newBuilder() + .addContract(Transaction.Contract.newBuilder().setType(ContractType.TriggerSmartContract)) + .setFeeLimit(1000000000)).build().toBuilder(); + transaction.addRet(Result.newBuilder().setContractRet(SUCCESS).build()); + transaction.addRet(Result.newBuilder().setContractRet(PRECOMPILED_CONTRACT).build()); + transaction.addRet(Result.newBuilder().setContractRet(BAD_JUMP_DESTINATION).build()); + TransactionCapsule transactionCapsule = new TransactionCapsule(transaction.build()); + transactionCapsule.removeRedundantRet(); + Assert.assertEquals(1, transactionCapsule.getInstance().getRetCount()); + Assert.assertEquals(SUCCESS, transactionCapsule.getInstance().getRet(0).getContractRet()); + } } \ No newline at end of file diff --git a/framework/src/test/java/org/tron/core/db/ManagerTest.java b/framework/src/test/java/org/tron/core/db/ManagerTest.java index a2a0718abaf..10cc766d8ed 100755 --- a/framework/src/test/java/org/tron/core/db/ManagerTest.java +++ b/framework/src/test/java/org/tron/core/db/ManagerTest.java @@ -1131,9 +1131,9 @@ public void testTooBigTransaction() { for (int i = 0; i < 6666; i++) { sb.append("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"); } - Transaction transaction = Transaction.newBuilder().setRawData(raw.newBuilder() - .setData(ByteString.copyFrom(sb.toString().getBytes(StandardCharsets.UTF_8))) - .addContract(Transaction.Contract.newBuilder().setParameter(Any.pack(transferContract)) + Transaction transaction = Transaction.newBuilder().setRawData(Transaction.raw.newBuilder() + .setData(ByteString.copyFrom(sb.toString().getBytes(StandardCharsets.UTF_8))) + .addContract(Transaction.Contract.newBuilder().setParameter(Any.pack(transferContract)) .setType(ContractType.TransferContract))).build(); TransactionCapsule trx = new TransactionCapsule(transaction); trx.setInBlock(false); @@ -1141,14 +1141,14 @@ public void testTooBigTransaction() { "Too big transaction with result, " + "TxId 1c05e9fca6a2d0c366ed4430456527eb40198e70c8b20f5ceca4739c68a79af8, " + "the size is 533483 bytes, maxTxSize 512000", - TooBigTransactionException.class, ()-> dbManager.validateCommon(trx)); + TooBigTransactionException.class, () -> dbManager.validateCommon(trx)); trx.setInBlock(true); assertThrows( "Too big transaction, " + "TxId 1c05e9fca6a2d0c366ed4430456527eb40198e70c8b20f5ceca4739c68a79af8, " + "the size is 1066643 bytes, maxTxSize 512000", - TooBigTransactionException.class, ()-> dbManager.validateCommon(trx)); + TooBigTransactionException.class, () -> dbManager.validateCommon(trx)); } } diff --git a/protocol/src/main/protos/core/Tron.proto b/protocol/src/main/protos/core/Tron.proto index 41ef968d907..2ffefbf9f3e 100644 --- a/protocol/src/main/protos/core/Tron.proto +++ b/protocol/src/main/protos/core/Tron.proto @@ -407,6 +407,7 @@ message Transaction { UNKNOWN = 13; TRANSFER_FAILED = 14; INVALID_CODE = 15; + // please fill in the order according to the serial number } int64 fee = 1; code ret = 2;