Exclamate EOS! : A.C.T
# commit-id: bf28f8bbf
본 시리즈는 개발자를 대상으로 하고 있으므로 친절하지 않을 수 있습니다.
소스 코드는 변경될 수 있으므로 매 글 상단에 분석 시점 기준 마지막 commit-id를 기록합니다.
Action/Contract/Transaction
EOS에서 액션과 컨트랙트는 컴퓨팅 시스템에서 이벤트와 이벤트 핸들러 간의 관계와 같다. 특정 액션(event)이 발생하면 사전에 deploy된 컨트랙트는 전달된 액션에 해당하는 함수(event handler)를 호출하여 블록체인의 상태를 변경한다. 트랜잭션은 하나 또는 그 이상의 액션으로 구성된 집합으로 EOS 상에서 메시지의 최소 전송 단위이다.
이하에서는 액션, 컨트랙트, 트랜잭션에 대해 분석한다. cleos
로 트랜잭션을 보낼 수 있는 독자를 전제하고 작성하였으므로 조금 더 기초적인 내용은 개발자 포탈이나 cleos
사용법과 관련된 다른 분의 포스팅을 참고하길 바란다.
Action
액션(action) 구조체는 다음과 같이 정의되어 있다.
- account: 액션 핸들러가 포함된 컨트랙트를 deploy한 계정명
- name: 액션 이름
- authorization: 액션 실행에 필요한 권한 정보
- data: 액션 실행에 필요한 데이터 (액션 핸들러에 전달될 인자)
cleos push action eosio.token create '["eosio", "10000000000.0000 EOS"]' -p eosio.token@active
발행자 eosio
, 최대 발행 개수 10,000,000,000개인 EOS 토큰을 만드는 위 명령은 다음과 같은 action을 생성한다.
account_name
, action_name
은 부호 없는 64비트 정수(uint64_t
)로 BASE32로 인코딩 된 문자열을 가리킨다. (정확히는 eosio::chain::name
구조체이지만 이는 문자열로부터 64비트 정수를 쉽게 생성하기 위한 wrapper이다)
BASE32 방식으로 나타낼 수 있는 문자의 개수는 32개인데 EOS는 소문자 a-z
(26개), 점 .
(1개), 숫자 1-5
(5개)를 사용한다. 따라서 이를 제외한 다른 문자(대문자, 밑줄, 숫자 0 또는 6–9 등)는 계정이나 액션의 이름에 사용할 수 없다.
문자 1개당 5비트(2⁵)를 사용하므로 64비트 정수는 최대 12자까지 포함할 수 있다. (5비트×12자+4비트=64비트) 나머지 4비트만 사용하는 불완전한 13번째 문자를 허용하는 것에 대해 논의가 있었으나 현재는 최대 12자까지 사용하는 것으로 결정되었다.
Contract
일반적인 프로그램을 실행하면 제일 먼저 main()
함수가 불리는 것처럼 EOS 컨트랙트에서는 apply()
함수가 불린다. apply()
함수에 액션이 전달되면 액션의 이름에 따라 컨트랙트 내부 메소드가 호출된다.
그러나 보통 컨트랙트 코드 내에 apply()
함수가 따로 정의되어 있지 않은데 이는 EOSIO_ABI()
매크로가 자동으로 apply()
함수를 생성하기 때문이다.
eosio.token
컨트랙트의 EOSIO_ABI( eosio::token, (create)(issue)(transfer) )
행이 전처리된 결과를 풀어쓰면 다음과 같다.
Transaction
cleos
가 제공하는 다양한 명령어들은 자주 사용하는 트랜잭션에 대한 프리셋(preset)이다. 예를 들어 아래 두 가지 명령은 내부적으로 같은 기능을 수행한다.
$ cleos transfer sender recipient "1.0000 EOS" "memo"$ cleos push action eosio.token transfer '["sender", "recipient", "1.0000 EOS", "memo"]' -p sender@active
위 두 명령에 -j
옵션을 추가하여 JSON 포맷으로 출력해보면 완전히 동일한 내용의 트랜잭션임을 알 수 있다.
cleos
는 nodeos
와 keosd
에 대해 CLI 인터페이스를 제공하는 애플리케이션이다. 동적으로 플러그인을 조합하여 핵심 기능을 구성하는 두 프로그램과 달리 단순 명령어 세트만 제공하므로 programs/cleos/main.cpp
에 기능의 대부분이 구현되어 있다.
cleos transfer sender recipient "1.0000 EOS" "memo"
명령은 아래 콜백 함수를 실행시킨다.
create_transfer()
로 생성한 액션을 send_actions()
를 통해 전달하고 있다. 이후의 실행 과정은 다음과 같다. (설명이 지나치게 복잡해지지 않도록 인자는 생략하였으나 대체로 액션 또는 액션이 포함된 트랜잭션의 구조체가 전달된다)
이 단계에서 cleos
는 nodeos
의 push_transaction
RPC API를 호출한다. nodeos
의 http_plugin
은 RPC API 호출을 다른 플러그인의 내부 함수로 연결해주는데 이 API는 chain_plugin
의 push_transaction()
함수로 연결된다. RPC API의 등록과 chain_plugin
과의 연결은 chain_api_plugin
이 담당하며, 플러그인의 시작(plugin_startup()
) 단계에서 이를 수행한다.
chain_plugin
으로 전달된 트랜잭션은 다음 단계를 거쳐 실행된다.
트랜잭션 내의 액션은 eosio::chain::apply_context::exec_one()
에서 실행되는데 네이티브 액션(native action)의 경우 네이티브 액션 핸들러를 먼저 실행하고(#1), 웹어셈블리로 컴파일 된 컨트랙트를 실행하는 곳(#2)에서 한 번 더 처리된다. 일반 액션의 경우 컨트랙트 부분만 실행된다.
Native Action
네이티브 액션은 컨트랙트가 아닌 EOS 엔진 내부에 액션 핸들러가 정의된 액션을 의미한다. contract_types.hpp
파일에 네이티브 액션의 구조체가 정의되어 있으며 eosio_contract.cpp
파일에는 각 타입별 핸들러가 정의되어 있다.
네이티브 액션의 종류는 아래와 같다.
newaccount
updateauth
deleteauth
linkauth
unlinkauth
setcode
setabi
canceldelay
eosio
계정으로 deploy하는 eosio.system
컨트랙트를 보면 newaccount
를 제외한 네이티브 액션들은 모두 구현부 없이 정의되어 있다. 현재로서는 네이티브 액션 핸들러의 처리 외에 컨트랙트 단에서 처리하는 부분은 없는 셈이다.
newaccount
의 경우 12자 미만의 프리미엄 계정명에 대한 경매(bidding)와 리소스 제한을 위한 코드가 컨트랙트로 작성되어 있다. 즉, 계정 생성과 관련된 핵심적인 부분은 엔진 내부에 정의하고 정책적으로 변경될 수 있는 부분에 대해서는 컨트랙트에서 처리하는 것이다.
트랜잭션과 이를 구성하는 액션, 그리고 액션 처리를 위한 컨트랙트에 대해 알아보았다. 다음 글에서는 EOS에서 가장 뜨거운 이슈인 RAM의 작동 원리를 분석할 예정이다.