Event: for outside watchers

in #ethereum5 years ago

Event: for outside watchers

이더리움은 비동기식 월드 컴퓨터다. 즉, 내가 요청한 작업이 언제 실행될지 확신하기가 힘들다. 이러한 환경에서 Dapp을 만들 때 가장 큰 패널티는 요청의 결과에 따라 다른 로직을 실행해야 할 때일 것이다.이런 경우 우리가 취할 수 있는 단순한 방법은 내작업이 실행될 때까지, 반복적으로 체인을 읽어 확인해 보는 것일 것이다.

당연히 이 방식은 납득하기 어렵다. 그래서 오늘은 이더리움이 이 문제를 어떻게 극복해 나가고 있는지, "Design-Rationale"(https://github.com/ethereum/wiki/wiki/Design-Rationale)문서를 시작으로 정리해 보는 시간을 가지기로 했다.

외부 관찰자에게 흥미있는 이벤트

이더리움 설계 근거에는 다음과 같은 내용이 나온다

Ethereum 설계시 프로토콜의 기능과 opcode들을 최대한 저수준으로 구현함으로써, 나중에 나올수 있는 더 좋은 응용 방식에 잘 대응 할 수 있도록 하고, (해당 방식에서)필요없는 부분을 쉽게 제거 할 수 있도록 하여 좀더 효율적인 동작을 지원하도록 한다.
그 예로 LOG opcode가 있다. "메시지"는 실제로 "함수 호출"과 "외부 관찰자에게 흥미있는 이벤트"를 비롯한 여러 개념이 통합되어 있으며, 이 두 가지는 분리할 만한 가치가 있다.(의역)

이더리움은 외부 관찰자가 관심있어하는 정보를 얻는 과정에서 겪을 어려움을 줄여주기 위해 Log Opcode를 제공한다.

솔리디티의 이벤트 정의

솔리디티 문서에서는 아래와 같이 이벤트를 정의한다.

  1. Solidity 이벤트는 EVM의 로깅 기능 위에 추상화를 제공합니다. 응용 프로그램은 Ethereum 클라이언트의 RPC 인터페이스를 통해 이러한 이벤트를 구독하고 청취 할 수 있습니다.
  2. 이벤트가 호출되면 인수(변수)가 트랜잭션 로그에 저장됩니다. 이 로그는 계약서 주소와 연결되어 블록 체인에 통합되며 블록이 액세스 가능한 한 계속 유지됩니다
  3. 로그 데이터의 일부는 블룸 필터에 저장되기 때문에 효율적이고 암호학적으로 안전한 방법으로 이 데이터를 검색 할 수 있습니다.
  4. 최대 세 개의 매개 변수에 색인 된 속성을 추가하여 로그의 데이터 부분 대신 "Topic"이라는 특수 데이터 구조에 추가 할 수 있습니다.

솔리디티 개발자는 함수의 실행결과를 확인하기 위해, 이벤트를 사용하고, 구독함으로서 요청한 연산의 완료여부나, 결과등을 편한방식으로 확인 할 수 있게 된다.

Log op code와 솔리디티의 Event의 관계

지금부터는 log opcode와 솔리디티의 Event에 대해 조금 더 자세히 분석하고
둘의 상관 관계를 이해해 보도록 하겠다.

LOG op코드는 언제 생성되는가?

        contract A {
            event E(...);
            function foo() public {
                emit E(...);
            }
        }

위 코드는 솔리디티에서 이벤트를 사용하는 패턴이다. 이러한 패턴의 코드는 솔리디티 컴파일러에서 다음과 같이 해석된다.

함수 호출 과정에서 솔리디티 컴파일러가 Contract 키워드를 인식하여 내용을 파싱하고, Event token과 block내의 Emit token을 인식한 후, 해당 블록에서 emit이 발생할 경우 LOG opcode를 삽입하는 과정을 확인할 수 있다.

대부분의 컴파일러와 같이 솔리디티 컴파일러도 parsing -> ast -> code generation순서이므로, 시간이 날때 한번쯤 봐두는것도 좋을것 같다.

EVM상에서의 LOG op코드의 실행

컴파일러에서 삽입한 LOG op코드는 evm에서 다음과 같이 처리된다.

Contract의 foo 함수를 호출하는 트렌젝션이 블록에 포함되면, 노드들은 해당 트렌젝션을 처리하기 위해 EVM을 생성한다. EVM상에서는 계약의 코드를 준비한 후, 인터프리터를 통해 실행시킨다. 이때 Program Counter가 증가하면서 해석된 opcode를 jump table에서 호출하게 되며, Log op코드의 경우 jump table에 등록된 makeLog함수가 호출된다.

makeLog함수 내부를 살펴보면(보라색) 스택상의 데이터로 토픽필드를 채우고, 데이터필드는 메모리 시작지점부터 크기만큼 읽는다. 이 결과들을 포함하는 LOG데이터가 stateDB에 저장된다.

토픽과 데이터

스택에서 읽은 토픽과, 메모리에서 읽은 데이터의 내용은 어떤것일까? 작성한 컨트렉트를 컴파일 해보았다.

solc --opcodes event.sol > opcodes.txt

======= event.sol:B =======
Opcodes:
...
PUSH32 0x2D4DD5FE18ADA5A020A9F5591539A8DC3010A5C074BA6A70E1C956659F02786A 
PUSH1 0x1 
PUSH1 0x40 
MLOAD
...
LOG1 
...

LOG1 op코드의 동작 시점을 기점으로 스택에는 이벤트의 원형에 대한 해시값이 들어있고, 뒤에 있는 2개의 스택 op와 mload로 인해 evm메모리의 0x40번지에 1이라는 값이 들어있게 된다. 결론적으로 topic 필드에는 이벤트의 원형이 들어갈 것이고, data 필드에는 메모리에서 1값을 채우게 된다. 실제 저장이 잘 되는지 리믹스를 통해 확인해보았다.


<input 0xc2985578 (함수명에 대한 해시값의 상위4바이트)>

<topic: 0x2d4dd5fe18ada5a020a9f5591539a8dc3010a5c074ba6a70e1c956659f02786a 이벤트의 원형에 대한 해시값>

[{"address":"0x8c1ed7e19abaa9f23c476da86dc1577f1ef401f5","data":"0x0000000000000000000000000000000000000000000000000000000000000001","topics":["0x2d4dd5fe18ada5a020a9f5591539a8dc3010a5c074ba6a70e1c956659f02786a"],"rawVMResponse":[{"type":"Buffer","data":[140,30,215,225,154,186,169,242,60,71,109,168,109,193,87,127,30,244,1,245]},[{"type":"Buffer","data":[45,77,213,254,24,173,165,160,32,169,245,89,21,57,168,220,48,16,165,192,116,186,106,112,225,201,86,101,159,2,120,106]}],{"type":"Buffer","data":[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1]}]}]

Yay!

이제 우리는 opcode가 실행되었을때 체인상에 어떤 데이터가 어떤 형태로 저장되는지 확인하였다.

Event 구독(RPC)

https://web3py.readthedocs.io/en/stable/contracts.html#event-log-object 의 예제를 시작으로 dapp과 solidity event와 rpc의 연계관계를 알아본다.

>>> transfer_filter = my_token_contract.eventFilter('Transfer', {'filter': {'_from': '0xdc3a9db694bcdd55ebae4a89b22ac6d12b3f0c24'}})
>>> transfer_filter.get_new_entries()

위 예제는 어떤 계약의 Transfer이벤트를 감지하는 필터를 생성하고, 필터를 통해 이벤트의 발생여부를 확인하는 dapp 예제이다. 먼저 web3의 역할을 보자

web3를 통해 "eth_newFilter"라는 json request를 만들어 https provider의 세션으로 post한 후, response를 받아 처리하는 것을 확인할 수 있다.(blocking동작이다) 이러한 https요청은 geth구현체의 rpc서버로 전달되어 처리된다.

rpc서버는 JSON request를 decoding할때 서비스와 함수이름을 "_"로 분리한다.
즉 그림에서는 eth서비스의 NewFilter함수가 호출되게 되며. 이함수 내부에서는 이더리움의 이벤트 시스템에서 발생하는 Log이벤트에 대해 subscription을 신청한다.

구독feed의 갱신(Log Opcode)

노드쪽의 Log이벤트는 새로운 block이 체인에 추가될때 InsertChain함수내에서 발생한다.
새롭게 수신한 블록의 처리가 모두 끝나면, 트렌젝션 처리중 Log opcode에 의해 생성된 로그를 모두 모아 EventSystem에 전달하고, 이 이벤트 시스템은 구독을 신청한 모든 요청자에게 해당 log를 broadcasting한다.

아까 우리의 rpc서버도 구독을 했기 때문에, 수신자에 포함될 것이며, 수신한 로그를 이용하여 필터를 업데이트 한다. 이후, get_new_entires가 dapp에서 호출되면 filter의 변경 여부를 판단하여 변경사항을 반환하게 된다.

eth_getFilterChanges가 node에 전달되면 eth/GetFilterChanges 함수가 호출될 것이다.

간단한 이벤트를 발생시키기 위해 정말 많은 일들이 벌어진다.

아래 그림이 오늘의 결론이다. Dapp은 이벤트를 통해 호출한 contract 함수의 feedback을 받을수 있으며, 이 feedback은 log opcode가 실행되어 로그가 생성되면, 이벤트 시스템과 rpc를 거쳐 dapp에게 전달된다.

마치며,

조금 다양한 스택을 모두 거쳐야 하다보니 깊이는 조금 부족한것 같지만
컨트렉트에 정의된 이벤트가 다시 dapp에게 돌아오기 까지 과정의 분석을 통해
이더리움의 외부 이벤트 시스템을 조금이나마 더 이해할수 있었다.

첨삭/수정은 언제든 감사히 받겠습니다.
alphabet@hotmail.co.kr로 연락부탁드립니다.

Sort:  

안녕하세요 sigmoid님

좋은 하루 보내세요!!

Turtle-lv1.gif

꾸준히 글을 써주세요. 응원합니다.

이더리움 개발 관련 글이네요.
내용을 파악하긴 힘들지만 보팅하고 갑니다. (큐레이터 중 개발 전문가가 있었으면 하는 생각이 문득 듭니다.)

제가 알 수 없는 이더리움 설계 철학과 솔리디티라는 난해한 프로그램 언어에 관한 글이군요. 제가 이해하지 못하더라도 이런 글을 읽고 이해할 사람은 많을 것 같아요. 스팀잇 판에는 워낙 날고 기는 사람이 많으니까요. 용기를 내서 많이 써주세요.

Congratulations @sigmoid! You have completed the following achievement on the Steem blockchain and have been rewarded with new badge(s) :

You received more than 250 upvotes. Your next target is to reach 500 upvotes.

You can view your badges on your Steem Board and compare to others on the Steem Ranking
If you no longer want to receive notifications, reply to this comment with the word STOP

Vote for @Steemitboard as a witness to get one more award and increased upvotes!