요약
트랜잭션은 8단계를 거칩니다: 수신, 서명 검증, 정제, 예산/기간 확인, 수수료 지불자 검증, 계정 로딩, 명령 실행, 커밋.
트랜잭션 처리 파이프라인
트랜잭션이 validator에 도착하면 일련의 검증 및 실행 단계를 거칩니다. 다음은 수신부터 커밋까지의 전체 파이프라인을 설명하며, agave validator 클라이언트의 소스 파일 참조를 포함합니다.
1. 수신 및 역직렬화
Validator는 UDP/QUIC를 통해 트랜잭션 바이트를 수신합니다. 원시 바이트는 단일
패킷(PACKET_DATA_SIZE = 1,232바이트) 내에 맞아야 합니다. 바이트는 서명
배열과 VersionedMessage (레거시 또는 v0)를 포함하는
VersionedTransaction 로 역직렬화됩니다.
다가오는 v1 형식은 한도를 4,096바이트로 높입니다. QUIC는 스트림 크기를 제한하지 않으므로, MTU를 초과하는 v1 트랜잭션은 거부되는 대신 수집 시 여러 QUIC 프레임으로 분할됩니다.
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_index및account_indices가 범위 내에 있음 - 수수료 지불자(계정 인덱스 0)가 쓰기 가능한 서명자임
RuntimeTransaction 래퍼는
TransactionMeta에서
사전 계산된 메타데이터를 캐시합니다: 메시지 해시, 투표 트랜잭션 플래그, 사전
컴파일 서명 수(Ed25519/secp256k1/secp256r1), 컴퓨트 예산 명령 세부 정보, 총 명령
데이터 길이.
4. 컴퓨트 예산, 기간 및 상태 캐시 확인
check_transactions
메서드는 트랜잭션당 여러 검사를 수행합니다:
컴퓨트 예산: 트랜잭션의 컴퓨트 예산 명령어가 먼저 파싱되고 검증됩니다.
수수료 세부 정보는 예산 한도와 우선순위 수수료로부터 계산됩니다. 컴퓨트 예산이
유효하지 않거나 충돌하는 경우, 트랜잭션은 DuplicateInstruction,
InstructionError(..., InvalidInstructionData) 또는
InvalidLoadedAccountsDataSizeLimit 와 같은 컴퓨트 예산 파싱 오류와 함께
실패합니다.
v1 트랜잭션의 경우 파싱할 컴퓨팅 예산 명령어가 없습니다. 한도와 우선순위 수수료는 메시지 설정에서 가져오며, 메시지 내의 ComputeBudget 명령어는 설정 목적으로는 무시됩니다. 설정되지 않은 설정 필드는 기본값으로 0이 되며, 이는 이후 계정 로딩 단계에서 문제가 됩니다.
블록해시 기간: 트랜잭션의 recent_blockhash는
BlockhashQueue에서
조회됩니다. 해시가 발견되고 그 기간이 MAX_PROCESSING_AGE (150 슬롯) 이내인
경우, 트랜잭션이 진행됩니다. 발견되지 않으면, validator는 유효한
durable nonce를 확인합니다.
상태 캐시: 트랜잭션의 메시지 해시가 상태 캐시와 대조됩니다. 발견되면,
트랜잭션은 AlreadyProcessed 와 함께 거부됩니다.
5. nonce 및 수수료 지불자 검증
SVM의
validate_transaction_nonce_and_fee_payer
메서드는 두 가지 검증을 처리합니다:
Nonce 검증(해당하는 경우): nonce 트랜잭션의 경우, validator는 nonce 계정을 로드하고 다음을 확인합니다:
- 계정이 System Program 소유인지
State::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)의 스냅샷이 RollbackAccounts 로
저장되며, 이는 실행이 실패하더라도 커밋되는 계정입니다.
6. 계정 로드
load_transaction는
트랜잭션에서 참조하는 모든 계정을 로드합니다.
AccountLoader는
외부 계정 저장소를 래핑하고 배치 로컬 캐시를 유지하여 동일한 배치의 이전
트랜잭션에서 수정된 계정이 이후 트랜잭션에서 보이도록 합니다.
각 비수수료 지불자 계정에 대해 로더는 다음을 수행합니다.
- 캐시 또는 accounts-db에서 계정을 가져옵니다
- 필요한 경우 임대료 면제 상태를 업데이트합니다
- 계정의 데이터 크기를
loaded_accounts_data_size_limit(기본값 64 MiB)에 누적합니다. 각 계정은TRANSACTION_ACCOUNT_BASE_SIZE(64바이트)의 기본 오버헤드와 데이터 길이를 발생시킵니다
트랜잭션의 명령어에 의해 호출되는 각 프로그램에 대해 로더는 program account가
존재하고 유효한 로더(NativeLoader 또는 PROGRAM_OWNERS 중 하나)가
소유하고 있는지 확인합니다. 유효하지 않은 프로그램은
ProgramAccountNotFound 또는 InvalidProgramForExecution 로
실패합니다.
LoaderV3(업그레이드 가능) 프로그램은 연결된 programdata 계정을 암시적으로 로드하며, 이 역시 로드된 데이터 크기 제한에 포함됩니다.
v1 트랜잭션은 이 한도를 기본값 0으로 설정합니다
64MiB 기본값은 레거시 및 v0 트랜잭션에 적용됩니다. v1
형식에서
로드된 계정 데이터 크기 한도는 메시지 설정에서 읽히며, 설정되지 않은 필드는
넉넉한 기본값 대신 0바이트로 기본 설정됩니다. 따라서 빈 설정의 v1
트랜잭션은 이 단계에서
MaxLoadedAccountsDataSizeExceeded 오류와 함께 실패합니다. 요청된 예산이 첫 번째로 로드되는 계정에도 부족하기 때문에, v1이나 설정에 대한 언급 없이 오류가 발생하며 어떠한 명령어도 실행되기 전에 실패합니다. v1 트랜잭션을 빌드할 때는 항상 컴퓨팅 유닛 한도와 로드된 계정 데이터 크기 한도를 명시적으로 설정하십시오.
계정 로드가 실패했지만 수수료 지불자가 성공적으로 검증된 경우, 트랜잭션은
FeesOnly
결과가 됩니다. 수수료는 여전히 징수되지만 명령어는 실행되지 않습니다.
7. 명령어 실행
execute_loaded_transaction는
로드된 모든 계정으로 TransactionContext 를 생성하고
process_message를
호출합니다. 명령어는 메시지에 나타나는 순서대로 순차적으로 실행됩니다. 각 명령어
호출은 InvokeContext 를 생성하고 대상 프로그램을 호출합니다.
명령어 처리 세부사항
런타임의
process_message
함수는 각 명령어를 반복하고 대상 프로그램을 호출합니다.
- 각 instruction에 대해 런타임은
prepare_next_top_level_instruction를 호출하여InstructionContext를 구성합니다. 이 컨텍스트에는 instruction의 계정(컴파일된 인덱스에서 해석됨), instruction data 및 program account 인덱스에 대한 참조가 포함됩니다. - 런타임은 프로그램이 precompile(Ed25519, Secp256k1, Secp256r1)인지 확인합니다. Precompile은 BPF VM을 호출하지 않고 직접 검증됩니다.
- 다른 모든 프로그램의 경우 런타임은
process_instruction를 호출하여 캐시에서 프로그램을 로드하고 BPF 가상 머신에서 실행합니다. - Instruction이 완료된 후 런타임은 모든 instruction 계정의 총 lamport 잔액이
변경되지 않았는지
검증합니다
(
UnbalancedInstruction확인). - Instruction이 실패하면 전체 트랜잭션이 롤백됩니다. 중간 상태 변경은 커밋되지 않습니다.
각 instruction은 instruction 추적을 증가시킵니다. 추적에는 최상위 instruction과
호출하는 모든 CPI가 포함됩니다. 총 추적 길이(최상위
instruction과 모든 중첩된 CPI)는 64를 초과할 수 없습니다
(MAX_INSTRUCTION_TRACE_LENGTH). 이 제한을 초과하면
InstructionError::MaxInstructionTraceLengthExceeded 가 반환됩니다.
실행 후 런타임은 다음을 검증합니다:
- 모든 계정의 lamport 합계가 변경되지 않았는지 확인
- 렌트 면제 상태에서 렌트 지불 상태로 전환된 계정이 없는지 확인
8. 커밋 또는 롤백
실행이 성공하면 TransactionContext의 수정된 계정 상태가 AccountLoader 의
배치 로컬 캐시에 기록됩니다. 실행이 실패하면 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 | 계정 로딩 | 총 로드된 데이터가 트랜잭션의 로드된 계정 데이터 크기 한도를 초과함 (기본값 64MiB; v1에서는 메시지 설정에 지정하지 않는 한 0) |
InvalidLoadedAccountsDataSizeLimit | 컴퓨트 예산 파싱 | SetLoadedAccountsDataSizeLimit가 0으로 설정됨 |
ResanitizationNeeded | 검증 | 트랜잭션이 기능 활성화 전후에 달라져 재검증이 필요함 |
ProgramExecutionTemporarilyRestricted | 계정 로딩 | 참조된 계정에서 프로그램 실행이 일시적으로 제한됨 |
UnbalancedTransaction | 실행 후 확인 | 트랜잭션 전 총 lamport 잔액이 트랜잭션 후 잔액과 같지 않음 |
ProgramCacheHitMaxLimit | 계정 로딩 | 프로그램 캐시가 최대 제한에 도달함 |
CommitCancelled | 커밋 | 커밋이 내부적으로 취소됨 |
Is this page helpful?