ACN
dAppOffice
Talaan ng nilalaman (4 / 16)

04. 백엔드 (Python 3.12 · FastAPI · web3.py · SQLite)

Sa kasalukuyan, sa wikang Korean lamang available ang teksto ng mga dokumento.

디렉터리 구조

backend/
├─ app/
│  ├─ main.py          # FastAPI 앱, CORS, 라우터 등록, 시작 시 DB 초기화
│  ├─ config.py        # pydantic-settings (.env)
│  ├─ db.py            # sqlite3 연결, 스키마, 쿼리 함수
│  ├─ chain.py         # web3 인스턴스, BEP20 ABI, Transfer 로그 디코딩
│  ├─ schemas.py       # 요청/응답 pydantic 모델, 주소 검증
│  ├─ ratelimit.py      # 프로세스 내 per-IP 고정 윈도 레이트리미터
│  ├─ indexer.py       # Transfer 이벤트 폴링 인덱서 (python -m app.indexer)
│  └─ routers/
│     ├─ token.py      # /api/token, /api/token/balance/{address}
│     └─ transfers.py  # /api/transfers (GET 목록, POST 기록)
├─ requirements.txt
└─ .env.example

엔드포인트

메서드경로설명
GET/health상태 확인
GET/api/token이름·심볼·소수점·총발행량 (체인에서 읽고 프로세스 캐시)
GET/api/token/balance/{address}주소의 ACN 잔액(wei 문자열)
GET/api/transfers?address=&limit=&offset=거래 내역. address는 보낸/받은 주소 모두 매치
GET/api/indexer인덱서 동기화 상태: 체인 헤드, 안전 헤드, 마지막 인덱싱 블록·시각, 남은 블록 수, 저장된 전송 건수, 현재 RPC, 시작 시각, 연속 실패 수, 마지막 오류. dApp의 "네트워크 동기화" 표시와 원격 진단에 사용
POST/api/transfers { "txHash": "0x…" }tx 영수증을 체인에서 조회·검증 후 ACN Transfer 로그 저장

OpenAPI 문서: http://localhost:8000/docs

POST /api/transfers 검증 규칙

  1. 클라이언트 IP당 분당 RECORD_RATE_LIMIT_PER_MINUTE(기본 10) 초과 → 429
  2. txHash 형식(0x + 64 hex) 검증 → 422 (pydantic)
  3. 영수증 없음(미채굴/존재하지 않음, TransactionNotFound) → 404 (클라이언트가 재시도)
  4. RPC 장애/타임아웃 → 502
  5. status != 1(revert) → 422
  6. 확정 수 부족: head - blockNumber < INDEXER_CONFIRMATIONS(기본 15) → 404 (재시도). reorg로 고아 행이 남지 않게 하기 위함
  7. 영수증 로그 중 ACN 컨트랙트 주소에서 발생한 Transfer 토픽 로그만 선별, 없으면 → 422 (이 경우 블록 조회 RPC 호출을 하지 않음)
  8. from/to/amount/blockTime은 전부 영수증·블록에서 읽는다. 클라이언트 입력은 hash 하나뿐이다.

큰 정수(wei)는 JSON 정밀도 문제를 피하려고 항상 문자열로 직렬화한다.

인덱서 (app/indexer.py)

  • indexer_state.last_indexed_block 다음 블록부터 INDEXER_BATCH_SIZE(기본 500) 블록씩 eth_getLogs.
  • 재조직 안전 마진: head - INDEXER_CONFIRMATIONS(기본 15) 까지만 인덱싱한다. BSC 최종성 이전 블록은 다루지 않으므로 짧은 reorg로 고아 이벤트가 남지 않는다. 블록 해시 검증까지는 하지 않으므로 15블록을 넘는 reorg는 수동 재색인이 필요하다.
  • 첫 실행 시 INDEXER_START_BLOCK이 0이면 head - confirmations - INDEXER_BACKFILL_BLOCKS부터 시작한다. 영구 디스크가 없는 호스트(Render 무료)에서는 재시작마다 DB가 비므로 INDEXER_BACKFILL_BLOCKS(운영 100000 ≈ 약 21시간)만큼 최근 구간을 다시 채운다. 과거 전체 백필은 INDEXER_START_BLOCK에 값 지정.
  • 블록 타임스탬프는 블록 단위로 캐시하며, 조회 실패 시 배치를 버리지 않고 block_time=NULL로 저장한다.
  • RPC 오류 시 배치를 절반씩 줄여(최소 1블록) 재시도하고, 성공하면 다시 늘린다. 최소 배치에서도 실패하면 지수 백오프(최대 120초). 403/429 응답이나 3회 연속 실패 시 BSC_RPC_URL 목록의 다음 엔드포인트로 회전한다.
  • 인덱서 스레드가 예외로 죽으면 30초 후 자동 재시작하며, 마지막 오류를 indexer_state.last_error에 남긴다.
  • 기동 시 eth_chainIdCHAIN_ID와 다르면 즉시 종료한다(잘못된 RPC 방지). API 서버도 동일하게 검사한다.

환경 변수 (backend/.env)

변수설명기본값
BSC_RPC_URLJSON-RPC 엔드포인트. 콤마로 여러 개 지정 가능: API는 첫 번째를 쓰고, 인덱서는 403/429 또는 연속 실패 시 다음 URL로 회전https://bsc-rpc.publicnode.com (운영: publicnode, 1rpc.io/bnb, drpc, blastapi)
CHAIN_ID56 / 9756
TOKEN_ADDRESSACN 컨트랙트(형식 검증 후 체크섬으로 정규화)0x84f9…6236
DATABASE_PATHSQLite 파일./data/alice.db
CORS_ORIGINS허용 오리진(콤마 구분)http://localhost:3000
INDEXER_START_BLOCK인덱서 시작 블록(0=현재)0
INDEXER_BACKFILL_BLOCKS체크포인트가 없을 때 헤드에서 뒤로 되감아 시작할 블록 수0 (운영 100000)
INDEXER_BATCH_SIZE배치 블록 수(최대치, 실패 시 자동 축소)500
INDEXER_POLL_SECONDS폴링 간격5
INDEXER_CONFIRMATIONS헤드에서 뒤로 둘 안전 블록 수. POST 기록에도 동일 적용15
RECORD_RATE_LIMIT_PER_MINUTEPOST /api/transfers IP당 분당 허용 횟수10

모든 숫자 설정은 pydantic 제약(ge=0/1)으로 검증되며 CHAIN_ID는 56/97만 허용한다. 잘못된 값이면 기동 시 실패한다.

실행

uvicorn app.main:app --reload --port 8000      # API
python -m app.indexer                          # 인덱서(별도 프로세스)