6. 기반 라이브러리 특징

6.1 io_uring 비동기 I/O (libubmsio_uring.so)

Linux io_uring을 직접 래핑한 고성능 I/O 엔진. libevent의 bufferevent를 대체하며, 훨씬 적은 시스템콜로 동일 작업을 수행.

핵심 최적화:

Multishot Accept/Recv  SQE 재등록 없이 연속 수신
Zero-Copy Send         커널이 직접 버퍼 참조, 메모리 복사 제거
Provided Buffer Ring   커널 관리 버퍼 풀, 할당 비용 제거
Fixed FD               fd 사전 등록, 조회 비용 제거
Batch Submit           여러 SQE를 한 번에 제출
Backpressure           수신/송신 큐 크기 제한으로 메모리 안정성

세대 번호(gen)로 stale CQE 감지: 연결이 끊긴 후 뒤늦게 도착하는 완료 이벤트를 안전하게 무시.

UBMS::UringSession이 per-fd 상태를 관리하며, 참조 카운팅(incRef/decRef)으로 안전하게 해제.

6.2 libevent 기반 이벤트 아키텍처 — Qt/Boost 없는 독자 설계

UBMS는 Qt의 시그널/슬롯, Boost.Asio의 이벤트 루프, Boost.Signals2의 옵저버 패턴을 libevent 하나로 전부 대체했다. 외부 GUI 프레임워크나 대형 라이브러리 의존 없이, libevent의 이벤트 루프를 기반으로 6가지 영역에 걸쳐 독자적인 이벤트 시스템을 구축.

6.2.1 이벤트 루프 엔진

libevent의 event_base를 독립 스레드로 구동하는 래퍼. EVLOOP_NO_EXIT_ON_EMPTY 플래그로 이벤트가 없어도 루프가 유지되며, event_base_loopbreak로 안전하게 종료. 이것이 UBMS 전체 이벤트 시스템의 심장.

UBMS::EventBase
  event_base_new()             이벤트 루프 생성
  event_base_loop()            EVLOOP_NO_EXIT_ON_EMPTY로 상주 구동
  event_base_loopbreak()       안전 종료
  evthread_use_pthreads()      멀티스레드 안전성 활성화

6.2.2 신호-슬롯 시스템 — libevent를 비동기 디스패처로 응용

Qt의 시그널/슬롯을 libevent 위에 재구현. 핵심 아이디어는 event_new로 타이머 이벤트를 만들되 timeval{0,0}으로 즉시 발화시켜, 이벤트 루프가 슬롯 콜백을 비동기 디스패치하게 만드는 것.

사용 예시:

// connect: 슬롯 등록 (weak_ptr로 수명 안전)
signal.connect(instance, "onBlock", &Handler::onNewBlock);

// emit: libevent 이벤트 큐에 즉시 예약, 이벤트 루프가 콜백 실행
signal.emit(instance, "onBlock", blockData);

emit 내부 동작:

1. COW 슬롯 스냅샷 획득 (shared_ptr 참조만 복사, 벡터 복사 제거)
2. EmitEvent 구조체 생성 (args + 슬롯 스냅샷 + 인스턴스)
3. event_new(base, -1, EV_TIMEOUT, cb_emit, emitEvent) 으로 libevent 이벤트 등록
4. event_add(ev, timeval{0,0}) 으로 즉시 발화 예약
5. 이벤트 루프 스레드에서 cb_emit이 슬롯 스냅샷을 순회하며 콜백 실행

안전성 설계 — 5중 방어:

weak_ptr 캡처          소멸된 객체에 콜백하지 않음 (UAF 방지)
COW 슬롯 벡터          emit 중 connect/disconnect 안전 (락프리 읽기)
3단계 상태 머신        Alive -> Marked -> Cleaning (CAS 전이, 재진입 방지)
m_cbCount 원자 카운터  소멸자가 진행 중 콜백 완료를 대기 (UAF 이중 방지)
event_base_once 정리   Marked 이벤트를 이벤트 루프에서 안전하게 지연 해제

Qt의 시그널/슬롯은 MOC 코드 생성기가 필요하고, Boost.Signals2는 헤더 비대로 컴파일 시간이 증가함. UBMS의 신호-슬롯은 순수 C++ 템플릿만으로 타입 안전한 비동기 신호-슬롯을 구현하며, 외부 코드 생성 도구가 불필요.

신호 관리자가 type_index 레지스트리로 전역 신호를 관리하여, 모듈 간 직접 참조 없이 느슨한 결합을 달성.

6.2.3 HTTP 서버 (libubmshttp.so) — libevent의 evhttp 기반 REST API

libevent의 evhttp를 기반으로 완전한 REST API 서버를 구축. 자체 이벤트 루프 스레드에서 독립 동작하며, 런타임 재바인딩(rebind)을 지원.

UBMS::Http          자체 UBMS::EventBase 소유, 독립 스레드 HTTP 서버
UBMS::Router        method+URI 기반 라우팅 테이블 (shared_mutex 보호)
                    addRoute: 외부 접근 가능 엔드포인트
                    addLocal: 로컬 전용 엔드포인트 (관리 API 분리)
UBMS::Forwarder     리버스 프록시, FIXED/ROUNDROBIN 정책
                    path/query 유지, 로컬 요청 제외, prefix 필터링
UBMS::Redirector    301/302/307/308 리다이렉트, ROUNDROBIN 분산
                    브라우저 캐시 고려한 상태코드 선택 (307 임시 vs 308 영구)
UBMS::Abuse         IP+URI별 요청 제한, LRU 캐시로 메모리 상한
UBMS::HttpRequest   onPreRoute 훅으로 포워딩/리다이렉트 파이프라인 구성

요청 처리 흐름:

evhttp 요청 도착
  -> Abuse 검사 (Rate Limiting)
  -> onPreRoute (Redirector -> Forwarder 순서로 가로채기)
  -> Router.route() (method+URI 매칭)
  -> 핸들러 실행 -> sendResponse

이 구조로 단일 HTTP 서버가 REST API + 리버스 프록시 + 리다이렉트 + 레이트 리미팅을 라이브러리 단 하나(libevent)로 제공. nginx나 별도 프록시 서버 없이 노드 자체가 이 모든 기능을 수행.

6.2.4 OS 시그널 핸들링

POSIX 시그널(SIGINT, SIGTERM 등)을 libevent의 이벤트 루프에 통합함. evsignal_new로 시그널을 이벤트로 변환하여, 비동기-안전하지 않은 작업도 이벤트 루프 스레드에서 안전하게 처리.

UBMS::SignalHandler _sigHandler;
_sigHandler.on(SIGINT,  [](int){ g_running.store(false); });
_sigHandler.on(SIGTERM, [](int){ g_running.store(false); });
_sigHandler.start(eventBase.getBase());

6.2.5 이벤트 루프 풀

이벤트 루프를 N개 생성하여 병렬 이벤트 처리를 지원. io_uring 풀과 대칭 구조로, io_uring이 I/O를 담당하고 이벤트 루프 풀이 로직 이벤트를 담당하는 이중 이벤트 엔진 아키텍처를 구성.

6.2.6 설계 요약 — libevent 단일 라이브러리로 6가지 역할

역할

libevent API

대체 대상

이벤트 루프 엔진

event_base_loop

Boost.Asio io_context

비동기 신호-슬롯

event_new + EV_TIMEOUT

Qt signal/slot, Boost.Signals2

HTTP REST 서버

evhttp

nginx, Express, Flask

리버스 프록시

evhttp + libcurl

nginx proxy_pass

OS 시그널 통합

evsignal_new

직접 signal() 호출

타이머/주기 이벤트

event_add + EV_PERSIST

별도 타이머 라이브러리

Qt(~50MB), Boost(~150MB 헤더) 같은 대형 의존성 없이, libevent(~1MB) 하나로 GUI 프레임워크급 이벤트 시스템과 웹 서버 기능을 모두 구현함. 이는 빌드 시간 단축, 배포 크기 최소화, 크로스 컴파일 용이성에 직접적으로 기여.

6.2.7 ubmsnotify — 라이브러리 범용성의 실증

ubmsnotify는 UBMS 블록체인의 독립 실행 응용 프로그램. 메인 노드(ubmsdaemon)와 별도 프로세스로 동작하며, 블록 이벤트를 실시간으로 수신하여 모바일 디바이스에 FCM 푸시 알림을 전송.

이 프로그램이 기술적으로 중요한 이유는 — libubmsevent.so, libubmsio_uring.so가 블록체인 노드 전용이 아니라 범용 서버 애플리케이션 프레임워크로 동작함을 실증하기 때문.

ubmsnotify의 아키텍처:

ubmsnotify (독립 실행 바이너리)
    UBMS::EventBase          libevent 이벤트 루프 (libubmsevent.so)
    UBMS::SignalHandler      SIGINT/SIGTERM 안전 처리 (libubmsevent.so)
    UBMS::UringPool          io_uring 기반 네트워크 I/O (libubmsio_uring.so)
    WebSocket 서버     모바일 클라이언트 접속 수신 (libubmsio_uring.so)
    WebSocketClient    외부 블록체인 노드 구독 (libubmsio_uring.so)
    WorkQueue          단일 워커 스레드 + 1초 주기 틱 (자체 구현)
    FcmSender          Google FCM HTTP v1 API (libcurl + OpenSSL)
    ConsoleView        터미널 대시보드 (fmt::color + ANSI escape + ioctl 터미널 감지)

동작 흐름:

1. WebSocket 서버로 모바일 앱 접속 수신, FCM 토큰 + 지갑 주소 등록
2. WebSocketClient로 블록체인 노드에 WSS 연결, 새 블록 구독
3. 블록 도착 시 TX/스테이크 보상에서 등록된 주소 매칭
4. 매칭된 주소의 FCM 토큰으로 푸시 알림 전송
5. 토큰 자동 갱신, NOT_FOUND 토큰 자동 제거, 외부 연결 자동 재접속

핵심 설계 특징:

스레드 안전성    모든 I/O 콜백이 WorkQueue.post()로 워커 스레드에 직렬화
                 데이터 보호용 mutex 불필요, 큐 조작용 mutex만 사용
자동 복구        FCM 토큰 만료 시 자동 갱신, 외부 노드 끊김 시 자동 재연결
리소스 관리      LRU 기반 토큰 맵, 유효하지 않은 토큰 자동 제거

이것이 입증하는 것:

libubmsevent.so (UBMS::EventBase + UBMS::SignalHandler)     이벤트 루프 프레임워크로 독립 사용 가능
libubmsio_uring.so (UBMS::UringPool + WebSocket)            네트워크 서버 프레임워크로 독립 사용 가능
두 라이브러리 조합                         범용 실시간 서버 애플리케이션 구축 가능

이 라이브러리들을 Ubuntu 패키지로 배포하면, 블록체인과 무관한 일반 서버 애플리케이션 (채팅 서버, IoT 게이트웨이, 실시간 모니터링 서버 등)에도 그대로 활용할 수 있다. 블록체인 프로젝트가 범용 인프라 라이브러리를 자체 생산한 사례로, 이는 프로젝트의 기술적 깊이를 넘어 산업적 활용 가치를 보여준다.


6.3 암호화 (libubmscrypto.so)

RSA-4096           키 생성, 서명, 검증, 암호화
SHA-256            블록/TX 해싱
RIPEMD-160         주소 생성 (독립 구현, OpenSSL 의존 없음)
Base58Check        비트코인 호환 주소 인코딩
AES-GCM            세션 암호화 (진행 중)
HMAC-SHA256        메시지 인증
zlib               메시지 압축 (Zip Bomb 방어: 64MB 제한)

OpenSSL 3.x의 EVP API를 사용하고, default + legacy provider를 모두 로드.

향후 계획: 타원곡선 암호(ECDSA/Ed25519)를 추가하여 서명 크기와 키 생성 속도를 개선하고, 양자내성 암호(PQC) 알고리즘 도입을 위한 OpenSSL 3.x의 provider 구조를 활용할 예정.

6.4 독자 직렬화 엔진 (libubmstype.so)

libubmstype.so는 UBMS의 모든 데이터 구조의 기반이 되는 독자 개발 직렬화 프레임워크.

// 실제 UBMS::S_UTXO의 describe() — 이것만으로 JSON + 바이너리 직렬화가 자동 생성됨
void UBMS::S_UTXO::describe() {
    reg("index", index);
    reg("amount", amount);
    reg("timestamp", timestamp);
    reg("owner", owner);
    reg("txid", txid);
    regStruct("stake", stake);    // S_STAKE 재귀 직렬화
    regStruct("ubms", ubms);      // S_UBMS 재귀 직렬화
}

Serial 계층 (리틀엔디안 바이너리 코덱):

UBMS::Serial::writeUInt64/readUInt64    8바이트 리틀엔디안
UBMS::Serial::writeString/readString    길이 접두 문자열
UBMS::Serial::writeVector/readVector    템플릿 특수화 벡터
  → 벡터 크기 상한으로 OOM 공격 방어
  → 남은 버퍼 크기 검사로 버퍼 오버리드 방지

안전 역직렬화 설계:

- 버퍼 경계 초과 시 나머지 필드 건너뛰기 (부분 객체 안전 생성)
- enum 값 범위 벗어나면 fallback 값으로 복원
- JSON 타입 불일치 시 필드 무시 (크래시 방지)
- 벡터 크기 상한 + 남은 버퍼 대비 검사

6.5 토폴로지 (libubmstopol.so) - 결정론적 2D 격자 + 지향성 가십

이 모듈은 특허 출원된 독자 기술. 핵심 아이디어: 모든 피어를 같은 시드로 같은 바둑판에 배치하고, 그 위에서 경로를 계산.

6.5.1 결정론적 격자 배치

1. 각 피어의 IP와 시드로 HMAC-SHA256 해시를 계산
2. 해시값 4등분 후 XOR로 합쳐서 임계값(threshold) 생성
3. (임계값, IP) 쌍으로 오름차순 정렬
4. 정렬 순서대로 S x S 격자에 순차 배치 (S = ceil(sqrt(N)))
5. 좌표: r = i / S, c = i % S

hp=LOWw(F(seed)⊕F(ip(p)))hp​=LOWw​(F(seed)⊕F(ip(p)))

정렬 키 = (h_p, ip) 오름차순

6.5.2 최단경로 탐색 알고리즘 — 직진 우선 BFS

2D 격자 위에서 출발 노드(start)와 목적 노드(goal)를 잇는 최단 경로를 찾는 것이 경로 전파 방식(GOSSIP_PATH)의 핵심임. UBMS는 일반적인 BFS를 격자 특성에 맞게 확장한 직진 우선 BFS(Straight-First BFS)를 사용.

그래프 모델링:

격자 S × S 위의 각 셀이 노드이고, 8방향 이웃이 간선(모두 가중치 1)임.
상하좌우 4방향 + 대각선 4방향 = 최대 8개 이웃.
경계 셀은 이웃 수가 줄어들고, 빈 셀(피어 미배치)은 건너뜀.

알고리즘 상세 (shorPathIndex):

입력: 시작 인덱스 s, 목표 인덱스 g
출력: s → g 최단 경로 (노드 인덱스 배열)

1. 초기화
   dist[N] = -1 (미방문),  prev[N] = -1 (부모 없음)
   pdx[N], pdy[N] = 0 (각 노드 도달 시 진입 방향 기록용)
   dist[s] = 0,  큐에 s 삽입

2. BFS 루프
   큐에서 u를 꺼냄
   u == g이면 즉시 종료

   u의 8이웃 목록을 구한 뒤, 탐색 순서를 정렬:

     정렬 규칙 1: 직선 이동 우선
       상·하·좌·우(축 정렬) 이웃을 대각선 이웃보다 먼저 탐색

     정렬 규칙 2: 직진 우선 (방향 연속성)
       부모→u 진입 방향과 u→v 방향이 같으면 우선 탐색
       (= 꺾이지 않고 직진하는 경로를 선호)

   정렬된 순서대로 이웃 v를 방문:
     미방문이면 dist[v] = dist[u] + 1, prev[v] = u
     v 도달 방향 (u→v)을 pdx[v], pdy[v]에 저장
     v == g이면 prev 체인을 역추적하여 경로 반환
     아니면 큐에 v 삽입

3. 경로 복원
   prev[g] → prev[prev[g]] → ... → s 역추적 후 reverse

직진 우선이 중요한 이유:

일반 BFS는 같은 거리의 경로 중 아무거나 반환함.
격자에서는 지그재그 경로와 직선 경로가 동일 거리일 수 있음.

예시 (5×5 격자, A→B):
  일반 BFS:    A → ↘ → → → ↗ → B     (지그재그, 같은 홉)
  직진 우선:   A → → → → → B           (직선, 같은 홉)

직선 경로가 메시지 전파에 유리:
  - 중간 노드의 포워딩 방향 판단이 일관됨
  - 편향 경로(P+, P-)와의 경로 분리가 명확해짐
  - 3경로 간 겹침(overlap)이 최소화됨

8이웃 탐색과 체비셰프 거리:

격자에서 8방향 이동 시 최단 거리는 체비셰프 거리(Chebyshev distance):
  d_cheb(A, B) = max(|Δr|, |Δc|)

대각선 이동 1칸 = 수직 1칸 + 수평 1칸을 한 번에 소화
따라서 BFS 홉 수 = max(|r₁-r₂|, |c₁-c₂|)

100×100 격자(10,000 노드)에서 최악 거리 = 99홉
평균 거리 ≈ 66홉

복잡도:

시간: O(N)  — 각 노드 최대 1회 방문, 이웃 정렬은 O(8 log 8) = O(1)
공간: O(N)  — dist, prev, pdx, pdy 배열
N = 격자 내 노드 수

6.5.3 3경로 라우팅 (최단 1 + 좌우 편향 2)

최단경로 BFS를 기반으로, 3개의 독립 경로를 생성하여 메시지 전달 신뢰성을 확보.

경로 P0: BFS 최단 경로 (직진 우선 8이웃 무가중 그래프)
경로 P+: 좌측 편향 경로 (중간점을 왼쪽으로 밀어서 우회)
경로 P-: 우측 편향 경로 (중간점을 오른쪽으로 밀어서 우회)

편향 경로 생성 과정 (Affine Waypoint):

편향 경로는 단순히 BFS를 두 번 호출하되, 중간 경유지(waypoint)를 아핀 변환으로 생성.

편향 경로 생성 단계:

  1. 진행 벡터 d = (goal.c - start.c, goal.r - start.r)
  2. 진행 거리 L = ‖d‖ = hypot(Δc, Δr)
  3. 진행각 θ = atan2(Δr, Δc)

  4. 직선 중간점(t=0.9 지점)을 로컬 프레임으로 변환
     [x_L, y_L] = R(-θ) × (t·Δc, t·Δr)     ← 역회전: 진행축을 x축에 정렬

  5. 법선(y축) 방향으로 편향 적용
     α = k × L × 0.2                          ← 휨 세기 (거리에 비례)
     y_L += α                                  ← 로컬 y축으로 밀어냄

  6. 월드 좌표로 복귀
     [dC, dR] = R(θ) × (x_L, y_L)            ← 정회전으로 원래 방향 복원
     mid = start + (dC, dR)                   ← 절대 좌표

  7. 격자 스냅 + 경계 클램프
     mid = snapClamp(mid)                     ← 부동소수 → 정수 셀, 격자 내 보정

  8. 빈 셀 보정
     mid 셀에 피어가 없으면 반경 3 이내에서 가장 가까운 유효 셀 채택

θ=atan2(Δr,Δc)θ=atan2(Δrc)

α=k×∥d⃗∥×0.2α=k×∥d∥×0.2

n^=[−sin⁡θ,cos⁡θ]n^=[−sinθ,cosθ]

pm=ps+t⋅d⃗±α⋅n^(t=0.9)pm​=ps​+td±αn^(t=0.9)

최종 경로 합성:
  P+ = BFS(start → mid+) ⊕ BFS(mid+ → goal)
  P- = BFS(start → mid-) ⊕ BFS(mid- → goal)

아핀 변환 적용 순서 (OpenGL의 SRT 행렬곱 순서와 동일):

S (Shear)  : 법선 방향 휨 적용 (α만큼 밀어냄)
R (Rotate) : 진행각 θ로 회전/역회전
T (Translate): 시작점 좌표로 평행이동

순서: 로컬 변환(역회전 → 휨 적용 → 정회전) → 절대 좌표 이동
이 순서가 뒤바뀌면 중간점이 격자 밖으로 벗어남

경로 분리 보장:

t = 0.9로 설정하여 중간점이 목적지의 90% 지점에 위치
→ 출발~중간점 구간에서 최단경로(P0)와 겹칠 확률이 극히 낮음
→ 3경로가 격자 위에서 부채꼴 형태로 펼쳐져 독립성 확보

+k (좌측)와 -k (우측)를 대칭 적용하므로
P+와 P-는 P0 기준 양쪽으로 갈라짐

6.5.4 지향성 가십 (Directional Gossip)

1. 발신 노드가 이웃에게 보낼 때 방향 벡터 u = (sx, sy)를 메시지에 내장
2. 수신 노드는 8이웃 중 "전방 이웃"만 선택해서 전달
   전방 조건: δ · u > 0 (이동벡터와 방향벡터의 내적이 양수)
3. 팬아웃 최대 3
4. TTL = max(3, floor(0.8 × D + 0.5)),  D = sqrt((S-1)² + (S-1)²)

성능 비교 (N=10,000 노드, S=100):

                     총 전송 수       지연         재현성
최단경로 전파:       ~100            O(√N)        결정론적
경로 3개 전파:       ~336            O(√N)        결정론적
지향성 가십:         ~30,000         O(√N)        결정론적
전통 가십:           ~399,000        O(log N)     비결정적

6.5.5 운용 전략

평시:     경로 3개로 전역 신호 전달 (대역 최소)
장애시:   지향성 가십 보조 활성화 (커버리지 확보)
전통가십: 피크/중복 비용이 커서 상시 운용 비권장

6.5.6 독자 DHT 피어 관리 — 아웃바운드 전용 버킷

UBMS의 DHT는 Kademlia 프로토콜이 아닌 완전한 독자 설계. 이름만 DHT일 뿐 구조와 동작 방식이 근본적으로 다르다.

XOR 거리:     SHA256(ip1) XOR SHA256(ip2)의 상위 64비트
버킷 구조:    고정 개수, log2(distance) 기반 분류 (Kademlia는 160개 k-bucket)
버킷 용도:    아웃바운드 전용 (연결할 대상 관리, 인바운드는 별도)
버킷 크기:    Kademlia의 k=20 대비 대폭 확장
정렬 기준:    XOR 거리 오름차순, 타이브레이커 ip 사전순
교체 정책:    가장 먼 피어 교체 (Kademlia는 가장 오래된 피어 유지)
COW 갱신:     atomic shared_ptr로 버킷 전체를 스냅샷 교체
회전 엣지:    Near/Far/Storage 3종 캐시, 20~30%씩 주기적 갱신
가십 중복:    해시셋 + FIFO 큐 (상한 제한)

Kademlia와의 핵심 차이:

Kademlia                        UBMS DHT
160개 k-bucket (비트별)         고정 개수 버킷 (log2 구간)
k=20 (버킷당 20개)             대폭 확장된 버킷 용량
양방향 (요청+응답)              아웃바운드 전용
가장 오래된 피어 우선           XOR 거리 가까운 피어 우선
FIND_NODE/STORE 프로토콜        격자 + 가십 + 회전 엣지 조합
핑-퐁 기반 활성 확인            client 유효성 직접 검사

버킷 수와 버킷 용량은 상수이며, 네트워크 규모에 따라 조정 가능. 독자 DHT에서 버킷은 피어 목록을 거리별로 분류하는 단순한 리스트이고, 특허 기술인 격자 라우팅과 지향성 가십이 메시지 전파의 핵심을 담당하므로 DHT는 피어 발견 보조 역할에 한정.

6.6 유틸리티 (libubmsutil.so)

UBMS::Worker<T> 템플릿: 큐 기반 워커 스레드의 공통 패턴.

push()로 작업 추가, process()에서 처리
callSync()로 워커 스레드에서 동기 실행 (promise/future)
60초 타임아웃 안전장치

UBMS::QueueSafe<T>: condition_variable 기반 스레드 안전 큐.

UBMS::Arith: uint64_t 오버플로/언더플로 검사. mulDiv()에서 uint128_t 중간값 사용.