Skip to content

Commit 7d33b6a

Browse files
wbclaude
authored andcommitted
fix(event): filter removed=true from solidity maps and sync contract trigger processing
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
1 parent 980c707 commit 7d33b6a

5 files changed

Lines changed: 265 additions & 9 deletions

File tree

framework/src/main/java/org/tron/common/logsfilter/capsule/ContractTriggerCapsule.java

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -135,11 +135,11 @@ public void processTrigger() {
135135
EventPluginLoader.getInstance().postContractEventTrigger((ContractEventTrigger) event);
136136
}
137137

138-
if (EventPluginLoader.getInstance().isSolidityEventTriggerEnable()) {
138+
if (EventPluginLoader.getInstance().isSolidityEventTriggerEnable()
139+
&& !contractTrigger.isRemoved()) {
139140
boolean result = Args.getSolidityContractEventTriggerMap().computeIfAbsent(event
140141
.getBlockNumber(), listBlk -> new LinkedBlockingQueue())
141142
.offer((ContractEventTrigger) event);
142-
143143
if (!result) {
144144
logger.info("too many triggers, solidity event trigger lost: {}",
145145
event.getUniqueId());
@@ -159,11 +159,11 @@ public void processTrigger() {
159159
EventPluginLoader.getInstance().postContractLogTrigger(logTrigger);
160160
}
161161

162-
if (EventPluginLoader.getInstance().isSolidityLogTriggerRedundancy()) {
162+
if (EventPluginLoader.getInstance().isSolidityLogTriggerRedundancy()
163+
&& !contractTrigger.isRemoved()) {
163164
boolean result = Args.getSolidityContractLogTriggerMap().computeIfAbsent(event
164165
.getBlockNumber(), listBlk -> new LinkedBlockingQueue())
165166
.offer(logTrigger);
166-
167167
if (!result) {
168168
logger.info("too many triggers, solidity log trigger lost: {}",
169169
logTrigger.getUniqueId());
@@ -175,11 +175,11 @@ public void processTrigger() {
175175
EventPluginLoader.getInstance().postContractLogTrigger((ContractLogTrigger) event);
176176
}
177177

178-
if (EventPluginLoader.getInstance().isSolidityLogTriggerEnable()) {
178+
if (EventPluginLoader.getInstance().isSolidityLogTriggerEnable()
179+
&& !contractTrigger.isRemoved()) {
179180
boolean result = Args.getSolidityContractLogTriggerMap().computeIfAbsent(event
180181
.getBlockNumber(), listBlk -> new LinkedBlockingQueue())
181182
.offer((ContractLogTrigger) event);
182-
183183
if (!result) {
184184
logger.info("too many triggers, solidity log trigger lost: {}",
185185
event.getUniqueId());

framework/src/main/java/org/tron/core/db/Manager.java

Lines changed: 19 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1377,6 +1377,7 @@ public void pushBlock(final BlockCapsule block)
13771377
} catch (Throwable throwable) {
13781378
logger.error(throwable.getMessage(), throwable);
13791379
khaosDb.removeBlk(block.getBlockId());
1380+
clearSolidityContractTriggerCache(block.getNum());
13801381
throw throwable;
13811382
}
13821383
long newSolidNum = getDynamicPropertiesStore().getLatestSolidifiedBlockNum();
@@ -2378,6 +2379,16 @@ private void reOrgContractTrigger() {
23782379
getDynamicPropertiesStore().getLatestBlockHeaderHash());
23792380
}
23802381
}
2382+
clearSolidityContractTriggerCache(getHeadBlockNum());
2383+
}
2384+
2385+
private void clearSolidityContractTriggerCache(long blockNum) {
2386+
if (eventPluginLoaded
2387+
&& (EventPluginLoader.getInstance().isSolidityEventTriggerEnable()
2388+
|| EventPluginLoader.getInstance().isSolidityLogTriggerEnable())) {
2389+
Args.getSolidityContractLogTriggerMap().remove(blockNum);
2390+
Args.getSolidityContractEventTriggerMap().remove(blockNum);
2391+
}
23812392
}
23822393

23832394
private void postContractTrigger(final TransactionTrace trace, boolean remove, String blockHash) {
@@ -2397,9 +2408,14 @@ private void postContractTrigger(final TransactionTrace trace, boolean remove, S
23972408
.getLatestSolidifiedBlockNum());
23982409
contractTriggerCapsule.setBlockHash(blockHash);
23992410

2400-
if (!triggerCapsuleQueue.offer(contractTriggerCapsule)) {
2401-
logger.info("Too many triggers, contract log trigger lost: {}.",
2402-
trigger.getTransactionId());
2411+
// Process synchronously to avoid race condition between async queue and
2412+
// reOrgContractTrigger cache clearing. Performance is not impacted because
2413+
// processTrigger() only enqueues events into the plugin's internal queue
2414+
// without blocking on actual I/O.
2415+
try {
2416+
contractTriggerCapsule.processTrigger();
2417+
} catch (Throwable throwable) {
2418+
logger.warn("Post contract trigger failed.", throwable);
24032419
}
24042420
}
24052421
}

framework/src/test/java/org/tron/common/logsfilter/capsule/ContractTriggerCapsuleTest.java

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,18 +3,24 @@
33
import static com.google.common.collect.Lists.newArrayList;
44
import static org.junit.Assert.assertEquals;
55
import static org.junit.Assert.assertTrue;
6+
import static org.mockito.Mockito.mock;
7+
import static org.mockito.Mockito.when;
68

79
import com.beust.jcommander.internal.Lists;
10+
import java.lang.reflect.Field;
811
import java.util.ArrayList;
912
import java.util.Arrays;
1013
import lombok.extern.slf4j.Slf4j;
1114
import org.apache.commons.collections4.CollectionUtils;
1215
import org.apache.commons.lang3.ArrayUtils;
1316
import org.junit.Before;
1417
import org.junit.Test;
18+
import org.tron.common.logsfilter.EventPluginLoader;
19+
import org.tron.common.logsfilter.trigger.ContractLogTrigger;
1520
import org.tron.common.logsfilter.trigger.ContractTrigger;
1621
import org.tron.common.runtime.vm.DataWord;
1722
import org.tron.common.runtime.vm.LogInfo;
23+
import org.tron.core.config.args.Args;
1824

1925
@Slf4j
2026
public class ContractTriggerCapsuleTest {
@@ -58,6 +64,45 @@ public void testSetAndGetContractTrigger() {
5864
}
5965
}
6066

67+
@Test
68+
public void testRemovedTriggerNotWrittenToSolidityMap() throws Exception {
69+
Args.getSolidityContractLogTriggerMap().clear();
70+
Args.getSolidityContractEventTriggerMap().clear();
71+
72+
EventPluginLoader mockLoader = mock(EventPluginLoader.class);
73+
when(mockLoader.isSolidityLogTriggerEnable()).thenReturn(true);
74+
when(mockLoader.isSolidityEventTriggerEnable()).thenReturn(false);
75+
when(mockLoader.isContractLogTriggerEnable()).thenReturn(false);
76+
when(mockLoader.isContractEventTriggerEnable()).thenReturn(false);
77+
when(mockLoader.isSolidityLogTriggerRedundancy()).thenReturn(false);
78+
when(mockLoader.isContractLogTriggerRedundancy()).thenReturn(false);
79+
80+
Field instanceField = EventPluginLoader.class.getDeclaredField("instance");
81+
instanceField.setAccessible(true);
82+
EventPluginLoader originalInstance = (EventPluginLoader) instanceField.get(null);
83+
instanceField.set(null, mockLoader);
84+
85+
try {
86+
ContractLogTrigger trigger = new ContractLogTrigger();
87+
trigger.setRemoved(true);
88+
trigger.setBlockNumber(100L);
89+
trigger.setTransactionId("abc");
90+
trigger.setContractAddress("0x01");
91+
LogInfo logInfo = new LogInfo(new byte[0], new ArrayList<>(), new byte[0]);
92+
trigger.setLogInfo(logInfo);
93+
94+
ContractTriggerCapsule capsule = new ContractTriggerCapsule(trigger);
95+
capsule.processTrigger();
96+
97+
assertTrue(Args.getSolidityContractLogTriggerMap().isEmpty());
98+
assertTrue(Args.getSolidityContractEventTriggerMap().isEmpty());
99+
} finally {
100+
instanceField.set(null, originalInstance);
101+
Args.getSolidityContractLogTriggerMap().clear();
102+
Args.getSolidityContractEventTriggerMap().clear();
103+
}
104+
}
105+
61106
@Test
62107
public void testLogInfo() {
63108
logger.info("log info to string: {}, ", logInfo.toString());

framework/src/test/java/org/tron/core/db/ManagerMockTest.java

Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,10 +20,14 @@
2020
import java.lang.reflect.InvocationTargetException;
2121
import java.lang.reflect.Method;
2222
import java.nio.charset.StandardCharsets;
23+
import java.util.ArrayList;
24+
import java.util.Collections;
25+
import java.util.List;
2326

2427
import lombok.SneakyThrows;
2528
import lombok.extern.slf4j.Slf4j;
2629
import org.junit.After;
30+
import org.junit.Assert;
2731
import org.junit.Test;
2832
import org.mockito.MockedConstruction;
2933
import org.mockito.MockedStatic;
@@ -32,8 +36,12 @@
3236
import org.mockito.stubbing.Answer;
3337

3438
import org.tron.common.cron.CronExpression;
39+
import org.tron.common.logsfilter.EventPluginLoader;
40+
import org.tron.common.logsfilter.trigger.ContractLogTrigger;
41+
import org.tron.common.logsfilter.trigger.ContractTrigger;
3542
import org.tron.common.parameter.CommonParameter;
3643
import org.tron.common.runtime.ProgramResult;
44+
import org.tron.common.runtime.vm.LogInfo;
3745
import org.tron.common.utils.Sha256Hash;
3846
import org.tron.core.ChainBaseManager;
3947
import org.tron.core.capsule.BlockCapsule;
@@ -438,4 +446,111 @@ public void testReOrgLogsFilter() throws Exception {
438446
privateMethod.invoke(dbManager);
439447
}
440448

449+
@Test
450+
public void testPostContractTriggerProcessesSync() throws Exception {
451+
Manager dbManager = spy(new Manager());
452+
Field eventLoadedField = Manager.class.getDeclaredField("eventPluginLoaded");
453+
eventLoadedField.setAccessible(true);
454+
eventLoadedField.set(dbManager, true);
455+
456+
ChainBaseManager cbm = mock(ChainBaseManager.class);
457+
DynamicPropertiesStore dps = mock(DynamicPropertiesStore.class);
458+
when(dps.getLatestSolidifiedBlockNum()).thenReturn(0L);
459+
when(cbm.getDynamicPropertiesStore()).thenReturn(dps);
460+
Field cbmField = Manager.class.getDeclaredField("chainBaseManager");
461+
cbmField.setAccessible(true);
462+
cbmField.set(dbManager, cbm);
463+
464+
EventPluginLoader mockLoader = mock(EventPluginLoader.class);
465+
when(mockLoader.isContractLogTriggerEnable()).thenReturn(false);
466+
when(mockLoader.isContractEventTriggerEnable()).thenReturn(false);
467+
when(mockLoader.isSolidityLogTriggerEnable()).thenReturn(true);
468+
when(mockLoader.isSolidityEventTriggerEnable()).thenReturn(false);
469+
470+
Field instanceField = EventPluginLoader.class.getDeclaredField("instance");
471+
instanceField.setAccessible(true);
472+
EventPluginLoader original = (EventPluginLoader) instanceField.get(null);
473+
instanceField.set(null, mockLoader);
474+
475+
Args.getSolidityContractLogTriggerMap().clear();
476+
477+
try {
478+
ContractLogTrigger trigger = new ContractLogTrigger();
479+
trigger.setBlockNumber(200L);
480+
trigger.setTransactionId("tx-id");
481+
trigger.setContractAddress("0x01");
482+
trigger.setLogInfo(new LogInfo(new byte[0], new ArrayList<>(), new byte[0]));
483+
484+
TransactionTrace traceMock = mock(TransactionTrace.class);
485+
ProgramResult resultMock = mock(ProgramResult.class);
486+
when(traceMock.getRuntimeResult()).thenReturn(resultMock);
487+
List<ContractTrigger> triggers = new ArrayList<>();
488+
triggers.add(trigger);
489+
when(resultMock.getTriggerList()).thenReturn(triggers);
490+
491+
Method method = Manager.class.getDeclaredMethod("postContractTrigger",
492+
TransactionTrace.class, boolean.class, String.class);
493+
method.setAccessible(true);
494+
method.invoke(dbManager, traceMock, false, "blockhash");
495+
496+
Assert.assertNotNull(
497+
"synchronous processTrigger should populate solidity log map",
498+
Args.getSolidityContractLogTriggerMap().get(200L));
499+
} finally {
500+
instanceField.set(null, original);
501+
eventLoadedField.set(dbManager, false);
502+
Args.getSolidityContractLogTriggerMap().clear();
503+
}
504+
}
505+
506+
@Test
507+
public void testPostContractTriggerSwallowsThrowable() throws Exception {
508+
Manager dbManager = spy(new Manager());
509+
Field eventLoadedField = Manager.class.getDeclaredField("eventPluginLoaded");
510+
eventLoadedField.setAccessible(true);
511+
eventLoadedField.set(dbManager, true);
512+
513+
ChainBaseManager cbm = mock(ChainBaseManager.class);
514+
DynamicPropertiesStore dps = mock(DynamicPropertiesStore.class);
515+
when(dps.getLatestSolidifiedBlockNum()).thenReturn(0L);
516+
when(cbm.getDynamicPropertiesStore()).thenReturn(dps);
517+
Field cbmField = Manager.class.getDeclaredField("chainBaseManager");
518+
cbmField.setAccessible(true);
519+
cbmField.set(dbManager, cbm);
520+
521+
EventPluginLoader mockLoader = mock(EventPluginLoader.class);
522+
when(mockLoader.isContractLogTriggerEnable()).thenReturn(false);
523+
when(mockLoader.isContractEventTriggerEnable()).thenReturn(false);
524+
when(mockLoader.isSolidityLogTriggerEnable()).thenReturn(true);
525+
when(mockLoader.isSolidityEventTriggerEnable()).thenReturn(false);
526+
527+
Field instanceField = EventPluginLoader.class.getDeclaredField("instance");
528+
instanceField.setAccessible(true);
529+
EventPluginLoader original = (EventPluginLoader) instanceField.get(null);
530+
instanceField.set(null, mockLoader);
531+
532+
try {
533+
// null logInfo → processTrigger throws NPE on logInfo.getTopics()
534+
ContractLogTrigger trigger = new ContractLogTrigger();
535+
trigger.setBlockNumber(300L);
536+
trigger.setTransactionId("tx-id");
537+
trigger.setContractAddress("0x01");
538+
539+
TransactionTrace traceMock = mock(TransactionTrace.class);
540+
ProgramResult resultMock = mock(ProgramResult.class);
541+
when(traceMock.getRuntimeResult()).thenReturn(resultMock);
542+
when(resultMock.getTriggerList())
543+
.thenReturn(Collections.singletonList((ContractTrigger) trigger));
544+
545+
Method method = Manager.class.getDeclaredMethod("postContractTrigger",
546+
TransactionTrace.class, boolean.class, String.class);
547+
method.setAccessible(true);
548+
// catch (Throwable) absorbs the NPE — invocation must complete normally
549+
method.invoke(dbManager, traceMock, false, "blockhash");
550+
} finally {
551+
instanceField.set(null, original);
552+
eventLoadedField.set(dbManager, false);
553+
}
554+
}
555+
441556
}

0 commit comments

Comments
 (0)