How to SCORE #4

external & eventlog Decorators

Joonyoung Choi
B!ock.Chain
12 min readApr 30, 2019

--

이번 시리즈를 이해하기 위해서는 Python 언어 및 ICON 이 제공하는 CLI 개발도구인 T-Bears 및 ICON SDK 활용법에 대한 약간의 사전지식이 필요합니다.

세번째 파트에서는 fallback 메소드와 @payable 데코레이터, ICX 전송과 예외 처리 에 대해 살펴보았습니다. 혹시 아직 못보신 분이 있으시면 파트 3을 먼저 보시는 것을 권장합니다.

시리즈의 네번째 파트에서는 지난 파트에서 살펴 본 @payable에 이어서 SCORE 외부로부터 SCORE에 구현된 메소드를 호출할 수 있도록 하는 @external 데코레이터, 이벤트를 통해 트랜잭션의 결과에 메소드 호출에 대한 기록을 남기는 @eventlog 데코레이터에 대해 살펴보겠습니다.

Table of Contents

Transaction & Query

  • Transaction
  • Query

external

  • external(readonly = True)
  • external(readonly = False)

eventlog

  • indexed
  • data

Hands-on Exercise

  • external
  • eventlog

Transaction & Query

이번 파트의 본격적인 내용에 들어가기 앞서 stateDB의 상태를 변형하는 트랜잭션과 stateDB로부터 데이터를 조회하는 쿼리에 대해 살펴보도록 하겠습니다.

Transaction

트랜잭션은 이전 파트에서 살펴 본 icx_sendTransaction를 통해 stateDB의 상태를 변형합니다.

icx_sendTransaction은 트랜잭션을 생성한 사용자의 개인키를 통한 유효한 signature가 반드시 있어야 합니다.

트랜잭션을 전송하는 것으로 datatype과 옵션에 따른 다섯가지 작업을 수행할 수 있습니다.

  • ICX 전송 : datatype = None
  • SCORE external 메소드 호출 : datatype = call
  • message 전송 : datatype = message
  • 새로운 SCORE 배포 : datatype = deploy
  • 기존의 SCORE 업데이트 : datatype = deploy

Query

쿼리는 이전 파트에서 살펴 본 icx_call을 통해 stateDB에 저장된 데이터를 조회합니다. icx_call의 경우 stateDB의 상태를 변화시키지 않습니다.

icx_sendTransaction과 달리 개인키를 통한 signature가 요구되지 않습니다.

쿼리를 요청하는 것으로 readonly 메소드를 통해 stateDB의 데이터를 조회할 수 있습니다.

  • SCORE external(readonly) 메소드 호출 : datatype = call

지금까지 stateDB의 상태를 변형하는 트랜잭션과 stateDB로부터 데이터를 조회하는 쿼리에 대해 살펴보았습니다. 이어서 본격적으로 @external@eventlog에 대해 살펴보도록 하겠습니다.

external

@external로 장식된 메소드는 exportable API list에 등록되어 SCORE 외부로부터 호출될 수 있습니다. 메소드는 readonly 옵션에 따라 읽기, 쓰기와 같은 서로 다른 두가지 기능을 수행합니다.

하나의 메소드에 @external가 중복되어 장식된 경우, IconScoreException이 발생합니다.

external(readonly = True)

ICON JSON-RPC API 의 icx_call을 통해 유효한 signature없이 메소드를 호출할 수 있으며, 메소드가 호출되면 stateDB로 부터 데이터를 조회하여 반환합니다. readonly 메소드 내에서 stateDB의 데이터를 변경하는 경우 DatabaseException(‘put is not allowed’)이 발생합니다. 반환하는 데이터의 타입이 반드시 명시되어 있어야 합니다.

readonly 메소드가 @payable로 장식된 경우 메소드 호출은 실패합니다.

지난 파트에서 살펴 본 @payable은 SCORE가 ICX를 전송받을 수 있도록 만드는 데코레이터입니다. ICX 전송은 자체로 상태변화를 일으키는 트랜잭션이므로 readonly와 함께 장식될 수 없습니다.

    def __init__(self, db: IconScoreDatabase) -> None:
self._owner_name = VarDB("owner_name", db, str)

def on_install(self) -> None:
self._owner_name.set("Life4honor")
@external(readonly=True)
def getOwnerName(self) -> str:
return self._owner_name.get()

getOwnerName메소드가 호출되면, stateDB로 부터 기존에 저장되어 있는 데이터를 self._owner_name.get()와 같이 조회하여 반환합니다.

external(readonly = False)

ICON JSON-RPC API의 icx_sendtransaction을 통해 메소드를 호출할 수 있으며, 메소드가 호출되면 작성한 메소드의 로직을 따라 stateDB의 상태를 변형합니다. 메소드를 호출하기 위해 트랜잭션 생성자의 개인키를 통한 유효한 signature가 반드시 필요합니다.

readonly 메소드와 달리 데이터를 반환하지 않고, IconService에 의해 생성된 트랜잭션의 hash를 결과로 반환합니다.

그렇다면 icx_sendtransaction을 통해 readonly 메소드를 호출하는 트랜잭션의 경우 어떠한 값이 반환될까요?

트랜잭션의 경우 IconService가 요청받은 트랜잭션을 실행하고 결과를 반환하게 되므로, readonly 메소드를 호출하는 경우라도 readonly 메소드가 반환하는 데이터가 아닌 트랜잭션에 대한 hash가 반환됩니다.

    def __init__(self, db: IconScoreDatabase) -> None:
self._owner_name = VarDB("owner_name", db, str)

def on_install(self) -> None:
self._owner_name.set("Life4honor")
@external
def setOwnerName(self):
self._owner_name.set("nanaones")

setOwnerName 메소드가 호출되면 stateDB의 상태가 변형됩니다. 예시의 경우 self._owner_name.get()의 결과가 변형됩니다. Life4honor -> nanaones

eventlog

@eventlog로 장식된 메소드는 해당 메소드를 호출하는 트랜잭션의 결과에 eventlogs로 기록되는 것 이외의 작업을 수행하지 않습니다. 따라서 예시와 같이 별도의 로직 구현 없이 선언하여 사용하면 됩니다.

    @eventlog(indexed:int = 0)
def eventMethod(self, parameter: primitive types):
pass

indexed를 별도로 설정하지 않는 경우 default값인 0이 적용되어 트랜잭션 결과에 기록됩니다.

메소드 선언 시 입력 받는 파라미터의 타입이 명시적으로 기록되어 있어야 합니다. 파라미터의 타입은 Address, int, str, bool, bytes를 지원하며, 만약 파라미터의 타입이 명시적으로 기록되지 않은 경우 메소드를 호출하는 트랜잭션은 실패합니다.

파라미터들의 값은 default 값을 설정하여 사용할 수 있습니다.

    @eventlog
def OwnerNameChanged(self, owner_name: str = "Life4honor"):
pass

@external
def setOwnerName(self, owner_name):
self._owner_name.set(owner_name)
self.OwnerNameChanged(owner_name)
@external
def resetOwnerName(self):
self._owner_name.set("Life4honor")
self.OwnerNameChanged()
setOwnerName 메소드에 파라미터로 입력한 “nanaones”가 적용된 트랜잭션 결과 조회
resetOwnerName 메소드를 통해 default 값이 적용된 트랜잭션 결과 조회

indexed

@eventlog에서 indexed 값이 설정되어 있는 경우, indexed 만큼의 파라미터가 입력 순서대로 Bloom filter에 포함됩니다. indexed를 통해 Bloom filter에 포함되는 파라미터의 수는 최대 3개로 제한되어 있습니다.

설정한 indexed가 실제 메소드가 입력받는 파라미터의 수 보다 큰 경우 에러가 발생합니다.

@eventlog로 장식된 메소드의 파라미터들은 모두 트랜잭션 결과에 모두 기록되며, Bloom filter에 포함되는 파라미터는 indexed로 기록되며, 이외의 파라미터는 data로 기록됩니다.

아직 Bloom filter를 통한 검색 기능은 지원하지 않습니다.

data

@eventlog로 장식된 메소드의 파라미터들은 모두 트랜잭션 결과에 모두 기록되며, data는 Bloom filter에 포함되는 파라미터를 제외한 파라미터를 의미합니다.

Hands-on Exercise

지난 파트 3 에서는 샘플 코드를 작성하며 revert, fallback, @payable를 활용해 보았습니다. 이번에는 파트 3에서 작성한 코드에 @external, @eventlog를 활용하여 기존의 self._owner_name을 조회 및 수정하고, 수정할 때 마다 트랜잭션 결과에 eventLogs로 기록되는 부분을 추가로 작성해 봅시다.

external

getOwnerName 메소드를 통해 현재 설정되어 있는 self._owner_name의 값을 조회하도록 구현해 봅시다.

setOnwerName 메소드를 통해 기존에 설정되었던 self._owner_name의 값을 수정하도록 구현해 봅시다.

    @external(readonly=True)
def getOwnerName(self) -> str:
return self._owner_name.get()
@external
def setOwnerName(self, owner_name):
self._owner_name.set(owner_name)

eventlog

setOwnerName 메소드가 호출될 때 마다 OwnerNameChanged 메소드가 트랜잭션 결과에 eventLogs로 기록되도록 setOwnerName 메소드에 @eventlog를 더하여 구현해 봅시다.

    @eventlog
def OwnerNameChanged(self, owner_name: str):
pass
@external
def setOwnerName(self, owner_name):
self._owner_name.set(owner_name)
self.OwnerNameChanged(owner_name)

이것으로 How to SCORE 시리즈의 네번째 파트를 마치겠습니다.

다음 파트에서는 InterfaceScore에 대해 알아보도록 하겠습니다.

ICON 과 관련된 추가적인 질문사항이 있는 경우, ICON 의 공식 개발자포럼 혹은 페이스북 그룹에 질문하시면 답변을 얻으실 수 있습니다.

아이콘 공식 개발자포럼

Dive into ICON 페이스북 그룹

ICON Developers 유튜브 채널

--

--

Joonyoung Choi
B!ock.Chain

Blockchain / Java / Python / Developer / Researcher