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
18 changes: 17 additions & 1 deletion actuator/src/main/java/org/tron/core/utils/ProposalUtil.java
Original file line number Diff line number Diff line change
Expand Up @@ -1029,6 +1029,21 @@ public static void validator(DynamicPropertiesStore dynamicPropertiesStore,
}
break;
}
case ALLOW_TVM_RESOURCE_OPTIMIZATION: {
if (!forkController.pass(ForkBlockVersionEnum.VERSION_4_8_3)) {
throw new ContractValidateException(
"Bad chain parameter id [ALLOW_TVM_RESOURCE_OPTIMIZATION]");
}
if (dynamicPropertiesStore.getAllowTvmResourceOptimization() == 1) {
throw new ContractValidateException(
"[ALLOW_TVM_RESOURCE_OPTIMIZATION] has been valid, no need to propose again");
}
if (value != 1) {
throw new ContractValidateException(
"This value[ALLOW_TVM_RESOURCE_OPTIMIZATION] is only allowed to be 1");
}
break;
}
default:
break;
}
Expand Down Expand Up @@ -1122,7 +1137,8 @@ public enum ProposalType { // current value, value range
ALLOW_STRICT_ECDSA_VALIDATION(100), // 0, 1
CLOSE_EXCHANGE(101), // 0, 1, 2
ALLOW_OPTIMIZE_TVM_STORAGE_KEY(102), // 0, 1
ALLOW_TVM_VOTE_POWER_OPTIMIZATION(103); // 0, 1
ALLOW_TVM_VOTE_POWER_OPTIMIZATION(103), // 0, 1
ALLOW_TVM_RESOURCE_OPTIMIZATION(104); // 0, 1
private long code;

ProposalType(long code) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ public static void load(StoreFactory storeFactory, boolean isolate) {
snapshot.allowStrictEcdsaValidation = ds.getAllowStrictEcdsaValidation() == 1;
snapshot.allowOptimizeTvmStorageKey = ds.allowOptimizeTvmStorageKey();
snapshot.allowTvmVotePowerOptimization = ds.getAllowTvmVotePowerOptimization() == 1;
snapshot.allowTvmResourceOptimization = ds.getAllowTvmResourceOptimization() == 1;
if (isolate) {
VMConfig.setLocalSnapshot(snapshot);
} else {
Expand Down
57 changes: 56 additions & 1 deletion actuator/src/main/java/org/tron/core/vm/program/Program.java
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
import static org.tron.common.math.Maths.min;
import static org.tron.common.math.Maths.multiplyExact;
import static org.tron.common.utils.ByteUtil.stripLeadingZeroes;
import static org.tron.core.config.Parameter.ChainConstant.MAX_VOTE_NUMBER;
import static org.tron.core.config.Parameter.ChainConstant.TRX_PRECISION;
import static org.tron.protos.contract.Common.ResourceCode.BANDWIDTH;
import static org.tron.protos.contract.Common.ResourceCode.ENERGY;
Expand Down Expand Up @@ -637,6 +638,10 @@ private long transferFrozenV2BalanceToInheritor(byte[] ownerAddr, byte[] inherit
AccountCapsule inheritorCapsule = repo.getAccount(inheritorAddr);
long now = repo.getHeadSlot();

long[] weightsBefore = VMConfig.allowTvmResourceOptimization()
&& ownerCapsule.getFrozenV2List().stream().anyMatch(frozen -> frozen.getAmount() > 0)
? frozenV2Weights(ownerCapsule, inheritorCapsule) : null;

if (optimizeVotePowerState(repo)
&& ownerCapsule.getFrozenV2List().stream().anyMatch(frozen -> frozen.getAmount() > 0)) {
if (inheritorCapsule.oldTronPowerIsNotInitialized()) {
Expand Down Expand Up @@ -701,9 +706,34 @@ private long transferFrozenV2BalanceToInheritor(byte[] ownerAddr, byte[] inherit
clearOwnerFreezeV2(ownerCapsule);
repo.updateAccount(ownerCapsule.createDbKey(), ownerCapsule);
repo.updateAccount(inheritorCapsule.createDbKey(), inheritorCapsule);
if (weightsBefore != null) {

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.

Using operation-local before/after weights keeps sequential transfers consistent with per-account rounding.

long[] weightsAfter = frozenV2Weights(repo.getAccount(ownerAddr), repo.getAccount(inheritorAddr));
repo.addTotalNetWeight(weightsAfter[0] - weightsBefore[0]);
repo.addTotalEnergyWeight(weightsAfter[1] - weightsBefore[1]);
repo.addTotalTronPowerWeight(weightsAfter[2] - weightsBefore[2]);
}
return expireUnfrozenBalance;
}

private long[] frozenV2Weights(AccountCapsule source, AccountCapsule beneficiary) {
long[] weights = accountFrozenV2Weights(source);
if (!FastByteComparisons.isEqual(source.createDbKey(), beneficiary.createDbKey())) {
long[] beneficiaryWeights = accountFrozenV2Weights(beneficiary);
for (int i = 0; i < weights.length; i++) {
weights[i] = addExact(weights[i], beneficiaryWeights[i], VMConfig.disableJavaLangMath());
}
}
return weights;
}

private long[] accountFrozenV2Weights(AccountCapsule account) {
return new long[] {
account.getFrozenV2BalanceWithDelegated(BANDWIDTH) / TRX_PRECISION,
account.getFrozenV2BalanceWithDelegated(ENERGY) / TRX_PRECISION,
account.getTronPowerFrozenV2Balance() / TRX_PRECISION
};
}

private void clearOwnerFreeze(AccountCapsule ownerCapsule) {
ownerCapsule.setFrozenForBandwidth(0, 0);
ownerCapsule.setFrozenForEnergy(0, 0);
Expand Down Expand Up @@ -948,6 +978,11 @@ this, new DataWord(newAddress), getContractAddress(), value, DataWord.ZERO(),
// Reuse the table prepared by the top-level execution.
VM.play(program, OperationRegistry.getTable(isConstantCall()));
createResult = program.getResult();

if (VMConfig.allowTvmResourceOptimization()) {
checkCPUTimeLimit(isCreate2 ? "create2" : "create");
}

getTrace().merge(program.getTrace());
// always commit nonce
this.nonce = program.nonce;
Expand Down Expand Up @@ -1182,6 +1217,10 @@ this, new DataWord(contextAddress),
VM.play(program, OperationRegistry.getTable(isConstantCall()));
callResult = program.getResult();

if (VMConfig.allowTvmResourceOptimization()) {
checkCPUTimeLimit(Op.getNameOf(msg.getOpCode()));
}

getTrace().merge(program.getTrace());
getResult().merge(callResult);
// always commit nonce
Expand Down Expand Up @@ -1775,7 +1814,13 @@ public void callToPrecompiledAddress(MessageCall msg,
contract.setResult(this.result);
contract.setConstantCall(isConstantCall());
contract.setVmShouldEndInUs(getVmShouldEndInUs());
Pair<Boolean, byte[]> out = contract.execute(data);
Pair<Boolean, byte[]> out;
try {
out = contract.execute(data);
} finally {
contract.setRepository(null);
contract.setResult(null);
}

if (out.getLeft()) { // success
this.refundEnergy(msg.getEnergy().longValue() - requiredEnergy, CALL_PRE_COMPILED);
Expand Down Expand Up @@ -2321,6 +2366,16 @@ public boolean voteWitness(int witnessArrayOffset, int witnessArrayLength,
if (witnessArrayLength != amountArrayLength) {
logger.warn("TVM VoteWitness: witness array length {} does not match amount array length {}",
witnessArrayLength, amountArrayLength);
if (VMConfig.allowTvmResourceOptimization() && internalTx != null) {
internalTx.reject();
}
return false;
}

if (VMConfig.allowTvmResourceOptimization() && witnessArrayLength > MAX_VOTE_NUMBER) {
if (internalTx != null) {
internalTx.reject();
}
return false;
}

Expand Down
44 changes: 43 additions & 1 deletion actuator/src/main/java/org/tron/core/vm/repository/Key.java
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
package org.tron.core.vm.repository;

import java.security.SecureRandom;
import java.util.Arrays;
import org.apache.commons.lang3.ArrayUtils;
import org.tron.core.vm.config.VMConfig;

public class Key {

Expand Down Expand Up @@ -49,6 +51,46 @@ public boolean equals(Object o) {

@Override
public int hashCode() {
return data != null ? ArrayUtils.hashCode(data) : 0;
if (!VMConfig.allowTvmResourceOptimization()) {
return data != null ? ArrayUtils.hashCode(data) : 0;
}
return optimizedHash(data, ResourceHashHolder.SEEDS, ResourceHashHolder.LENGTH_SEED);
}

private static class ResourceHashHolder {

// Twenty 32-bit words cover production keys, including the variable-length reward keys.
// Keep random initialization out of the pre-proposal path.
private static final long[] SEEDS = new long[20];
private static final long LENGTH_SEED;

static {
SecureRandom random = new SecureRandom();
LENGTH_SEED = random.nextLong();
for (int i = 0; i < SEEDS.length; i++) {
SEEDS[i] = random.nextLong();
}
}
}

static int optimizedHash(byte[] bytes, long[] seeds, long lengthSeed) {
if (bytes.length > seeds.length * 4) {
// Preserve arbitrary-length inputs without reusing coefficients or ignoring the tail.
return ArrayUtils.hashCode(bytes);
}
long hash = lengthSeed * bytes.length;
for (int offset = 0, index = 0; offset < bytes.length; offset += 4, index++) {
int length = bytes.length - offset;
if (length > 4) {
length = 4;
}
// Every unsigned 32-bit word, including the tail, has its own coefficient.
long word = 0L;
for (int i = 0; i < length; i++) {
word |= ((long) bytes[offset + i] & 0xff) << (8 * i);
}
hash += word * seeds[index];
}
return (int) (hash >>> 32);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -990,6 +990,12 @@ private boolean hardenResourceCalculation() {

public long calculateGlobalEnergyLimit(AccountCapsule accountCapsule) {
long frozeBalance = accountCapsule.getAllFrozenBalanceForEnergy();
// Resource queries use the fractional Stake 2.0 weight. Keep the truncated weight until
// resource optimization so earlier executions do not change how much TRX is burned.
if (VMConfig.allowTvmResourceOptimization()
&& getDynamicPropertiesStore().supportUnfreezeDelay()) {
return calculateGlobalEnergyLimitV2(frozeBalance);
}
if (frozeBalance < TRX_PRECISION) {
return 0;
}
Expand All @@ -1006,6 +1012,27 @@ public long calculateGlobalEnergyLimit(AccountCapsule accountCapsule) {
return (long) (energyWeight * ((double) totalEnergyLimit / totalEnergyWeight));
}

/**
* Same limit as {@code EnergyProcessor#calculateGlobalEnergyLimitV2}. Fractional suns count,
* and a balance below one TRX is not forced to zero.
*/
private long calculateGlobalEnergyLimitV2(long frozeBalance) {
long totalEnergyLimit = getDynamicPropertiesStore().getTotalEnergyCurrentLimit();
long totalEnergyWeight = getDynamicPropertiesStore().getTotalEnergyWeight();
if (totalEnergyWeight == 0) {
return 0;
}
if (hardenResourceCalculation()) {
return BigInteger.valueOf(frozeBalance)
.multiply(BigInteger.valueOf(totalEnergyLimit))
.divide(BigInteger.valueOf(TRX_PRECISION)
.multiply(BigInteger.valueOf(totalEnergyWeight)))
.longValueExact();
}
double energyWeight = (double) frozeBalance / TRX_PRECISION;
return (long) (energyWeight * ((double) totalEnergyLimit / totalEnergyWeight));
}

public long getHeadSlot() {
return getSlotByTimestampMs(getDynamicPropertiesStore().getLatestBlockHeaderTimestamp());
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
package org.tron.core.vm.program;

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 static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;

import java.util.Map;
import org.apache.commons.collections4.map.LRUMap;
import org.junit.After;
import org.junit.Test;
import org.tron.common.runtime.InternalTransaction;
import org.tron.common.runtime.vm.DataWord;
import org.tron.core.vm.config.VMConfig;
import org.tron.core.vm.program.invoke.ProgramInvoke;
import org.tron.core.vm.repository.Key;
import org.tron.core.vm.repository.Repository;

public class ProgramPrecompileCacheTest {

@After
public void tearDown() {
VMConfig.clearLocalSnapshot();
}

@Test
public void cacheCanRecompileAcrossProposalActivationAndRollback() {
for (boolean enabled : new boolean[]{false, true, false, true}) {
setResourceOptimization(enabled);
ProgramPrecompile compiled = program().getProgramPrecompile();
assertTrue(compiled.hasJumpDest(0));
assertTrue(compiled.hasJumpDest(3));
assertFalse(compiled.hasJumpDest(1));
assertFalse(compiled.hasJumpDest(2));
assertFalse(compiled.hasJumpDest(4));
assertSame(compiled, program().getProgramPrecompile());
}
}

@Test
public void lruEvictsEntriesWithOldHashesWithoutExceedingCapacity() {
LRUMap<Key, Integer> cache = new LRUMap<>(4);
for (boolean enabled : new boolean[]{false, true, false, true}) {
setResourceOptimization(enabled);
// Replace every entry after each switch, including entries from the previous hash mode.
for (int value = 0; value < 16; value++) {
cache.put(cacheKey(value), value);
assertEquals(Integer.valueOf(value), cache.get(cacheKey(value)));
assertTrue(cache.size() <= 4);
}
assertEquals(4, cache.size());
for (Map.Entry<Key, Integer> entry : cache.entrySet()) {
assertEquals(entry.getValue(), cache.get(entry.getKey(), false));
}
}
}

private static Program program() {
byte[] address = new byte[21];
address[0] = 0x41;
address[20] = 0x5a;
ProgramInvoke invoke = mock(ProgramInvoke.class);
when(invoke.getContractAddress()).thenReturn(new DataWord(address));
when(invoke.getDeposit()).thenReturn(mock(Repository.class));
return new Program(new byte[]{0x5b, 0x60, 0x5b, 0x5b, 0}, address, invoke,
mock(InternalTransaction.class));
}

private static Key cacheKey(int value) {
byte[] bytes = new byte[53];
bytes[52] = (byte) value;
return Key.create(bytes);
}

private static void setResourceOptimization(boolean enabled) {
VMConfig.Snapshot snapshot = new VMConfig.Snapshot();
snapshot.allowTvmResourceOptimization = enabled;
VMConfig.setLocalSnapshot(snapshot);
}
}
Loading
Loading