Skip to content

feat: add ResolveDependencies for concrete volume/metadata discovery#135

Draft
gfyrag wants to merge 1 commit into
mainfrom
feat/resolve-dependencies
Draft

feat: add ResolveDependencies for concrete volume/metadata discovery#135
gfyrag wants to merge 1 commit into
mainfrom
feat/resolve-dependencies

Conversation

@gfyrag
Copy link
Copy Markdown
Contributor

@gfyrag gfyrag commented May 18, 2026

Context

Ledger v3 uses a Raft architecture where transactions go through two stages:

  1. Admission (leader): discovers script dependencies, preloads volumes, proposes a Raft order
  2. FSM (all nodes): applies the order deterministically

The admission layer needs to know which volumes to preload before executing the script — but the volumes depend on the script itself.

The current approach uses GetInvolvedAccounts which returns expression trees (InvolvedAccountExpr) that the consumer must resolve. This led to ~500 lines of expression tree walking code on the ledger side, with bugs (GetAmount/GetAsset extracting wrong component, IsValidCall never called) and limitations (balance-dependent account addresses rejected).

Proposal

Add ResolveDependencies() which returns concrete data instead of expression trees:

type ResolvedDependencies struct {
    Volumes  map[string]map[string]*big.Int  // account → asset → balance
    Metadata map[string]map[string]string    // account → key → value
}

func (p ParseResult) ResolveDependencies(ctx, vars, store) (*ResolvedDependencies, error)

The consumer provides a Store callback (same interface as Run), gets back concrete reads, and handles the rest (hashing, preloading, drift detection) on its side.

How the ledger uses it

Admission: call ResolveDependencies → get volumes + metadata → hash inputs → preload into Raft order

FSM: call ResolveDependencies again → hash inputs → compare with admission hash → if match, execute; if mismatch, reject (retryable)

This is classic optimistic concurrency control.

What this replaces on the ledger side

  • ~500 lines of resolveExprToString, resolveExprWithMetadata, collectFnMetaReads, ResolveDeferredAccounts
  • No more coupling with InvolvedAccountExpr types
  • No more IsValidCall / non-deterministic script rejection
  • All features (meta chains, balance in addresses, mid-script calls) work transparently

Implementation

  • recordingStore wraps a Store and records all GetBalances/GetAccountsMetadata calls
  • ResolveDependencies runs the script with the recording store, discards the execution result, returns the recorded reads
  • The internal implementation can be optimized later (partial execution, static analysis) without changing the API

Test plan

  • Simple transfer — records source balance
  • World source — no balance recorded
  • meta() call — records metadata read
  • Multiple sources — records all source balances
  • Variables — records resolved account balances
  • balance() function — records balance read
  • Multiple sends — records all reads
  • set_account_meta — no metadata reads produced
  • Nested meta() chain — records all chained metadata reads
  • Send all (*) — records source balance
  • Empty reads — world-only script produces no reads
  • Full existing test suite passes

🤖 Generated with Claude Code

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 18, 2026

Important

Review skipped

Draft detected.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: ed96509b-76c7-434d-ae57-eb296350b6cb

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/resolve-dependencies

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@gfyrag gfyrag force-pushed the feat/resolve-dependencies branch from aee00c8 to 0b8b90c Compare May 18, 2026 15:53
@codecov
Copy link
Copy Markdown

codecov Bot commented May 18, 2026

Codecov Report

❌ Patch coverage is 74.62687% with 17 lines in your changes missing coverage. Please review.
✅ Project coverage is 67.10%. Comparing base (7856fd0) to head (52a99af).

Files with missing lines Patch % Lines
internal/interpreter/resolve_dependencies.go 76.31% 5 Missing and 4 partials ⚠️
internal/interpreter/recording_store.go 84.00% 2 Missing and 2 partials ⚠️
internal/interpreter/interpreter_error.go 0.00% 2 Missing ⚠️
numscript.go 0.00% 2 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main     #135      +/-   ##
==========================================
+ Coverage   66.96%   67.10%   +0.13%     
==========================================
  Files          47       49       +2     
  Lines        5068     5135      +67     
==========================================
+ Hits         3394     3446      +52     
- Misses       1477     1487      +10     
- Partials      197      202       +5     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

Add ParseResult.ResolveDependencies() which resolves all balance and
metadata reads a script depends on, returning concrete values rather
than expression trees.

This enables consumers (e.g. ledger v3) to:
- Preload exact volumes needed before FSM execution
- Compute an input hash for optimistic concurrency control
- Detect drift between admission and execution time

Unlike GetInvolvedAccounts (which returns expression trees requiring
consumer-side resolution), ResolveDependencies returns concrete
(account, asset) → balance and (account, key) → value maps. The
consumer doesn't need to walk expression trees or handle deferred
resolution.

All features are supported transparently: meta() chains, balance()
in account addresses, mid-script function calls, oneof, etc.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@gfyrag gfyrag force-pushed the feat/resolve-dependencies branch from 0b8b90c to 52a99af Compare May 18, 2026 16:06
@gfyrag
Copy link
Copy Markdown
Contributor Author

gfyrag commented May 18, 2026

Note on experimental-mid-script-function-call

ResolveDependencies performs Phases 1-2 of RunProgram (variable resolution + balance preloading) but does not execute statements. This means it captures all store reads for standard scripts.

The exception is experimental-mid-script-function-call, which allows calling balance() between send statements:

#![feature("experimental-mid-script-function-call")]

send [USD/2 100] (
  source = @world
  destination = @acc
)

// This balance() reads a value that depends on the first send's result.
// It cannot be discovered without actually executing the statements.
send balance(@acc, USD/2) (
  source = @acc
  destination = @dest
)

In this case, the mid-script balance() call produces a store read that depends on the execution of prior statements. ResolveDependencies cannot capture it because it doesn't execute statements.

To handle this, ResolveDependenciesOptions includes a ForbiddenFlags field. Consumers that need a complete dependency list can forbid this feature:

deps, err := parsed.ResolveDependencies(ctx, vars, store, numscript.ResolveDependenciesOptions{
    ForbiddenFlags: map[string]struct{}{
        "experimental-mid-script-function-call": {},
    },
})

Scripts that declare this feature will be rejected with a ForbiddenFeature error, making the incompleteness explicit rather than silent.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant