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
10 changes: 10 additions & 0 deletions actuator/src/main/java/org/tron/core/actuator/VMActuator.java
Original file line number Diff line number Diff line change
Expand Up @@ -217,6 +217,9 @@ public void execute(Object object) throws ContractExeException {
} else {
result.spendEnergy(saveCodeEnergy);
if (VMConfig.allowTvmConstantinople()) {
CreateSmartContract createContract =
ContractCapsule.getSmartContractFromTransaction(trx);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Quick question: since create() already unpacked this CreateSmartContract from trx, does calling getSmartContractFromTransaction again here add any measurable cost on the deployment path?

checkContractHashFields(createContract.getNewContract());

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is placing this check immediately before saveCode intentional so that the restriction is applied at the code-persistence boundary, while the preceding contract validation flow keeps its existing behavior?

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes. The check is intentionally performed when the deployment reaches the code-persistence stage. If either field is populated after activation, execution is stopped before the deployed code is saved, while earlier validation behavior remains unchanged.

rootRepository.saveCode(program.getContractAddress().getNoLeadZeroesData(), code);
}
}
Expand Down Expand Up @@ -330,6 +333,7 @@ private void create()
if (contract == null) {
throw new ContractValidateException("Cannot get CreateSmartContract from transaction");
}

SmartContract newSmartContract;
if (VMConfig.allowTvmCompatibleEvm()) {
newSmartContract = contract.getNewContract().toBuilder().setVersion(1).build();
Expand Down Expand Up @@ -455,6 +459,12 @@ private void create()

}

static void checkContractHashFields(SmartContract contract) {
if (!contract.getCodeHash().isEmpty() || !contract.getTrxHash().isEmpty()) {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Covering both fields with a single || and relying on proto3 isEmpty() correctly handles unset vs. empty — normal deployments are untouched.

MUtil.checkCPUTimeForContractHashFields();
}
}

/**
* **
*/
Expand Down
6 changes: 6 additions & 0 deletions actuator/src/main/java/org/tron/core/vm/utils/MUtil.java
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,12 @@ public static boolean isNotNullOrEmpty(String str) {
return !isNullOrEmpty(str);
}

public static void checkCPUTimeForContractHashFields() {
if (ForkController.instance().pass(Parameter.ForkBlockVersionEnum.VERSION_4_8_2_2)) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is the activation check intentionally kept inside this helper so that the call site can remain unconditional once the persistence path is reached, with the helper becoming a no-op before activation?

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Correct. Keeping the activation decision in the helper centralizes the fork-dependent behavior. Before activation it returns normally, and after activation it applies the new restriction.

throw new OutOfTimeException("CPU timeout for contract hash fields");
}
}

public static void checkCPUTime() {
if (ForkController.instance().pass(Parameter.ForkBlockVersionEnum.VERSION_4_7_1)) {
throw new OutOfTimeException("CPU timeout for 0x0a executing");
Expand Down
5 changes: 3 additions & 2 deletions common/src/main/java/org/tron/core/config/Parameter.java
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,8 @@ public enum ForkBlockVersionEnum {
VERSION_4_8_0_1(33, 1596780000000L, 70),
VERSION_4_8_1(34, 1596780000000L, 80),
VERSION_4_8_1_1(35, 1596780000000L, 70),
VERSION_4_8_2(36, 1596780000000L, 80);
VERSION_4_8_2(36, 1596780000000L, 80),
VERSION_4_8_2_2(37, 1596780000000L, 70);
// if add a version, modify BLOCK_VERSION simultaneously

@Getter
Expand Down Expand Up @@ -79,7 +80,7 @@ public class ChainConstant {
public static final int SINGLE_REPEAT = 1;
public static final int BLOCK_FILLED_SLOTS_NUMBER = 128;
public static final int MAX_FROZEN_NUMBER = 1;
public static final int BLOCK_VERSION = 36;
public static final int BLOCK_VERSION = 37;
public static final long FROZEN_PERIOD = 86_400_000L;
public static final long DELEGATE_PERIOD = 3 * 86_400_000L;
public static final long TRX_PRECISION = 1000_000L;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
package org.tron.core.actuator;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertThrows;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;

import com.google.protobuf.ByteString;
import org.junit.Test;
import org.mockito.MockedStatic;
import org.mockito.Mockito;
import org.tron.common.utils.ForkController;
import org.tron.core.config.Parameter.ForkBlockVersionEnum;
import org.tron.core.vm.program.Program.OutOfTimeException;
import org.tron.protos.contract.SmartContractOuterClass.SmartContract;

public class ContractHashValidationTest {

@Test
public void acceptsHashFieldsBeforeActivation() {
SmartContract contract = SmartContract.newBuilder()
.setCodeHash(ByteString.copyFromUtf8("code"))
.setTrxHash(ByteString.copyFromUtf8("transaction"))
.build();

runWithActivation(false, () -> VMActuator.checkContractHashFields(contract));
}

@Test
public void rejectsCodeHashAfterActivation() {
SmartContract contract = SmartContract.newBuilder()
.setCodeHash(ByteString.copyFromUtf8("code"))
.build();

OutOfTimeException exception = assertThrows(OutOfTimeException.class,
() -> runWithActivation(true, () -> VMActuator.checkContractHashFields(contract)));

assertEquals("CPU timeout for contract hash fields", exception.getMessage());
}

@Test
public void rejectsTransactionHashAfterActivation() {
SmartContract contract = SmartContract.newBuilder()
.setTrxHash(ByteString.copyFromUtf8("transaction"))
.build();

OutOfTimeException exception = assertThrows(OutOfTimeException.class,
() -> runWithActivation(true, () -> VMActuator.checkContractHashFields(contract)));

assertEquals("CPU timeout for contract hash fields", exception.getMessage());
}

@Test
public void acceptsEmptyHashFieldsAfterActivation() {
runWithActivation(true,
() -> VMActuator.checkContractHashFields(SmartContract.getDefaultInstance()));
}

private void runWithActivation(boolean activated, Runnable action) {
ForkController controller = mock(ForkController.class);
when(controller.pass(ForkBlockVersionEnum.VERSION_4_8_2_2)).thenReturn(activated);
try (MockedStatic<ForkController> controllerMock = Mockito.mockStatic(ForkController.class)) {
controllerMock.when(ForkController::instance).thenReturn(controller);
action.run();
}
}
}
Loading