트랜잭션 파이프라인

요약

트랜잭션은 8단계를 거칩니다: 수신, 서명 검증, 정제, 예산/기간 확인, 수수료 지불자 검증, 계정 로딩, 명령 실행, 커밋.

트랜잭션 처리 파이프라인

트랜잭션이 validator에 도착하면 일련의 검증 및 실행 단계를 거칩니다. 다음은 수신부터 커밋까지의 전체 파이프라인을 설명하며, agave validator 클라이언트의 소스 파일 참조를 포함합니다.

1. 수신 및 역직렬화

Validator는 UDP/QUIC를 통해 트랜잭션 바이트를 수신합니다. 원시 바이트는 단일 패킷(PACKET_DATA_SIZE = 1,232바이트) 내에 맞아야 합니다. 바이트는 서명 배열과 VersionedMessage(레거시 또는 v0)를 포함하는 *rsVersionedTransaction*로 역직렬화됩니다.

2. 서명 검증(sigverify)

서명은 트랜잭션이 뱅킹 단계에 진입하기 전에 sigverify 단계에서 검증됩니다. 인덱스 i의 각 서명에 대해 검증자는 Ed25519(signatures[i], account_keys[i], message_bytes)를 확인합니다. 서명이 유효하지 않으면 패킷이 폐기됩니다.

검증은 병렬화됩니다: validator는 패킷 배치를 VERIFY_PACKET_CHUNK_SIZE(128) 청크로 분할하여 병렬로 처리합니다.

3. 정제

역직렬화된 트랜잭션은 정제되어 SanitizedTransaction(또는 RuntimeTransaction)를 생성합니다. 정제는 구조적 불변성을 검증합니다:

  • 서명 수가 헤더의 num_required_signatures와 일치
  • 모든 명령 program_id_indexaccount_indices가 범위 내에 있음
  • 수수료 지불자(계정 인덱스 0)가 쓰기 가능한 서명자임

RuntimeTransaction 래퍼는 TransactionMeta에서 사전 계산된 메타데이터를 캐시합니다: 메시지 해시, 투표 트랜잭션 플래그, 사전 컴파일 서명 수(Ed25519/secp256k1/secp256r1), 컴퓨트 예산 명령 세부 정보, 총 명령 데이터 길이.

4. 컴퓨트 예산, 기간 및 상태 캐시 확인

check_transactions 메서드는 트랜잭션당 여러 검사를 수행합니다:

컴퓨트 예산: 트랜잭션의 컴퓨트 예산 명령어가 먼저 파싱되고 검증됩니다. 수수료 세부 정보는 예산 한도와 우선순위 수수료로부터 계산됩니다. 컴퓨트 예산이 유효하지 않거나 충돌하는 경우, 트랜잭션은 DuplicateInstruction, InstructionError(..., InvalidInstructionData) 또는 *rsInvalidLoadedAccountsDataSizeLimit*와 같은 컴퓨트 예산 파싱 오류와 함께 실패합니다.

블록해시 기간: 트랜잭션의 recent_blockhashBlockhashQueue에서 조회됩니다. 해시가 발견되고 그 기간이 MAX_PROCESSING_AGE(150 슬롯) 이내인 경우, 트랜잭션이 진행됩니다. 발견되지 않으면, validator는 유효한 durable nonce를 확인합니다.

상태 캐시: 트랜잭션의 메시지 해시가 상태 캐시와 대조됩니다. 발견되면, 트랜잭션은 *rsAlreadyProcessed*와 함께 거부됩니다.

5. nonce 및 수수료 지불자 검증

SVM의 validate_transaction_nonce_and_fee_payer 메서드는 두 가지 검증을 처리합니다:

Nonce 검증(해당하는 경우): nonce 트랜잭션의 경우, validator는 nonce 계정을 로드하고 다음을 확인합니다:

  • 계정이 System Program 소유인지
  • *rsState::Initialized*로 파싱되는지
  • 저장된 durable nonce가 트랜잭션의 recent_blockhash와 일치하는지
  • nonce가 진행될 수 있는지(현재 durable nonce가 다음 durable nonce와 다른지, 즉 nonce가 현재 블록에서 이미 사용되지 않았는지)
  • nonce 권한이 트랜잭션에 서명했는지

유효한 경우, nonce는 다음 durable nonce 값으로 진행됩니다. validate_transaction_nonce를 참조하세요.

수수료 지불자 검증: 수수료 지불자 계정(항상 인덱스 0)이 로드되고 validate_fee_payer에 의해 확인됩니다:

  • 계정이 존재해야 함(lamports > 0), 그렇지 않으면 AccountNotFound
  • 계정이 시스템 계정 또는 nonce 계정이어야 함, 그렇지 않으면 InvalidAccountForFee
  • Lamports가 min_balance + total_fee를 충당해야 함, 여기서 min_balance는 시스템 계정의 경우 0이거나 nonce 계정의 경우 rent.minimum_balance(NonceState::size())임; 그렇지 않으면 InsufficientFundsForFee
  • 수수료 차감 후, 계정은 렌트 면제 상태를 유지해야 함(렌트 면제에서 렌트 지불로 전환할 수 없음)

이 단계에서 수수료 지불자로부터 수수료가 차감됩니다. 수수료가 차감된 수수료 지불자(및 해당되는 경우 고급 nonce)의 스냅샷이 *rsRollbackAccounts*로 저장되며, 이는 실행이 실패하더라도 커밋되는 계정입니다.

6. 계정 로드

load_transaction는 트랜잭션에서 참조하는 모든 계정을 로드합니다. AccountLoader는 외부 계정 저장소를 래핑하고 배치 로컬 캐시를 유지하여 동일한 배치의 이전 트랜잭션에서 수정된 계정이 이후 트랜잭션에서 보이도록 합니다.

각 비수수료 지불자 계정에 대해 로더는 다음을 수행합니다.

  1. 캐시 또는 accounts-db에서 계정을 가져옵니다
  2. 필요한 경우 임대료 면제 상태를 업데이트합니다
  3. 계정의 데이터 크기를 loaded_accounts_data_size_limit(기본값 64 MiB)에 누적합니다. 각 계정은 TRANSACTION_ACCOUNT_BASE_SIZE(64바이트)의 기본 오버헤드와 데이터 길이를 발생시킵니다

트랜잭션의 명령어에 의해 호출되는 각 프로그램에 대해 로더는 program account가 존재하고 유효한 로더(NativeLoader 또는 PROGRAM_OWNERS 중 하나)가 소유하고 있는지 확인합니다. 유효하지 않은 프로그램은 ProgramAccountNotFound 또는 *rsInvalidProgramForExecution*로 실패합니다.

LoaderV3(업그레이드 가능) 프로그램은 연결된 programdata 계정을 암시적으로 로드하며, 이 역시 로드된 데이터 크기 제한에 포함됩니다.

계정 로드가 실패했지만 수수료 지불자가 성공적으로 검증된 경우, 트랜잭션은 FeesOnly 결과가 됩니다. 수수료는 여전히 징수되지만 명령어는 실행되지 않습니다.

7. 명령어 실행

execute_loaded_transaction는 로드된 모든 계정으로 *rsTransactionContext*를 생성하고 process_message를 호출합니다. 명령어는 메시지에 나타나는 순서대로 순차적으로 실행됩니다. 각 명령어 호출은 *rsInvokeContext*를 생성하고 대상 프로그램을 호출합니다.

명령어 처리 세부사항

런타임의 process_message 함수는 각 명령어를 반복하고 대상 프로그램을 호출합니다.

  1. 각 instruction에 대해 런타임은 prepare_next_top_level_instruction를 호출하여 InstructionContext를 구성합니다. 이 컨텍스트에는 instruction의 계정(컴파일된 인덱스에서 해석됨), instruction data 및 program account 인덱스에 대한 참조가 포함됩니다.
  2. 런타임은 프로그램이 precompile(Ed25519, Secp256k1, Secp256r1)인지 확인합니다. Precompile은 BPF VM을 호출하지 않고 직접 검증됩니다.
  3. 다른 모든 프로그램의 경우 런타임은 process_instruction를 호출하여 캐시에서 프로그램을 로드하고 BPF 가상 머신에서 실행합니다.
  4. Instruction이 완료된 후 런타임은 모든 instruction 계정의 총 lamport 잔액이 변경되지 않았는지 검증합니다 (UnbalancedInstruction 확인).
  5. Instruction이 실패하면 전체 트랜잭션이 롤백됩니다. 중간 상태 변경은 커밋되지 않습니다.

각 instruction은 instruction 추적을 증가시킵니다. 추적에는 최상위 instruction과 호출하는 모든 CPI가 포함됩니다. 총 추적 길이(최상위 instruction과 모든 중첩된 CPI)는 64를 초과할 수 없습니다 (MAX_INSTRUCTION_TRACE_LENGTH). 이 제한을 초과하면 *rsInstructionError::MaxInstructionTraceLengthExceeded*가 반환됩니다.

실행 후 런타임은 다음을 검증합니다:

  • 모든 계정의 lamport 합계가 변경되지 않았는지 확인
  • 렌트 면제 상태에서 렌트 지불 상태로 전환된 계정이 없는지 확인

8. 커밋 또는 롤백

실행이 성공하면 TransactionContext의 수정된 계정 상태가 *rsAccountLoader*의 배치 로컬 캐시에 기록됩니다. 실행이 실패하면 RollbackAccounts(수수료가 차감되고 nonce가 진행된 수수료 지불자)만 기록됩니다. 수수료는 여전히 징수되지만 다른 모든 계정 변경 사항은 폐기됩니다.

파이프라인 요약

Receive packet (UDP/QUIC)
--> Deserialize into VersionedTransaction
--> Sigverify (parallel Ed25519 verification)
--> Sanitize (structural validation, metadata extraction)
--> Parse compute budget, calculate fees
--> Check blockhash age (or verify nonce account)
--> Check status cache (dedup)
--> Validate nonce authority and advanceability (if nonce transaction)
--> Validate fee payer (load, check balance, deduct fee)
--> Load all accounts (with data size limits)
--> Load programs (verify loaders)
--> Execute instructions sequentially
--> Verify post-conditions (lamport balance, rent state)
--> Commit account changes (or rollback on failure)

트랜잭션 오류 참조

다음 표는 모든 TransactionError 변형과 발생하는 파이프라인 단계를 나열합니다:

오류단계원인
AccountInUse스케줄링계정이 동일한 배치의 다른 트랜잭션에 의해 이미 잠겨 있음
AccountLoadedTwice스케줄링pubkey가 트랜잭션의 account_keys에 두 번 나타남
AccountNotFound수수료 지불자 검증수수료 지불자 계정이 존재하지 않음
ProgramAccountNotFound계정 로딩호출된 프로그램이 존재하지 않음
InsufficientFundsForFee수수료 지불자 검증수수료 지불자가 수수료 + 렌트 면제 최소값을 충당할 수 없음
InvalidAccountForFee수수료 지불자 검증수수료 지불자가 시스템 또는 nonce 계정이 아님
AlreadyProcessed상태 캐시트랜잭션이 이미 처리됨
BlockhashNotFound기간 확인블록해시가 큐에 없고 유효한 nonce가 아님
InstructionError실행명령어 처리 중 오류 발생(명령어 인덱스 및 특정 InstructionError 포함)
CallChainTooDeep계정 로딩로더 호출 체인이 너무 깊음
MissingSignatureForFee검증트랜잭션에 수수료가 필요하지만 서명이 없음
InvalidAccountIndex검증트랜잭션에 잘못된 계정 참조가 포함됨
SignatureFailure서명 검증Ed25519 서명이 검증되지 않음(패킷이 폐기됨)
InvalidProgramForExecution계정 로딩프로그램이 유효한 로더의 소유가 아님
SanitizeFailure검증트랜잭션이 계정 오프셋을 올바르게 검증하지 못함
ClusterMaintenance스케줄링클러스터 유지보수로 인해 트랜잭션이 현재 비활성화됨
AccountBorrowOutstanding실행트랜잭션 처리가 미해결 차용 참조가 있는 계정을 남김
WouldExceedMaxBlockCostLimit스케줄링트랜잭션이 최대 블록 비용 제한을 초과함
UnsupportedVersion검증트랜잭션 버전이 지원되지 않음
InvalidWritableAccount계정 로딩트랜잭션이 쓸 수 없는 쓰기 가능 계정을 로드함
WouldExceedMaxAccountCostLimit스케줄링트랜잭션이 블록 내 최대 계정 비용 제한을 초과함
WouldExceedAccountDataBlockLimit스케줄링트랜잭션이 블록 내 계정 데이터 제한을 초과함
TooManyAccountLocks스케줄링트랜잭션이 너무 많은 계정을 잠금
AddressLookupTableNotFound계정 로딩주소 조회 테이블 계정이 존재하지 않음
InvalidAddressLookupTableOwner계정 로딩주소 조회 테이블이 잘못된 프로그램의 소유임
InvalidAddressLookupTableData계정 로딩주소 조회 테이블에 잘못된 데이터가 포함됨
InvalidAddressLookupTableIndex계정 로딩주소 테이블 조회가 잘못된 인덱스를 사용함
InvalidRentPayingAccount실행 후 확인계정이 렌트 면제에서 렌트 지불로 전환됨
WouldExceedMaxVoteCostLimit스케줄링트랜잭션이 최대 투표 비용 제한을 초과함
WouldExceedAccountDataTotalLimit스케줄링트랜잭션이 총 계정 데이터 제한을 초과함
DuplicateInstruction컴퓨트 예산 파싱동일한 트랜잭션에 중복된 컴퓨트 예산 명령어 변형
InsufficientFundsForRent실행 후 확인계정에 데이터 크기에 대한 렌트를 충당할 충분한 lamport가 없음
MaxLoadedAccountsDataSizeExceeded계정 로딩로드된 총 데이터가 64 MiB 제한을 초과함
InvalidLoadedAccountsDataSizeLimit컴퓨트 예산 파싱SetLoadedAccountsDataSizeLimit가 0으로 설정됨
ResanitizationNeeded검증트랜잭션이 기능 활성화 전후에 달라져 재검증이 필요함
ProgramExecutionTemporarilyRestricted계정 로딩참조된 계정에서 프로그램 실행이 일시적으로 제한됨
UnbalancedTransaction실행 후 확인트랜잭션 전 총 lamport 잔액이 트랜잭션 후 잔액과 같지 않음
ProgramCacheHitMaxLimit계정 로딩프로그램 캐시가 최대 제한에 도달함
CommitCancelled커밋커밋이 내부적으로 취소됨

Is this page helpful?

관리자

© 2026 솔라나 재단.
모든 권리 보유.
연결하기