Repository navigation
feat: multi-tx 익스플로잇 후보 중 실제 drain tx 선택 (RCA 전) - #12
Merged
Merged
Conversation
When a single alert names more than one exploit tx (e.g. OriginTrail — authority/state-setup tx + the actual asset-drain tx), hack-detector now sends up to 3 candidate hashes (PR hack-detector#14). This teaches Backlight to pick the real asset-drain among them before spending the expensive RCA budget. How it works (no lumoskit core change, no DB migration): - POST /signals accepts optional candidate_tx_hashes[] (≤3, validated). It is stashed inside the existing metadata blob, so the worker can read it later. - Before the full RCA, the worker runs the cheap deterministic flow_context prefix (cefg→localize→lift→flow_context, no agent/LLM) on each candidate via the existing lumoskit subprocess contract (ADR-0018), reads each artifacts/flow_context/fund_flows.json, and picks the candidate with the largest net fund movement — the drain. A setup tx moves ~no funds. - If the winner differs from the case's primary tx, the case is re-pointed at it (UpdateCaseTxHash; the (chain,tx_hash) index is non-unique and dedup already ran, so this is safe) and a drain_tx_selected event records the ranking. Cost: only the cheap prefix runs per candidate; the expensive RCA still runs once, on the winner. Single-tx submissions are completely unaffected (selection is skipped when <2 candidates and on stage resumes). Boundary: helios stays an orchestrator — lumoskit computes the economics (flow_context), helios only reads the artifact and makes the routing choice. Verified: go build ./... + go vet clean; new selector unit/integration tests pass; worker/api/store suites green. (Pre-existing macOS-only failure TestRunnerUsesLumoskitRepoRootForBinaryUnderBin — /private/var vs /var symlink — also fails on origin/main and is unrelated.) Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01K3KnRg8X2mkfPwM3LLJtys
selectDrainTx fanned out each candidate's cheap flow_context prefix sequentially (N×prefix wall-clock). Run them concurrently instead — each candidate writes to an isolated output subdir and Runner is stateless, so there is no contention. Selection stays deterministic: ranking is rebuilt in candidate order and ties keep the earlier candidate, independent of completion order. Drops added wall-clock from N×prefix to ~1×prefix; the single expensive RCA on the winner is unchanged. Single-tx path unaffected. Verified: go build/vet clean; selector tests (incl. new 3-candidate order-determinism test) pass under -race. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01K3KnRg8X2mkfPwM3LLJtys
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
한 줄 요약
알림에 exploit tx가 여러 개일 때, 비싼 RCA를 돌리기 전에 후보 중 "진짜 자금 유출(drain) tx"를 싸게 골라내는 단계를 추가합니다. lumoskit 코어 변경 0, DB 마이그레이션 0.
왜 (배경)
OriginTrail 해킹 알림에는 exploit tx가 2개였습니다:
0x18ccaa7b…— authority/state setup tx (자금 이동 거의 없음)0xb8e2a8f2…— 실제 27M TRAC drain tx기존 파이프라인은 알림 텍스트에 먼저 적힌 setup tx만 분석하고 drain tx를 영구 누락했습니다(어느 게 drain인지 고르는 로직이 아예 없고, 서술 순서가 결과를 정함). 그 결과 RCA가 "이건 자산 유출이 아니라 권한 설정"이라고 나오고 경제 피해가 0으로 집계됐습니다.
"어느 tx가 진짜 drain인가"는 온체인 사실이라 텍스트 전용인 hack-detector는 판단할 수 없습니다. 그래서 역할을 나눕니다:
hack-detector#14): 후보를 버리지 않고 최대 3개 수집해candidate_tx_hashes[]로 전달.어떻게 동작하나 (그림)
비용: 후보들의 싼 prefix는 동시(병렬) 실행되어 추가 wall-clock이 N×가 아니라 ~1×prefix입니다(각 후보는 격리된 output 하위 디렉터리에 기록, Runner 무상태). 비싼 RCA는 여전히 1번(승자만) = LLM 비배수. 단일 tx 알림(후보 <2)이나 stage resume에서는 선택 단계를 건너뜀 → 기존 동작 그대로(추가비용 ≈ 메타데이터 파싱 1회, 약 4µs).
정확히 바뀌는 것 (파일별)
internal/api/types.goSubmissionRequest에CandidateTxHashes []string(omitempty)internal/api/validation.gotxHashRegex검증tx_hash검증 유지internal/api/signals.gowithCandidateTxHashes()로 후보를 기존 metadata blob에 주입internal/lumoskit/runner.gostages()에"flow_context_select"→[cefg, localize, lift, flow_context]internal/store/cases.goUpdateCaseTxHash()추가 (case를 승자 tx로 재지정)internal/worker/worker.goselectDrainTxIfNeeded()호출 (signal-context 쓰기 전)internal/worker/selector.go(신규)internal/worker/selector_test.go(신규)lumoskit (Rust) 변경 없음. 기존
--stage+fund_flows.json만 재사용합니다.아키텍처 경계 (리뷰 포인트)
ADR-0018대로 helios는 오케스트레이터입니다. 분석(flow_context 계산)은 lumoskit이 subprocess로 수행하고, helios는 그 산출물(
fund_flows.json)을 읽어서 라우팅(어느 tx를 RCA할지)만 결정합니다. helios는 분석을 직접 하지 않습니다.팀 검토를 바라는 결정 두 가지:
case.tx_hash를 승자로 갱신합니다. 대안(라벨은 primary 유지, 분석만 승자)은 케이스 tx와 실제 분석이 불일치해 운영자가 혼란스럽습니다. → 재지정을 택했고,drain_tx_selected이벤트로 원본/선택/랭킹을 남깁니다.artifacts/flow_context/fund_flows.json(lumoskit 내부 산출물)을 읽습니다 — 계약상 명시된summary.json보다 한 단계 깊은 결합입니다. 더 깔끔한 대안은 lumoskit이 drain 크기를 안정 필드로 노출하는 것이나, 그건 lumoskit 코어 변경이라 보수성을 위해 보류했습니다.랭킹 신호는
net_flows의 최대 절대 delta입니다 — USD 정밀 수치가 아니라 "drain vs setup"을 가르는 거친 판별자입니다(정밀 피해액은 승자의 full RCA가 산출). 모든 후보가 움직임 0이거나 prefix가 실패하면 primary를 그대로 유지해 오늘보다 나빠지지 않습니다.검증
go build ./...+go vet ./internal/...— cleanTestCandidateTxHashesFromMetadata,TestReadNetFlowMagnitude,TestSelectDrainTxPicksLargestNetMovement,TestSelectDrainTxKeepsPrimaryWhenNoMovement— passinternal/worker/internal/api/internal/store스위트 — greeninternal/lumoskit/TestRunnerUsesLumoskitRepoRootForBinaryUnderBin— macOS/private/varvs/var심링크 이슈로, origin/main에서도 동일하게 실패(이 PR과 무관). CI(Linux)에서는 통과 예상.성능 (검증됨)
-race통과.후속(별도 PR 후보)
--stage를 무시).🤖 Generated with Claude Code
https://claude.ai/code/session_01K3KnRg8X2mkfPwM3LLJtys