← 목록
기타 2026-08-01 7KB 읽기 7분

crowny-dex 체결 → chain.crowny.org 체인기록 연동

개요

dex.crowny.org(포트 9402, DEX서버.한선)에서 실제 체결(스왑 성공, 지정가 매칭 체결)이 발생할 때마다 chain.crowny.org(포트 9729, 크라우니코인.한선)에 POST /api/tx로 트랜잭션을 제출해 mempool에 등록하는 기능을 추가했다. DEX 거래 자체는 체인기록 실패/타임아웃과 완전히 격리되어 항상 정상 응답한다.

구현 방식 — curl 셸아웃 (TCP 내장함수 아님)

크라우니 VM에 아웃바운드 HTTP 클라이언트 내장함수는 없다(TCP연결 등은 서버측 accept/read/write용). 기존 crowny-int2/INT서버.한선, crowny-aimed/*.한선 등에서 이미 검증된 패턴을 그대로 재사용: 체계("curl -s --connect-timeout 2 -m 3 -X POST -H 'Content-Type: application/json' --data-binary @<임시파일> '<URL>' > <응답임시파일> 2>/dev/null </dev/null"). 셸 인젝션 방지를 위해 요청 본문(JSON)은 항상 쓰기()로 임시파일에 먼저 저장하고 --data-binary @파일로 전달했다(문자열 통짜 이스케이프 없음). 임시파일명은 /tmp/crowny_dex_chaintx_<지금시각()>_<seq>.json으로 타임스탬프+전역 증가 시퀀스(체인기록_seq)를 조합해 충돌을 피했다.

/api/tx는 체인 쪽 코드(크라우니코인.한선 574~579행) 확인 결과 인증 불필요(무토큰 큐잉)임을 확인해 서명 헤더(X-Chain-Sign/X-Chain-Time, libs/체인인증.한선)는 붙이지 않았다. /api/mine은 호출하지 않는다.

코드 변경 — /Users/ef/crowny-dex/DEX서버.한선

  • 전역 상태: 체인기록_URL, 체인기록_pending경로(data/chain_pending.tsv), 체인기록_성공수,
체인기록_실패수, 체인기록_폐기수, 체인기록_최근시각/체인기록_최근요약(최근 20건 캡).
  • _체인기록_전송원시(json내용) — curl 1회 시도 원시 프리미티브. 응답에 "queued" 포함이면 성공(1),
아니면 실패(0). 예외를 던지지 않음(파일 I/O·체계() 모두 안전 실패).
  • 체인기록제출(요약, json내용) — 진입점. 성공 시 최근목록 갱신, 실패 시 chain_pending.tsv
시각\t재시도횟수\t요약\tjson 한 줄 append.
  • 체인기록재시도() — pending 큐를 읽어 한 틱당 최대 50줄 재전송 시도, 성공 시 제거, 5회(체인기록_최대재시도)
초과 시 폐기(체인기록_폐기수 증가). 요청처리() 상단에서 표준나머지(총요청수, 20) == 0일 때 호출 (기존 20~10000 범위 나눗셈 함정 회피용 내림나눗/표준나머지 헬퍼 재사용, % 직접 사용 안 함).
  • 체인상태JSON()GET /api/chain/status 응답 본문(pending/success/fail/dropped/recent 20건).
  • 호출 지점:
  • 스왑실행() — 감사기록 직후, 응답문자열 조립 전. type=dex_swap.
  • 주문매칭시도() 내부 체결 루프 — 감사기록(...,"trade_fill",...) 두 건 직후, 체결 1건마다 1회.
  • type=dex_trade.
  • 에이전트스왑실행() — 동일 패턴(agent_swap), type=dex_swap.
  • 내장 분리()가 이 파일에 없어(다른 곳에서도 수동 파싱 관례) _체인_줄분리()/_체인_탭분리()
  • 부분()/글자() 기반으로 직접 구현(자격불러오기()와 동일 패턴).

    함정 기록 (재발방지)

    • hanseonc_high 파일에 내장 분리()가 없다는 걸 처음엔 놓쳤다 — 컴파일 에러로 3건 즉시 노출(줄 411/418/458).
    이 파일은 문자열.한선을 가져오기 하지 않고 전 구간에서 부분()+글자() 수동 파싱 관례를 쓰고 있음 (// 내장 분리() 없음 — 줄단위 수동 파싱 주석이 이미 644행에 존재 — 새 코드 작성 전에 grep했어야 함).
    • 체계() curl 실패 시 latency: 로컬에서 리스너 없는 포트(9999)로 겨냥하면 connection refused가 즉시
    반환되어 지연이 사실상 0(실측 0.024s) — --connect-timeout 2 -m 3이 실제로 발동하는 경우(방화벽 drop· 네트워크 행)는 별도 실측 못 함(로컬 환경 한계). 운영상 최악 케이스는 여전히 최대 3초 블로킹 가능성을 인지하고 있어야 한다(단일 스레드 동기 VM이므로 그 시간만큼 해당 요청의 응답이 늦어짐 — 사용자 체감 지연이지 거래 실패는 아님).

    채굴(/api/mine) 미트리거 판단 근거

    지시대로 /api/tx(mempool 등록)까지만 호출하고 /api/mine은 호출하지 않는다. 이유:
    1. DEX 체결마다 채굴하면 체인 블록이 트랜잭션 1건당 1블록으로 폭증 — 체인 부하·저장공간 낭비.
    2. 채굴 주기/정책(난이도, 보상 halving 등)은 체인 운영자(chain.crowny.org 자체 프로세스) 소관이며,
    DEX가 임의로 채굴을 트리거하면 체인 쪽 운영 로직과 충돌할 수 있다.
    1. mempool 큐잉만으로 "기록됨"이라는 목적은 충분히 달성(제출 시점이 곧 트랜잭션 존재 증거).

    QA 결과

    항목결과
    컴파일(hanseonc_high)PASS (0 에러, 62128 큐브)
    배포(launchctl unload/load)PASS, localhost:9402/health / dex.crowny.org/health 200
    스왑 1건 → chain mempool 반영PASS — S-1 스왑 후 chain.crowny.org/api/mempooldex_swap tx 확인
    지정가 매칭 체결(sell+buy 크로스) → chain 반영PASS — O-2 체결(filled_qty:5) 후 /api/chain/status recent에 trade T-1 확인
    /api/chain/status 응답 형식PASS — {pending, success_count, fail_count, dropped_count, recent:[{ts,summary}]}
    장애격리(체인 URL을 127.0.0.1:9999로 변경 후 재컴파일+재배포)PASS — 스왑 API 여전히 200 정상 응답, 실패 tx는 pending 큐에 적재(pending:1, fail_count:1)
    장애격리 복원(URL 9729로 원복, 재컴파일+재배포)PASS — health 정상, /api/chain/status 초기화 확인
    체감 지연시간: 로컬 connection-refused 케이스는 응답 전체 0.024s로 사실상 무시할 수준. 위 함정 기록 참고 — 실제 네트워크 drop/행(hang) 상황에서의 최대 지연(최대 3초, -m 3)은 이번 로컬 환경에서는 재현·실측하지 못했다(별도 iptables DROP 환경 필요 — 다음 세션 잔여 이슈로 남김).

    잔여 이슈

    • 방화벽 drop/네트워크 hang 상황의 실제 curl 타임아웃(최대 3초) 체감은 미실측 — 로컬은 항상 즉시
    connection-refused라 재현 불가.
    • QA 중 생성한 테스트 계정(chaintest1, ctA, ctB, ctIso)이 data/audit_log.tsv·data/credentials.tsv
    등 운영 데이터 파일에 남아있음. 기존에도 donation_ledger.tsv.bak-테스트오염정리전-... 백업이 존재하는 걸 보면 테스트 오염 자체는 기존 관례상 허용되는 듯하나, 필요 시 정리 권장.
    • pending 재시도 트리거가 총요청수 기반(20건마다)이라 트래픽이 거의 없는 새벽 시간대엔 재시도가
    드물게 발생할 수 있음 — 필요하면 별도 타이머 기반으로 전환 고려.