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
Original file line number Diff line number Diff line change
@@ -1,24 +1,49 @@
package org.tron.core.utils;

import java.util.Set;
import java.util.concurrent.atomic.AtomicBoolean;
import lombok.extern.slf4j.Slf4j;
import org.reflections.Reflections;
import org.tron.core.actuator.AbstractActuator;
import org.tron.core.exception.TronError;

@Slf4j(topic = "TransactionRegister")
public class TransactionRegister {

private static final AtomicBoolean REGISTERED = new AtomicBoolean(false);
private static final String PACKAGE_NAME = "org.tron.core.actuator";

public static void registerActuator() {
Reflections reflections = new Reflections("org.tron");
Set<Class<? extends AbstractActuator>> subTypes = reflections
.getSubTypesOf(AbstractActuator.class);
for (Class _class : subTypes) {
try {
_class.newInstance();
} catch (Exception e) {
logger.error("{} contract actuator register fail!", _class, e);
if (REGISTERED.get()) {
logger.info("Actuator already registered.");
return;
}

synchronized (TransactionRegister.class) {
if (REGISTERED.get()) {
logger.info("Actuator already registered.");
return;
}

logger.info("Register actuator start.");
Reflections reflections = new Reflections(PACKAGE_NAME);
Set<Class<? extends AbstractActuator>> subTypes = reflections
.getSubTypesOf(AbstractActuator.class);

for (Class<? extends AbstractActuator> clazz : subTypes) {
try {
logger.debug("Registering actuator: {} start", clazz.getName());
clazz.getDeclaredConstructor().newInstance();
logger.debug("Registering actuator: {} done", clazz.getName());
} catch (Exception e) {
throw new TronError(clazz.getName() + ": "
+ (e.getCause() == null ? e.getMessage() : e.getCause().getMessage()),
e, TronError.ErrCode.ACTUATOR_REGISTER);
}
}

REGISTERED.set(true);
logger.info("Register actuator done, total {}.", subTypes.size());
}
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ public enum ErrCode {
RATE_LIMITER_INIT(1),
SOLID_NODE_INIT(0),
PARAMETER_INIT(1),
ACTUATOR_REGISTER(1),
JDK_VERSION(1);

private final int code;
Expand Down
8 changes: 5 additions & 3 deletions framework/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -136,11 +136,11 @@ test {
exclude 'org/tron/core/ShieldedTRC20BuilderTest.class'
exclude 'org/tron/common/runtime/vm/WithdrawRewardTest.class'
}
maxHeapSize = "1024m"
maxHeapSize = "512m"
maxParallelForks = Math.max(1, Math.min(2, Runtime.runtime.availableProcessors()))
doFirst {
// Restart the JVM after every 100 tests to avoid memory leaks and ensure test isolation
forkEvery = 100
jvmArgs "-XX:MetaspaceSize=128m","-XX:MaxMetaspaceSize=256m", "-XX:+UseG1GC"
}
}

Expand Down Expand Up @@ -175,7 +175,9 @@ def binaryRelease(taskName, jarName, mainClass) {
exclude "META-INF/*.SF"
exclude "META-INF/*.DSA"
exclude "META-INF/*.RSA"

// for service SPI loader for dnsjava
// see https://issues.apache.org/jira/browse/HADOOP-19288
exclude "META-INF/services/java.net.spi.InetAddressResolverProvider"
manifest {
attributes "Main-Class": "${mainClass}"
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,161 @@
package org.tron.core.utils;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertThrows;
import static org.junit.Assert.assertTrue;
import static org.mockito.Mockito.mockConstruction;
import static org.mockito.Mockito.when;

import java.lang.reflect.Field;
import java.util.Collections;
import java.util.HashSet;
import java.util.concurrent.atomic.AtomicBoolean;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.MockedConstruction;
import org.mockito.junit.MockitoJUnitRunner;
import org.reflections.Reflections;
import org.tron.core.actuator.AbstractActuator;
import org.tron.core.actuator.TransferActuator;
import org.tron.core.config.args.Args;
import org.tron.core.exception.TronError;

@RunWith(MockitoJUnitRunner.class)
public class TransactionRegisterTest {

@Before
public void init() throws Exception {
Args.getInstance().setActuatorSet(new HashSet<>());
resetRegisteredField();
}

@After
public void destroy() {
Args.clearParam();
}

private void resetRegisteredField() throws Exception {
Field registeredField = TransactionRegister.class.getDeclaredField("REGISTERED");
registeredField.setAccessible(true);
AtomicBoolean registered = (AtomicBoolean) registeredField.get(null);
registered.set(false);
}

@Test
public void testAlreadyRegisteredSkipRegistration() throws Exception {

TransactionRegister.registerActuator();

Field registeredField = TransactionRegister.class.getDeclaredField("REGISTERED");
registeredField.setAccessible(true);
AtomicBoolean registered = (AtomicBoolean) registeredField.get(null);
assertTrue("First registration should be completed", registered.get());

TransactionRegister.registerActuator();
assertTrue("Registration should still be true", registered.get());
}

@Test
public void testConcurrentAccessThreadSafe() throws Exception {
final int threadCount = 5;
Thread[] threads = new Thread[threadCount];
final AtomicBoolean testPassed = new AtomicBoolean(true);

for (int i = 0; i < threadCount; i++) {
threads[i] = new Thread(() -> {
try {
TransactionRegister.registerActuator();
} catch (Exception e) {
testPassed.set(false);
}
});
}

for (Thread thread : threads) {
thread.start();
}

for (Thread thread : threads) {
thread.join();
}

assertTrue("All threads should complete successfully", testPassed.get());

Field registeredField = TransactionRegister.class.getDeclaredField("REGISTERED");
registeredField.setAccessible(true);
AtomicBoolean registered = (AtomicBoolean) registeredField.get(null);
assertTrue("Registration should be completed", registered.get());
}

@Test
public void testDoubleCheckLockingAtomicBoolean() throws Exception {
Field registeredField = TransactionRegister.class.getDeclaredField("REGISTERED");
registeredField.setAccessible(true);
AtomicBoolean registered = (AtomicBoolean) registeredField.get(null);

assertFalse("Initial registration state should be false", registered.get());

TransactionRegister.registerActuator();
assertTrue("After first call, should be registered", registered.get());

TransactionRegister.registerActuator();
assertTrue("After second call, should still be registered", registered.get());
}

@Test
public void testSynchronizationBlock() throws Exception {
final AtomicBoolean completedRegistration = new AtomicBoolean(false);

Thread registrationThread = new Thread(() -> {
TransactionRegister.registerActuator();
completedRegistration.set(true);

});

registrationThread.start();
registrationThread.join();

assertTrue("Registration should have completed", completedRegistration.get());

Field registeredField = TransactionRegister.class.getDeclaredField("REGISTERED");
registeredField.setAccessible(true);
AtomicBoolean registered = (AtomicBoolean) registeredField.get(null);
assertTrue("Should be registered after completion", registered.get());
}

@Test
public void testMultipleCallsConsistency() throws Exception {
Field registeredField = TransactionRegister.class.getDeclaredField("REGISTERED");
registeredField.setAccessible(true);
AtomicBoolean registered = (AtomicBoolean) registeredField.get(null);

assertFalse("Should start unregistered", registered.get());

TransactionRegister.registerActuator();

assertTrue("Should be registered after first call", registered.get());

for (int i = 0; i < 5; i++) {
TransactionRegister.registerActuator();
assertTrue("Should remain registered after call " + (i + 2), registered.get());
}
}

@Test
public void testThrowsTronError() {
try (MockedConstruction<Reflections> ignored = mockConstruction(Reflections.class,
(mock, context) -> when(mock.getSubTypesOf(AbstractActuator.class))
.thenReturn(Collections.singleton(TransferActuator.class)));
MockedConstruction<TransferActuator> ignored1 = mockConstruction(TransferActuator.class,
(mock, context) -> {
throw new RuntimeException("boom");
})) {
TronError error = assertThrows(TronError.class, TransactionRegister::registerActuator);
assertEquals(TronError.ErrCode.ACTUATOR_REGISTER, error.getErrCode());
assertTrue(error.getMessage().contains("TransferActuator"));
}
}
}
4 changes: 3 additions & 1 deletion framework/src/test/resources/logback-test.xml
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,15 @@
<pattern>%d{HH:mm:ss.SSS} %p [%c{1}] %m%n</pattern>
</encoder>
<filter class="ch.qos.logback.classic.filter.ThresholdFilter">
<level>INFO</level>
<level>ERROR</level>
</filter>
</appender>

<appender class="ch.qos.logback.core.rolling.RollingFileAppender"
name="FILE">
<file>./logs/tron-test.log</file>
<bufferSize>8192</bufferSize>
<immediateFlush>true</immediateFlush>
<rollingPolicy
class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
<!-- rollover daily -->
Expand Down