멀티 RPC 로테이션과 폴백 설계 — 죽는 노드에도 살아남는 온체인 봇

English version

TECH INSIGHT / EXPLAINED

RPC 폴백은 대체 노드가 올바른 체인과 호환되는 상태를 제공할 때만 유용합니다. 접속이 빠르거나 블록 번호가 높다는 이유만으로 견적을 신뢰할 수는 없습니다.

동료 세 명이 RPC 장애 전환과 상태 일치 주제를 그림으로 설명하는 모습
이해를 돕기 위한 AI 생성 설명 일러스트. 실제 운영 화면이나 실적 자료가 아닙니다.

실제 프로젝트의 로컬 실행 화면

기존 프로젝트의 화면 또는 함수를 로컬에서 실행해 촬영했습니다. 개인정보 없는 샘플 입력을 사용했으며, 실행한 범위는 각 이미지 아래에 설명했습니다.

ArbitrageBot 설정 화면의 rpc_urls 필드를 로컬에서 확인했습니다. .invalid 주소 두 개는 접속하지 않는 샘플입니다. 이 캡처는 설정 위치를 보여 주며 실제 장애 전환 시험 결과는 아닙니다.
ArbitrageBot 설정 화면의 rpc_urls 필드를 로컬에서 확인했습니다. .invalid 주소 두 개는 접속하지 않는 샘플입니다. 이 캡처는 설정 위치를 보여 주며 실제 장애 전환 시험 결과는 아닙니다.

클라이언트 생성이 아니라 실제 서비스를 검사한다

HTTP에서는 첫 RPC를 보내기 전에 클라이언트 생성이 성공할 수 있습니다. 연결과 실제 체인·헤더 조회를 함께 타임아웃으로 감싸고 엔드포인트 점검 동시성도 제한합니다. 아래 연동 코드 조각은 다른 체인을 거절합니다. 완전한 선택에는 헤더 시각·동기화·오류·상태 일치도 추가해야 하며 쓰지 않는 클라이언트는 닫습니다.

// Integration excerpt: imports context, errors, math/big, time,
// and github.com/ethereum/go-ethereum/ethclient.
func checkedClient(parent context.Context, url string,
    wantChain *big.Int) (*ethclient.Client, error) {
    ctx, cancel := context.WithTimeout(parent, 3*time.Second)
    defer cancel()
    c, err := ethclient.DialContext(ctx, url)
    if err != nil { return nil, err }
    id, err := c.ChainID(ctx)
    if err != nil { c.Close(); return nil, err }
    if id.Cmp(wantChain) != 0 {
        c.Close(); return nil, errors.New("wrong chain")
    }
    if _, err = c.HeaderByNumber(ctx, nil); err != nil {
        c.Close(); return nil, err
    }
    return c, nil // caller owns Close; this is only a basic probe
}

한 판단에는 일관된 상태를 쓴다

여러 노드의 각자 latest로 한 경로를 계산하면 함께 존재한 적 없는 상태가 섞일 수 있습니다. 지원되는 방식으로 합의한 블록/해시를 기준으로 조회하고 견적에 기록하며 reorg를 감지합니다. safe/finalized 의미와 지원은 체인·제공자별로 확인하세요.

nil 클라이언트보다 명시적인 사용 불가가 낫다

잠금 안에서 엔드포인트 목록을 복사하고 회전 인덱스도 보호합니다. 빈 목록을 0으로 나누거나 Dial 오류를 무시한 nil을 반환하면 안 됩니다. 모두 실패하면 오류를 반환하고 해당 상태가 필요한 행동을 중지하세요. 쿨다운과 제한된 회복 점검으로 장애 제공자를 계속 두드리지 않습니다.

전송 폴백은 트랜잭션 신원을 유지한다

제출 뒤 RPC 타임아웃은 결과 불명입니다. 전송 전 서명 바이트·해시·체인·nonce를 저장하고 영수증·pending 상태를 조회합니다. 같은 서명 거래 재전파와 새 nonce 거래 생성은 다릅니다. 수수료 대체는 의도적인 같은 nonce 정책이지 일반 재시도가 아닙니다. 읽기 가용성과 거래 수명주기 대사를 분리하세요.

교체 가능한 서버의 조건을 명세한다

엔드포인트마다 체인 ID뿐 아니라 필요한 메서드, 과거 상태 조회 범위, 블록 해시 지정 지원, 요청 크기 제한을 표로 관리합니다. 현재 헤더 조회가 되는 서버라도 오래된 블록의 상태가 없을 수 있으므로 모든 읽기의 대체재는 아닙니다. 처음 등록할 때와 장애 후 복귀할 때 같은 검사를 수행하고, 체인 ID가 같다는 사실만으로 신뢰 검증이 끝났다고 보지 않습니다.

애플리케이션이 신뢰하는 네트워크 설정과 알려진 체크포인트도 대조할 수 있습니다. 여러 URL이 같은 사업자의 같은 백엔드를 가리키면 독립적인 장애 대비나 합의 증거가 약해집니다. URL 개수보다 제공자·지역·기반 시설의 실제 독립성을 확인하고, 인증정보가 들어 있는 URL은 진단 기록에서 별칭으로 바꿉니다.

한 번의 견적을 하나의 읽기 묶음으로 다룬다

가상의 두 풀을 읽는다면 먼저 사용할 체인, 블록 높이, 블록 해시를 정합니다. A 서버에서 첫 풀을 읽다가 B 서버로 전환할 때도 B가 같은 해시의 상태를 제공하는지 확인해야 합니다. EIP-1898은 지원하는 상태 조회에서 blockHash와 requireCanonical을 지정하는 방식을 정의합니다. 실제 메서드와 제공자의 지원 여부는 따로 시험합니다.

정해 둔 해시를 B가 모르면 B의 latest로 이어 읽지 않습니다. 다른 적합한 서버를 찾거나 전체 견적을 새 기준으로 다시 계산합니다. requireCanonical은 해당 노드가 보는 정규 체인 여부를 검사하며 영구적인 최종성을 보장하지 않습니다. 숫자 높이 조회 전후의 해시 비교는 일부 재조직을 감지하지만 해시 지정과 동등한 보장은 아니므로 강한 일관성이 필요한 경로에서는 지원 서버를 요구하는 편이 명확합니다.

장애 종류에 따라 회로를 다르게 연다

실패처리 방향
체인 불일치후보에서 제외, 설정 조사
요청 속도 제한제공자 단위 감속과 대기
특정 과거 상태 없음해당 기능의 후보에서만 제외
정상적인 컨트랙트 revert호출 결과로 분류, 서버 장애와 구분

같은 revert를 만날 때마다 서버를 바꾸면 잘못된 요청을 여러 제공자에 퍼뜨립니다. 반대로 일시적인 전송 장애에는 쿨다운 후 소수의 탐색 요청으로 회복을 확인할 수 있습니다. 단 한 번의 빠른 성공만으로 트래픽을 전부 되돌리지 말고, 필요한 기능의 정상 응답이 유지되는지 보고 점진적으로 복귀시킵니다. 이는 RPC 규격의 필수값이 아닌 운영 정책입니다.

요청 예산을 계산한 뒤 장애를 주입한다

전략 작업자 10개가 각각 서버 3곳을 두 번 시험하면 한 판단 묶음에서 최대 60회 호출이 생길 수 있습니다. 각 작업자의 재시도 제한만으로는 전체 부하를 제어하지 못합니다. 의존성별 동시 호출 한도와 전체 시간 예산을 공유하고, 먼저 끝난 경로 뒤에 남은 요청도 취소하거나 회수합니다.

로컬 가짜 RPC에는 잘못된 체인, 같은 높이의 다른 해시, 오래된 정상 응답, 속도 제한, 과거 상태 누락을 주입하세요. 통과 조건은 '끝내 값이 나옴'이 아니라 서로 다른 기준의 값이 섞이지 않고 제한 시간·호출 수가 지켜지는 것입니다. 이 시험은 읽기 선택기의 검증이며 트랜잭션 전송, 실거래 안전성, 제공자의 정직성을 검증하지 않습니다.

공식 문서와 이어 읽기

내용·예제 검토: 2026-10-01

댓글