프로젝트

일반

사용자정보

Actions

이슈 #314

진행중
CP JL

17-5. ALP 수익 계산 API

이슈 #314: 17-5. ALP 수익 계산 API

Chris Park이(가) 약 7시간 전에 추가함.

상태:
해결
우선순위:
높음
담당자:
목표버전:
시작일:
2026/05/19
완료일:
2026/05/23 (약 5달 지연)
진척도:

0%

추정시간:

설명

GET /api/nodes/:id/earnings


연결된 일감 1 (한 건 진행 중 — 모두 미완료)

다음 일감과 관련됨: 이슈 #375: JWT 전역 인터셉터 구현 (alphea-ai-edge)해결James Lee2026/04/15

Actions

CP Chris Park이(가) 7달 전에 변경 Actions #1

  • 프로젝트을(를) 14에서 Alphea App(으)로 변경되었습니다.
  • 목표버전을(를) 6월 중간 버전(으)로 지정되었습니다.

JL James Lee이(가) 7달 전에 변경 Actions #2

  • 상태을(를) 신규에서 해결(으)로 변경되었습니다.

17-5. ALP 수익 계산 API 구현 완료

WalletService.GetReceipts + WalletService.GetEarningsSummary RPC 를 구현했습니다. 모두 JWT 인증이 필요한 보호 RPC 입니다.

  • Repository: alphea-ai-edge
  • Commit: commit:server|c56e3d6 — feat: add WalletService GetReceipts and GetEarningsSummary
  • 관련 이슈: #254 (새 마이그레이션 0003)

구현된 RPC

1. GetReceipts(WalletReceiptsRequest) → WalletReceiptsResponse — 보호 RPC

message WalletReceiptsRequest {
  string wallet_address = 1;
  Pagination pagination = 2;
}

message ReceiptRecord {
  string job_id = 1;
  string node_id = 2;
  JobType job_type = 3;
  NodeTier tier = 4;
  int32 exec_time_ms = 5;
  string status = 6;
  double reward_agx = 7;
  double cpu_seconds = 8;
  int64 network_bytes = 9;
  double storage_gb = 10;
  int64 completed_at = 11;
}
  • 페이지네이션: 기본 limit=50, 최대 200
  • 정렬: completed_at DESC
  • 응답에 PageInfo { total, limit, offset, has_more } 포함

2. GetEarningsSummary(WalletEarningsSummaryRequest) → WalletEarningsSummaryResponse — 보호 RPC

message WalletEarningsSummaryResponse {
  double all_time_agx = 1;
  double week_agx = 2;
  double today_agx = 3;
}

단일 SQL 문으로 세 값을 동시에 계산 (Postgres FILTER 구문):

SELECT
    COALESCE(SUM(reward_agx), 0) AS all_time,
    COALESCE(SUM(reward_agx) FILTER (WHERE completed_at >= NOW() - INTERVAL '7 days'), 0) AS week,
    COALESCE(SUM(reward_agx) FILTER (WHERE completed_at >= DATE_TRUNC('day', NOW())), 0) AS today
FROM job_receipts
WHERE wallet_address = $1

(wallet_address, completed_at DESC) 인덱스로 3개 파티션 모두 빠르게 스캔됩니다.

인증 모델

domain::auth::require_auth 를 재사용하되, WalletHandler 에 resolve_wallet 공통 헬퍼를 두어 지갑 주소 검증을 일원화했습니다:

fn resolve_wallet<T>(request, jwt_secret, requested) -> Result<String, Status> {
    let caller = require_auth(request, jwt_secret)?;
    if requested.is_empty() { return Ok(caller); }
    if !requested.eq_ignore_ascii_case(&caller) {
        return Err(Status::permission_denied(
            "wallet_address does not match the authenticated caller"
        ));
    }
    Ok(requested)
}

의미:

  • 요청의 wallet_address 필드가 비어있으면 JWT sub 를 자동 사용
  • 값이 있으면 JWT sub 와 반드시 일치해야 함 (대소문자 무관)
  • 결과: 유효한 JWT 를 가진 사용자가 타인의 지갑 영수증을 조회할 수 없음

나머지 WalletService RPC 스텁 처리

현재 포팅 범위 외이므로 Status::unimplemented 로 남겼습니다:

RPC 용도 향후 계획
GetJobs 지갑의 작업 이력 작업 이력 테이블 도입 시 구현
GetNodes 지갑 보유 노드 목록 노드 조회는 이미 NodeService.ListNodes 로 가능. 필요성 재검토
ClaimVirtualNodes 가상 노드 패키지 클레임 가상 노드 시스템 포팅 시
GetEarnings 일별 집계 영수증 aggregated_earnings 테이블 도입 시 (현재는 GetReceipts 로 충분)
GetEarningsToday 오늘의 잔고/수익 가상 노드 수익 필드 때문에 가상 노드 포팅 이후
GetNodesDetail 노드별 수익 상세 (주간/누적) 별도 이슈로 분리 가능
GetEarningsByRange 차트 포인트 기반 수익 집계 대시보드 UI 요구사항에 맞춰

E2E 검증 결과 (examples/wallet_earnings.rs)

네 개의 영수증을 시드한 뒤 각 시나리오를 검증했습니다:

시드 데이터

# 시점 reward_agx exec_ms
0 방금 (today) 0.25 1500
1 6시간 전 (today) 0.18 1200
2 2일 전 (week) 0.40 2400
3 10일 전 (older) 0.17 900

결과

=== GetReceipts (owner) ===
  returned=4 total=4
  job=job-6dacfd65-0 ... reward=0.25 ...
  job=job-6dacfd65-1 ... reward=0.18 ...
  job=job-6dacfd65-2 ... reward=0.40 ...
  job=job-6dacfd65-3 ... reward=0.17 ...

=== GetEarningsSummary (owner) ===
  all_time=1.0000  week=0.8300  today=0.4300
  expected: all_time=1.0000  week=0.8300  today=0.4300
✓ summary matches expected sums
✓ stranger blocked: PermissionDenied — wallet_address does not match the authenticated caller
✓ unauth blocked:   Unauthenticated — missing authorization header
  • 4건 전부 목록 반환, PageInfo.total=4
  • 합계가 클라이언트 측 예상값과 부동소수점 오차 범위 내 정확 일치
    • all_time = 0.25 + 0.18 + 0.40 + 0.17 = 1.0000
    • week = 0.25 + 0.18 + 0.40 = 0.8300 (10일 전 행 제외)
    • today = 0.25 + 0.18 = 0.4300
  • stranger JWT 로 owner 지갑 조회 시도 → PermissionDenied
  • 토큰 없이 조회 → Unauthenticated

Section 17 완료

이슈 제목 상태
#310 17-1. 노드 목록 조회 API ✅ 해결
#311 17-2. 노드 상세 정보 API ✅ 해결
#312 17-3. 노드 추가 API ✅ 해결
#313 17-4. 노드 삭제 API ✅ 해결
#314 17-5. ALP 수익 계산 API ✅ 해결 (이번 커밋)

Section 17 의 모든 이슈가 포팅 완료되었습니다.

JL James Lee이(가) 7달 전에 변경 Actions #3

  • 다음 일감과 관련됨:에 이슈 #375: JWT 전역 인터셉터 구현 (alphea-ai-edge)이(가) 추가되었습니다.

JL James Lee이(가) 7달 전에 변경 Actions #4

GetReceipts / GetEarningsSummary 를 전역 JWT 인터셉터로 전환

commit:server|b2c3d29

#375 에서 도입한 AuthLayer 전역 미들웨어로 Bearer JWT 검증이 이관되면서, WalletService 의 두 보호 RPC 가 더 이상 핸들러에서 JWT 를 재검증하지 않습니다.

변경 내용

resolve_wallet 헬퍼가 jwt_secret 을 받지 않도록 시그니처를 간소화했고, 내부적으로 caller_wallet(request) 를 호출해 미들웨어가 주입한 AuthContext 에서 wallet 을 읽습니다. wallet 불일치 검사 (PermissionDenied) 로직은 그대로.

// BEFORE
fn resolve_wallet<T>(request: &Request<T>, jwt_secret: &str, requested: &str)
    -> Result<String, Status>
{
    let caller = require_auth(request, jwt_secret)?;
    // ...
}

// AFTER
fn resolve_wallet<T>(request: &Request<T>, requested: &str)
    -> Result<String, Status>
{
    let caller = caller_wallet(request)?;
    // ...
}

회귀 검증

examples/wallet_earnings.rs 모든 단계 통과:

  • 4 receipts seeded → GetReceipts total=4
  • GetEarningsSummary all_time/week/today 수치가 기대값과 정확히 일치
  • stranger JWT 로 owner wallet 조회 시도 → PermissionDenied: wallet_address does not match the authenticated caller
  • 토큰 없음 → Unauthenticated: missing authorization header (미들웨어에서 반환)

관련: #375

Actions

내보내기 PDF Atom