diff --git a/actuator/src/main/java/org/tron/core/actuator/VMActuator.java b/actuator/src/main/java/org/tron/core/actuator/VMActuator.java index 326e247275..08d0f5a8da 100644 --- a/actuator/src/main/java/org/tron/core/actuator/VMActuator.java +++ b/actuator/src/main/java/org/tron/core/actuator/VMActuator.java @@ -38,6 +38,7 @@ import org.tron.core.utils.TransactionUtil; import org.tron.core.vm.EnergyCost; import org.tron.core.vm.LogInfoTriggerParser; +import org.tron.core.vm.Op; import org.tron.core.vm.OperationRegistry; import org.tron.core.vm.VM; import org.tron.core.vm.VMConstant; @@ -189,6 +190,13 @@ public void execute(Object object) throws ContractExeException { VM.play(program, OperationRegistry.getTable()); result = program.getResult(); + if (VMConfig.allowEnergyAdjustment()) { + // If the last op consumed too much execution time, the CPU time limit for the whole tx can be exceeded. + // This is not fair for other txs in the same block. + // So when allowFairEnergyAdjustment is on, the CPU time limit will be checked at the end of tx execution. + program.checkCPUTimeLimit(Op.getNameOf(program.getLastOp()) + "(TX_LAST_OP)"); + } + if (TrxType.TRX_CONTRACT_CREATION_TYPE == trxType && !result.isRevert()) { byte[] code = program.getResult().getHReturn(); if (code.length != 0 && VMConfig.allowTvmLondon() && code[0] == (byte) 0xEF) { diff --git a/actuator/src/main/java/org/tron/core/utils/ProposalUtil.java b/actuator/src/main/java/org/tron/core/utils/ProposalUtil.java index c82ed0a830..8f3501442f 100644 --- a/actuator/src/main/java/org/tron/core/utils/ProposalUtil.java +++ b/actuator/src/main/java/org/tron/core/utils/ProposalUtil.java @@ -750,6 +750,21 @@ public static void validator(DynamicPropertiesStore dynamicPropertiesStore, } break; } + case ALLOW_ENERGY_ADJUSTMENT: { + if (!forkController.pass(ForkBlockVersionEnum.VERSION_4_7_5)) { + throw new ContractValidateException( + "Bad chain parameter id [ALLOW_ENERGY_ADJUSTMENT]"); + } + if (dynamicPropertiesStore.getAllowEnergyAdjustment() == 1) { + throw new ContractValidateException( + "[ALLOW_ENERGY_ADJUSTMENT] has been valid, no need to propose again"); + } + if (value != 1) { + throw new ContractValidateException( + "This value[ALLOW_ENERGY_ADJUSTMENT] is only allowed to be 1"); + } + break; + } case MAX_CREATE_ACCOUNT_TX_SIZE: { if (!forkController.pass(ForkBlockVersionEnum.VERSION_4_7_5)) { throw new ContractValidateException( @@ -841,6 +856,7 @@ public enum ProposalType { // current value, value range ALLOW_CANCEL_ALL_UNFREEZE_V2(77), // 0, 1 MAX_DELEGATE_LOCK_PERIOD(78), // (86400, 10512000] ALLOW_OLD_REWARD_OPT(79), // 0, 1 + ALLOW_ENERGY_ADJUSTMENT(81), // 0, 1 MAX_CREATE_ACCOUNT_TX_SIZE(82); // [500, 10000] private long code; diff --git a/actuator/src/main/java/org/tron/core/vm/EnergyCost.java b/actuator/src/main/java/org/tron/core/vm/EnergyCost.java index 3b62b2f549..6638b22d47 100644 --- a/actuator/src/main/java/org/tron/core/vm/EnergyCost.java +++ b/actuator/src/main/java/org/tron/core/vm/EnergyCost.java @@ -259,12 +259,19 @@ public static long getSuicideCost(Program ignored) { return SUICIDE; } + public static long getSuicideCost2(Program program) { + DataWord inheritorAddress = program.getStack().peek(); + if (isDeadAccount(program, inheritorAddress)) { + return getSuicideCost(program) + NEW_ACCT_CALL; + } + return getSuicideCost(program); + } + public static long getBalanceCost(Program ignored) { return BALANCE; } public static long getFreezeCost(Program program) { - Stack stack = program.getStack(); DataWord receiverAddressWord = stack.get(stack.size() - 3); if (isDeadAccount(program, receiverAddressWord)) { @@ -306,7 +313,27 @@ public static long getUnDelegateResourceCost(Program ignored) { } public static long getVoteWitnessCost(Program program) { + Stack stack = program.getStack(); + long oldMemSize = program.getMemSize(); + DataWord amountArrayLength = stack.get(stack.size() - 1).clone(); + DataWord amountArrayOffset = stack.get(stack.size() - 2); + DataWord witnessArrayLength = stack.get(stack.size() - 3).clone(); + DataWord witnessArrayOffset = stack.get(stack.size() - 4); + + DataWord wordSize = new DataWord(DataWord.WORD_SIZE); + + amountArrayLength.mul(wordSize); + BigInteger amountArrayMemoryNeeded = memNeeded(amountArrayOffset, amountArrayLength); + + witnessArrayLength.mul(wordSize); + BigInteger witnessArrayMemoryNeeded = memNeeded(witnessArrayOffset, witnessArrayLength); + + return VOTE_WITNESS + calcMemEnergy(oldMemSize, + (amountArrayMemoryNeeded.compareTo(witnessArrayMemoryNeeded) > 0 + ? amountArrayMemoryNeeded : witnessArrayMemoryNeeded), 0, Op.VOTEWITNESS); + } + public static long getVoteWitnessCost2(Program program) { Stack stack = program.getStack(); long oldMemSize = program.getMemSize(); DataWord amountArrayLength = stack.get(stack.size() - 1).clone(); @@ -317,9 +344,11 @@ public static long getVoteWitnessCost(Program program) { DataWord wordSize = new DataWord(DataWord.WORD_SIZE); amountArrayLength.mul(wordSize); + amountArrayLength.add(wordSize); // dynamic array length is at least 32 bytes BigInteger amountArrayMemoryNeeded = memNeeded(amountArrayOffset, amountArrayLength); witnessArrayLength.mul(wordSize); + witnessArrayLength.add(wordSize); // dynamic array length is at least 32 bytes BigInteger witnessArrayMemoryNeeded = memNeeded(witnessArrayOffset, witnessArrayLength); return VOTE_WITNESS + calcMemEnergy(oldMemSize, diff --git a/actuator/src/main/java/org/tron/core/vm/OperationRegistry.java b/actuator/src/main/java/org/tron/core/vm/OperationRegistry.java index e4b1e17470..a2d3259ba8 100644 --- a/actuator/src/main/java/org/tron/core/vm/OperationRegistry.java +++ b/actuator/src/main/java/org/tron/core/vm/OperationRegistry.java @@ -67,6 +67,10 @@ public static JumpTable getTable() { adjustMemOperations(table); } + if (VMConfig.allowEnergyAdjustment()) { + adjustForFairEnergy(table); + } + return table; } @@ -635,4 +639,17 @@ public static void appendShangHaiOperations(JumpTable table) { OperationActions::push0Action, proposal)); } + + public static void adjustForFairEnergy(JumpTable table) { + table.set(new Operation( + Op.VOTEWITNESS, 4, 1, + EnergyCost::getVoteWitnessCost2, + OperationActions::voteWitnessAction, + VMConfig::allowTvmVote)); + + table.set(new Operation( + Op.SUICIDE, 1, 0, + EnergyCost::getSuicideCost2, + OperationActions::suicideAction)); + } } diff --git a/actuator/src/main/java/org/tron/core/vm/VMUtils.java b/actuator/src/main/java/org/tron/core/vm/VMUtils.java index abdf6c7fe4..1df0e0e22f 100644 --- a/actuator/src/main/java/org/tron/core/vm/VMUtils.java +++ b/actuator/src/main/java/org/tron/core/vm/VMUtils.java @@ -12,7 +12,6 @@ import java.io.InputStream; import java.io.OutputStream; import java.util.Arrays; -import java.util.Map; import java.util.zip.Deflater; import java.util.zip.DeflaterOutputStream; import lombok.extern.slf4j.Slf4j; @@ -20,6 +19,7 @@ import org.tron.common.utils.ByteUtil; import org.tron.common.utils.Commons; import org.tron.common.utils.DecodeUtil; +import org.tron.core.Constant; import org.tron.core.capsule.AccountCapsule; import org.tron.core.exception.ContractValidateException; import org.tron.core.vm.config.VMConfig; @@ -33,6 +33,11 @@ public final class VMUtils { private VMUtils() { } + public static int getAddressSize() { + return VMConfig.allowEnergyAdjustment() ? + Constant.TRON_ADDRESS_SIZE : Constant.STANDARD_ADDRESS_SIZE; + } + public static void closeQuietly(Closeable closeable) { try { if (closeable != null) { diff --git a/actuator/src/main/java/org/tron/core/vm/config/ConfigLoader.java b/actuator/src/main/java/org/tron/core/vm/config/ConfigLoader.java index 463d8c8995..63c3ff791d 100644 --- a/actuator/src/main/java/org/tron/core/vm/config/ConfigLoader.java +++ b/actuator/src/main/java/org/tron/core/vm/config/ConfigLoader.java @@ -39,6 +39,7 @@ public static void load(StoreFactory storeFactory) { VMConfig.initDynamicEnergyIncreaseFactor(ds.getDynamicEnergyIncreaseFactor()); VMConfig.initDynamicEnergyMaxFactor(ds.getDynamicEnergyMaxFactor()); VMConfig.initAllowTvmShangHai(ds.getAllowTvmShangHai()); + VMConfig.initAllowEnergyAdjustment(ds.getAllowEnergyAdjustment()); } } } diff --git a/actuator/src/main/java/org/tron/core/vm/config/VMConfig.java b/actuator/src/main/java/org/tron/core/vm/config/VMConfig.java index 9720243259..3eb1f8fd4b 100644 --- a/actuator/src/main/java/org/tron/core/vm/config/VMConfig.java +++ b/actuator/src/main/java/org/tron/core/vm/config/VMConfig.java @@ -49,6 +49,8 @@ public class VMConfig { private static boolean ALLOW_TVM_SHANGHAI = false; + private static boolean ALLOW_ENERGY_ADJUSTMENT = false; + private VMConfig() { } @@ -136,6 +138,10 @@ public static void initAllowTvmShangHai(long allow) { ALLOW_TVM_SHANGHAI = allow == 1; } + public static void initAllowEnergyAdjustment(long allow) { + ALLOW_ENERGY_ADJUSTMENT = allow == 1; + } + public static boolean getEnergyLimitHardFork() { return CommonParameter.ENERGY_LIMIT_HARD_FORK; } @@ -211,4 +217,8 @@ public static long getDynamicEnergyMaxFactor() { public static boolean allowTvmShanghai() { return ALLOW_TVM_SHANGHAI; } + + public static boolean allowEnergyAdjustment() { + return ALLOW_ENERGY_ADJUSTMENT; + } } diff --git a/actuator/src/main/java/org/tron/core/vm/program/Program.java b/actuator/src/main/java/org/tron/core/vm/program/Program.java index e02ba225c6..0b0ef5bc9b 100644 --- a/actuator/src/main/java/org/tron/core/vm/program/Program.java +++ b/actuator/src/main/java/org/tron/core/vm/program/Program.java @@ -275,6 +275,10 @@ public void setLastOp(byte op) { this.lastOp = op; } + public byte getLastOp() { + return this.lastOp; + } + /** * Returns the last fully executed OP. */ @@ -457,7 +461,8 @@ public void suicide(DataWord obtainerAddress) { InternalTransaction internalTx = addInternalTx(null, owner, obtainer, balance, null, "suicide", nonce, getContractState().getAccount(owner).getAssetMapV2()); - if (FastByteComparisons.compareTo(owner, 0, 20, obtainer, 0, 20) == 0) { + int ADDRESS_SIZE = VMUtils.getAddressSize(); + if (FastByteComparisons.compareTo(owner, 0, ADDRESS_SIZE, obtainer, 0, ADDRESS_SIZE) == 0) { // if owner == obtainer just zeroing account according to Yellow Paper getContractState().addBalance(owner, -balance); byte[] blackHoleAddress = getContractState().getBlackHoleAddress(); diff --git a/chainbase/src/main/java/org/tron/core/store/DynamicPropertiesStore.java b/chainbase/src/main/java/org/tron/core/store/DynamicPropertiesStore.java index 57a21753b9..2b50ec76af 100644 --- a/chainbase/src/main/java/org/tron/core/store/DynamicPropertiesStore.java +++ b/chainbase/src/main/java/org/tron/core/store/DynamicPropertiesStore.java @@ -219,6 +219,8 @@ public class DynamicPropertiesStore extends TronStoreWithRevoking private static final byte[] ALLOW_OLD_REWARD_OPT = "ALLOW_OLD_REWARD_OPT".getBytes(); + private static final byte[] ALLOW_ENERGY_ADJUSTMENT = "ALLOW_ENERGY_ADJUSTMENT".getBytes(); + private static final byte[] MAX_CREATE_ACCOUNT_TX_SIZE = "MAX_CREATE_ACCOUNT_TX_SIZE".getBytes(); @Autowired @@ -2852,6 +2854,17 @@ public long getAllowOldRewardOpt() { .orElse(CommonParameter.getInstance().getAllowOldRewardOpt()); } + public void saveAllowEnergyAdjustment(long allowEnergyAdjustment) { + this.put(ALLOW_ENERGY_ADJUSTMENT, new BytesCapsule(ByteArray.fromLong(allowEnergyAdjustment))); + } + + public long getAllowEnergyAdjustment() { + return Optional.ofNullable(getUnchecked(ALLOW_ENERGY_ADJUSTMENT)) + .map(BytesCapsule::getData) + .map(ByteArray::toLong) + .orElse(CommonParameter.getInstance().getAllowEnergyAdjustment()); + } + public void saveMaxCreateAccountTxSize(long maxCreateAccountTxSize) { this.put(MAX_CREATE_ACCOUNT_TX_SIZE, new BytesCapsule(ByteArray.fromLong(maxCreateAccountTxSize))); diff --git a/common/src/main/java/org/tron/common/parameter/CommonParameter.java b/common/src/main/java/org/tron/common/parameter/CommonParameter.java index d511f6b0d4..2215906333 100644 --- a/common/src/main/java/org/tron/common/parameter/CommonParameter.java +++ b/common/src/main/java/org/tron/common/parameter/CommonParameter.java @@ -666,6 +666,10 @@ public class CommonParameter { @Setter public long allowOldRewardOpt; + @Getter + @Setter + public long allowEnergyAdjustment; + @Getter @Setter public long maxCreateAccountTxSize = 1000L; diff --git a/common/src/main/java/org/tron/core/Constant.java b/common/src/main/java/org/tron/core/Constant.java index 729f59c831..a0d53d4d6f 100644 --- a/common/src/main/java/org/tron/core/Constant.java +++ b/common/src/main/java/org/tron/core/Constant.java @@ -18,6 +18,8 @@ public class Constant { public static final String ADD_PRE_FIX_STRING_MAINNET = "41"; public static final byte ADD_PRE_FIX_BYTE_TESTNET = (byte) 0xa0; //a0 + address public static final String ADD_PRE_FIX_STRING_TESTNET = "a0"; + public static final int STANDARD_ADDRESS_SIZE = 20; + public static final int TRON_ADDRESS_SIZE = 21; public static final int NODE_TYPE_FULL_NODE = 0; public static final int NODE_TYPE_LIGHT_NODE = 1; @@ -379,4 +381,6 @@ public class Constant { public static final String MAX_UNSOLIDIFIED_BLOCKS = "node.maxUnsolidifiedBlocks"; public static final String COMMITTEE_ALLOW_OLD_REWARD_OPT = "committee.allowOldRewardOpt"; + + public static final String COMMITTEE_ALLOW_ENERGY_ADJUSTMENT = "committee.allowEnergyAdjustment"; } diff --git a/framework/src/main/java/org/tron/core/Wallet.java b/framework/src/main/java/org/tron/core/Wallet.java index 50dffcf3c8..56fc9e10f0 100755 --- a/framework/src/main/java/org/tron/core/Wallet.java +++ b/framework/src/main/java/org/tron/core/Wallet.java @@ -1334,6 +1334,11 @@ public Protocol.ChainParameters getChainParameters() { .setValue(dbManager.getDynamicPropertiesStore().getAllowOldRewardOpt()) .build()); + builder.addChainParameter(Protocol.ChainParameters.ChainParameter.newBuilder() + .setKey("getAllowEnergyAdjustment") + .setValue(dbManager.getDynamicPropertiesStore().getAllowEnergyAdjustment()) + .build()); + builder.addChainParameter(Protocol.ChainParameters.ChainParameter.newBuilder() .setKey("getMaxCreateAccountTxSize") .setValue(dbManager.getDynamicPropertiesStore().getMaxCreateAccountTxSize()) diff --git a/framework/src/main/java/org/tron/core/config/args/Args.java b/framework/src/main/java/org/tron/core/config/args/Args.java index a8547b7394..422efefaed 100644 --- a/framework/src/main/java/org/tron/core/config/args/Args.java +++ b/framework/src/main/java/org/tron/core/config/args/Args.java @@ -232,6 +232,7 @@ public static void clearParam() { PARAMETER.unsolidifiedBlockCheck = false; PARAMETER.maxUnsolidifiedBlocks = 54; PARAMETER.allowOldRewardOpt = 0; + PARAMETER.allowEnergyAdjustment = 0; } /** @@ -1205,6 +1206,10 @@ public static void setParam(final String[] args, final String confFileName) { } PARAMETER.allowOldRewardOpt = allowOldRewardOpt; + PARAMETER.allowEnergyAdjustment = + config.hasPath(Constant.COMMITTEE_ALLOW_ENERGY_ADJUSTMENT) ? config + .getInt(Constant.COMMITTEE_ALLOW_ENERGY_ADJUSTMENT) : 0; + logConfig(); } diff --git a/framework/src/main/java/org/tron/core/consensus/ProposalService.java b/framework/src/main/java/org/tron/core/consensus/ProposalService.java index 73628c6e2b..d0c106a7e5 100644 --- a/framework/src/main/java/org/tron/core/consensus/ProposalService.java +++ b/framework/src/main/java/org/tron/core/consensus/ProposalService.java @@ -359,6 +359,10 @@ public static boolean process(Manager manager, ProposalCapsule proposalCapsule) manager.getDynamicPropertiesStore().saveAllowOldRewardOpt(entry.getValue()); break; } + case ALLOW_ENERGY_ADJUSTMENT: { + manager.getDynamicPropertiesStore().saveAllowEnergyAdjustment(entry.getValue()); + break; + } case MAX_CREATE_ACCOUNT_TX_SIZE: { manager.getDynamicPropertiesStore().saveMaxCreateAccountTxSize(entry.getValue()); break; diff --git a/framework/src/test/java/org/tron/common/runtime/vm/OperationsTest.java b/framework/src/test/java/org/tron/common/runtime/vm/OperationsTest.java index be031d7c00..2a70364b8d 100644 --- a/framework/src/test/java/org/tron/common/runtime/vm/OperationsTest.java +++ b/framework/src/test/java/org/tron/common/runtime/vm/OperationsTest.java @@ -3,6 +3,8 @@ import static org.junit.Assert.assertEquals; import java.util.List; +import java.util.Random; + import lombok.SneakyThrows; import lombok.extern.slf4j.Slf4j; import org.bouncycastle.util.encoders.Hex; @@ -15,6 +17,7 @@ import org.tron.common.BaseTest; import org.tron.common.parameter.CommonParameter; import org.tron.common.runtime.InternalTransaction; +import org.tron.common.utils.DecodeUtil; import org.tron.core.Constant; import org.tron.core.config.args.Args; import org.tron.core.exception.ContractValidateException; @@ -864,6 +867,73 @@ public void testPush0() throws ContractValidateException { VMConfig.initAllowTvmShangHai(0); } + @Test + public void testSuicideCost() throws ContractValidateException { + invoke = new ProgramInvokeMockImpl(StoreFactory.getInstance(), new byte[0], new byte[21]); + program = new Program(null, null, invoke, + new InternalTransaction( + Protocol.Transaction.getDefaultInstance(), + InternalTransaction.TrxType.TRX_UNKNOWN_TYPE)); + + byte[] receiver1 = generateRandomAddress(); + program.stackPush(new DataWord(receiver1)); + Assert.assertEquals(0, EnergyCost.getSuicideCost(program)); + invoke.getDeposit().createAccount(receiver1, Protocol.AccountType.Normal); + Assert.assertEquals(0, EnergyCost.getSuicideCost(program)); + + byte[] receiver2 = generateRandomAddress(); + program.stackPush(new DataWord(receiver2)); + Assert.assertEquals(25000, EnergyCost.getSuicideCost2(program)); + invoke.getDeposit().createAccount(receiver2, Protocol.AccountType.Normal); + Assert.assertEquals(0, EnergyCost.getSuicideCost2(program)); + } + + @Test + public void testSuicideAction() throws ContractValidateException { + invoke = new ProgramInvokeMockImpl( + StoreFactory.getInstance(), + new byte[0], + Hex.decode("41471fd3ad3e9eeadeec4608b92d16ce6b500704cc")); + + program = new Program(null, null, invoke, + new InternalTransaction( + Protocol.Transaction.getDefaultInstance(), + InternalTransaction.TrxType.TRX_UNKNOWN_TYPE)); + + VMConfig.initAllowEnergyAdjustment(1); + byte prePrefixByte = DecodeUtil.addressPreFixByte; + DecodeUtil.addressPreFixByte = Constant.ADD_PRE_FIX_BYTE_MAINNET; + + program.suicide(new DataWord( + dbManager.getAccountStore().getBlackhole().getAddress().toByteArray())); + + DecodeUtil.addressPreFixByte = prePrefixByte; + VMConfig.initAllowEnergyAdjustment(0); + } + + @Test + public void testVoteWitnessCost() throws ContractValidateException { + // Build stack environment, the stack from top to bottom is 0x00, 0x80, 0x00, 0x80 + program = new Program(null, null, new ProgramInvokeMockImpl(), + new InternalTransaction( + Protocol.Transaction.getDefaultInstance(), + InternalTransaction.TrxType.TRX_UNKNOWN_TYPE)); + program.stackPush(DataWord.of((byte) 0x80)); + program.stackPush(DataWord.of((byte) 0x00)); + program.stackPush(DataWord.of((byte) 0x80)); + program.stackPush(DataWord.of((byte) 0x00)); + + // Test VoteWitness before EnergyAdjustment, should not have memory expand energy + Assert.assertEquals(30000, EnergyCost.getVoteWitnessCost(program)); + + // Test VoteWitness after EnergyAdjustment, should have memory expand energy + VMConfig.initAllowEnergyAdjustment(1); + + long memWords = (0x80 + 32) / 32; + long memoryExpandEnergy = 3 * memWords + memWords * memWords / 512; + Assert.assertEquals(30000 + memoryExpandEnergy, EnergyCost.getVoteWitnessCost2(program)); + } + private void testOperations(Program program) { try { while (!program.isStopped()) { @@ -954,4 +1024,10 @@ private byte[] compile(String code) { return new BytecodeCompiler().compile(code); } + private byte[] generateRandomAddress() { + byte[] address = new byte[21]; + new Random().nextBytes(address); + address[0] = DecodeUtil.addressPreFixByte; + return address; + } } diff --git a/framework/src/test/java/org/tron/core/actuator/utils/ProposalUtilTest.java b/framework/src/test/java/org/tron/core/actuator/utils/ProposalUtilTest.java index 8e2b764796..db122f90c4 100644 --- a/framework/src/test/java/org/tron/core/actuator/utils/ProposalUtilTest.java +++ b/framework/src/test/java/org/tron/core/actuator/utils/ProposalUtilTest.java @@ -3,7 +3,9 @@ import com.google.protobuf.ByteString; import java.util.ArrayList; import java.util.Arrays; +import java.util.HashMap; import java.util.List; +import java.util.Map; import javax.annotation.Resource; import lombok.extern.slf4j.Slf4j; import org.junit.Assert; @@ -14,9 +16,11 @@ import org.tron.common.utils.ForkController; import org.tron.core.Constant; import org.tron.core.capsule.BytesCapsule; +import org.tron.core.capsule.ProposalCapsule; import org.tron.core.config.Parameter; import org.tron.core.config.Parameter.ForkBlockVersionEnum; import org.tron.core.config.args.Args; +import org.tron.core.consensus.ProposalService; import org.tron.core.exception.ContractValidateException; import org.tron.core.store.DynamicPropertiesStore; import org.tron.core.utils.ProposalUtil; @@ -381,11 +385,75 @@ public void validateCheck() { e.getMessage()); } + testEnergyAdjustmentProposal(); + forkUtils.getManager().getDynamicPropertiesStore() .statsByVersion(ForkBlockVersionEnum.ENERGY_LIMIT.getValue(), stats); forkUtils.reset(); } + private void testEnergyAdjustmentProposal() { + // Should fail because cannot pass the fork controller check + try { + ProposalUtil.validator(dynamicPropertiesStore, forkUtils, + ProposalType.ALLOW_ENERGY_ADJUSTMENT.getCode(), 1); + Assert.fail(); + } catch (ContractValidateException e) { + Assert.assertEquals( + "Bad chain parameter id [ALLOW_ENERGY_ADJUSTMENT]", + e.getMessage()); + } + + long maintenanceTimeInterval = forkUtils.getManager().getDynamicPropertiesStore() + .getMaintenanceTimeInterval(); + + long hardForkTime = + ((ForkBlockVersionEnum.VERSION_4_7_5.getHardForkTime() - 1) / maintenanceTimeInterval + 1) + * maintenanceTimeInterval; + forkUtils.getManager().getDynamicPropertiesStore() + .saveLatestBlockHeaderTimestamp(hardForkTime + 1); + + byte[] stats = new byte[27]; + Arrays.fill(stats, (byte) 1); + forkUtils.getManager().getDynamicPropertiesStore() + .statsByVersion(ForkBlockVersionEnum.VERSION_4_7_5.getValue(), stats); + + // Should fail because the proposal value is invalid + try { + ProposalUtil.validator(dynamicPropertiesStore, forkUtils, + ProposalType.ALLOW_ENERGY_ADJUSTMENT.getCode(), 2); + Assert.fail(); + } catch (ContractValidateException e) { + Assert.assertEquals( + "This value[ALLOW_ENERGY_ADJUSTMENT] is only allowed to be 1", + e.getMessage()); + } + + // Should succeed + try { + ProposalUtil.validator(dynamicPropertiesStore, forkUtils, + ProposalType.ALLOW_ENERGY_ADJUSTMENT.getCode(), 1); + } catch (Throwable t) { + Assert.fail(); + } + + ProposalCapsule proposalCapsule = new ProposalCapsule(ByteString.empty(), 0); + Map parameter = new HashMap<>(); + parameter.put(81L, 1L); + proposalCapsule.setParameters(parameter); + ProposalService.process(dbManager, proposalCapsule); + + try { + ProposalUtil.validator(dynamicPropertiesStore, forkUtils, + ProposalType.ALLOW_ENERGY_ADJUSTMENT.getCode(), 1); + Assert.fail(); + } catch (ContractValidateException e) { + Assert.assertEquals( + "[ALLOW_ENERGY_ADJUSTMENT] has been valid, no need to propose again", + e.getMessage()); + } + } + @Test public void blockVersionCheck() { for (ForkBlockVersionEnum forkVersion : ForkBlockVersionEnum.values()) {