EVM 동작 정리

내용

컨트랙트 생성

2024-03-05 10 12 28

  • EOA, 사용자 계정
  • CA, 컨트랙트 계정
    • 상태값의 storage hash
    • evm 코드의 code hash 2024-03-05 11 36 39
  • EOA가 컨트랙트를 생성한다.
  • 바이트 코드가 이더리움 상태 데이터베이스(Level DB)에 저장된다.
  • 트랜잭션이 컨트랙트를 호출할 때마다 EVM에 의해 실행된다.
  • 컨트랙트 생성 TX 예시
{
  "action": {
    "from": "0xb5d4b9c8294945f0c03989717e1e525d2b229463",
    "gas": "0x158f24",
    "init": "0x608060405260006006553480...",
    "value": "0x0"
  },
  "blockHash": "0x298c7947d8dfb50961787fbe57cd06c37d40236d8e2286796e6f3d69ce171afe",
  "blockNumber": 18811499,
  "result": {
    "address": "0x508e00d5cef397b02d260d035e5ee80775e4c821",
    "code": "0x608060405234801561001057600080...",
    "gasUsed": "0x158f24"
  },
  "subtraces": 0,
  "traceAddress": [],
  "transactionHash": "0xf6f2b686cf9497e61ffc9b8dfd6589b3d84106fce3ff5e758022f6f41492b520",
  "transactionPosition": 46,
  "type": "create"
}

컨트랙트 호출

2024-03-05 14 05 27

  • tx의 input으로 함수를 실행한다.
    • input의 앞 4바이트는 function selector로 컨트랙트의 함수를 나타낸다.
  • 컨트랙트 명령어 TX 예시
  {
    "action": {
      "from": "0x2f04c134d609e1adc2ad1dad13335d1a47fb43eb",
      "callType": "call",
      "gas": "0x602f",
      "input": "0x095ea7b3000000000000000000000000afa7636c0d6a79b832a11f1dddba34b1380d5e75000000000000000000000000000000000000000000000878678326eac9000000",
      "to": "0x508e00d5cef397b02d260d035e5ee80775e4c821",
      "value": "0x0"
    },
    "blockHash": "0xf0b441a3ac6dfec0dfba2cd8b764360436966b37bec19cb2e14637ca41b188f4",
    "blockNumber": 18825430,
    "result": {
      "gasUsed": "0x602f",
      "output": "0x0000000000000000000000000000000000000000000000000000000000000001"
    },
    "subtraces": 0,
    "traceAddress": [],
    "transactionHash": "0xc40fbd854fb70ca4f1e9b6b752227b6bfb5526298d7138f3512f2aa142260a24",
    "transactionPosition": 49,
    "type": "call"
  }

Bootstrap of EVM in Geth

Bootstrap of EVM in Geth

  • ApplyTransaction
    • 블록체인에서 트랜잭션을 처리하고 결과를 반영하여 영수증을 생성하는 과정이다.
    • https://github.com/ethereum/go-ethereum/blob/master/core/state_processor.go
    • EVM 트랜잭션 컨텍스트 생성
    • EVM 초기화
    • 트랜잭션 적용
    • 상태 업데이트
    • 영수증 생성
    • 추가 영수증 정보 설정
    • 컨트랙트 생성 주소 설정
    • 로그 및 블룸 필터설정
    • 영수증 반환
  • ApplyMessage
  • TransactionDb
    • 블록체인에서 트랜잭션을 처리하고 결과를 반환한다.
    • https://github.com/ethereum/go-ethereum/blob/master/core/state_transition.go
    • 사전 검사 (메시지 규칙 확인)
    • 트레이서 (디버깅 및 모니터링)
    • 가스 검사
    • 자금 검사 (잔액 확인)
    • 초기화 코드 크기 검사
    • 상태 전환 준비
    • EVM 실행
    • 가스비 환불 처리
    • 수수료 처리 (수수료 계산하고 채굴자에게 수수료 지급)
    • 실행 결과 반환
  • Call
    • EVM내에서 특정주소에 연결된 컨트랙트를 실행하는 역할
    • https://github.com/ethereum/go-ethereum/blob/master/core/vm/evm.go
    • 실행 깊이 검사: 깊이 제한을 초과하는 컨트랙트 실행을 방지한다. (무한 루프 방지)
    • 잔액 검사: 호출자가 전송하는 잔액이 있는지 확인한다.
    • 상태 스냅샷 생성
    • 실행 중 오류를 대비하여 현재 상태의 스냅샷을 생성
    • 사전 컴파일된 컨트랙트 검사
    • 계정 생성 및 잔액 전송
    • 트레이서 처리
    • 컨트랙트 실행(Run)
    • 오류 처리 및 상태 롤백
    • 결과 반환
  • Run
    • 호출 깊이 증가 및 복원: 함수 시작시 호출 깊이 증가, 끝나면 복원
    • 읽기 전용 모드 설정
    • 반환 데이터 초기화
    • 코드 실행 여부 결정
    • 실행 환경 설정: 메모리, 스택 및 컨텍스트 설정
    • 디버깅 모드 처리
    • 메인 루프 실행: 컨트랙트 코드를 한 명령어씩 읽어가며 실행 (인터프리터)
      • 가스비용 계산
      • 메모리 확장
      • 명령어 실행
        • input data를 읽어온다.
        • 앞 4byte는 function selector로 evm의 어떤 함수인지 확인 한다.
        • 바이트코드를 opCode로 변환한다.
        • opCode단위로 실행한다.
        • 컨트랙트 실행 TX 추적 예시
    • 오류 및 중단 처리
    • 결과 반환

2024-03-05 10 17 07

	for {
        // 점프 테이블에서 연산을 가져오고 스택의 유효성을 검사하여
        // 연산을 수행할 수 있는 충분한 스택 항목이 있는지 확인
		op = contract.GetOp(pc)
		operation := in.table[op]
		cost = operation.constantGas // 추적을 위함
		// 스택 유효성 검사
		if sLen := stack.len(); sLen < operation.minStack {
			return nil, &ErrStackUnderflow{stackLen: sLen, required: operation.minStack}
		} else if sLen > operation.maxStack {
			return nil, &ErrStackOverflow{stackLen: sLen, limit: operation.maxStack}
		}
		if !contract.UseGas(cost) {
			return nil, ErrOutOfGas
		}
		if operation.dynamicGas != nil {
			// 동적 메모리 사용량이 있는 모든 작업에는 동적 가스 비용도 있다.
			var memorySize uint64
			// 새 메모리 크기를 계산하고 
            // 연산에 맞게 메모리를 확장한다.
            // 동적 가스 부분을 평가하기 전에 메모리 검사를 수행해야 
            // 계산 오버플로를 감지할 수 있다.
			if operation.memorySize != nil {
				memSize, overflow := operation.memorySize(stack)
				if overflow {
					return nil, ErrGasUintOverflow
				}
				// 메모리는 32바이트 단위로 확장
				// 가스도 동일
				if memorySize, overflow = math.SafeMul(toWordSize(memSize), 32); overflow {
					return nil, ErrGasUintOverflow
				}
			}
			// 가스를 소비하고 사용 가능한 가스가 충분하지 않으면 오류를 반환한다.
            // 캡처 상태 지연 메서드가 적절한 비용을 얻을 수 있도록 비용이 명시적으로 설정된다.
			var dynamicCost uint64
			dynamicCost, err = operation.dynamicGas(in.evm, contract, stack, mem, memorySize)
			cost += dynamicCost // 추적을 위함
			if err != nil || !contract.UseGas(dynamicCost) {
				return nil, ErrOutOfGas
			}
			if memorySize > 0 {
				mem.Resize(memorySize)
			}
		} else if debug {
			in.evm.Config.Tracer.CaptureState(pc, op, gasCopy, cost, callContext, in.returnData, in.evm.depth, err)
			logged = true
		}
		// execute the operation
		res, err = operation.execute(&pc, in, callContext)
		if err != nil {
			break
		}
		pc++
	}

정리

  • EVM evm.drawio
  • CosmWasm 1_nyxzu7kMxpY7CIoVAio5KQ

참고