-
Notifications
You must be signed in to change notification settings - Fork 1.7k
feat: optimize delegate and undelegate instruction handling #6920
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -139,6 +139,7 @@ public class RepositoryImpl implements Repository { | |
| private final HashMap<Key, Value<DelegatedResourceAccountIndex>> delegatedResourceAccountIndexCache = new HashMap<>(); | ||
| private final HashBasedTable<Key, Key, Value<byte[]>> transientStorage = HashBasedTable.create(); | ||
| private final HashSet<Key> newContractCache = new HashSet<>(); | ||
| private final HashSet<Key> selfDestructCache = new HashSet<>(); | ||
|
|
||
| public static void removeLruCache(byte[] address) { | ||
| } | ||
|
|
@@ -572,6 +573,29 @@ public boolean isNewContract(byte[] address) { | |
| } | ||
| } | ||
|
|
||
| @Override | ||
| public void markSelfDestruct(byte[] address) { | ||
| selfDestructCache.add(Key.create(address)); | ||
| } | ||
|
|
||
| @Override | ||
| public boolean isSelfDestructed(byte[] address) { | ||
| Key key = Key.create(address); | ||
| if (selfDestructCache.contains(key)) { | ||
| return true; | ||
| } | ||
|
|
||
| if (parent != null) { | ||
| boolean isSelfDestructed = parent.isSelfDestructed(address); | ||
| if (isSelfDestructed) { | ||
| selfDestructCache.add(key); | ||
| } | ||
| return isSelfDestructed; | ||
| } else { | ||
| return false; | ||
| } | ||
| } | ||
|
|
||
| @Override | ||
| public void updateAccount(byte[] address, AccountCapsule accountCapsule) { | ||
| accountCache.put(Key.create(address), | ||
|
|
@@ -780,6 +804,7 @@ public void commit() { | |
| commitDelegatedResourceAccountIndexCache(repository); | ||
| commitTransientStorage(repository); | ||
| commitNewContractCache(repository); | ||
| commitSelfDestructCache(repository); | ||
| } | ||
|
|
||
| @Override | ||
|
|
@@ -1142,6 +1167,12 @@ public void commitNewContractCache(Repository deposit) { | |
| } | ||
| } | ||
|
|
||
| private void commitSelfDestructCache(Repository deposit) { | ||
| if (deposit != null) { | ||
| selfDestructCache.forEach(key -> deposit.markSelfDestruct(key.getData())); | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Does propagating the marker only during commit mean that it follows the same lifecycle as the other repository state, so a reverted child repository requires no separate cleanup?
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Correct. The marker is propagated to the parent only when the child repository commits. If the child execution is reverted, the child repository and its marker are discarded together. |
||
| } | ||
| } | ||
|
|
||
| /** | ||
| * Get the block id from the number. | ||
| */ | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -3,6 +3,7 @@ | |
| import static org.junit.Assert.assertEquals; | ||
| import static org.mockito.ArgumentMatchers.any; | ||
| import static org.tron.core.config.Parameter.ChainConstant.FROZEN_PERIOD; | ||
| import static org.tron.core.config.Parameter.ForkBlockVersionEnum.VERSION_4_8_2_2; | ||
|
|
||
| import java.util.List; | ||
| import java.util.Locale; | ||
|
|
@@ -16,6 +17,7 @@ | |
| import org.junit.BeforeClass; | ||
| import org.junit.Ignore; | ||
| import org.junit.Test; | ||
| import org.mockito.MockedStatic; | ||
| import org.mockito.Mockito; | ||
| import org.springframework.beans.factory.annotation.Autowired; | ||
| import org.springframework.util.StringUtils; | ||
|
|
@@ -24,6 +26,7 @@ | |
| import org.tron.common.parameter.CommonParameter; | ||
| import org.tron.common.runtime.InternalTransaction; | ||
| import org.tron.common.utils.DecodeUtil; | ||
| import org.tron.common.utils.ForkController; | ||
| import org.tron.core.Constant; | ||
| import org.tron.core.Wallet; | ||
| import org.tron.core.capsule.AccountCapsule; | ||
|
|
@@ -42,6 +45,7 @@ | |
| import org.tron.core.vm.config.ConfigLoader; | ||
| import org.tron.core.vm.config.VMConfig; | ||
| import org.tron.core.vm.program.Program; | ||
| import org.tron.core.vm.program.Program.OutOfTimeException; | ||
| import org.tron.core.vm.program.invoke.ProgramInvokeMockImpl; | ||
| import org.tron.core.vm.repository.Repository; | ||
| import org.tron.protos.Protocol; | ||
|
|
@@ -1182,6 +1186,7 @@ public void testSuicideAction() throws ContractValidateException { | |
|
|
||
| program.suicide(new DataWord( | ||
| dbManager.getAccountStore().getBlackhole().getAddress().toByteArray())); | ||
| Assert.assertTrue(program.getContractState().isSelfDestructed(program.getContextAddress())); | ||
|
|
||
| DecodeUtil.addressPreFixByte = prePrefixByte; | ||
| VMConfig.initAllowEnergyAdjustment(0); | ||
|
|
@@ -1240,6 +1245,7 @@ public void testSuicideAction2() throws ContractValidateException { | |
| OperationActions.suicideAction2(program); | ||
|
|
||
| Assert.assertEquals(1, program.getResult().getDeleteAccounts().size()); | ||
| Assert.assertTrue(program.getContractState().isSelfDestructed(contractAddr)); | ||
|
|
||
|
|
||
| invoke = new ProgramInvokeMockImpl(StoreFactory.getInstance(), new byte[0], contractAddr); | ||
|
|
@@ -1256,6 +1262,7 @@ public void testSuicideAction2() throws ContractValidateException { | |
| dbManager.getAccountStore().getBlackhole().getAddress().toByteArray())); | ||
|
|
||
| Assert.assertEquals(0, spyProgram.getResult().getDeleteAccounts().size()); | ||
| Assert.assertTrue(spyProgram.getContractState().isSelfDestructed(contractAddr)); | ||
|
|
||
| DecodeUtil.addressPreFixByte = prePrefixByte; | ||
| VMConfig.initAllowEnergyAdjustment(0); | ||
|
|
@@ -1266,6 +1273,31 @@ public void testSuicideAction2() throws ContractValidateException { | |
| VMConfig.initAllowTvmVote(0); | ||
| } | ||
|
|
||
| @Test | ||
| public void testSuicide2RejectsSelfDestructedBeneficiaryAfterFork() | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Could we also add a pre-fork case asserting that suicide2() succeeds when VERSION_4_8_2_2 is not activated? Since the beneficiary check is invoked unconditionally and gated inside MUtil, this would explicitly protect backward compatibility before the fork.
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Thanks for pointing this out. Before VERSION_4_8_2_2 is activated, checkCPUTimeForSelfDestructedBeneficiary() is a no-op and execution continues through the existing suicide2() path. The existing testSuicideAction2() still covers the successful legacy flow, while the newly added test focuses specifically on the rejection behavior introduced after activation. Given that the behavioral change is confined to the activated branch, we would prefer to keep the current test scope unless there is another pre-activation side effect that needs to be covered.
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Could we also add a pre-fork case asserting that suicide2() succeeds when VERSION_4_8_2_2 is not activated? Since the beneficiary check is invoked unconditionally and gated inside MUtil, this would explicitly protect backward compatibility before the fork.
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Thanks. This appears to be the same concern as the discussion above. The pre-activation check returns without throwing, so the existing suicide2() behavior remains unchanged. I have added the detailed explanation in the other thread. |
||
| throws ContractValidateException { | ||
| byte[] contractAddr = Hex.decode("41471fd3ad3e9eeadeec4608b92d16ce6b500704cc"); | ||
| byte[] beneficiary = Hex.decode("411111111111111111111111111111111111111111"); | ||
| invoke = new ProgramInvokeMockImpl(StoreFactory.getInstance(), new byte[0], contractAddr); | ||
| program = new Program(null, null, invoke, | ||
| new InternalTransaction( | ||
| Protocol.Transaction.getDefaultInstance(), | ||
| InternalTransaction.TrxType.TRX_UNKNOWN_TYPE)); | ||
| program.getContractState().markSelfDestruct(beneficiary); | ||
|
|
||
| ForkController forkController = Mockito.mock(ForkController.class); | ||
| try (MockedStatic<ForkController> fork = Mockito.mockStatic(ForkController.class)) { | ||
| fork.when(ForkController::instance).thenReturn(forkController); | ||
| Mockito.when(forkController.pass(VERSION_4_8_2_2)).thenReturn(true); | ||
|
|
||
| OutOfTimeException exception = Assert.assertThrows(OutOfTimeException.class, | ||
| () -> program.suicide2(new DataWord(beneficiary))); | ||
| Assert.assertEquals( | ||
| "CPU timeout for SELFDESTRUCT with selfdestructed beneficiary", | ||
| exception.getMessage()); | ||
| } | ||
| } | ||
|
|
||
| @Test | ||
| public void testVoteWitnessCost() throws ContractValidateException { | ||
| // Build stack environment, the stack from top to bottom is 0x00, 0x80, 0x00, 0x80 | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is caching the positive result from the parent repository intentional here? My understanding is that the self-destruct state is monotonic within the transaction, while a negative result is left uncached so that a later parent update can still be observed.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, that is the intention. Once an address is marked, it remains self-destructed for the current transaction, so caching a positive result avoids repeated parent lookups. Negative results are not cached because the parent repository may receive the marker later.