고스트 프로토콜(GHOST Protocol)은 비트코인의 성능 향상과 보안성 향상을 위해 나온 알고리즘입니다. 이더리움은 고스트 프로토콜을 수정하여 적용한 결과, 빠른 블록 생성 속도를 가지면서 보안성도 높이는 결과를 얻었습니다. 비트코인은 블록 생성 속도가 느리기 때문에 stale 블록의 생성확률이 낮고 보안성이 높습니다. 반면, 이더리움은 블록 생성 속도가 빠르기 때문에 stale 블록의 생성확률이 높고 보안성이 낮습니다. 이러한 문제를 보안하기 위해 이더리움은 고스트 프로토콜을 적용하여 stale 블록을 잘 처리하고 보안성도 높였습니다.

고스트 프로토콜은 stale 블록의 처리에 대한 알고리즘이라고 볼 수 있습니다. 고스트 프로토콜은 bitcoin을 위해 생성됬습니다. 비트코인은 블록생성시간이 약 10분으로 느리기 때문에 stale 블록의 생성확률이 매우 낮습니다. 만약, stale 블록이 발생하면 가장 긴 블록을 메인 블록체인(Canonical Blockchain)에 연결하고 나머지 stale 블록은 버립니다. 하지만 이를 위해 10분을 기다리므로 비트코인은 매우 느립니다. 이를 보완하기 위해 이더리움은 블록생성 시간을 약 12초 정도로 매우 빠르게 만들었습니다. 그러나 블록이 빠르게 생성되면 그만큼 stale 블록이 생성될 확률이 높아지므로 안정성이 떨어집니다. 이를 보완하기 위해 이더리움은 수정된 고스트 프로토콜을 적용한 이유입니다.

상관관계

블록 크기, 블록 생성 시간, 트랜잭션 처리 성능, fork 수, 보안성은 서로 상관관계를 갖고 있습니다. 

블록 사이즈와 블록생성속도가 커지면 트랜잭션 처리율이 올라가고 fork되는 블록의 수가 늘어납니다. 결과적으로 블록체인 네트워크의 보안성이 떨어집니다.

비트코인의 경우 블록생성속도는 약 10분으로 느립니다. 느린 블록생성속도 때문에 트랜잭션 처리 성능도 낮아지는 대신 fork되는 블록의 수는 줄어듭니다. fork의 수가 줄어들기 떄문에 stale 블록이 발생하는 빈도 또한 줄어서 자연스럽게 보안성이 늘어납니다. 하지만 블록생성속도가 매우 느리다는 치명적인 단점을 갖고 있습니다. 이더리움에서는 이것을 개선하여 블록생성속도를 약 12초 정도로 매우 빠르게 유지합니다. 하지만 블록생성속도를 올리면 상관관계에 의해 fork의 수가 늘어납니다. 즉 늘어난 fork의 수 때문에 네트워크의 보안성을 낮춥니다. 이를 해결하기 위해 이더리움은 수정된 고스트 프로토콜을 사용합니다. stale 블록에 대해 보상을 주고 가장 긴 것이 아닌 가장 무거운 것을 사용합니다. 수정된 고스트 프로토콜을 적용함으로써 네트워크의 보안성을 올리고 트랜잭션 처리율을 유지하면서 블록생성속도도 빠르게 할 수 있는 것입니다.

왜 빠른 블록생성이 좋지만은 않을까?

현재 블록체인의 빠른 승인 시간은 높은 stale 생성 비율로 인해 보안성이 낮아진다는 문제가 있습니다. 왜냐하면 블록이 네트워크를 통해 전파되기 위해서는 일정 시간이 필요하기 때문입니다. 예를 들어, 마이너 A가 블록을 채굴하고, 이 블록이 전파되기 전에 마이너 B가 블록을 채굴한 경우, 마이너 B의 블록은 버려지고(stale) 이는 네트워크의 안정성에 기여하지 못합니다. 또한 중앙화 이슈가 있는데, 만약 마이너 A가 마이닝 풀이며 30%의 해시 파워를 가지고 있고 마이너 B가 10%의 해시 파워를 가진다면, A는 전체 시간 중 70%의 시간에서 stale 블록을 생성할 가능성이 있지만 (30%로 자신이 생성한 블록은 즉시 전파될 것이므로) B는 90%의 시간동안 리스크를 가지게 됩니다.

그러므로 블록 인터벌이 높은 stale 생성 비율을 가지게 될 만큼 짧다면 A는 실질적으로 단순히 그 규모로 인해 훨씬 효율적이 될 수 있습니다. 이런 이유로 블록을 빠르게 생성하는 블록체인은 많은 해시 파워를 가진 한 마이닝 풀이 마이닝 프로세스에 있어서 실질적인 통제력을 가지게 될 수 있습니다.

고스트 프로토콜이란?

고스트 프로토콜은 Greedy Heaviest Object subTree의 약자로 '가장 큰 무게를 가진 subtree'를 선택하는 알고리즘입니다. 비트코인의 경우, fork가 발생했을 때 길이가 더 길게 연결된 블록을 메인 블록체인으로 하고 stale블록은 버립니다. 이더리움의 경우, fork가 발생했을 때 더 무거운 쪽을 선택하게 됩니다. 이더리움은 비트코인의 고스트 프로토콜을 살짝 수정하여 사용하고 있습니다.

이더리움의 고스트 프로토콜 알고리즘을 간단하게 설명하면 '제네시스 블록에서 출발하여 각 subtree들이 얼마나 블록을 포함하고 있고, 그 블록들의 개수가 많은 체인을 메인체인의 블록으로 선택하겠다' 입니다.

예시


이더리움 고스트 프로토콜을 위 그림으로 다시 예시를 들어보면 시작은 제네시스 블록인 0에서 시작합니다. 0를 부모로 가지는 노드는 1B와 1A입니다. 1B를 루트로 한 subtree의 노드 개수는 12개이고 1A는 6개입니다. 따라서 메인체인에 붙게 되는 것은 1B입니다. 다음으로 1B를 부모로 가지는 노드는 2D, 2C, 2B입니다. subtree의 노드개수는 각각 4개, 5개, 2개이므로 메인에 붙게 되는 노드는 2C입니다. 이러한 방식을 subtree에 전부 적용하면 선택되는 메인체인은 0-1B-2C-3D-4B 입니다. 비트코인의 경우, 위 그림에서 동그라미로 표시되어 있는 것을 메인체인으로 선택합니다.



수정된 GHOST Protocol의 내용은 다음과 같습니다.

  • 하나의 블록은 반드시 하나의 부모 블록을 지정하며, 0 또는 그 이상의 엉클 블록을 지정. 현재 2개까지 지원하고 있다.
  • 블록 A의 K번째 조상의 직접적인 자손이어야 한다.
  • 블록 A의 조상이어서는 안된다.
  • 엉클블록 마이너 보상은 블록 생성 시에 받는 보상의 93.75%를 보상으로 받고, 엉클 블록이 포함된 정상블록의 마이너는 엉클블록 1개당 3.125%의 추가 보상을 받게 됩니다.


요약

블록생성속도를 올리는 것은 블록체인 네트워크 전체의 처리율과 비례관계를 갖지만 보안성과는 반비례 관계를 갖습니다. 고스트 프로토콜을 이용하여 이를 보완할 수 있습니다. 따라서 이더리움의 고스트 프로토콜은 전체 성능과 보안에 아주 중요한 역할을 한다고 할 수 있습니다.



비트코인과 이더리움 블록 비교

비트코인의 블록은 헤더와 바디로 나누어 볼 수 있습니다. 이더리움도 비트코인의 블록구조와 크게 다르진 않지만 블록의 헤더부분에 uncle_list와 stack_trace 라는 값이 추가되어 있는 형태입니다.


  • uncle list: 비트코인의 이전블록을 이더리움에서 parent 블록이라고 말하기 때문에 생긴 용어. stale 블록을 uncle 블록이라 칭함 (자세한 설명은 아래에서)

비트코인과 이더리움 블록헤더 비교


비트코인이더리움
블록해시hashstate_root
이전 블록해시prev_lbockparent_hash
거래관련된 루트해시mrkl_root

TRIEHASH(transaction_list)

TRIEHASH(uncle_list)

TRIEHASH(stack_trace)

난이도bitsdifficulty
타임스탬프timetimestamp
넌스noncenonce
그외 데이터

size

ver

n_tx

extra_data

(block) numbber

coinbase address (채굴주소)

.... 등등

비트코인과 블록헤더의 구조는 거의 유사하나 블록해시를 구할 때, 몇 가지 항목이 더 추가됨

  • uncle_list, stack_trace, extra_data 등등

이더리움 블록 구성

위에서 비트코인과 간략하게 비교했으니 이제 이더리움만 자세하게 파봅시다.



https://etherscan.io/block/5529581 해당 주소에서 이더리움의 블록정보를 확인한 정보입니다.


이더리움의 코드(GO)를 보면 아래와 같이 블록과 블록헤더가 struct로 선언되어 있습니다.

블록

// Block represents an entire block in the Ethereum blockchain.
type Block struct {
	header       *Header
	uncles       []*Header
	transactions Transactions
	// caches
	hash atomic.Value
	size atomic.Value
	// Td is used by package core to store the total difficulty
	// of the chain up to and including the block.
	td *big.Int
	// These fields are used by package eth to track
	// inter-peer block relay.
	ReceivedAt   time.Time
	ReceivedFrom interface{}
}

블록헤더

// Header represents a block header in the Ethereum blockchain.
type Header struct {
	ParentHash  common.Hash    `json:"parentHash"       gencodec:"required"`
	UncleHash   common.Hash    `json:"sha3Uncles"       gencodec:"required"`
	Coinbase    common.Address `json:"miner"            gencodec:"required"`
	Root        common.Hash    `json:"stateRoot"        gencodec:"required"`
	TxHash      common.Hash    `json:"transactionsRoot" gencodec:"required"`
	ReceiptHash common.Hash    `json:"receiptsRoot"     gencodec:"required"`
	Bloom       Bloom          `json:"logsBloom"        gencodec:"required"`
	Difficulty  *big.Int       `json:"difficulty"       gencodec:"required"`
	Number      *big.Int       `json:"number"           gencodec:"required"`
	GasLimit    uint64         `json:"gasLimit"         gencodec:"required"`
	GasUsed     uint64         `json:"gasUsed"          gencodec:"required"`
	Time        *big.Int       `json:"timestamp"        gencodec:"required"`
	Extra       []byte         `json:"extraData"        gencodec:"required"`
	MixDigest   common.Hash    `json:"mixHash"          gencodec:"required"`
	Nonce       BlockNonce     `json:"nonce"            gencodec:"required"`
}

블록헤더 설명

  • ParentHash : 부모 블록의 해시값
  • UncleHash : 현재 블록의 엉클 블록들의 해시값
  • Coinbase : 채굴 후 해당 트랜잭션의 수수료를 받을 계정 주소
  • Root: 계정의 상태정보가 모여있는 머클 패트리시아 트리의 루트 노드 해시값
  • TxHash: 블록의 모든 트랜잭션에 대한 머클트리의 루트노드 해시값
  • ReceiptHash: 블록 내 모든 트랜잭션에 대한 receipt들의 머클트리의 루트노드 해시값
  • Bloom: 로그 정보를 사용하는데 사용하는 32바이트 블룸필터
  • Difficulty: 블록 난이도로 이전블록의 난이도와 타임스탬프로 계산
  • Number : 현재 블록번호
  • GasLimit: 블록당 지급가능한 최대 가스 총합
  • GasUsed: 블록내 트랜잭션에 사용된 가스의 총합
  • Time : 블록의 최초 생성시간
  • Extra : 블록의 기타정보
  • MixDigestNonce : 작업증명에서 해시값을 계산하는데 충분한 계산횟수를 보장하기 위해 사용하는 값


Root, TxHash, ReceiptHash는 머클트리의 각 노드를 해시한 최종 루트해시값에 해당하고 나머지 값들은 각각 하나의 상수로 볼수 있습니다.


제네시스 블록

제네시스 블록은 블록의 첫번째에 위치하는 최초 블록을 말하며 블록번호 0번에 해당되고 어떤 트랜잭션도 포함되지 않습니다.

// Genesis specifies the header fields, state of a genesis block. It also defines hard
// fork switch-over blocks through the chain configuration.
type Genesis struct {
	Config     *params.ChainConfig `json:"config"`
	Nonce      uint64              `json:"nonce"`
	Timestamp  uint64              `json:"timestamp"`
	ExtraData  []byte              `json:"extraData"`
	GasLimit   uint64              `json:"gasLimit"   gencodec:"required"`
	Difficulty *big.Int            `json:"difficulty" gencodec:"required"`
	Mixhash    common.Hash         `json:"mixHash"`
	Coinbase   common.Address      `json:"coinbase"`
	Alloc      GenesisAlloc        `json:"alloc"      gencodec:"required"`
	// These fields are used for consensus tests. Please don't use them
	// in actual genesis blocks.
	Number     uint64      `json:"number"`
	GasUsed    uint64      `json:"gasUsed"`
	ParentHash common.Hash `json:"parentHash"`
}

제네시스 블록 설명

  • Alloc : 일정량의 이더를 초기 계정에 할당할때 사용하는 값으로 채굴작업 없이 초기 일정량의 이더를 발생하여 ICO를 통해 판매하는데 사용할수 있음
  • Coinbase : 채굴 후 보상코인을 수령할 주소인데 제네시스 블록에서는 채굴이 일어나지 않기에 어떠한 값을 할당해도 상관없음
  • Nonce : mixhash와 함께 채굴 시에 사용되는 값, 8바이트
  • Mixhash : Nonce와 함께 채굴 시에 사용되는 값, 32바이트
  • Difficulty : 채굴시의 목표값
  • ExtraData : 32바이트 옵션항목
  • GasLimit : 블록의 최대 가스량
  • ParentHash : 이전 부모블록의 해시값, 제네시스블록은 부모가 없기 때문에 0으로 할당
  • TimeStamp : 블록의 생성시간

이더리움 블록체인과 채굴

비트코인: 거래(이체) 데이터 저장방식 (거래정보만 블록에 저장)

이더리움: 계정 데이터 저장방식 (모든 계정정보를 저장)

  • 계정의 balance등의 정보를 직접변경
  • patricia tree를 사용하여 저장 데이터의 용량을 줄임


위와 같이 이더리움 블록체인은 비트코인과 유사하나 다른점이 존재합니다. 비트코인과는 달리 이더리움 블록은 transaction list와 가장 최근의 상태(state) 복사본을 가지고 있다는 것입니다. 이외에도 블록의 넘버와 difficulty 또한 블록 내에 저장됩니다. 기본적인 이더리움 블록 검증 알고리즘은 아래와 같습니다.


  1. 참조하고 있는 이전 블록이 존재하는지와 유효한지 확인
  2. 현재 블록의 타임스탬프가 참조하고 있는 이전 블록의 타임스탬프보다 크고 현 시점을 기준으로 15분 후보다 작은 값인지 확인
  3. 블록 넘버, difficulty, 트랜잭션 루트, uncle 루트, gas limit등이 유효한지 확인
  4. 블록에 포함된 작업 증명이 유효한지 확인
  5. 위 그림의 S[0] 이 이전 블록의 마지막 상태(state)라고 가정
    1. TX를 현재 블록의 n개의 트랜잭션 리스트라고 가정하고 0 부터 n-1에 대해, S[i+1] = APPLY(S[i], TX[i]) 로 설정했을 때, 어플리케이션이 오류를 반환하거나, 이 시점까지 블록에서 소모된 총 gas가 GASLIMIT를 초과하면 오류를 반환
  6. 채굴자에게 지불된 보상 블록을 S[n] 덧붙인 후 이것을 S_FINAL 이라 가정
    1. 상태 S_FINAL의 머클 트리 루트가 블록 헤더가 가지고 있는 최종 상태 루트와 같은지를 검증. 이 값이 같으면 그 블록은 유효한 블록이고 다르면 유효하지 않은 것으로 판단

이러한 접근은 모든 상태를 각 블록에 저장할 필요성 때문에 매우 비효율적인 것처럼 보이지만, 실제로는 효율성의 측면에서는 비트코인과 비교할만 합니다. 그 이유는 상태가 트리 구조로 저장되고, 모든 블록 후에 단지 트리의 작은 부분만이 변경되기 때문입니다. 보통 인접한 두 개의 블록간에는 트리의 대부분의 내용이 같아서 한번 데이터가 저장되면 포인터(서브트리의 해쉬)를 사용하여 참조될 수 있기 때문입니다. 패트리시아 트리(Patricia tree)로 알려진 이러한 종류의 특별한 트리는 머클 트리 개념을 수정하여 노드를 수정할 뿐만 아니라, 효율적으로 삽입되거나 삭제하여 이러한 작업을 수행할 수 있도록 해줍니다. 또한, 모든 상태 정보가 마지막 블록에 포함되어 있기 때문에, 전체 블록체인 히스토리를 모두 저장할 필요가 없어지게 됩니다. 이러한 방법으로 비트코인에 적용된 기술과 비교하면 5~20배의 저장 공간 절약의 효과가 생깁니다.

물리적인 하드웨어 관점에서 볼 때, 컨트랙트 코드는 “어디에서" 실행되는가 하는 의문이 쉽게 들 수 있습니다. 컨트랙트 코드를 실행하는 프로세스는 상태 전환 함수 정의의 한 부분이고, 이것은 블록 검증 알고리즘의 부분이므로, 트랜잭션이 블록 B에 포함되면 그 트랜잭션에 의해 발생할 코드의 실행은 블록 B를 다운로드 하고 검증하는 모든 노드들에 의해 실행됩니다.

엉클블록

동시에 블록이 생성되어 전파되는경우 블록의 유효성 검증은 통과되었지만, 블록의 난이도가 상대적으로 낮아 블록으로 채택되지못한 블록을 엉클블록이라고 합니다. 엉클블록이 많아지만 다음과 같은 문제가 발생합니다.

  1.  엉클블록에 포함된 트랜잭션은 처리가 되지않기 때문에 트랜잭션처리의 지연문제가 발생
  2. 엉클블록은 체인에 연결되지않기 때문에 엉클블록을 발견하는데 사용된 컴퓨팅파워가 낭비되는 문제가 발생
  3. 엉클블록이 생성된 후 다음 블록이 생성되면 평균 블록 생성시간이 늘어나 블록생성 난이도가 낮아지기 때문에 네트워크 보안수준을 떨어뜨리게 됨

이더리움은 이러한 문제를 고스트 알고리즘을 사용하여 해결하였습니다. 고스트 알고리즘 (고스트 프로토콜)은 다음 번에 설명하겠습니다.

  1. LEEBM 2019.03.06 01:28

    안녕하세요 ~ 검색하다 우연히 보게 되었는데.. 이더리움 백서를 보신거 같으신데

    이더리움 마이닝 과정에서 6번에 '채굴자에게 지불된 보상 블록'이라는게 이해가 안가는데 무슨 의미인가요

상태변환함수



이더리움을 송금했다고 했을 때 계정의 상태가 어떻게 변하는지 아래에서 설명합니다.

상태변환 단계

  1. 트랜잭션이 형식에 제대로 맞는지(즉, 올바른 개수의 값을 가지고 있는지) 체크하고, 서명이 유효한지, nonce가 발신처 계정의 nonce와 일치하는지를 체크. 그렇지 않다면 오류를 반환
  2. gaslimit * gasprice  로 트랜잭션 수수료를 계산하고, 서명으로부터 발신처 주소를 결정
  3. 발신처 계정 잔고에서 위에서 구한 트랜잭션 수수료를 빼고 발신자 nonce를 증가. 발신처 잔고가 충분하지 않으면 오류를 반환
  4. GAS = gaslimit 으로 초기화 한 후, 트랜잭션에서 사용된 바이트에 대한 값을 지불하기 위해 바이트당 gas의 특정 양을 차감
  5. 발신처 계정에서 수신처 계정으로 트랜잭션 값을 보냄. 수신처 계정이 존재하지 않으면 새로 생성
  6. 수신처 계정이 컨트랙트면, 컨트랙트의 코드를 끝까지, 또는 GAS가 모두 소모될 때 까지 수행
  7. 발신처가 충분한 ‘돈'을 가지고 있지 못해서 값 전송이 실패하거나, 코드 수행시 GAS가 부족하면, 모든 상태 변경를 원상태로 돌려놓음. 단, 수수료(코드를 수행한 GAS)는 제외되고 이 수수료는 채굴자 계정에 더해지게 됨. 그 외에는, 모든 남아있는 모든 GAS에 대한 수수료를 발신처에게 돌려주고, 소모된 GAS에 지불된 수수료를 채굴자에게 보냄

상태변환 예제

아래와 같이 값을 가지고 있다고 가정하면 상태 변환 함수의 프로세스는 다음과 같습니다.

컨트랙트 계정의 storage는 0이라고 가정.

트랜잭션은 아래와 같이 필드를 가지고 전달된다고 가정

  • value = 10 ether
  • gaslimit = 2000
  • gasprice = 0.001ether
  • data = 64바이트(0-31 바이트까지는 숫자 2를 나타내고, 32-63 바이트는 CHARLIE 라는 문자열)

트랜잭션은 총 170바이트 길이를 가지고, 바이트당 GAS수수료는 5라고 가정

코드 실행 시, index 설정 작업의 GAS수수료는 187GAS로 가정


  1. 트랜잭션이 유효하고 형식이 제대로 맞는지 확인
  2. 트랜잭션 발송처가 최소 2000 * 0.001 = 2 ether를 가지고 있는지 확인하고 유효한 경우, 발송처의 계정에서 2ether를 차감
  3. GAS = 2000으로 초기화 한 후, 트랜잭션 처리 수수료인 850 GAS를 차감하면 1150 GAS가 남음
  4. 발송처 계정에서 보내고자 하는 10ether를 차감하고 이것을 컨트랙트 계정에 더함
  5. 코드(이미 작성되어 있는 컨트랙트 코드)를 실행. 이 경우는 간단한데, 컨트랙트의 index 2에 해당하는 스토리지가 사용되었는지 확인하고 (이 경우, 사용되지 않음) index 2에 해당하는 스토리지 값을 CHARLIE 로 설정. 이 작업을 하는 GAS 수수료를 차감하면, 남아있는 GAS의 양은 1150 - 187 = 963
  6. 963*0.001 = 0.963 ether를 송신처의 계좌로 되돌려주고, 결과상태를 반환


트랜잭션의 수신처에 컨트랙트가 없으면, 총 트랜잭션 수수료는 제공된 gasprice와 트랜잭션의 바이트 수를 곱한 값과 같아지고, 트랜잭션과 함께 보내진 데이터는 관련이 없어지게 됩니다. 즉, 위 예제를 사용하여 설명하면 수신처에 컨트랙트가 없으면 1150 GAS가 총 수수료이고 코드를 실행시킬 필요가 없기 때문에 187GAS 수수료를 사용하지 않게 됩니다.

메시지는 트랜잭션과 마찬가지 방식으로, 상태를 원래 상태로 되돌린다는 것에 주목해야 합니다. 메시지 실행시 GAS가 부족하게 되면 메시지 실행과 실행에 의해 발생된 다른 모든 실행들은 원래대로 되돌려지게 되지만, 그 부모 실행은 되돌려질 필요가 없습니다. 이것은 컨트랙트가 다른 컨트랙트를 호출하는 것은 안전하다는 것을 의미합니다. 예를 들어, A가 100 GAS를 가지고 B를 호출하면, A의 실행은 최대 100 GAS만 잃는다는 것을 보장받게 됩니다. 컨트랙트를 생성하는 CREATE라는 opcode를 보면, 실행 방식은 대체로 CALL과 유사하나, 실행 결과는 새로 생성된 컨트랙트의 코드를 결정한다는 차이가 있습니다.

코드 실행

코드 실행은 이미 작성되어 있는 컨트랙트의 코드를 실행하는 것입니다. 이미 예약되어 있는 작업을 실행하는 것으로 볼 수 있습니다.

이더리움 컨트랙트를 구성하는 코드는 이더리움 버추얼 머신 코드 또는 EVM 코드로 불리는 로우-레벨, 스택 기반의 바이트코드 언어로 작성됩니다. 이 코드는 연속된 바이트로 구성되어 있고, 각각의 바이트는 연산(operation)을 나타냅니다. 보통, 코드 실행은 0부터 시작하는 프로그램 카운터(pc)를 하나씩 증가 시키면서 반복적으로 연산을 수행하도록 구성된 무한 루프이고 코드의 마지막에 도달하거나 ERROR, STOP, RETURN 명령을 만나면 실행을 멈추게 됩니다. 연산을 수행하기 위해서는 아래 데이터를 저장하는 세가지 타입의 공간에 접근할 수 있어야 합니다.

  • 스택(stack): Last-In-First-Out(LIFO) 컨테이너(리스트형태)로 여기에 값들을 넣거나(push) 제거(pop) 할수 있음
  • 메모리(memory): 무한대로 확장 가능한 바이트 배열
  • 컨트랙트의 영속적인(long-term) 저장소(storage): 키/값 저장소 (key/value storage). 계산이 끝나면 사라지는 스택이나 메모리와는 달리 저장소는 영속적으로 유지

코드는 또한 블록 헤더의 데이터 뿐만 아니라 특정 값이나 발송자 및 수신되는 메시지의 데이터에 접근할 수 있고 계산하여 결과값을 데이터의 바이트 배열을 반환할 수도 있습니다.

EVM 코드의 공식 실행 모델은 단순한 구조로 되어 있습니다. EVM이 실행되는 동안 모든 계산 상태는 (block_state, transaction, message, code, memory, stack, pc, gas) 으로 튜플로 정의될 수 있고, block_state는 모든 계정을 포함하는 전역상태(global state)로서 잔고(value)와 저장소(storage)를 포함합니다. 무한 루프에 의해 반복되는 코드를 실행 할 때, code의 pc(프로그램 카운터)번째 바이트의 현재 명령이 실행되고(pc 가 코드의 길이보다 같거나 크면 pc는 0으로 초기화), 실행되는 각각의 명령은 튜플을 어떻게 변화시킬지에 대한 자신의 정의를 알고 있습니다. 예를 들어, ADD는 스택에서 두개의 아이템을 꺼내(pop), 그 합을 구한 후 다시 스택에 넣고(push) GAS를 1만큼 감소시키고, pc는 1 증가시킵니다. SSTORE 는 스택에서 두개의 아이템을 꺼내 이 아이템의 첫번째 값이 가리키는 컨트랙트 저장소 인덱스에 두번째 아이템을 넣습니다.

이더리움 계정 (Account)

이더리움 계정에는 20바이트의 주소와 정보를 직접 전달해주는 상태변환을 가지고 있으며 다음 네 개의 필드가 존재합니다.

  • nonce: 각 트랜잭션이 오직 한번만 처리되게 하는 카운터
  • value: 계정의 현재 ether 잔고
  • contract code: 계정의 컨트랙트코드 (값이 없을 수 있음)
  • storage: 계정의 저장공간 (초기설정에는 비어있음)

이더리움의 계정에는 2가지 종류가 있습니다. 외부 소유 계정(External Owned Account - EOA)과 계약 계정(Contract Account - CA)입니다. EOA는 누군가가 소유하고 있는 지갑과 같은 계정이라고 생각하시면 됩니다. CA는 배포된 코드와 저장공간이 추가로 존재하며 스마트 컨트랙트 역할을 할 수 있습니다. 두 가지 계정 중 EOA가 상위 계정이고 새로 생성되는 트랜잭션은 모두 이 EOA에서 시작하게 됩니다. (CA는 EOA가 만든 컨트랙트만 실행할 수 있음 - 생성 X)

EOA는 아무런 코드도 가지고 있지 않으며, 이 계정에서 메시지를 보내기 위해서는 새로운 트랜잭션을 만들고 서명을 해야 합니다. CA는 이러한 메시지를 받을 때마다 자신의 코드를 활성화 시키고 이 활성화된 코드에 따라 메시지를 읽거나 내부 저장공간에 기록하고 다른 메시지들을 보내거나 컨트랙트들을 차례로 생성하게 됩니다. 즉, CA는 컨트랙트를 생성할 수 없고 EOA가 만든 컨트랙트만 실행할 수 있습니다. 모든 컨트랙트에는 해당 컨트랙트가 참이라는 인증이 존재해야 하는데 인증을 담당하는 것은 개인키를 통한 서명이고, 서명을 할 수 있는 것은 EOA이기 때문입니다.

이더리움에서 컨트랙트는 수행되거나 컴파일 되어져야 할 어떤 것이라기 보다는 이더리움의 실행 환경안에 살아있는 자율 에이전트로서 메시지나 트랜잭션이 도착하면 항상 특정한 코드를 실행하고 자신의 이더 잔고와 영속적인 변수들을 추적하기 위해 자신의 키/값 저장소를 직접적으로 통제하는 역할을 합니다. 이러한 개념을 스마트 컨트랙트(Smart Contract)라 부릅니다.


이더리움은 다양한 매개변수를 가지는 타원곡선암호(=ECC)를 사용하여 비대칭 암호 키를 생성합니다. 매개변수는 속도와 보안성을 조절하는 데 사용되며, 이더리움은 secp256k1을 사용합니다. 이더리움의 개인 키와 공개 키는 256bit의 숫자이고, 모든 계정은 주소로 표현됩니다. 공개 키를 이용해 주소를 만드는 순서는 다음과 같습니다.

  1. 공개 키의 keccak-256 해시 생성
  2. 앞 96비트(12바이트)를 버림
  3. 주소를 16진수 문자열로 인코딩. 최종적으로 남은 40개 문자의 바이트 스트링이 유저의 계정 주소.

트랜잭션 (Transaction)

트랜잭션(transaction)은 EOA가 보낼 메시지를 가지고 있는 서명된 데이터 패키지를 말합니다. 이 트랜잭션은 다음 값들을 포함하고 있습니다.

  • to: 메시지 수신처
  • signature: 발신처를 확인할 수 있는 서명
  • value: 발신처가 수신처로 보내는 이더의 양
  • data: 선택적(optional) 데이터 필드 -> 컨트랙트 메시지를 담을 수 있는 데이터 필드
  • gaslimit: STARTGAS 값, 트랜잭션 실행이 수행되도록 허용된 최대 계산 단계수
  • gasprice: GASPRICE 값, 매 계산단계마다 발신처가 지불하는 수수료

트랜잭션은 하나의 서명된 데이터 패키지입니다. 이더를 한 계정에서 다른 계정으로, 또는 컨트랙트로 보내거나 컨트랙트의 함수 호출 및 새 컨트랙트를 배포할 때의 서명으로 쓰입니다. 트랜잭션은 타원곡선암호를 기반으로 하는 디지털 서명 알고리즘인 ECDSA를 이용하여 서명됩니다. 트랜잭션은 메시지 수신자, 송신자를 식별하고 의도를 증명하기 위한 서명, 전송할 이더의 양, 트랜잭션 실행을 위해 허용되는 최대 연산 단계, 트랜잭션 송신자가 각 연산 단계를 위해 지불할 의사가 있는 비용을 포함합니다.


위 트랜잭션이 포함하고 있는 값들 중, 처음 세 항목(to, signature, value)은 암호화폐에서 거의 표준처럼 사용되는 값입니다. 

data는 초기값으로 설정된 기능은 가지고 있지 않지만, 버추얼 머신(EVM)은 컨트랙트가 이 데이터에 접근할 때 사용할 수행코드(opcode)를 가지고 있습니다. 예를 들어, 블록체인 위에 도메인 등록 서비스로 기능하고 있는 컨트랙트가 있을 경우, 이 컨트랙트로 보내지는 data는 두개의 필드를 가지고 있는 것으로 해석할 수 있습니다. 첫번째 필드는 등록하고자 하는 도메인이고, 두번째 필드는 IP 주소입니다. 컨트랙트는 메시지 데이터로부터 이 값들을 읽어서 저장소 내 적당한 위치에 저장합니다. 내용이 복잡할 수 있는데 간단하게 EVM에는 해당 데이터에 어떤 값이 존재하는지 미리 알고 있다고 이해하면 편합니다. 

gaslimit과 gasprice 필드는 이더리움의 anti-DoS 모델에 있어서 매우 중요한 역할을 합니다. 코드 내의 무한루프 (우연이거나 악의적 전부), 또는 계산 낭비를 방지하기 위해 각각의 트랜잭션은 사용할 수 있는 코드 실행의 계산 단계 수를 제한하도록 설정되어야 합니다. 계산의 기본 단위는 gas이고 보통, 계산 단계는 1 gas의 비용이 소요되지만 어떤 연산은 더 비싼 계산 비용을 치루거나 상태의 일부분으로 저장되어야 하는 데이터의 양이 많을 경우엔, 더 많은 gas 비용이 필요하게 됩니다. 또한 트랜잭션 데이터에 있는 모든 바이트는 바이트당 5 gas 의 수수료가 듭니다. 이러한 수수료 시스템의 의도는 만약 해커가 이더리움이나 dAapp을 해킹했을 때, 해커들이 실행하는 모든 리소스에 비례하여 강제로 수수료를 지불하게 하는데 있습니다.

메시지 (Message)

위 트랜잭션에서 설명한 것과 같이 컨트랙트는 다른컨트랙트에게 메시지를 전달할 수 있습니다. 메시지는 따로 저장될 필요가 없는 이더리움의 실행 환경(EVM)에서만 존재하는 가상의 객체입니다. 메시지는 다음 값들을 포함하고 있습니다.

  • from: (암묵적으로) 메시지 발신처
  • to: 메시지 수신처
  • value: 메시지와 함께 전달되는 이더
  • data: 선택적 데이터 필드
  • gaslimit: STARTGAS 값

메시지는 EOA가 아닌 컨트랙트에 의해 생성된다는 것을 제외하면 트랜잭션과 유사합니다. 현재 코드 수행을 하고 있는 컨트랙트가 메시지를 생성하고 실행하라는 수행코드(opcode)를 만나게 되면 메시지를 생성합니다. 트랜잭션과 마찬가지로, 메시지는 해당 코드를 실행하는 수신자 계정(컨트랙트 계정)에 도달하게 됩니다. 따라서, 컨트랙트는 EOA가 하는 것과 정확히 같은 방식으로 다른 컨트랙트와 관계를 맺을 수 있습니다. 트랜잭션이나 컨트랙트에 의해 할당된 gas 허용치는 그 트랜잭션과 모든 하위 실행에 의해 소모된 총 gas 에 적용됩니다. 예를 들어, EOA인 A가 B에게 1000 gas와 함께 트랜잭션을 보내고, B는 600 gas를 소모한 뒤 C에게 메시지를 보내고, C의 내부 실행에 300 gas를 소모한 후 반환하면, B는 gas가 모두 소모되기 전에 100 gas를 더 사용할 수 있습니다.

예시

A가 B에게 5ETH를 전송하면서 이더리움이 100만원이 되면 B가 2ETH를 C에게 전송 

위와 같은 트랜잭션을 만든다고 가정합니다. 


우선 A는 자신의 EOA로 이 내용을 담은 트랜잭션을 만들어서 서명을 하고 블록에 포함시켜야 합니다. A가 보낸 트랜잭션은 모든 노드들이 검증한 후 블록에 담겨 B에게 전송됩니다. 이 트랜잭션에는 이더를 송금하는 것 외에 이더리움이 100만원이 되면 C에게 2ETH 전송 이라는 조건을 담은 메시지가 들어있어 B의 CA는 이 메시지를 저장됩니다. 이더리움이 100만원이 되면, B의 CA는 A로부터 받은 계약을 자동으로 실행시킵니다. 3ETH를 B의 EOA로 전송하고 남은 2ETH를 C의 EOA로 전송합니다. C에게 전송할 땐 조건이 없어 CA로 보낼 필요가 없습니다.

이렇게 자동으로 실행되는 것이 스마트 컨트랙트입니다.

비트코인의 한계

튜링불완전성: 비트코인 스크립트 언어로 할 수 있는 작업이 많긴 하지만, 모든 경우의 프로그래밍을 다 지원하지는 않습니다. 거래 증명을 할 때 무한 순환에 빠지는 것을 막기 위해 while이나 for와 같은 순환(loop) 명령 카테고리가 빠져 있습니다. 이론적으로는 튜링불완전성은 개발자가 극복할 수 있지만 (if 문과 같은 반복문으로) 코드상 공간낭비와 개발자의 시간낭비, 실수 등 아주 비효율적입니다.

가치무지하다: UTXO 스크립트만으로는 거래의 인출 액수를 세밀하게 통제할 방법이 없습니다. 그 이유는 http://brownbears.tistory.com/382?category=281929 에서 자세히 확인할 수 있습니다.

다양한 상태를 표현할 수 없다: UTXO가 표현할 수 있는 상태는 사용되었거나 안 되거나 둘 뿐입니다. 

블록체인을 해독할 방법이 없다: UTXO는 nonce, 타임스탬프,이전 블록해시같은 블록체인 자료를 해독하지 못합니다. 그래서 도박이나 여러 다른 분야의 어플리케이션을 만드는 데 한계를 보입니다.

이더리움이란?

이더리움은 비트코인의 한계를 넘는 코인이라 볼 수 있습니다. 이더리움 설명에 앞서 비유를 먼저 들자면 비트코인이 화폐역할을 하는 어플이라 생각하면 이더리움은 스마트폰으로 생각하면 됩니다. 이더리움은 대규모 분산 어플리케이션에 유용할 것이라 생각되는 다른 종류의 제작기법을 제공하며, 빠른 개발 시간, 작고 드물게 사용되는 어플리케이션을 위한 보안, 다른 어플리케이션과의 효율적인 상호작용이 중요한 상황에 특히 주안점을 두고 있습니다. 간단하게, 분산 어플리케이션(Decentralized Application - dApp)을 이용할 수 있는 플랫폼을 제공합니다. 또한 튜링 안전성을 갖춰 개발자들이 주로 사용하는 언어를 사용하여 다양한 기능을 개발할 수 있습니다. 여기서 주로 사용되는 언어는 Solidity(Javascript)와 Serpent(Python)입니다. 이 언어를 사용하여 복잡한 다중계약인 Smart Contract를 가능하게 하고 분산 어플리케이션을 구현합니다. 여기서 스마트 컨트랙트(Smart Contract)를 유지하기위해 가스(Gas)라는 개념이 등장합니다.

가스(Gas)란?

일반 사용자는 이더리움(ETH)을 송금할 때 가스를 확인할 수 있습니다. 한 마디로 가스는 일종의 수수료입니다. 아래에서 가스가 무엇인지에 대해 자세히 설명하겠습니다.

모든 블록체인은 거래의 증명을 위해 노드들이 채굴활동을 진행합니다. 이더리움은 EVM(Ethereum Virtual Machine) 이라는 블록체인 환경에서 실행이 됩니다.  즉, 이더리움 네트워크에 참여하고 있는 모든 노드들이 블록을 확인하는 프로토콜의 일부로 EVM을 실행하고 있습니다. 이더리움을 보낸다면 네트워크의 모든 노드들이 동일한 계산을 수행하고 동일한 값을 저장해야 하는 일련의 '작업'이 필요해 집니다. 그리고 이런 작업량을 Gas, 혹은 특정금액으로 환산되기 때문에 Gas Value라고 표현합니다.

왜 가스란 이름이 나왔을까?

여기서 네이밍 센스를 확인할 수 있습니다. 이더(Ether)는 석유화학물질 에테르 입니다. 에테르를 얻기 위해 화합물을 분해하면 기체물질이 생성되는데 여기서 발생된 기체를 Gas라고 합니다.


즉, 가스는 이더리움이 Smart Contract를 유지하는데 연산력과 저장공간 제공의 연료로서 사용됩니다.

그렇다면 뭐하러 가스라는 단위를 생성했을까?

이더리움은 시장변화에 따라 큰 폭으로 변화하곤 합니다. 이러한 이더리움 가격으로 수수료가 정해진다면 하루동안에도 수수료가 0.001ETH, 0.0005ETH... 와 같이 계속 변하게 됩니다. 이러한 문제로 거의 일정한 가격을 가진 단위를 만들고(Gas) 개수에 따라 수수료를 표시하게 됩니다.


위 이미지는 가스의 가격변동을 보여주는 차트인데 초기를 제외하곤 큰 변동이 없는 것을 확인할 수 있습니다.



  1. hans 2019.12.03 20:18

    설명이 엄청 잘되어있네요~ ^^ 좋은 글 감사합니다.

UTXO란 Unspent Transaction Output 의 약자로, 아직 쓰지않은 잔액 이라는 의미입니다. 비트코인 네트워크에서는 잔액이라는 개념은 애초에 존재하지 않고, 트랜잭션에 의한 결과물들의 합을 잔액이라는 개념으로 사용하는데 이를 UTXO 데이터로 대체합니다. 각 지갑의 UTXO들은 해당 지갑 주인(소유주)에 대해 공개키 암호로 잠겨있습니다.

UTXO 거래 예시


아래는 UTXO를 설명하기 위한 예시입니다. A, B가 F에게 각 1BTC, 2BTC를 송금해주고 C, D, E가 G에게 3BTC, 4BTC, 10BTC를 송금을 하면 F와 G는 각 UTXO가 2개, 3개가 되어 총 UTXO가 5개 생성됩니다.

다음 아래는 G가 H에게 9BTC를 보내려고 하는 그림입니다. 먼저 G가 가진 UTXO 중 9BTC 이상인 값을 찾습니다. 아래 그림에는 10BTC인 UTXO가 존재하여 해당 값을 입력값으로 넣습니다. H의 지갑에서 출력값으로 찍히게 되는 9BTC를 제외하고 1BTC는 G의 지갑에 찍히게 됩니다.

위 절차를 거치게 되면 UTXO는 5개에서 6개로 늘어난 것을 확인할 수 있습니다.


이와 같은 예시가 UTXO의 개념입니다. 만원짜리를 찢어서 2천원, 4천원과 같이 사용할 수 없는 것 처럼 UTXO 또한 찢어 사용할 수 없고 비트코인 지갑이 사용할 수 있는 UTXO를 찾아 데이터를 전송하는 역할을 해줍니다. 

큰 단위를 찢어서 사용할 수 없는것처럼 작은 단위를 합쳐서 사용할 수 없습니다.

만약 H에게 9BTC가 아닌 17BTC를 전송해야된다고 해서 G의 UTXO를 전부 더해 보낼 수 없습니다. (17BTC의 UTXO가 없기 때문에 ERROR가 발생함)


'블록체인' 카테고리의 다른 글

이더리움 - 계정(Account), 트랜잭션(Transaction), 메시지 (Message)  (0) 2018.07.29
이더리움이란?  (1) 2018.07.28
비트코인 - UTXO란?  (0) 2018.07.24
양자컴퓨터란?  (0) 2018.07.23
블록체인 타임스탬프  (0) 2018.07.18
51% 공격(Attack)이란?  (0) 2018.07.17

+ Random Posts