How to SCORE #4
external & eventlog Decorators
이번 시리즈를 이해하기 위해서는 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()
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 의 공식 개발자포럼 혹은 페이스북 그룹에 질문하시면 답변을 얻으실 수 있습니다.