hiero-ledger / hiero-ledger/hiero-consensus-node
Standalone simulation rejects missing payer (PAYER_ACCOUNT_NOT_FOUND) — blocks geth-compatible eth_call from
- Dominant language
- Java
- Stars
- 406
- Forks
- 226
- Avg merge
- 3d 4h
- Merged PRs (30d)
- 210
Description
### Background
Mirror Node and the JSON-RPC relay implement `eth_call` / `eth_estimateGas` by building a HAPI `TransactionBody` and executing it through the consensus-node **standalone** `TransactionExecutor` (`TransactionExecutors` — documented for "Mirror Node gas estimation and `eth_call`").
On Ethereum, `eth_call`'s `from` may be any address and is treated as a **zero-state EOA** for a fee-less simulation. On Hedera, an unregistered `from` fails with `PAYER_ACCOUNT_NOT_FOUND` (`"Sender account not found."`) before the EVM runs. That breaks a common tooling pattern (simulate / estimate as a never-seen sender).
**Reproduction (via MN `/contracts/call` or relay `eth_call`):**
```text
POST /api/v1/contracts/call
{ "from": "0x1111111111111111111111111111111111111111", "to": "", "data": "0x...", "gas": 300000 }
→ PAYER_ACCOUNT_NOT_FOUND
{ "to": "", "data": "0x...", "gas": 300000 } # omit from, value 0
→ executes (MN substitutes treasury)
```
**Why CN must change:** [hiero-mirror-node#12356](https://github.com/hiero-ledger/hiero-mirror-node/issues/12356) already tried relaxing MN's early sender check; the failure still came from hedera-app `PreHandleWorkflowImpl` when `accountStore.getAccountById(payer) == null`. MN maintainers asked for a services change before dropping fail-fast.
**Root cause (CN):** `StandaloneDispatchFactory.newDispatch` → `preHandleWorkflow.getCurrentPreHandleResult` → payer null → `PAYER_ACCOUNT_NOT_FOUND`. `ReadableAccountStoreImpl.getAccountLeaf` only loads `ACCOUNT_NUM` ids (alias-form payers also resolve to null). Live ingest correctly requires a real payer via `SolvencyPreCheck`; the bug is applying that rule on the **standalone simulation** path with no ephemeral account.
**Proposed approach (standalone only — do not weaken live ingest):**
In `StandaloneDispatchFactory` / standalone executor setup, if the payer is missing from readable state, materialize an **ephemeral EOA** on the savepoint stack before pre-handle:
- temporary `AccountID` (num) + alias map entry for the 20-byte EVM `from` when applicable
- `ethereumNonce = 0`, no code
- `tinybarBalance` sufficient for typical MN sim `transactionFee` **or** documented fee no-op for standalone
- stack-local only — never committed to consensus state
Then existing pre-handle / dispatch proceeds; EVM `msg.sender` matches the client `from`.
**Non-goals:** do not relax `SolvencyPreCheck` for live ingest / `eth_sendRawTransaction` / HAPI submit; do not change live smart-contract-as-payer behavior.
**Open design points for implementation / review:**
1. `value > 0` with unknown `from` — prefer geth (run sim; transfer fails in-EVM if balance 0) vs reject when `value > 0`
2. Ephemeral balance vs standalone fee no-op (coordinate with MN fee behavior; see also [hiero-mirror-node#12437](https://github.com/hiero-ledger/hiero-mirror-node/issues/12437))
3. How temporary account nums are allocated without colliding with real entity ids
**Code pointers:** `TransactionExecutors`, `StandaloneDispatchFactory`, `PreHandleWorkflowImpl`, `ReadableAccountStoreImpl.getAccountLeaf`, `SolvencyPreCheck`.
### Acceptance Criteria
1. Standalone `TransactionExecutor` can execute a `ContractCall` / Ethereum simulation whose payer is **not** initially in state and returns an EVM result (success or contract revert), **not** `PAYER_ACCOUNT_NOT_FOUND`.
2. In that simulation, `msg.sender` equals the requested EVM `from` (not silently substituted with treasury).
3. Ephemeral payer does **not** persist after the standalone dispatch (no consensus state mutation).
4. Live ingest / normal pre-handle with a missing payer still returns `PAYER_ACCOUNT_NOT_FOUND`.
5. Alias-form and long-zero `from` both behave correctly for `msg.sender`.
6. A sim using MN-typical `transactionFee` does not spuriously fail with `INSUFFICIENT_PAYER_BALANCE` for the ephemeral payer.
7. Unit (and HAPI/standalone integration as applicable) tests cover: missing payer on standalone succeeds; missing payer on live pre-handle still fails; alias `from` → correct `msg.sender`.
8. MN can land [hiero-mirror-node#12356](https://github.com/hiero-ledger/hiero-mirror-node/issues/12356) against a CN build that includes this (remove early reject; re-verify `/contracts/call` and `/opcodes` [hiero-mirror-node#13830](https://github.com/hiero-ledger/hiero-mirror-node/issues/13830)).
### Dependencies
1. **Blocks / unblocks:** [hiero-mirror-node#12356](https://github.com/hiero-ledger/hiero-mirror-node/issues/12356) — MN still must remove early `getSenderAccountID` reject and pass a usable payer `AccountID`; this CN change is the unblocker they requested.
2. Related: [hiero-mirror-node#13830](https://github.com/hiero-ledger/hiero-mirror-node/issues/13830) (`/opcodes`), [hiero-mirror-node#12437](https://github.com/hiero-ledger/hiero-mirror-node/issues/12437) (sim payer balance).
3. Precedent on MN: synthetic accounts via state overrides (`AccountReadableKVState.applyStateOverride`); contract-as-sender sims already handled (MN #12923).
### Definition of Ready (DoR) Checklist
- [x] Clear acceptance criteria
- [x] Clear and detailed description
- [x] Dependencies identified
- [x] Links to documentation / related issues
- [ ] Should be completable in 2-3 Days *(estimate in grooming — may be larger given fee/alias design points)*
- [ ] Initial draft of Low-level design document *(ephemeral account allocation + fee policy)*
- [x] At least high level test plan *(standalone unit + live pre-handle negative + MN follow-on verification)*
- [ ] Groomed/Estimated
### Definition of Done (DoD) Checklist
- [ ] Acceptance Criteria complete
- [ ] No Codacy issues greater than minor (in new code)
- [ ] JavaDocs updated/created
- [ ] Code commented
- [ ] Unit tests created/updated
- [ ] 80% test code coverage (in new code)
- [ ] Happy Path and major negative cases in HAPI tests as applicable
Contributor guide
Research direction
Start with TransactionExecutors and StandaloneDispatchFactory, then trace PreHandleWorkflowImpl and ReadableAccountStoreImpl.getAccountLeaf for the missing-payer path. Review SolvencyPreCheck and the linked Mirror Node issues before resolving the ephemeral-account, alias, and fee-policy design points. Done means standalone simulations preserve msg.sender without state persistence, while live pre-handle still returns PAYER_ACCOUNT_NOT_FOUND, with the specified tests covering both paths.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- java
- Domain
- api, backend, testing
- Issue type
- Bug
- Difficulty
- 5/5
- Estimated time
- Over a week
- Activity status
- Active
- Clarity
- Mostly clear
- Newbie friendliness
- 35/100