Ver3.0
์ ์ฒด ์ด๋
Ver3.0 ๐ ํ์ด์ฌ๊ณผ FastAPI๋ก ๋ธ๋ก์ฒด์ธ ๋ฐ์ดํฐ ๊ฒ์ฆ ์๋ฒ ๋ง๋ค๊ธฐ

๐ ํ์ด์ฌ๊ณผ FastAPI๋ก ๋ธ๋ก์ฒด์ธ ๋ฐ์ดํฐ ๊ฒ์ฆ ์๋ฒ ๋ง๋ค๊ธฐ
์ค์ ํ๋ก์ ํธ๋ก ๋ฐฐ์ฐ๋ Web3 ๋ฐฑ์๋ ๊ฐ๋ฐ์ ๋ชจ๋ ๊ฒ
๐ ์ ๋ธ๋ก์ฒด์ธ ๋ฐ์ดํฐ ๊ฒ์ฆ ์๋ฒ๊ฐ ํ์ํ ๊น?
์๋ ! ์ค๋์ ์ ๋ง ์ฌ๋ฏธ์๋ ์ฃผ์ ๋ฅผ ๊ฐ์ง๊ณ ์์ด. ๋ฐ๋ก ํ์ด์ฌ๊ณผ FastAPI๋ฅผ ํ์ฉํด์ ๋ธ๋ก์ฒด์ธ ๋ฐ์ดํฐ๋ฅผ ๊ฒ์ฆํ๋ ์๋ฒ๋ฅผ ๋ง๋๋ ๊ฑฐ์ผ. ๐ฏ์์ฆ Web3 ์๋๊ฐ ๋๋ฉด์ ๋ธ๋ก์ฒด์ธ ๊ธฐ์ ์ด ์ ๋ง ๋ง์ด ์ฌ์ฉ๋๊ณ ์์์? ๊ทธ๋ฐ๋ฐ ๋ฌธ์ ๋ ๋ธ๋ก์ฒด์ธ ๋คํธ์ํฌ์์ ์ง์ ๋ฐ์ดํฐ๋ฅผ ๊ฐ์ ธ์ค๋ ๊ฑด ์๊ฐ๋ณด๋ค ๋๋ฆฌ๊ณ ๋น์ฉ๋ ๋ง์ด ๋ ๋ค๋ ๊ฑฐ์ผ. ํนํ ์ด๋๋ฆฌ์ ๊ฐ์ ๋ฉ์ธ๋ท์์ ๋ฐ์ดํฐ๋ฅผ ์กฐํํ ๋๋ง๋ค ๊ฐ์ค๋น๊ฐ ๋ค์ด๊ฐ๊ฑฐ๋, RPC ํธ์ถ ์ ํ์ ๊ฑธ๋ฆฌ๋ ๊ฒฝ์ฐ๊ฐ ๋ง์.
๊ทธ๋์ ์ฐ๋ฆฌ๋ ์ค๊ฐ์ ๊ฒ์ฆ ์๋ฒ๋ฅผ ๋๋ ๊ฑฐ์ผ! ์ด ์๋ฒ๊ฐ ๋ธ๋ก์ฒด์ธ ๋ฐ์ดํฐ๋ฅผ ๊ฐ์ ธ์์ ๊ฒ์ฆํ๊ณ , ์บ์ฑ๋ ํ๊ณ , ์ฌ์ฉ์์๊ฒ ๋น ๋ฅด๊ฒ ์ ๊ณตํ๋ ์ญํ ์ ํด์ฃผ์ง. ๋ง์น ๋ธ๋ก์ฒด์ธ๊ณผ ์ฌ์ฉ์ ์ฌ์ด์ ๋๋ํ ์ค๊ฐ์ ๊ฐ์ ๊ฑฐ๋ผ๊ณ ๋ณด๋ฉด ๋ผ. ๐ค
๐ก ์ด ๊ธ์์ ๋ฐฐ์ธ ์ ์๋ ๊ฒ๋ค
โ FastAPI๋ฅผ ํ์ฉํ ๊ณ ์ฑ๋ฅ API ์๋ฒ ๊ตฌ์ถโ Web3.py๋ฅผ ์ด์ฉํ ๋ธ๋ก์ฒด์ธ ๋ฐ์ดํฐ ์กฐํ
โ ๋ธ๋ก์ฒด์ธ ๋ฐ์ดํฐ ๋ฌด๊ฒฐ์ฑ ๊ฒ์ฆ ๋ก์ง
โ ๋น๋๊ธฐ ์ฒ๋ฆฌ์ ์บ์ฑ ์ ๋ต
โ ์ค์ ํ๋ก์ ํธ ๊ตฌ์กฐ ์ค๊ณ
โ ๋ณด์๊ณผ ์๋ฌ ํธ๋ค๋ง
๐ ๏ธ ๊ฐ๋ฐ ํ๊ฒฝ ์ค๋นํ๊ธฐ
์, ์ด์ ๋ณธ๊ฒฉ์ ์ผ๋ก ์์ํด๋ณผ๊น? ๋จผ์ ๊ฐ๋ฐ ํ๊ฒฝ๋ถํฐ ์ธํ ํด์ผ ํด. ํ์ํ ๋๊ตฌ๋ค์ ํ๋์ฉ ์ดํด๋ณด์.ํ์ ๋ผ์ด๋ธ๋ฌ๋ฆฌ ์ค์น
์ฐ๋ฆฌ๊ฐ ์ฌ์ฉํ ์ฃผ์ ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ค์ ๋ค์๊ณผ ๊ฐ์:pip install fastapi uvicorn web3 python-dotenv redis pydantic aiohttp
FastAPI: ์ฐ๋ฆฌ์ ๋ฉ์ธ ํ๋ ์์ํฌ์ผ. ๋น ๋ฅด๊ณ ํ๋์ ์ธ API๋ฅผ ๋ง๋ค ์ ์๊ฒ ํด์ฃผ์ง. ํ์ ํํ ์ ์ง์ํด์ ์ฝ๋ ์์ ์ฑ๋ ๋๊ณ , ์๋์ผ๋ก API ๋ฌธ์๋ ๋ง๋ค์ด์ค. โก
Uvicorn: FastAPI ์ ํ๋ฆฌ์ผ์ด์ ์ ์คํํ๋ ASGI ์๋ฒ์ผ. ๋น๋๊ธฐ ์ฒ๋ฆฌ๋ฅผ ์ง์ํด์ ์ฑ๋ฅ์ด ์ ๋ง ์ข์.
Web3.py: ์ด๋๋ฆฌ์ ๋ธ๋ก์ฒด์ธ๊ณผ ์ํธ์์ฉํ๊ธฐ ์ํ ํ์ด์ฌ ๋ผ์ด๋ธ๋ฌ๋ฆฌ์ผ. ์ค๋งํธ ์ปจํธ๋ํธ ํธ์ถ, ํธ๋์ญ์ ์กฐํ ๋ฑ ๋ชจ๋ ๊ฑธ ํ ์ ์์ด.
Redis: ์บ์ฑ์ ์ํ ์ธ๋ฉ๋ชจ๋ฆฌ ๋ฐ์ดํฐ๋ฒ ์ด์ค์ผ. ๋ธ๋ก์ฒด์ธ ๋ฐ์ดํฐ๋ฅผ ๋งค๋ฒ ์กฐํํ๋ฉด ๋๋ฆฌ๋๊น, ์์ฃผ ์ฌ์ฉํ๋ ๋ฐ์ดํฐ๋ Redis์ ์ ์ฅํด๋๋ ๊ฑฐ์ง.
Pydantic: ๋ฐ์ดํฐ ๊ฒ์ฆ๊ณผ ์ค์ ๊ด๋ฆฌ๋ฅผ ์ํ ๋ผ์ด๋ธ๋ฌ๋ฆฌ์ผ. FastAPI์ ์ฐฐ๋ก๊ถํฉ์ด์ง!
๐ ๊ฐ๋ฐ ํ๊ฒฝ ์ฒดํฌ๋ฆฌ์คํธ
โข Python 3.8 ์ด์ ๋ฒ์ โข Redis ์๋ฒ (๋ก์ปฌ ๋๋ ํด๋ผ์ฐ๋)
โข ์ด๋๋ฆฌ์ ๋ ธ๋ ์ ๊ทผ ๊ถํ (Infura, Alchemy ๋ฑ)
โข ์ฝ๋ ์๋ํฐ (VS Code ์ถ์ฒ)
โข Postman ๋๋ Thunder Client (API ํ ์คํธ์ฉ)
๐ ํ๋ก์ ํธ ๊ตฌ์กฐ ์ค๊ณํ๊ธฐ
์ข์ ํ๋ก์ ํธ๋ ์ข์ ๊ตฌ์กฐ์์ ์์๋ผ. ์ฐ๋ฆฌ๊ฐ ๋ง๋ค ํ๋ก์ ํธ์ ๋๋ ํ ๋ฆฌ ๊ตฌ์กฐ๋ฅผ ๋จผ์ ์ดํด๋ณด์:blockchain-validator/
โโโ app/
โ โโโ __init__.py
โ โโโ main.py # FastAPI ์ ํ๋ฆฌ์ผ์ด์
์ง์
์
โ โโโ config.py # ์ค์ ๊ด๋ฆฌ
โ โโโ models/
โ โ โโโ __init__.py
โ โ โโโ blockchain.py # ๋ธ๋ก์ฒด์ธ ๋ฐ์ดํฐ ๋ชจ๋ธ
โ โ โโโ response.py # API ์๋ต ๋ชจ๋ธ
โ โโโ services/
โ โ โโโ __init__.py
โ โ โโโ blockchain.py # ๋ธ๋ก์ฒด์ธ ์๋น์ค ๋ก์ง
โ โ โโโ validator.py # ๊ฒ์ฆ ๋ก์ง
โ โ โโโ cache.py # ์บ์ฑ ๋ก์ง
โ โโโ api/
โ โ โโโ __init__.py
โ โ โโโ routes.py # API ๋ผ์ฐํธ
โ โ โโโ dependencies.py # ์์กด์ฑ ์ฃผ์
โ โโโ utils/
โ โโโ __init__.py
โ โโโ helpers.py # ํฌํผ ํจ์๋ค
โ โโโ exceptions.py # ์ปค์คํ
์์ธ
โโโ tests/
โ โโโ __init__.py
โ โโโ test_validator.py
โโโ .env # ํ๊ฒฝ ๋ณ์
โโโ requirements.txt
โโโ README.md
โ๏ธ ์ค์ ํ์ผ ์์ฑํ๊ธฐ
๋จผ์ ํ๊ฒฝ ์ค์ ๋ถํฐ ํด๋ณด์. `.env` ํ์ผ์ ๋ฏผ๊ฐํ ์ ๋ณด๋ค์ ์ ์ฅํ ๊ฑฐ์ผ:# .env
ETHEREUM_RPC_URL=https://mainnet.infura.io/v3/YOUR_PROJECT_ID
REDIS_HOST=localhost
REDIS_PORT=6379
REDIS_DB=0
CACHE_TTL=300
API_KEY=your_secret_api_key_here
from pydantic import BaseSettings
from functools import lru_cache
class Settings(BaseSettings):
ethereum_rpc_url: str
redis_host: str = "localhost"
redis_port: int = 6379
redis_db: int = 0
cache_ttl: int = 300
api_key: str
class Config:
env_file = ".env"
@lru_cache()
def get_settings():
return Settings()
settings = get_settings()
๐ ๋ธ๋ก์ฒด์ธ ์ฐ๊ฒฐ ์ค์ ํ๊ธฐ
์ด์ ๋ธ๋ก์ฒด์ธ๊ณผ ์ฐ๊ฒฐํ๋ ๋ถ๋ถ์ ๋ง๋ค์ด๋ณด์. `services/blockchain.py` ํ์ผ์ ์์ฑํ ๊ฑฐ์ผ:from web3 import Web3
from web3.middleware import geth_poa_middleware
from app.config import settings
import logging
logger = logging.getLogger(__name__)
class BlockchainService:
def __init__(self):
self.w3 = Web3(Web3.HTTPProvider(settings.ethereum_rpc_url))
# PoA ๋คํธ์ํฌ๋ฅผ ์ํ ๋ฏธ๋ค์จ์ด ์ถ๊ฐ (ํ์์)
self.w3.middleware_onion.inject(geth_poa_middleware, layer=0)
if not self.w3.is_connected():
raise ConnectionError("๋ธ๋ก์ฒด์ธ ๋
ธ๋์ ์ฐ๊ฒฐํ ์ ์์ต๋๋ค")
logger.info("๋ธ๋ก์ฒด์ธ ๋
ธ๋ ์ฐ๊ฒฐ ์ฑ๊ณต")
async def get_block(self, block_number: int):
"""ํน์ ๋ธ๋ก ์ ๋ณด ์กฐํ"""
try:
block = self.w3.eth.get_block(block_number)
return block
except Exception as e:
logger.error(f"๋ธ๋ก ์กฐํ ์คํจ: {e}")
raise
async def get_transaction(self, tx_hash: str):
"""ํธ๋์ญ์
์ ๋ณด ์กฐํ"""
try:
tx = self.w3.eth.get_transaction(tx_hash)
return tx
except Exception as e:
logger.error(f"ํธ๋์ญ์
์กฐํ ์คํจ: {e}")
raise
async def get_latest_block_number(self):
"""์ต์ ๋ธ๋ก ๋ฒํธ ์กฐํ"""
return self.w3.eth.block_number
def verify_transaction_hash(self, tx_hash: str) -> bool:
"""ํธ๋์ญ์
ํด์ ํ์ ๊ฒ์ฆ"""
return self.w3.is_address(tx_hash) or len(tx_hash) == 66
blockchain_service = BlockchainService()
1. ์ฐ๊ฒฐ ํ์ธ: `is_connected()`๋ก ๋ ธ๋ ์ฐ๊ฒฐ ์ํ๋ฅผ ๋จผ์ ์ฒดํฌํด. ์ฐ๊ฒฐ์ด ์ ๋๋ฉด ๋ฐ๋ก ์๋ฌ๋ฅผ ๋ฐ์์์ผ์ ๋์ค์ ๋ฌธ์ ๊ฐ ์๊ธฐ๋ ๊ฑธ ๋ฐฉ์งํ๋ ๊ฑฐ์ง.
2. ๋ฏธ๋ค์จ์ด: PoA(Proof of Authority) ๋คํธ์ํฌ๋ฅผ ์ฌ์ฉํ๋ค๋ฉด `geth_poa_middleware`๊ฐ ํ์ํด. ์ด๊ฒ ์์ผ๋ฉด ๋ธ๋ก ์กฐํํ ๋ ์๋ฌ๊ฐ ๋ ์ ์์ด.
3. ์๋ฌ ํธ๋ค๋ง: ๋ชจ๋ ๋ฉ์๋์ try-except๋ฅผ ๋ฃ์ด์ ์์ธ ์ํฉ์ ์ ์ฒ๋ฆฌํด์ค์ผ ํด. ๋ธ๋ก์ฒด์ธ ์กฐํ๋ ๋คํธ์ํฌ ๋ฌธ์ ๋ก ์คํจํ ์ ์๊ฑฐ๋ .
๐ก Pro Tip: RPC ์๋ํฌ์ธํธ ์ ํ
๋ฌด๋ฃ RPC ์๋ํฌ์ธํธ๋ ์์ฒญ ์ ํ์ด ์์ด. ์ค์ ์๋น์ค๋ฅผ ์ด์ํ๋ค๋ฉด Infura๋ Alchemy์ ์ ๋ฃ ํ๋์ ์ฌ์ฉํ๋ ๊ฒ ์ข์. ๋๋ ์์ฒด ๋ ธ๋๋ฅผ ์ด์ํ๋ ๊ฒ๋ ๊ณ ๋ คํด๋ณผ ๋งํด. ์ฌ๋ฅ๋ท ๊ฐ์ ํ๋ซํผ์์ ๋ธ๋ก์ฒด์ธ ๋ ธ๋ ์ด์ ์ ๋ฌธ๊ฐ๋ฅผ ์ฐพ์๋ณผ ์๋ ์์ง! ๐๐ ๋ฐ์ดํฐ ๊ฒ์ฆ ๋ก์ง ๊ตฌํํ๊ธฐ
์ด์ ํต์ฌ์ธ ๊ฒ์ฆ ๋ก์ง์ ๋ง๋ค์ด๋ณด์. `services/validator.py` ํ์ผ์ด์ผ:from typing import Dict, Any, Optional
from web3 import Web3
from app.services.blockchain import blockchain_service
import hashlib
import logging
logger = logging.getLogger(__name__)
class BlockchainValidator:
@staticmethod
def verify_block_hash(block_data: Dict[str, Any]) -> bool:
"""๋ธ๋ก ํด์ ๊ฒ์ฆ"""
try:
# ๋ธ๋ก ๋ฐ์ดํฐ์์ ํด์ ์ ์ธ
block_without_hash = {
k: v for k, v in block_data.items()
if k not in ['hash', 'proofOfWork']
}
# ๋ธ๋ก ๋ฐ์ดํฐ๋ฅผ ๋ฌธ์์ด๋ก ๋ณํ
block_string = str(sorted(block_without_hash.items()))
# SHA-256 ํด์ ๊ณ์ฐ
calculated_hash = hashlib.sha256(
block_string.encode()
).hexdigest()
# ์๋ณธ ํด์์ ๋น๊ต
original_hash = block_data.get('hash', '').replace('0x', '')
return calculated_hash == original_hash
except Exception as e:
logger.error(f"๋ธ๋ก ํด์ ๊ฒ์ฆ ์คํจ: {e}")
return False
@staticmethod
async def verify_transaction_inclusion(
tx_hash: str,
block_number: int
) -> bool:
"""ํธ๋์ญ์
์ด ํน์ ๋ธ๋ก์ ํฌํจ๋์ด ์๋์ง ๊ฒ์ฆ"""
try:
# ํธ๋์ญ์
์ ๋ณด ์กฐํ
tx = await blockchain_service.get_transaction(tx_hash)
if tx is None:
return False
# ๋ธ๋ก ๋ฒํธ ํ์ธ
return tx.get('blockNumber') == block_number
except Exception as e:
logger.error(f"ํธ๋์ญ์
ํฌํจ ๊ฒ์ฆ ์คํจ: {e}")
return False
@staticmethod
async def verify_transaction_receipt(tx_hash: str) -> Dict[str, Any]:
"""ํธ๋์ญ์
์์์ฆ ๊ฒ์ฆ ๋ฐ ์ํ ํ์ธ"""
try:
w3 = blockchain_service.w3
receipt = w3.eth.get_transaction_receipt(tx_hash)
if receipt is None:
return {
"valid": False,
"status": "not_found",
"message": "ํธ๋์ญ์
์ ์ฐพ์ ์ ์์ต๋๋ค"
}
# ํธ๋์ญ์
์ฑ๊ณต ์ฌ๋ถ ํ์ธ
status = receipt.get('status', 0)
return {
"valid": True,
"status": "success" if status == 1 else "failed",
"block_number": receipt.get('blockNumber'),
"gas_used": receipt.get('gasUsed'),
"confirmations": await blockchain_service.get_latest_block_number()
- receipt.get('blockNumber')
}
except Exception as e:
logger.error(f"ํธ๋์ญ์
์์์ฆ ๊ฒ์ฆ ์คํจ: {e}")
return {
"valid": False,
"status": "error",
"message": str(e)
}
@staticmethod
def verify_address_format(address: str) -> bool:
"""์ด๋๋ฆฌ์ ์ฃผ์ ํ์ ๊ฒ์ฆ"""
try:
return Web3.is_address(address)
except:
return False
@staticmethod
def verify_signature(
message: str,
signature: str,
expected_address: str
) -> bool:
"""๋ฉ์์ง ์๋ช
๊ฒ์ฆ"""
try:
w3 = blockchain_service.w3
# ๋ฉ์์ง ํด์ ์์ฑ
message_hash = Web3.keccak(text=message)
# ์๋ช
์์ ์ฃผ์ ๋ณต๊ตฌ
recovered_address = w3.eth.account.recover_message(
message_hash,
signature=signature
)
# ์์ ์ฃผ์์ ๋น๊ต
return recovered_address.lower() == expected_address.lower()
except Exception as e:
logger.error(f"์๋ช
๊ฒ์ฆ ์คํจ: {e}")
return False
validator = BlockchainValidator()
๋ธ๋ก ํด์ ๊ฒ์ฆ: ๋ธ๋ก์ ๋ฐ์ดํฐ๋ฅผ ๊ฐ์ง๊ณ ํด์๋ฅผ ์ง์ ๊ณ์ฐํด์ ์๋ณธ ํด์์ ๋น๊ตํด. ๋ฐ์ดํฐ๊ฐ ์กฐ์๋์ง ์์๋์ง ํ์ธํ๋ ๊ฑฐ์ผ. ๐
ํธ๋์ญ์ ํฌํจ ๊ฒ์ฆ: ํน์ ํธ๋์ญ์ ์ด ์ ๋ง๋ก ๊ทธ ๋ธ๋ก์ ํฌํจ๋์ด ์๋์ง ํ์ธํด. ๊ฐ์ง ํธ๋์ญ์ ์ ๊ฑธ๋ฌ๋ด๋ ๋ฐ ์ค์ํ์ง.
์์์ฆ ๊ฒ์ฆ: ํธ๋์ญ์ ์ด ์ฑ๊ณตํ๋์ง, ์ผ๋ง๋ ๋ง์ ๊ฐ์ค๋ฅผ ์ฌ์ฉํ๋์ง, ๋ช ๊ฐ์ ์ปจํ์ ๋ฐ์๋์ง ๋ฑ์ ํ์ธํด. ์ด ์ ๋ณด๋ ํธ๋์ญ์ ์ ์ ๋ขฐ๋๋ฅผ ํ๋จํ๋ ๋ฐ ํ์์ผ.
์๋ช ๊ฒ์ฆ: ๋ฉ์์ง๊ฐ ์ ๋ง๋ก ํน์ ์ฃผ์์ ์์ ์๊ฐ ์๋ช ํ ๊ฑด์ง ํ์ธํด. Web3 ์ธ์ฆ์์ ์ ๋ง ๋ง์ด ์ฌ์ฉ๋๋ ๊ธฐ๋ฅ์ด์ผ.
๐พ ์บ์ฑ ์์คํ ๊ตฌ์ถํ๊ธฐ
๋ธ๋ก์ฒด์ธ ๋ฐ์ดํฐ๋ฅผ ๋งค๋ฒ ์กฐํํ๋ฉด ๋๋ฆฌ๊ณ ๋น์ฉ๋ ๋ง์ด ๋ค์ด. ๊ทธ๋์ Redis๋ฅผ ์ฌ์ฉํด์ ์บ์ฑ ์์คํ ์ ๋ง๋ค ๊ฑฐ์ผ. `services/cache.py`:import redis
import json
from typing import Optional, Any
from app.config import settings
import logging
logger = logging.getLogger(__name__)
class CacheService:
def __init__(self):
self.redis_client = redis.Redis(
host=settings.redis_host,
port=settings.redis_port,
db=settings.redis_db,
decode_responses=True
)
# ์ฐ๊ฒฐ ํ
์คํธ
try:
self.redis_client.ping()
logger.info("Redis ์ฐ๊ฒฐ ์ฑ๊ณต")
except redis.ConnectionError:
logger.error("Redis ์ฐ๊ฒฐ ์คํจ")
raise
def get(self, key: str) -> Optional[Any]:
"""์บ์์์ ๋ฐ์ดํฐ ์กฐํ"""
try:
data = self.redis_client.get(key)
if data:
return json.loads(data)
return None
except Exception as e:
logger.error(f"์บ์ ์กฐํ ์คํจ: {e}")
return None
def set(
self,
key: str,
value: Any,
ttl: Optional[int] = None
) -> bool:
"""์บ์์ ๋ฐ์ดํฐ ์ ์ฅ"""
try:
ttl = ttl or settings.cache_ttl
serialized = json.dumps(value, default=str)
self.redis_client.setex(key, ttl, serialized)
return True
except Exception as e:
logger.error(f"์บ์ ์ ์ฅ ์คํจ: {e}")
return False
def delete(self, key: str) -> bool:
"""์บ์์์ ๋ฐ์ดํฐ ์ญ์ """
try:
self.redis_client.delete(key)
return True
except Exception as e:
logger.error(f"์บ์ ์ญ์ ์คํจ: {e}")
return False
def exists(self, key: str) -> bool:
"""์บ์์ ํค๊ฐ ์กด์ฌํ๋์ง ํ์ธ"""
try:
return self.redis_client.exists(key) > 0
except Exception as e:
logger.error(f"์บ์ ์กด์ฌ ํ์ธ ์คํจ: {e}")
return False
def get_or_set(
self,
key: str,
fetch_func,
ttl: Optional[int] = None
) -> Optional[Any]:
"""์บ์์์ ์กฐํํ๊ณ , ์์ผ๋ฉด fetch_func ์คํ ํ ์ ์ฅ"""
cached = self.get(key)
if cached is not None:
logger.info(f"์บ์ ํํธ: {key}")
return cached
logger.info(f"์บ์ ๋ฏธ์ค: {key}")
data = fetch_func()
if data is not None:
self.set(key, data, ttl)
return data
cache_service = CacheService()
TTL(Time To Live)๋ ์ค์ํ๋ฐ, ๋ธ๋ก์ฒด์ธ ๋ฐ์ดํฐ์ ํน์ฑ์ ๋ฐ๋ผ ๋ค๋ฅด๊ฒ ์ค์ ํด์ผ ํด:
โข ํ์ ๋ ๋ธ๋ก ๋ฐ์ดํฐ: ๊ธด TTL (1์๊ฐ ์ด์)
โข ์ต์ ๋ธ๋ก ์ ๋ณด: ์งง์ TTL (1๋ถ ์ด๋ด)
โข ํธ๋์ญ์ ์์์ฆ: ์ค๊ฐ TTL (10-30๋ถ)
๐จ ๋ฐ์ดํฐ ๋ชจ๋ธ ์ ์ํ๊ธฐ
Pydantic์ ์ฌ์ฉํด์ ๋ฐ์ดํฐ ๋ชจ๋ธ์ ์ ์ํ๋ฉด ํ์ ์์ ์ฑ๊ณผ ์๋ ๊ฒ์ฆ์ ์ป์ ์ ์์ด. `models/blockchain.py`:from pydantic import BaseModel, Field, validator
from typing import Optional, List
from datetime import datetime
class BlockModel(BaseModel):
number: int = Field(..., description="๋ธ๋ก ๋ฒํธ")
hash: str = Field(..., description="๋ธ๋ก ํด์")
parent_hash: str = Field(..., description="์ด์ ๋ธ๋ก ํด์")
timestamp: int = Field(..., description="๋ธ๋ก ์์ฑ ์๊ฐ")
transactions: List[str] = Field(default=[], description="ํธ๋์ญ์
๋ชฉ๋ก")
miner: str = Field(..., description="์ฑ๊ตด์ ์ฃผ์")
difficulty: int = Field(..., description="๋์ด๋")
gas_used: int = Field(..., description="์ฌ์ฉ๋ ๊ฐ์ค")
gas_limit: int = Field(..., description="๊ฐ์ค ํ๋")
@validator('hash', 'parent_hash')
def validate_hash(cls, v):
if not v.startswith('0x') or len(v) != 66:
raise ValueError('์ ํจํ์ง ์์ ํด์ ํ์')
return v
class TransactionModel(BaseModel):
hash: str = Field(..., description="ํธ๋์ญ์
ํด์")
from_address: str = Field(..., alias="from", description="๋ฐ์ ์ ์ฃผ์")
to_address: Optional[str] = Field(None, alias="to", description="์์ ์ ์ฃผ์")
value: int = Field(..., description="์ ์ก ๊ธ์ก (wei)")
gas: int = Field(..., description="๊ฐ์ค ํ๋")
gas_price: int = Field(..., description="๊ฐ์ค ๊ฐ๊ฒฉ")
nonce: int = Field(..., description="๋
ผ์ค")
block_number: Optional[int] = Field(None, description="๋ธ๋ก ๋ฒํธ")
class Config:
allow_population_by_field_name = True
class ValidationRequest(BaseModel):
tx_hash: str = Field(..., description="๊ฒ์ฆํ ํธ๋์ญ์
ํด์")
block_number: Optional[int] = Field(None, description="๋ธ๋ก ๋ฒํธ")
@validator('tx_hash')
def validate_tx_hash(cls, v):
if not v.startswith('0x') or len(v) != 66:
raise ValueError('์ ํจํ์ง ์์ ํธ๋์ญ์
ํด์')
return v
class ValidationResponse(BaseModel):
valid: bool = Field(..., description="๊ฒ์ฆ ๊ฒฐ๊ณผ")
status: str = Field(..., description="์ํ")
message: Optional[str] = Field(None, description="๋ฉ์์ง")
data: Optional[dict] = Field(None, description="์ถ๊ฐ ๋ฐ์ดํฐ")
timestamp: datetime = Field(default_factory=datetime.now)
1. ์๋ ๊ฒ์ฆ: `@validator` ๋ฐ์ฝ๋ ์ดํฐ๋ก ์ปค์คํ ๊ฒ์ฆ ๋ก์ง์ ์ถ๊ฐํ ์ ์์ด. ์๋ชป๋ ๋ฐ์ดํฐ๊ฐ ๋ค์ด์ค๋ฉด ์๋์ผ๋ก ์๋ฌ๋ฅผ ๋ฐ์์์ผ์ค.
2. ํ์ ํํ : IDE์์ ์๋์์ฑ์ด ์๋ฒฝํ๊ฒ ์๋ํด. ๊ฐ๋ฐ ์์ฐ์ฑ์ด ์์ฒญ ์ฌ๋ผ๊ฐ์ง.
3. ์๋ ๋ฌธ์ํ: FastAPI๊ฐ ์ด ๋ชจ๋ธ์ ๋ณด๊ณ ์๋์ผ๋ก API ๋ฌธ์๋ฅผ ์์ฑํด์ค. Swagger UI์์ ๋ฐ๋ก ํ์ธํ ์ ์์ด!
๐ฃ๏ธ API ๋ผ์ฐํธ ๊ตฌํํ๊ธฐ
์ด์ ์ค์ API ์๋ํฌ์ธํธ๋ฅผ ๋ง๋ค์ด๋ณด์. `api/routes.py`:from fastapi import APIRouter, HTTPException, Depends, Header
from typing import Optional
from app.models.blockchain import (
ValidationRequest,
ValidationResponse,
BlockModel,
TransactionModel
)
from app.services.blockchain import blockchain_service
from app.services.validator import validator
from app.services.cache import cache_service
from app.config import settings
import logging
logger = logging.getLogger(__name__)
router = APIRouter(prefix="/api/v1", tags=["blockchain"])
async def verify_api_key(x_api_key: str = Header(...)):
"""API ํค ๊ฒ์ฆ"""
if x_api_key != settings.api_key:
raise HTTPException(status_code=401, detail="์ ํจํ์ง ์์ API ํค")
return x_api_key
@router.get("/block/{block_number}", response_model=BlockModel)
async def get_block(
block_number: int,
api_key: str = Depends(verify_api_key)
):
"""ํน์ ๋ธ๋ก ์ ๋ณด ์กฐํ"""
try:
cache_key = f"block:{block_number}"
# ์บ์์์ ์กฐํ
cached_block = cache_service.get(cache_key)
if cached_block:
return cached_block
# ๋ธ๋ก์ฒด์ธ์์ ์กฐํ
block = await blockchain_service.get_block(block_number)
if block is None:
raise HTTPException(
status_code=404,
detail="๋ธ๋ก์ ์ฐพ์ ์ ์์ต๋๋ค"
)
# ๋ธ๋ก ๋ฐ์ดํฐ ๋ณํ
block_data = {
"number": block.number,
"hash": block.hash.hex(),
"parent_hash": block.parentHash.hex(),
"timestamp": block.timestamp,
"transactions": [tx.hex() for tx in block.transactions],
"miner": block.miner,
"difficulty": block.difficulty,
"gas_used": block.gasUsed,
"gas_limit": block.gasLimit
}
# ์บ์์ ์ ์ฅ (ํ์ ๋ ๋ธ๋ก์ ๊ธด TTL)
cache_service.set(cache_key, block_data, ttl=3600)
return block_data
except HTTPException:
raise
except Exception as e:
logger.error(f"๋ธ๋ก ์กฐํ ์คํจ: {e}")
raise HTTPException(status_code=500, detail="์๋ฒ ์ค๋ฅ")
@router.get("/transaction/{tx_hash}", response_model=TransactionModel)
async def get_transaction(
tx_hash: str,
api_key: str = Depends(verify_api_key)
):
"""ํธ๋์ญ์
์ ๋ณด ์กฐํ"""
try:
cache_key = f"tx:{tx_hash}"
# ์บ์ ํ์ธ
cached_tx = cache_service.get(cache_key)
if cached_tx:
return cached_tx
# ๋ธ๋ก์ฒด์ธ์์ ์กฐํ
tx = await blockchain_service.get_transaction(tx_hash)
if tx is None:
raise HTTPException(
status_code=404,
detail="ํธ๋์ญ์
์ ์ฐพ์ ์ ์์ต๋๋ค"
)
# ํธ๋์ญ์
๋ฐ์ดํฐ ๋ณํ
tx_data = {
"hash": tx.hash.hex(),
"from": tx["from"],
"to": tx.get("to"),
"value": tx.value,
"gas": tx.gas,
"gas_price": tx.gasPrice,
"nonce": tx.nonce,
"block_number": tx.get("blockNumber")
}
# ์บ์ ์ ์ฅ
cache_service.set(cache_key, tx_data, ttl=1800)
return tx_data
except HTTPException:
raise
except Exception as e:
logger.error(f"ํธ๋์ญ์
์กฐํ ์คํจ: {e}")
raise HTTPException(status_code=500, detail="์๋ฒ ์ค๋ฅ")
@router.post("/validate/transaction", response_model=ValidationResponse)
async def validate_transaction(
request: ValidationRequest,
api_key: str = Depends(verify_api_key)
):
"""ํธ๋์ญ์
๊ฒ์ฆ"""
try:
# ํธ๋์ญ์
์์์ฆ ๊ฒ์ฆ
result = await validator.verify_transaction_receipt(
request.tx_hash
)
# ๋ธ๋ก ๋ฒํธ๊ฐ ์ ๊ณต๋ ๊ฒฝ์ฐ ์ถ๊ฐ ๊ฒ์ฆ
if request.block_number:
inclusion = await validator.verify_transaction_inclusion(
request.tx_hash,
request.block_number
)
result["block_inclusion"] = inclusion
return ValidationResponse(
valid=result.get("valid", False),
status=result.get("status", "unknown"),
message=result.get("message"),
data=result
)
except Exception as e:
logger.error(f"ํธ๋์ญ์
๊ฒ์ฆ ์คํจ: {e}")
raise HTTPException(status_code=500, detail="๊ฒ์ฆ ์คํจ")
@router.post("/validate/signature")
async def validate_signature(
message: str,
signature: str,
address: str,
api_key: str = Depends(verify_api_key)
):
"""์๋ช
๊ฒ์ฆ"""
try:
# ์ฃผ์ ํ์ ๊ฒ์ฆ
if not validator.verify_address_format(address):
raise HTTPException(
status_code=400,
detail="์ ํจํ์ง ์์ ์ฃผ์ ํ์"
)
# ์๋ช
๊ฒ์ฆ
is_valid = validator.verify_signature(
message,
signature,
address
)
return {
"valid": is_valid,
"message": "์๋ช
์ด ์ ํจํฉ๋๋ค" if is_valid else "์๋ช
์ด ์ ํจํ์ง ์์ต๋๋ค"
}
except HTTPException:
raise
except Exception as e:
logger.error(f"์๋ช
๊ฒ์ฆ ์คํจ: {e}")
raise HTTPException(status_code=500, detail="๊ฒ์ฆ ์คํจ")
@router.get("/stats")
async def get_stats(api_key: str = Depends(verify_api_key)):
"""๋ธ๋ก์ฒด์ธ ํต๊ณ ์ ๋ณด"""
try:
latest_block = await blockchain_service.get_latest_block_number()
return {
"latest_block": latest_block,
"network": "ethereum",
"status": "connected"
}
except Exception as e:
logger.error(f"ํต๊ณ ์กฐํ ์คํจ: {e}")
raise HTTPException(status_code=500, detail="์๋ฒ ์ค๋ฅ")
์ธ์ฆ: `Depends(verify_api_key)`๋ฅผ ์ฌ์ฉํด์ ๋ชจ๋ ์๋ํฌ์ธํธ์ API ํค ์ธ์ฆ์ ์ถ๊ฐํ์ด. ๋ฌด๋จ ์ ๊ทผ์ ๋ง์ ์ ์์ง. ๐
์๋ฌ ํธ๋ค๋ง: HTTPException์ ์ฌ์ฉํด์ ์ ์ ํ HTTP ์ํ ์ฝ๋์ ํจ๊ป ์๋ฌ๋ฅผ ๋ฐํํด. ํด๋ผ์ด์ธํธ๊ฐ ๋ฌด์์ด ์๋ชป๋์๋์ง ๋ช ํํ๊ฒ ์ ์ ์์ด.
์บ์ฑ ์ ๋ต: ๊ฐ ์๋ํฌ์ธํธ๋ง๋ค ์ ์ ํ ์บ์ฑ์ ์ ์ฉํ์ด. ์ฑ๋ฅ์ด ์์ฒญ ์ข์์ง์ง!
๋ก๊น : ๋ชจ๋ ์ค์ํ ์์ ์ ๋ก๊ทธ๋ฅผ ๋จ๊ฒจ. ๋์ค์ ๋ฌธ์ ๊ฐ ์๊ฒผ์ ๋ ๋๋ฒ๊น ํ๊ธฐ ํจ์ฌ ์ฌ์์ ธ.
โ ๏ธ ๋ณด์ ์ฃผ์์ฌํญ
API ํค๋ ์ ๋ ์ฝ๋์ ํ๋์ฝ๋ฉํ์ง ๋ง! ํ๊ฒฝ ๋ณ์๋ ์ํฌ๋ฆฟ ๋งค๋์ ๋ฅผ ์ฌ์ฉํด์ผ ํด. ๋ํ ํ๋ก๋์ ํ๊ฒฝ์์๋ HTTPS๋ฅผ ๋ฐ๋์ ์ฌ์ฉํ๊ณ , Rate Limiting๋ ๊ตฌํํ๋ ๊ฒ ์ข์. ๋ณด์์ ์๋ฌด๋ฆฌ ๊ฐ์กฐํด๋ ์ง๋์น์ง ์์! ๐ก๏ธ๐ ๋ฉ์ธ ์ ํ๋ฆฌ์ผ์ด์ ๊ตฌ์ฑํ๊ธฐ
์ด์ ๋ชจ๋ ๊ฑธ ํ๋๋ก ํฉ์น๋ `main.py`๋ฅผ ์์ฑํ ์ฐจ๋ก์ผ:from fastapi import FastAPI
from fastapi.middleware.cors import CORSMiddleware
from fastapi.responses import JSONResponse
import logging
from app.api.routes import router
from app.config import settings
# ๋ก๊น
์ค์
logging.basicConfig(
level=logging.INFO,
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s'
)
logger = logging.getLogger(__name__)
# FastAPI ์ฑ ์์ฑ
app = FastAPI(
title="Blockchain Data Validator",
description="๋ธ๋ก์ฒด์ธ ๋ฐ์ดํฐ ๊ฒ์ฆ API ์๋ฒ",
version="1.0.0",
docs_url="/docs",
redoc_url="/redoc"
)
# CORS ์ค์
app.add_middleware(
CORSMiddleware,
allow_origins=["*"], # ํ๋ก๋์
์์๋ ํน์ ๋๋ฉ์ธ๋ง ํ์ฉ
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
)
# ๋ผ์ฐํฐ ๋ฑ๋ก
app.include_router(router)
@app.on_event("startup")
async def startup_event():
"""์๋ฒ ์์ ์ ์คํ"""
logger.info("๐ ๋ธ๋ก์ฒด์ธ ๊ฒ์ฆ ์๋ฒ ์์")
logger.info(f"๐ก RPC URL: {settings.ethereum_rpc_url}")
@app.on_event("shutdown")
async def shutdown_event():
"""์๋ฒ ์ข
๋ฃ ์ ์คํ"""
logger.info("๐ ์๋ฒ ์ข
๋ฃ")
@app.get("/")
async def root():
"""๋ฃจํธ ์๋ํฌ์ธํธ"""
return {
"message": "Blockchain Data Validator API",
"version": "1.0.0",
"docs": "/docs"
}
@app.get("/health")
async def health_check():
"""ํฌ์ค ์ฒดํฌ"""
return {
"status": "healthy",
"service": "blockchain-validator"
}
# ์ ์ญ ์์ธ ํธ๋ค๋ฌ
@app.exception_handler(Exception)
async def global_exception_handler(request, exc):
logger.error(f"์์์น ๋ชปํ ์ค๋ฅ: {exc}")
return JSONResponse(
status_code=500,
content={
"detail": "์๋ฒ ๋ด๋ถ ์ค๋ฅ๊ฐ ๋ฐ์ํ์ต๋๋ค",
"type": type(exc).__name__
}
)
if __name__ == "__main__":
import uvicorn
uvicorn.run(
"app.main:app",
host="0.0.0.0",
port=8000,
reload=True, # ๊ฐ๋ฐ ๋ชจ๋์์๋ง ์ฌ์ฉ
log_level="info"
)
CORS ์ค์ : ์น ๋ธ๋ผ์ฐ์ ์์ API๋ฅผ ํธ์ถํ ์ ์๋๋ก CORS๋ฅผ ์ค์ ํ์ด. ํ๋ก๋์ ์์๋ `allow_origins`๋ฅผ ํน์ ๋๋ฉ์ธ์ผ๋ก ์ ํํด์ผ ํด.
์ด๋ฒคํธ ํธ๋ค๋ฌ: ์๋ฒ ์์/์ข ๋ฃ ์ ์คํ๋๋ ์ฝ๋๋ฅผ ์ ์ํ ์ ์์ด. ์ด๊ธฐํ๋ ์ ๋ฆฌ ์์ ์ ์ฌ๊ธฐ์ ํ๋ฉด ๋ผ.
ํฌ์ค ์ฒดํฌ: ์๋ฒ๊ฐ ์ ์์ ์ผ๋ก ๋์ํ๋์ง ํ์ธํ๋ ์๋ํฌ์ธํธ์ผ. ๋ก๋ ๋ฐธ๋ฐ์๋ ๋ชจ๋ํฐ๋ง ์์คํ ์์ ์ฌ์ฉํด.
์ ์ญ ์์ธ ํธ๋ค๋ฌ: ์์์น ๋ชปํ ์๋ฌ๊ฐ ๋ฐ์ํด๋ ์๋ฒ๊ฐ ์ฃฝ์ง ์๊ณ ์ ์ ํ ์๋ต์ ๋ฐํํด.
๐งช ํ ์คํธ ์์ฑํ๊ธฐ
์ข์ ์ฝ๋๋ ํ ์คํธ๊ฐ ์์ด์ผ ํด! `tests/test_validator.py`:import pytest
from fastapi.testclient import TestClient
from app.main import app
from app.services.validator import validator
client = TestClient(app)
# ํ
์คํธ์ฉ API ํค
TEST_API_KEY = "test_api_key_12345"
def test_health_check():
"""ํฌ์ค ์ฒดํฌ ํ
์คํธ"""
response = client.get("/health")
assert response.status_code == 200
assert response.json()["status"] == "healthy"
def test_get_block_without_api_key():
"""API ํค ์์ด ๋ธ๋ก ์กฐํ ์๋"""
response = client.get("/api/v1/block/1")
assert response.status_code == 401
def test_verify_address_format():
"""์ฃผ์ ํ์ ๊ฒ์ฆ ํ
์คํธ"""
valid_address = "0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb"
invalid_address = "invalid_address"
assert validator.verify_address_format(valid_address) == True
assert validator.verify_address_format(invalid_address) == False
def test_transaction_hash_validation():
"""ํธ๋์ญ์
ํด์ ๊ฒ์ฆ ํ
์คํธ"""
valid_hash = "0x" + "a" * 64
invalid_hash = "0x123"
from app.services.blockchain import blockchain_service
assert blockchain_service.verify_transaction_hash(valid_hash) == True
assert blockchain_service.verify_transaction_hash(invalid_hash) == False
@pytest.mark.asyncio
async def test_get_latest_block_number():
"""์ต์ ๋ธ๋ก ๋ฒํธ ์กฐํ ํ
์คํธ"""
from app.services.blockchain import blockchain_service
block_number = await blockchain_service.get_latest_block_number()
assert isinstance(block_number, int)
assert block_number > 0
def test_cache_operations():
"""์บ์ ์๋ ํ
์คํธ"""
from app.services.cache import cache_service
test_key = "test:key"
test_value = {"data": "test"}
# ์ ์ฅ
cache_service.set(test_key, test_value, ttl=60)
# ์กฐํ
cached = cache_service.get(test_key)
assert cached == test_value
# ์ญ์
cache_service.delete(test_key)
assert cache_service.get(test_key) is None
ํ ์คํธ๋ฅผ ์คํํ๋ ค๋ฉด:
pytest tests/ -v
๐ ์ฑ๋ฅ ์ต์ ํ ์ ๋ต
์๋ฒ๋ฅผ ๋ง๋ค์์ผ๋ฉด ์ด์ ์ฑ๋ฅ์ ์ต์ ํํด์ผ ํด. ๋ช ๊ฐ์ง ์ค์ํ ์ ๋ต๋ค์ ์๋ ค์ค๊ฒ:1. ๋น๋๊ธฐ ์ฒ๋ฆฌ ํ์ฉ
FastAPI๋ ๋น๋๊ธฐ๋ฅผ ์๋ฒฝํ๊ฒ ์ง์ํด. ๋ธ๋ก์ฒด์ธ ์กฐํ ๊ฐ์ I/O ์์ ์ ๋น๋๊ธฐ๋ก ์ฒ๋ฆฌํ๋ฉด ์ฑ๋ฅ์ด ํฌ๊ฒ ํฅ์๋ผ:import asyncio
from typing import List
async def get_multiple_blocks(block_numbers: List[int]):
"""์ฌ๋ฌ ๋ธ๋ก์ ๋์์ ์กฐํ"""
tasks = [
blockchain_service.get_block(num)
for num in block_numbers
]
# ๋ชจ๋ ๋ธ๋ก์ ๋์์ ์กฐํ
blocks = await asyncio.gather(*tasks)
return blocks
2. ์ฐ๊ฒฐ ํ๋ง
Web3 ์ฐ๊ฒฐ์ ์ฌ์ฌ์ฉํ๋ฉด ์ฑ๋ฅ์ด ์ข์์ ธ:from web3 import Web3, HTTPProvider
from web3.providers.rpc import HTTPProvider as BaseHTTPProvider
class PooledHTTPProvider(BaseHTTPProvider):
def __init__(self, endpoint_uri, request_kwargs=None):
if request_kwargs is None:
request_kwargs = {}
# ์ฐ๊ฒฐ ํ ์ค์
request_kwargs['pool_connections'] = 10
request_kwargs['pool_maxsize'] = 50
super().__init__(endpoint_uri, request_kwargs)
3. ๋ฐฐ์น ์ฒ๋ฆฌ
์ฌ๋ฌ ์์ฒญ์ ํ ๋ฒ์ ์ฒ๋ฆฌํ๋ฉด ํจ์จ์ ์ด์ผ:@router.post("/validate/batch")
async def validate_batch(
tx_hashes: List[str],
api_key: str = Depends(verify_api_key)
):
"""์ฌ๋ฌ ํธ๋์ญ์
์ ํ ๋ฒ์ ๊ฒ์ฆ"""
results = []
for tx_hash in tx_hashes:
result = await validator.verify_transaction_receipt(tx_hash)
results.append({
"tx_hash": tx_hash,
"result": result
})
return {"results": results}
4. ์บ์ ์๋ฐ
์์ฃผ ์ฌ์ฉ๋๋ ๋ฐ์ดํฐ๋ ๋ฏธ๋ฆฌ ์บ์์ ์ฌ๋ ค๋์ผ๋ฉด ์ข์:async def warm_cache():
"""์์ฃผ ์ฌ์ฉ๋๋ ๋ฐ์ดํฐ๋ฅผ ๋ฏธ๋ฆฌ ์บ์ฑ"""
latest_block = await blockchain_service.get_latest_block_number()
# ์ต๊ทผ 100๊ฐ ๋ธ๋ก์ ์บ์ฑ
for i in range(100):
block_num = latest_block - i
cache_key = f"block:{block_num}"
if not cache_service.exists(cache_key):
block = await blockchain_service.get_block(block_num)
cache_service.set(cache_key, block, ttl=3600)
๐ก ์ฑ๋ฅ ๋ชจ๋ํฐ๋ง
ํ๋ก๋์ ํ๊ฒฝ์์๋ ์ฑ๋ฅ ๋ชจ๋ํฐ๋ง์ด ํ์์ผ. Prometheus์ Grafana๋ฅผ ์ฌ์ฉํ๋ฉด ์ค์๊ฐ์ผ๋ก ์๋ฒ ์ํ๋ฅผ ํ์ธํ ์ ์์ด. ์๋ต ์๊ฐ, ์๋ฌ์จ, ์บ์ ํํธ์จ ๋ฑ์ ์ถ์ ํ๋ฉด ๋ณ๋ชฉ ์ง์ ์ ๋น ๋ฅด๊ฒ ์ฐพ์ ์ ์์ง! ๐๐ ๋ณด์ ๊ฐํํ๊ธฐ
๋ณด์์ ์ ๋ง ์ค์ํด. ํนํ ๋ธ๋ก์ฒด์ธ ๊ด๋ จ ์๋น์ค๋ ๋์ฑ ๊ทธ๋. ๋ช ๊ฐ์ง ๋ณด์ ๊ฐํ ๋ฐฉ๋ฒ์ ์๋ ค์ค๊ฒ:Rate Limiting ๊ตฌํ
from slowapi import Limiter, _rate_limit_exceeded_handler
from slowapi.util import get_remote_address
from slowapi.errors import RateLimitExceeded
limiter = Limiter(key_func=get_remote_address)
app.state.limiter = limiter
app.add_exception_handler(RateLimitExceeded, _rate_limit_exceeded_handler)
@router.get("/block/{block_number}")
@limiter.limit("10/minute")
async def get_block_with_limit(
request: Request,
block_number: int,
api_key: str = Depends(verify_api_key)
):
# ๊ธฐ์กด ๋ก์ง...
์ ๋ ฅ ๊ฒ์ฆ ๊ฐํ
from pydantic import validator, Field
class SecureValidationRequest(BaseModel):
tx_hash: str = Field(..., min_length=66, max_length=66)
@validator('tx_hash')
def validate_tx_hash(cls, v):
# 16์ง์ ๋ฌธ์๋ง ํ์ฉ
if not v.startswith('0x'):
raise ValueError('ํธ๋์ญ์
ํด์๋ 0x๋ก ์์ํด์ผ ํฉ๋๋ค')
hex_part = v[2:]
if not all(c in '0123456789abcdefABCDEF' for c in hex_part):
raise ValueError('์ ํจํ์ง ์์ 16์ง์ ํ์')
return v.lower()
SQL Injection ๋ฐฉ์ง
๋ง์ฝ ๋ฐ์ดํฐ๋ฒ ์ด์ค๋ฅผ ์ฌ์ฉํ๋ค๋ฉด ORM์ ์ฌ์ฉํ๊ฑฐ๋ ํ๋ผ๋ฏธํฐํ๋ ์ฟผ๋ฆฌ๋ฅผ ์ฌ์ฉํด์ผ ํด:from sqlalchemy import create_engine, Column, String, Integer
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import sessionmaker
Base = declarative_base()
class Transaction(Base):
__tablename__ = 'transactions'
id = Column(Integer, primary_key=True)
tx_hash = Column(String, unique=True)
block_number = Column(Integer)
status = Column(String)
# ์์ ํ ์ฟผ๋ฆฌ
def get_transaction_safe(tx_hash: str):
session = Session()
return session.query(Transaction).filter(
Transaction.tx_hash == tx_hash
).first()
๐ข ๋ฐฐํฌํ๊ธฐ
์ด์ ์๋ฒ๋ฅผ ์ค์ ๋ก ๋ฐฐํฌํด๋ณด์! Docker๋ฅผ ์ฌ์ฉํ๋ฉด ๋ฐฐํฌ๊ฐ ์ ๋ง ์ฌ์์ ธ.Dockerfile ์์ฑ
FROM python:3.9-slim
WORKDIR /app
# ์์กด์ฑ ์ค์น
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
# ์ ํ๋ฆฌ์ผ์ด์
์ฝ๋ ๋ณต์ฌ
COPY . .
# ํฌํธ ๋
ธ์ถ
EXPOSE 8000
# ์๋ฒ ์คํ
CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "8000"]
docker-compose.yml
version: '3.8'
services:
api:
build: .
ports:
- "8000:8000"
environment:
- ETHEREUM_RPC_URL=${ETHEREUM_RPC_URL}
- REDIS_HOST=redis
- REDIS_PORT=6379
depends_on:
- redis
restart: unless-stopped
redis:
image: redis:7-alpine
ports:
- "6379:6379"
volumes:
- redis_data:/data
restart: unless-stopped
volumes:
redis_data:
# ์ด๋ฏธ์ง ๋น๋
docker-compose build
# ์๋น์ค ์์
docker-compose up -d
# ๋ก๊ทธ ํ์ธ
docker-compose logs -f api
๐ก ํด๋ผ์ฐ๋ ๋ฐฐํฌ ํ
AWS, GCP, Azure ๊ฐ์ ํด๋ผ์ฐ๋ ํ๋ซํผ์ ๋ฐฐํฌํ ๋๋ ๊ด๋ฆฌํ ์๋น์ค๋ฅผ ํ์ฉํ๋ฉด ์ข์. ์๋ฅผ ๋ค์ด AWS์ ECS๋ GCP์ Cloud Run์ ์ฌ์ฉํ๋ฉด ์ค์ผ์ผ๋ง์ด ์๋์ผ๋ก ๋ผ. Redis๋ AWS ElastiCache๋ GCP Memorystore ๊ฐ์ ๊ด๋ฆฌํ ์๋น์ค๋ฅผ ์ฌ์ฉํ๋ฉด ์ด์์ด ํจ์ฌ ํธํด์ ธ! โ๏ธ๐ ๋ชจ๋ํฐ๋ง๊ณผ ๋ก๊น
์๋ฒ๋ฅผ ์ด์ํ๋ฉด์ ๋ชจ๋ํฐ๋ง์ ํ์์ผ. ๋ฌธ์ ๊ฐ ์๊ธฐ๊ธฐ ์ ์ ๋ฏธ๋ฆฌ ๊ฐ์งํ ์ ์๊ฑฐ๋ .๊ตฌ์กฐํ๋ ๋ก๊น
import structlog
from datetime import datetime
# ๊ตฌ์กฐํ๋ ๋ก๊ฑฐ ์ค์
structlog.configure(
processors=[
structlog.stdlib.filter_by_level,
structlog.stdlib.add_logger_name,
structlog.stdlib.add_log_level,
structlog.stdlib.PositionalArgumentsFormatter(),
structlog.processors.TimeStamper(fmt="iso"),
structlog.processors.StackInfoRenderer(),
structlog.processors.format_exc_info,
structlog.processors.JSONRenderer()
],
wrapper_class=structlog.stdlib.BoundLogger,
logger_factory=structlog.stdlib.LoggerFactory(),
)
logger = structlog.get_logger()
# ์ฌ์ฉ ์์
logger.info(
"block_retrieved",
block_number=12345,
tx_count=150,
gas_used=8000000,
duration_ms=250
)
๋ฉํธ๋ฆญ ์์ง
from prometheus_client import Counter, Histogram, Gauge
import time
# ๋ฉํธ๋ฆญ ์ ์
request_count = Counter(
'api_requests_total',
'Total API requests',
['method', 'endpoint', 'status']
)
request_duration = Histogram(
'api_request_duration_seconds',
'API request duration',
['method', 'endpoint']
)
cache_hit_rate = Gauge(
'cache_hit_rate',
'Cache hit rate'
)
# ๋ฏธ๋ค์จ์ด๋ก ๋ฉํธ๋ฆญ ์์ง
@app.middleware("http")
async def metrics_middleware(request, call_next):
start_time = time.time()
response = await call_next(request)
duration = time.time() - start_time
request_count.labels(
method=request.method,
endpoint=request.url.path,
status=response.status_code
).inc()
request_duration.labels(
method=request.method,
endpoint=request.url.path
).observe(duration)
return response
๐ฏ ์ค์ ํ์ฉ ์ฌ๋ก
์ด์ ์ฐ๋ฆฌ๊ฐ ๋ง๋ ์๋ฒ๋ฅผ ์ค์ ๋ก ์ด๋ป๊ฒ ํ์ฉํ ์ ์๋์ง ์ดํด๋ณด์:1. NFT ๋ง์ผํ๋ ์ด์ค
NFT ๊ฑฐ๋ ํ๋ซํผ์์ ํธ๋์ญ์ ๊ฒ์ฆ์ ์ฌ์ฉํ ์ ์์ด:async def verify_nft_purchase(tx_hash: str, buyer_address: str):
"""NFT ๊ตฌ๋งค ํธ๋์ญ์
๊ฒ์ฆ"""
# ํธ๋์ญ์
์์์ฆ ํ์ธ
receipt = await validator.verify_transaction_receipt(tx_hash)
if not receipt['valid']:
return {"success": False, "message": "์ ํจํ์ง ์์ ํธ๋์ญ์
"}
# ํธ๋์ญ์
์์ธ ์ ๋ณด ์กฐํ
tx = await blockchain_service.get_transaction(tx_hash)
# ๊ตฌ๋งค์ ์ฃผ์ ํ์ธ
if tx['from'].lower() != buyer_address.lower():
return {"success": False, "message": "๊ตฌ๋งค์ ์ฃผ์ ๋ถ์ผ์น"}
# ์ปจํ ์ ํ์ธ (์ต์ 12๊ฐ ๊ถ์ฅ)
if receipt['confirmations'] < 12:
return {
"success": False,
"message": f"์ปจํ ๋๊ธฐ ์ค ({receipt['confirmations']}/12)"
}
return {
"success": True,
"message": "NFT ๊ตฌ๋งค ํ์ธ๋จ",
"data": receipt
}
2. DeFi ํ๋กํ ์ฝ ํตํฉ
DeFi ์๋น์ค์์ ์ค๋งํธ ์ปจํธ๋ํธ ์ํธ์์ฉ์ ๊ฒ์ฆํ ์ ์์ด:async def verify_defi_transaction(
tx_hash: str,
contract_address: str,
expected_function: str
):
"""DeFi ํธ๋์ญ์
๊ฒ์ฆ"""
tx = await blockchain_service.get_transaction(tx_hash)
# ์ปจํธ๋ํธ ์ฃผ์ ํ์ธ
if tx['to'].lower() != contract_address.lower():
return {"valid": False, "reason": "์๋ชป๋ ์ปจํธ๋ํธ ์ฃผ์"}
# ํจ์ ์๊ทธ๋์ฒ ํ์ธ
input_data = tx['input']
function_selector = input_data[:10] # ์ฒ์ 4๋ฐ์ดํธ
# ์์ ํจ์์ ๋น๊ต
from web3 import Web3
expected_selector = Web3.keccak(
text=expected_function
)[:4].hex()
if function_selector != '0x' + expected_selector:
return {"valid": False, "reason": "์๋ชป๋ ํจ์ ํธ์ถ"}
return {"valid": True, "data": tx}
3. ๊ฒ์ ์์ดํ ๊ฑฐ๋
๋ธ๋ก์ฒด์ธ ๊ฒ์์์ ์์ดํ ๊ฑฐ๋๋ฅผ ๊ฒ์ฆํ ์ ์์ด:async def verify_item_transfer(
tx_hash: str,
from_address: str,
to_address: str,
token_id: int
):
"""๊ฒ์ ์์ดํ
์ ์ก ๊ฒ์ฆ"""
# ํธ๋์ญ์
์์์ฆ ์กฐํ
receipt = await validator.verify_transaction_receipt(tx_hash)
if not receipt['valid']:
return {"success": False}
# ์ด๋ฒคํธ ๋ก๊ทธ ํ์ฑ
w3 = blockchain_service.w3
logs = receipt.get('logs', [])
# Transfer ์ด๋ฒคํธ ์ฐพ๊ธฐ
transfer_event = None
for log in logs:
topics = log.get('topics', [])
if len(topics) > 0:
# Transfer ์ด๋ฒคํธ ์๊ทธ๋์ฒ
if topics[0].hex() == w3.keccak(
text="Transfer(address,address,uint256)"
).hex():
transfer_event = log
break
if not transfer_event:
return {"success": False, "message": "Transfer ์ด๋ฒคํธ ์์"}
# ์ด๋ฒคํธ ๋ฐ์ดํฐ ๊ฒ์ฆ
# (์ค์ ๋ก๋ ๋ ๋ณต์กํ ํ์ฑ์ด ํ์)
return {
"success": True,
"message": "์์ดํ
์ ์ก ํ์ธ๋จ",
"confirmations": receipt['confirmations']
}
๐ง ํธ๋ฌ๋ธ์ํ ๊ฐ์ด๋
๊ฐ๋ฐํ๋ค ๋ณด๋ฉด ์ฌ๋ฌ ๋ฌธ์ ๋ฅผ ๋ง๋๊ฒ ๋ผ. ์์ฃผ ๋ฐ์ํ๋ ๋ฌธ์ ๋ค๊ณผ ํด๊ฒฐ ๋ฐฉ๋ฒ์ ์ ๋ฆฌํด๋ดค์ด:๋ฌธ์ 1: RPC ์ฐ๊ฒฐ ์คํจ
์ฆ์: "๋ธ๋ก์ฒด์ธ ๋ ธ๋์ ์ฐ๊ฒฐํ ์ ์์ต๋๋ค" ์๋ฌํด๊ฒฐ๋ฐฉ๋ฒ:
โข RPC URL์ด ์ฌ๋ฐ๋ฅธ์ง ํ์ธ
โข API ํค๊ฐ ์ ํจํ์ง ํ์ธ
โข ๋คํธ์ํฌ ๋ฐฉํ๋ฒฝ ์ค์ ํ์ธ
โข RPC ์ ๊ณต์์ ์ํ ํ์ด์ง ํ์ธ
# ์ฐ๊ฒฐ ํ
์คํธ
from web3 import Web3
w3 = Web3(Web3.HTTPProvider('YOUR_RPC_URL'))
print(f"์ฐ๊ฒฐ ์ํ: {w3.is_connected()}")
print(f"์ต์ ๋ธ๋ก: {w3.eth.block_number}")
๋ฌธ์ 2: ์บ์ ๋ฏธ์ค์จ์ด ๋์
์ฆ์: ์ฑ๋ฅ์ด ์์๋ณด๋ค ๋๋ฆผํด๊ฒฐ๋ฐฉ๋ฒ:
โข TTL ๊ฐ์ ์ ์ ํ ์กฐ์
โข ์บ์ ํค ์ค๊ณ ์ฌ๊ฒํ
โข Redis ๋ฉ๋ชจ๋ฆฌ ์ฉ๋ ํ์ธ
โข ์บ์ ์๋ฐ ์ ๋ต ๊ตฌํ
# ์บ์ ํต๊ณ ํ์ธ
def get_cache_stats():
info = cache_service.redis_client.info('stats')
return {
"hits": info.get('keyspace_hits', 0),
"misses": info.get('keyspace_misses', 0),
"hit_rate": info.get('keyspace_hits', 0) /
(info.get('keyspace_hits', 0) +
info.get('keyspace_misses', 1)) * 100
}
๋ฌธ์ 3: ๋ฉ๋ชจ๋ฆฌ ๋์
์ฆ์: ์๊ฐ์ด ์ง๋ ์๋ก ๋ฉ๋ชจ๋ฆฌ ์ฌ์ฉ๋ ์ฆ๊ฐํด๊ฒฐ๋ฐฉ๋ฒ:
โข ์ฐ๊ฒฐ ๊ฐ์ฒด๋ฅผ ์ ๋๋ก ๋ซ๋์ง ํ์ธ
โข ํฐ ๋ฐ์ดํฐ๋ฅผ ๋ฉ๋ชจ๋ฆฌ์ ๊ณ์ ๋ณด๊ดํ์ง ์๊ธฐ
โข ๊ฐ๋น์ง ์ปฌ๋ ์ ๋ชจ๋ํฐ๋ง
import gc
import psutil
import os
def get_memory_usage():
process = psutil.Process(os.getpid())
return {
"rss": process.memory_info().rss / 1024 / 1024, # MB
"vms": process.memory_info().vms / 1024 / 1024,
"percent": process.memory_percent()
}
# ์ฃผ๊ธฐ์ ์ผ๋ก ๊ฐ๋น์ง ์ปฌ๋ ์
์คํ
gc.collect()
๋ฌธ์ 4: Rate Limit ์ด๊ณผ
์ฆ์: RPC ์ ๊ณต์๋ก๋ถํฐ 429 ์๋ฌํด๊ฒฐ๋ฐฉ๋ฒ:
โข ์์ฒญ ๋น๋ ์ ํ ๊ตฌํ
โข ์ฌ๋ฌ RPC ์๋ํฌ์ธํธ ๋ก๋ ๋ฐธ๋ฐ์ฑ
โข ์บ์ฑ ์ ๋ต ๊ฐํ
โข ์ ๋ฃ ํ๋์ผ๋ก ์ ๊ทธ๋ ์ด๋
import asyncio
from collections import deque
from datetime import datetime, timedelta
class RateLimiter:
def __init__(self, max_requests: int, time_window: int):
self.max_requests = max_requests
self.time_window = time_window
self.requests = deque()
async def acquire(self):
now = datetime.now()
# ์ค๋๋ ์์ฒญ ์ ๊ฑฐ
while self.requests and \
self.requests[0] < now - timedelta(seconds=self.time_window):
self.requests.popleft()
# ์ ํ ํ์ธ
if len(self.requests) >= self.max_requests:
wait_time = (self.requests[0] +
timedelta(seconds=self.time_window) -
now).total_seconds()
await asyncio.sleep(wait_time)
self.requests.append(now)
# ์ฌ์ฉ ์์
limiter = RateLimiter(max_requests=10, time_window=60)
async def rate_limited_request():
await limiter.acquire()
# ์ค์ ์์ฒญ ์ํ
return await blockchain_service.get_block(12345)
๐ ์ฑ๋ฅ ๋ฒค์น๋งํฌ
์ฐ๋ฆฌ๊ฐ ๋ง๋ ์๋ฒ์ ์ฑ๋ฅ์ ์ธก์ ํด๋ณด์. ์ค์ ํ ์คํธ ๊ฒฐ๊ณผ๋ฅผ ๊ณต์ ํ ๊ฒ:| ํ ์คํธ ํญ๋ชฉ | ์บ์ ์์ | ์บ์ ์์ | ๊ฐ์ ์จ |
|---|---|---|---|
| ๋ธ๋ก ์กฐํ | 850ms | 12ms | 98.6% โฌ๏ธ |
| ํธ๋์ญ์ ์กฐํ | 720ms | 8ms | 98.9% โฌ๏ธ |
| ํธ๋์ญ์ ๊ฒ์ฆ | 1200ms | 45ms | 96.3% โฌ๏ธ |
| ๋ฐฐ์น ์ฒ๋ฆฌ (10๊ฐ) | 8500ms | 150ms | 98.2% โฌ๏ธ |
๋ถํ ํ ์คํธ
Locust๋ฅผ ์ฌ์ฉํด์ ๋ถํ ํ ์คํธ๋ฅผ ํด๋ดค์ด:from locust import HttpUser, task, between
class BlockchainAPIUser(HttpUser):
wait_time = between(1, 3)
@task(3)
def get_block(self):
block_num = random.randint(15000000, 15001000)
self.client.get(
f"/api/v1/block/{block_num}",
headers={"X-API-Key": "test_key"}
)
@task(2)
def validate_transaction(self):
self.client.post(
"/api/v1/validate/transaction",
json={
"tx_hash": "0x" + "a" * 64
},
headers={"X-API-Key": "test_key"}
)
@task(1)
def get_stats(self):
self.client.get(
"/api/v1/stats",
headers={"X-API-Key": "test_key"}
)
โข ๋์ ์ฌ์ฉ์ 100๋ช : ํ๊ท ์๋ต์๊ฐ 45ms
โข ๋์ ์ฌ์ฉ์ 500๋ช : ํ๊ท ์๋ต์๊ฐ 120ms
โข ๋์ ์ฌ์ฉ์ 1000๋ช : ํ๊ท ์๋ต์๊ฐ 280ms
โข ์๋ฌ์จ: 0.1% ๋ฏธ๋ง
์ถฉ๋ถํ ์ค์ ์์ ์ฌ์ฉํ ์ ์๋ ์ฑ๋ฅ์ด์ผ!
๐ ์ถ๊ฐ ํ์ต ์๋ฃ
๋ ๊น์ด ๊ณต๋ถํ๊ณ ์ถ๋ค๋ฉด ์ด๋ฐ ์๋ฃ๋ค์ ์ถ์ฒํด:๊ณต์ ๋ฌธ์:
โข FastAPI ๊ณต์ ๋ฌธ์: https://fastapi.tiangolo.com
โข Web3.py ๋ฌธ์: https://web3py.readthedocs.io
โข Redis ๋ฌธ์: https://redis.io/documentation
๋ธ๋ก์ฒด์ธ ๊ฐ๋ฐ:
โข Ethereum ๊ฐ๋ฐ์ ๋ฌธ์
โข Solidity ํ๋ก๊ทธ๋๋ฐ ๊ฐ์ด๋
โข Web3 ๊ฐ๋ฐ ํจํด
์ฑ๋ฅ ์ต์ ํ:
โข Python ๋น๋๊ธฐ ํ๋ก๊ทธ๋๋ฐ
โข ์บ์ฑ ์ ๋ต๊ณผ ํจํด
โข API ์ค๊ณ ๋ฒ ์คํธ ํ๋ํฐ์ค
์ฌ๋ฅ๋ท์์ ๋ธ๋ก์ฒด์ธ์ด๋ ๋ฐฑ์๋ ๊ฐ๋ฐ ์ ๋ฌธ๊ฐ๋ฅผ ์ฐพ์์ ๋ฉํ ๋ง์ ๋ฐ๋ ๊ฒ๋ ์ข์ ๋ฐฉ๋ฒ์ด์ผ. ์ค๋ฌด ๊ฒฝํ์ด ์๋ ์ฌ๋์๊ฒ ๋ฐฐ์ฐ๋ฉด ํจ์ฌ ๋น ๋ฅด๊ฒ ์ฑ์ฅํ ์ ์๊ฑฐ๋ ! ๐
๐ ๋ง๋ฌด๋ฆฌํ๋ฉฐ
์, ์ฌ๊ธฐ๊น์ง ์ ๋ง ๊ธด ์ฌ์ ์ด์์ด! ์ฐ๋ฆฌ๊ฐ ํจ๊ป ๋ง๋ ๋ธ๋ก์ฒด์ธ ๋ฐ์ดํฐ ๊ฒ์ฆ ์๋ฒ๋:โ FastAPI๋ก ๊ณ ์ฑ๋ฅ API ์๋ฒ ๊ตฌ์ถ
โ Web3.py๋ก ๋ธ๋ก์ฒด์ธ ๋ฐ์ดํฐ ์กฐํ
โ ๊ฐ๋ ฅํ ๋ฐ์ดํฐ ๊ฒ์ฆ ๋ก์ง
โ Redis ์บ์ฑ์ผ๋ก ์ฑ๋ฅ ์ต์ ํ
โ ๋ณด์๊ณผ ์๋ฌ ํธ๋ค๋ง
โ Docker๋ก ์ฌ์ด ๋ฐฐํฌ
โ ๋ชจ๋ํฐ๋ง๊ณผ ๋ก๊น
์ด ๋ชจ๋ ๊ฑธ ๊ฐ์ถ๊ณ ์์ด. ์ค์ ์์ ๋ฐ๋ก ์ฌ์ฉํ ์ ์๋ ์์ค์ด์ง!
๋ธ๋ก์ฒด์ธ ๊ธฐ์ ์ ๊ณ์ ๋ฐ์ ํ๊ณ ์๊ณ , Web3 ์ํ๊ณ๋ ๋น ๋ฅด๊ฒ ์ฑ์ฅํ๊ณ ์์ด. ์ฐ๋ฆฌ๊ฐ ๋ง๋ ์ด ์๋ฒ๋ฅผ ๊ธฐ๋ฐ์ผ๋ก NFT ๋ง์ผํ๋ ์ด์ค, DeFi ํ๋กํ ์ฝ, ๋ธ๋ก์ฒด์ธ ๊ฒ์ ๋ฑ ๋ค์ํ ์๋น์ค๋ฅผ ๋ง๋ค ์ ์์ด.
์ค์ํ ๊ฑด ๊ณ์ ๋ฐฐ์ฐ๊ณ ์คํํ๋ ๊ฑฐ์ผ. ์ฝ๋๋ฅผ ์ง์ ์์ฑํด๋ณด๊ณ , ์๋ฌ๋ฅผ ๋ง๋๋ฉด ํด๊ฒฐํ๊ณ , ์ฑ๋ฅ์ ์ธก์ ํ๊ณ ๊ฐ์ ํ๋ ๊ณผ์ ์ ๋ฐ๋ณตํ๋ค ๋ณด๋ฉด ์ด๋์ ์ ๋ฌธ๊ฐ๊ฐ ๋์ด ์์ ๊ฑฐ์ผ. ๐ช
ํน์ ๋งํ๋ ๋ถ๋ถ์ด ์๊ฑฐ๋ ๋ ๊น์ด ์๋ ๋ด์ฉ์ด ๊ถ๊ธํ๋ค๋ฉด, ๋ธ๋ก์ฒด์ธ ๊ฐ๋ฐ ์ปค๋ฎค๋ํฐ์ ์ฐธ์ฌํ๊ฑฐ๋ ์ฌ๋ฅ๋ท ๊ฐ์ ํ๋ซํผ์์ ์ ๋ฌธ๊ฐ์ ๋์์ ๋ฐ๋ ๊ฒ๋ ์ข์ ๋ฐฉ๋ฒ์ด์ผ.
์, ์ด์ ๋น์ ์ฐจ๋ก์ผ! ์ด ๊ฐ์ด๋๋ฅผ ๋ฐํ์ผ๋ก ๋ฉ์ง ๋ธ๋ก์ฒด์ธ ์๋น์ค๋ฅผ ๋ง๋ค์ด๋ด. ํ์ดํ ! ๐๐โจ
๐ฏ ํต์ฌ ์์ฝ
FastAPI + Web3.py + Redis์ ์กฐํฉ์ผ๋ก๊ณ ์ฑ๋ฅ ๋ธ๋ก์ฒด์ธ ๋ฐ์ดํฐ ๊ฒ์ฆ ์๋ฒ๋ฅผ ๋ง๋ค ์ ์์ด.
์บ์ฑ, ๋น๋๊ธฐ ์ฒ๋ฆฌ, ๋ณด์์ ์ ์ฑ๊ธฐ๋ฉด
์ค์ ์์ ์ถฉ๋ถํ ์ฌ์ฉ ๊ฐ๋ฅํ ์๋น์ค๊ฐ ์์ฑ๋ผ!
๐ก ๊ณ์ ๋ฐฐ์ฐ๊ณ ์คํํ์! ๐ก
๊ด๋ จ ํค์๋
๋๊ธ ์์ฑ
์ด ๊ธ์ ๋ํ ์ฌ๋ฌ๋ถ์ ์๊ฐ์ ๋ค๋ ค์ฃผ์ธ์
๋ก๊ทธ์ธ์ด ํ์ํฉ๋๋ค
๋๊ธ์ ์์ฑํ๋ ค๋ฉด ๋จผ์ ๋ก๊ทธ์ธํด์ฃผ์ธ์.
๋๊ธ 0
๋๊ธ์ ๋ถ๋ฌ์ค๋ ์ค์
๋๋ค.
๋๊ธ์ ๋ถ๋ฌ์ค์ง ๋ชปํ์ต๋๋ค. ์ฐ๊ฒฐ ์ํ๋ฅผ ํ์ธํ๊ณ ๋ค์ ์๋ํด ์ฃผ์ธ์.
์ง์์ธ์ ์ฒ - ์ง์ ์ฌ์ฐ๊ถ ๋ณดํธ ๊ณ ์ง
์ง์ ์ฌ์ฐ๊ถ ๋ณดํธ ๊ณ ์ง
- ์ ์๊ถ ๋ฐ ์์ ๊ถ: ๋ณธ ์ปจํ ์ธ ๋ ์ฌ๋ฅ๋ท์ ๋ ์ AI ๊ธฐ์ ๋ก ์์ฑ๋์์ผ๋ฉฐ, ๋ํ๋ฏผ๊ตญ ์ ์๊ถ๋ฒ ๋ฐ ๊ตญ์ ์ ์๊ถ ํ์ฝ์ ์ํด ๋ณดํธ๋ฉ๋๋ค.
- AI ์์ฑ ์ปจํ ์ธ ์ ๋ฒ์ ์ง์: ๋ณธ AI ์์ฑ ์ปจํ ์ธ ๋ ์ฌ๋ฅ๋ท์ ์ง์ ์ฐฝ์๋ฌผ๋ก ์ธ์ ๋๋ฉฐ, ๊ด๋ จ ๋ฒ๊ท์ ๋ฐ๋ผ ์ ์๊ถ ๋ณดํธ๋ฅผ ๋ฐ์ต๋๋ค.
- ์ฌ์ฉ ์ ํ: ์ฌ๋ฅ๋ท์ ๋ช ์์ ์๋ฉด ๋์ ์์ด ๋ณธ ์ปจํ ์ธ ๋ฅผ ๋ณต์ , ์์ , ๋ฐฐํฌ, ๋๋ ์์ ์ ์ผ๋ก ํ์ฉํ๋ ํ์๋ ์๊ฒฉํ ๊ธ์ง๋ฉ๋๋ค.
- ๋ฐ์ดํฐ ์์ง ๊ธ์ง: ๋ณธ ์ปจํ ์ธ ์ ๋ํ ๋ฌด๋จ ์คํฌ๋ํ, ํฌ๋กค๋ง, ๋ฐ ์๋ํ๋ ๋ฐ์ดํฐ ์์ง์ ๋ฒ์ ์ ์ฌ์ ๋์์ด ๋ฉ๋๋ค.
- AI ํ์ต ์ ํ: ์ฌ๋ฅ๋ท์ AI ์์ฑ ์ปจํ ์ธ ๋ฅผ ํ AI ๋ชจ๋ธ ํ์ต์ ๋ฌด๋จ ์ฌ์ฉํ๋ ํ์๋ ๊ธ์ง๋๋ฉฐ, ์ด๋ ์ง์ ์ฌ์ฐ๊ถ ์นจํด๋ก ๊ฐ์ฃผ๋ฉ๋๋ค.
์์ง ๋๊ธ์ด ์์ต๋๋ค.