hiero-ledger / hiero-ledger/hiero-consensus-node
State singletons with `null` values
- Dominant language
- Java
- Stars
- 406
- Forks
- 226
- Avg merge
- 3d 4h
- Merged PRs (30d)
- 210
Description
Hi team,
With the `hedera-app` bump from 0.64.3 to 0.65.0 we noticed the following exception that started to appear in our logs:
```
2025-09-24 03:31:37.038
2025-09-24T00:31:37.028Z ERROR tomcat-handler-26249 c.h.n.a.w.h.DispatchProcessor Possibly CATASTROPHIC failure - exception thrown while handling dispatch java.lang.NullPointerException: null
at java.base/java.util.Objects.requireNonNull(Unknown Source)
at com.hedera.node.app.throttle.ThrottleServiceManager.resetThrottlesUnconditionally(ThrottleServiceManager.java:237)
at com.hedera.node.app.workflows.handle.throttle.DispatchUsageManager.screenForCapacity(DispatchUsageManager.java:72)
at com.hedera.node.app.workflows.handle.DispatchProcessor.tryHandle(DispatchProcessor.java:139)
at com.hedera.node.app.workflows.handle.DispatchProcessor.processDispatch(DispatchProcessor.java:114)
at com.hedera.node.app.workflows.standalone.TransactionExecutors.lambda$newExecutor$0(TransactionExecutors.java:224)
at com.hedera.node.app.workflows.standalone.TransactionExecutors$DefaultTracerBinding.runWhere(TransactionExecutors.java:326)
at com.hedera.node.app.workflows.standalone.TransactionExecutors.lambda$newExecutor$1(TransactionExecutors.java:223)
at org.hiero.mirror.web3.service.TransactionExecutionService.execute(TransactionExecutionService.java:83)
at org.hiero.mirror.web3.service.ContractCallService.doProcessCall(ContractCallService.java:126)
...
```
Please note that we have custom implementations of all needed in the state singletons, such as `EntityCountsSingleton`, `EntityIdSingleton`, `ThrottleUsageSingleton`, etc. All of them have a no-op `set(...)` method and a `get()` method that always returns a default instance. This is sufficient for our needs.
However, after the `hedera-app` bump to 0.65.0 we started noticing that the singletons in some rare cases started to have `null` values which results in exceptions such as the one above.
After comparing the changes in the `hedera-app` bump I noticed that `WritableSingletonStateBase#get()` was:
```
public T get() {
// If there is a modification, then we've already done a "put" or "remove"
// and should return based on the modification
if (isModified()) {
final var currentValue = currentValue();
return currentValue != null ? currentValue : super.get();
} else {
return super.get();
}
}
```
and became:
```
public T get() {
// If there is a modification, then we've already done a "put" or "remove"
// and should return based on the modification
if (isModified()) {
return currentValue();
} else {
return super.get();
}
}
```
Even with our custom implementations there are still cases like `WrappedWritableSingletonState.get()` that call this method directly bypassing our code. This change in the `WritableSingletonStateBase#get()` method now seems to allow `null` values which causes issues on our side.
Please let me know if my conclusions are correct and if so - is there some fix that can be done on your side? In which cases `null` is a valid value?
Contributor guide
Research direction
Start with WritableSingletonStateBase#get() and WrappedWritableSingletonState.get(), then trace the call to ThrottleServiceManager.resetThrottlesUnconditionally shown in the stack trace. Compare the 0.64.3 and 0.65.0 behavior to determine when a null value is valid for modified singleton state. Done means the expected null semantics are established and the reported unexpected NullPointerException is prevented or clearly addressed.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- java
- Domain
- backend
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Stale
- Clarity
- Mostly clear
- Newbie friendliness
- 45/100