본문으로 바로가기

어카운트

이더리움 플랫폼에서 어카운트는 모든 트랜잭션의 실행 주체로 가장 기본적인 단위입니다.

이더리움에서는 다음과 같이 2개의 어카운트 타입을 가지고 있습니다.


외부소유 어카운트 (EOA : Externally Owned Account)

  - 일반적으로 거래에 사용되는 사용자의 지갑주소를 말합니다. 주소는 이전에 블록체인의 이론에서 설명한것처럼 개인키로부터 파생된 공개키로부터 생성됩니다. 일반적인 EOA 간의 거래는 이더의 전송을 의미하며, EOA에서 컨트랙트 주소로 메세지를 보내 해당 코드를 실행하여 어떠한 결과를 만들어낼수도 있습니다. EOA에서 컨트랙트 주소로의 메세지전송은 일반적으로 이더의 전송을 의미합니다.


컨트랙트 어카운트 (CA : Contract Account)

  - 스마트 컨트랙트의 주소에 해당되며, 스마트 컨트랙트가 블록에 포함되어 배포될때 해당 스마트 컨트랙트에 대한 주소가 생성이 되며, 이 주소를 통해서 메세지 전송이나 특정함수 실행을 할수 있습니다.



이 두개 주소의 동작에 있어서 차이점은 다음 그림으로 알수 있습니다.

다음 그림과 같이 외부소유 어카운트간에는 이더의 전송에 해당하는 트랜잭션만 수행이 될수 있습니다.

외부소요 어카운트에서 컨트랙트 어카운트 간에도 이더의 전송인 트랜잭션이 수행될수 있지만, 컨트랙트 어카운트간에는 메세지를 통한 스마트 컨트랙트 수행만이 동작될수 있습니다. 이러한 컨트랙트 어카운트간의 트랜잭션은 단독으로는 이루어질수 없고, 외부소유 어카운트나 컨트랙트 어카운트의 응답에 의해서만 실행될수 있습니다.


[그림참조 : How does Ethereum work, anyway? ]


이더리움 블럭에서 이 정보를 한번 확인해보겠습니다. 하기의 현재시간 가장 최신의 이더리움 블록을 보면 Transaction 항목에 일반적인 Transaction과 Internal Transaction 2종류로 나누어져 있습니다. Transaction 들을 보면 From은 모두 EOA에 해당됨을 알수 있고, To는 EOA가 될수도 있고, CA가 될수도 있습니다. Internal Transaction의 경우는 일반 Transaction과는 다른 Call Type 으로 되어있음을 볼수 있고, From에 해당하는 주소는 모두 CA임을 확인할수 있습니다.


Block Information

 


어카운트는 다음과 같은 4개의 정보를 가지고 있습니다.


// Account is the Ethereum consensus representation of accounts.
// These objects are stored in the main account trie.
type Account struct {
Nonce uint64
Balance *big.Int
Root common.Hash // merkle root of the storage trie
CodeHash []byte
}


Nonce : 0부터 시작되는 값으로 EOA인 경우에는 해당 어카운트에서 수행된 트랜잭션의 수, CA인 경우에는 해당 어카운트에서 만들어진 컨트랙트의 수

Balance : 해당 어카운트의 이더 잔액(Wei 단위 기준)

Root : 해당 어카운트가 저장될 머클 패트리시아 트리의 루트

CodeHash : 해당 어카운트의 스마트 컨트랙트 바이트 코드의 해시로 이 코드값이 nil 로 비어있으면 해당 어카운트는 CA가 아닌 EOA라는 의미


개인키, 공개키, 어카운트 주소

일반적으로 어카운트 주소값은 개인키과 공개키를 통해 만들어집니다.

이러한 키 생성과정은 이전 포스팅을 참고로 하고, 이더리움에서는 다음과 같은 과정을 통해 어카운트 주소값을 생성합니다.

다음 그림과 같이 개인키를 가지고 ECDSA(Elliptic Curve Digital Signature Algorithm) 알고리즘을 사용하여 공개키를 생성하고, 생성된 공개키를 keccak256 해쉬함수를 사용하여 32바이트의 최종값을 만들어냅니다. 이렇게 만들어진 32바이트에서 뒷부분 20바이트만을 취하여 이더리움 어카운트 주소를 생성합니다.






개인키 : EC secp256k1 에서 개인키는 32바이트 크기의 1 to 2^256-1 사이의 모든값이 유효한 개인키에 해당합니다.

  형식 : 3a1076bf45ab87712ad64ccb3b10217737f7faacbf2872e88fdd9a537d8fe266


공개키 : 공개키는 타원곡선곱셈함수인 EC 함수를 사용하여 개인키를 입력을 받아 생성되는 64바이트의 값입니다.

  형식 : 04836b35a026743e823a90a0ee3b91bf615c6a757e2b60b9e1dc1826fd0dd16106f7bc1e8179f665015f43c6c81f39062fc2086ed849625c06e04697698b21855e


어카운트 주소 : 64바이트로 생성된 공개키를 keccak256 hash 함수를 통한 결과값인 32바이트 값에서 하위 20바이트가 주소에 해당됩니다.

  형식 : 0x7e5f4552091a69125d5dfcb7b8c2659029395bdf


geth 에서 여러개의 어카운트 주소를 생성한 경우 다음과같이 각각의 생성된 어카운트 주소가 보여집니다.


$ geth account list


account #0: {a94f5374fce5edbc8e2a8697c15331677e6ebf0b}

account #1: {c385233b188811c9f355d4caec14df86d6248235}

account #2: {7f444580bfef4b9bc7e14eb7fb2a029336b07c9d} 


이전에 라이트코인 소스를 기반으로 알트코인 만들기에서 pubkey를 생성했던 과정을 예를 들어 보면 다음과 같이 각각 256비트와 512비트의 개인키, 공개키를 생성할수 있습니다. 이더리움에서는 이와같이 secp256k1 암호화 함수를 사용해서 개인키, 공개키를 생성하여 사용합니다.


> openssl ecparam -name secp256k1 -genkey -noout | openssl ec -text -noout read EC key

Private-Key: (256 bit)

priv:

    20:80:65:a2:47:ed:be:5d:f4:d8:6f:bd:c0:17:13:

    03:f2:3a:76:96:1b:e9:f6:01:38:50:dd:2b:dc:75:

    9b:bb

pub:

    04:83:6b:35:a0:26:74:3e:82:3a:90:a0:ee:3b:91:

    bf:61:5c:6a:75:7e:2b:60:b9:e1:dc:18:26:fd:0d:

    d1:61:06:f7:bc:1e:81:79:f6:65:01:5f:43:c6:c8:

    1f:39:06:2f:c2:08:6e:d8:49:62:5c:06:e0:46:97:

    69:8b:21:85:5e 

ASN1 OID: secp256k1  


상태(State)

어카운트들이 모인것을 상태(State)라고 하고, state 오브젝트들이 모인 stateObject 구조체로 표현을 합니다.

각각의 어카운트의 상태를 변경하려면 stateObjecgt 객체를 통해 접근한후에 상태를 변경할수 있습니다.

변경된 어카운트는 CommitTrie() 함수를 호출하여 레벨DB에 업데이트 됩니다.


type stateObject struct {
address common.Address
addrHash common.Hash // hash of ethereum address of the account
data Account
db *StateDB
// DB error.
// State objects are used by the consensus core and VM which are
// unable to deal with database-level errors. Any error that occurs
// during a database read is memoized here and will eventually be returned
// by StateDB.Commit.
dbErr error
// Write caches.
trie Trie // storage trie, which becomes non-nil on first access
code Code // contract bytecode, which gets set when


address : 어드레스

addrHash : 어카운트 주소의 keccak256 해쉬

data : 이더리움 어카운트

db : 상태를 저장할 DBMS에 대한 포인터

Trie : Trie 저장소

Code : 컨트랙트의 바이트 코드


다음 그림과 같이 이더리움은 초기 Genesis 상태부터 시작하여 트랜잭션에 의해서 State가 변화되는 "Transaction based State Machine" 입니다.




트랜잭션(Transaction)

이더리움에서 트랜잭션은 다른 어카운트나 컨트랙트로 보낼 데이타 구조체로서 전자서명으로 암호화됩니다.

어카운트에서 다른 어카운트로 이더를 전송하거나 스마트 컨트랙트의 특정함수를 호출할때 트랜잭션이 사용됩니다.

트랜잭션의 발신자는 해당 트랜잭션이 유효함을 입증하기 위해 ECDSA 알고리즘을 사용하여 개인키로 서명을 해야합니다.

다음은 이더리움의 트랜잭션 구조입니다.


type Transaction struct {
data txdata
// caches
hash atomic.Value
size atomic.Value
from atomic.Value
}
type txdata struct {
AccountNonce uint64 `json:"nonce" gencodec:"required"`
Price *big.Int `json:"gasPrice" gencodec:"required"`
GasLimit uint64 `json:"gas" gencodec:"required"`
Recipient *common.Address `json:"to" rlp:"nil"` // nil means contract creation
Amount *big.Int `json:"value" gencodec:"required"`
Payload []byte `json:"input" gencodec:"required"`
// Signature values
V *big.Int `json:"v" gencodec:"required"`
R *big.Int `json:"r" gencodec:"required"`
S *big.Int `json:"s" gencodec:"required"`
// This is only used when marshaling to JSON.
Hash *common.Hash `json:"hash" rlp:"-"`
}


AccountNonce : 발신자에 의해 보내진 트랜잭션의 갯수로 0부터 시작

Price : 각 실행단계에서 지급되는 Gas 비용으로 Wei 단위

GasLimit : 트랜잭션 수행시 지급가능한 최대범위

Recipient : 메세지 수신처의 주소, 수신자가 nil로 비어있는 경우는 해당 수신자가 컨트랙트 어카운드를 의미함

Amount : 수신처로 전송할 이더의 양으로 Wei 단위

Payload : 옵션필드로 메세지 호출시 매개변수등이 전달

V,R,S : ECDSA 전자서명을 만드는데 사용되는 값(V는 1바이트로 ECDSA가 복원한 공개키 4개중 어떤공개키를 사용할지 지정한값이고, R, S는 32바이트 서명데이타)


트랜잭션의 실행비용은 Price * GasLimit로 계산됩니다.


그럼 이러한 트랜잭션 데이타를 개인키로 서명하는 과정을 알아보도록 하겠습니다.

이 과정을 javascript 의 코드예를 가지고 알아보겠습니다.


먼저 서명을 하기위한 개인키를 하나 정의하고 특정주소로 1000 wei를 전송하는 트랜잭션을 하나 정의해보겠습니다. 이 트랜잭션에는 '0xc0de' 의 추가데이타를 포함합니다.


var privateKey = '0xc0dec0dec0dec0dec0dec0dec0dec0dec0dec0dec0dec0dec0dec0dec0dec0de';
var rawTx = {
nonce: web3.toHex(0),
gasPrice: web3.toHex(20000000000),
gasLimit: web3.toHex(100000),
to: '0x687422eEA2cB73B5d3e242bA5456b782919AFc85',
value: web3.toHex(1000),
data: '0xc0de'
};


위의 트랜잭션을 개인키로 서명하게되면 위의 트랜잭션 구조에 포함되는 V,R,S 값이 만들어집니다.


 

이렇게 서명된 V,R,S 값을 사용하여 서명이 완료된 트랜잭션 데이타가 만들어지고, 이 데이타를 이더리움 네트워크로 전송을 하게되면 다음의 과정으로 만들어지는 트랜잭션 ID 값으로 해당 트랜잭션을 확인할수 있습니다.




위에서 언급한것처럼 이더리움은 "transaction-based state machine" 입니다.

트랜잭션에 의해서 각 상태가 변경되며, 어카운트의 종류에 따라서 각각 다른 종류의 트랜잭션이 사용됩니다.




리시트(Receipt)

이더리움은 모든 트랜잭션의 로그를 리시트(Receipt)에 저장을 합니다. 즉 리시트는 트랜잭션의 실행과정에 대한 모든 기록을 말합니다.

다음은 이더리움의 리시트 구조입니다.


// Receipt represents the results of a transaction.
type Receipt struct {
// Consensus fields
PostState []byte `json:"root"`
Status uint `json:"status"`
CumulativeGasUsed uint64 `json:"cumulativeGasUsed" gencodec:"required"`
Bloom Bloom `json:"logsBloom" gencodec:"required"`
Logs []*Log `json:"logs" gencodec:"required"`
// Implementation fields (don't reorder!)
TxHash common.Hash `json:"transactionHash" gencodec:"required"`
ContractAddress common.Address `json:"contractAddress"`
GasUsed uint64 `json:"gasUsed" gencodec:"required"`
}


PostState : 트랜잭션이 실행된 후의 상태정보

Status : 트랜잭션 실행후의 상태결과

CumulativeGasUsed : 트랜잭션이 포함된 블럭에서 사용된 전체 가스비용

Bloom : 로그정보를 빠르게 검색하기 위한 블룸필터

Logs : 생성된 로그

TxHash : 트랜잭션 hash id

ContractAddress : 스마트 컨트랙트에서 생성된 트랜잭션인 경우 해당 스마트 컨트랙트의 주소

GasUsed : 트랜잭션 실행에 사용된 가스비용