hiero-ledger / hiero-ledger/hiero-consensus-node

Standalone simulation rejects missing payer (PAYER_ACCOUNT_NOT_FOUND) — blocks geth-compatible eth_call from

Open
#26,968 0 comments 0 reactions 0 assignees View on GitHub
Bug Hedera Smart Contract Service Impact Pending Triage
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

Open the contributing 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

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.