winterjung blog


현실적인 스마트 컨트랙트 고려하기

최근 면접을 본 블록체인 관련 회사에서 Technical questions을 받았는데 흥미로운 주제고 생각할 거리가 많은 질문이어서 그에 대한 답변을 공유하려한다.

질문과 답변

질문은 중요한 부분이 포함되게 적당히 편집하였습니다. 질문의 어조는 실제와 다릅니다.

1. decentralized marketplace

여러 사람이 상품 등록, 구매를 할 수 있는 decentralized marketplace를 smart contract를 설계하고 그 구조와 각 contract들의 역할을 서술하라.

의견

탈중앙화된 마켓 플랫폼을 고려하는데 이를 운영하는 서비스 제공 업자가 있다고 가정하며 ipfs같은 분산 저장 솔루션을 사용하는 활용한 완전한 탈중앙화 마켓은 고려하지 않는다고 가정한다. 임의의 marketplace.sol 스마트 컨트랙트를 가정한다. 컨트랙트 내부엔 RegistryUser, GetUser, RegistryAsset, GetAsset, BuyAsset, DeleteAsset 함수가 있다고 가정한다. 별도의 토큰을 사용할 수도 있겠지만 여기서는 ETH만을 사용한다고 가정한다. 오프라인 상품을 등록할 수 있으며 실제 상품 배송과 관련된 부분은 고려하지 않는다. Destroyable, Owned, Pausable 컨트랙트는 포함되어 있다고 가정한다.

사용자

사용자의 정보로 아이디, 등록한 상품 목록, 어카운트 주소를 우선적으로 고려할 수 있다. 이를 User struct로 처리한다. 어카운트 주소와 아이디는 유니크 한 값으로 각 사용자를 구분할 수 있다. 사용자 집합 users는 array 혹은 mapping으로 관리한다. mapping으로 관리한다면 key값만을 들고있는 array가 별도로 존재하는 편이 관리하는데 좋다.

사용자가 이더리움 어카운트를 직접적으로 관리하지 않는 사용자 편의성이 높은 마켓을 고려한다면 클라이언트 단에서 아이디와 암호를 해쉬한 값을 받아 일반 사이트처럼 서비스 운영 업자 데이터베이스에 저장하고 사용자 유일 식별자를 통해 컨트랙트에 접근한다.

RegistryUser을 통해 새로운 사용자를 추가하고 GetUser를 통해 특정한 유저 정보를 받아올 수 있다.

상품 등록 및 삭제

상품의 정보로 상품의 이름, 카테고리, 사진, 등록날짜, 등록인, 가격만을 고려한다.

상품 자체는 Asset struct로 처리할 수 있으며 상품 집합 assets은 array 혹은 mapping으로 관리한다. mapping으로 관리한다면 유저 정보를 조회할 때 상품 식별자를 array로 갖고 있으며, 이 식별자를 key값으로 Asset struct 객체를 가져올 수 있다. key값은 별도의 array로 관리해주는 편이 좋다. 각 필드는 string, int 자료형을 고려하고 사진같은 경우 이미지를 base64 인코딩하여 string으로 올릴 수 있겠지만 비용 문제를 감안한다면 어느정도 타협해서 신뢰성 있는 이미지 호스팅 서비스를 이용하고 해당 이미지를 가져올 수 있는 url 등을 등록할 수도 있을 것이다. 상품 등록을 위해 RegistryAsset 함수를 사용한다.

등록된 상품을 삭제하기 위해서는 상품을 등록한 사용자의 주소로 트랜잭션이 발생됐는지 확인한 후 DeleteAsset(user, asset) 함수를 통해 유저 정보에서 등록한 상품 목록에서 asset을 삭제하고, assets 집합에서 asset을 삭제한다.

상품 조회

모든 상품을 조회한다고 가정했을 때 서버단에서 asset 식별자 key를 갖고있는 array를 length만큼 순회하면서 GetAsset을 통해 상품 정보를 반환받는다.

상품 구매

BuyAsset 함수를 통해 asset 가격만큼의 잔고를 보유하고 있는지 확인하고 지불 절차를 실행한 후 등록된 asset을 삭제한다. 판매 완료로 표시하려면 관련된 필드를 User, Asset에 추가해주고 삭제하는 대신 판매 완료 상품 array로 이동시킨다.

기타 의견

진정한 탈중앙화된 마켓을 구현하기 위해선 구매자가 상품을 받으면 상품을 수령했다고 체크하는 과정이 필요하며 지불 절차를 이 때 수행해야 한다. 다만 판매자가 상품을 줬는데 구매자가 못받았다고 거짓 진술하는 경우, 판매자가 상품을 안줘서 구매자가 못받았다고 하는데 판매자는 줬다고 거짓 진술하는 경우를 고려해야한다. 이런 상황은 스마트 컨트랙트로 해결할 수 없으므로 고려하지 않는다.

2. fee 문제

위의 decentralized marketplace를 Ethereum main net에 올려 서비스를 한다고 할 때, 발생할 수 있는 fee 관련 문제에 대해 서술하고 그 문제를 해결하기 위해 시도할 수 있는 방안에 대해 서술하라. 단 아직 완전히 구현되지 않은 기술(Casper, Plasma, Sharding 등)을 이용하는 미래형 문제해결 보다는 현실적으로 도입 가능한 방안으로 해결하길 권장한다.

의견

위에서 서술한 구조는 트랜잭션 수수료 문제와 더불어 사용자의 편의성도 낮다. 이를 해결하기 위해선 현재 거래소 시스템처럼 탈중앙화 비율을 낮추는 점이 현실적인 대안이 될 수 있다. 트랜잭션 수수료는 컨트랙트에 저장하려는 정보가 많을수록, 트랜잭션 빈도가 높을수록 부담이 되는데 두가지 방법을 고려해볼 수 있다.

첫째로 사용자가 직접 어카운트에서 트랜잭션을 발행하는 것이 아닌 서비스 제공 업자 내부에서 핫 월렛처럼 어카운트를 관리하고 내부 데이터베이스 처리를 하는 방법이다. 물론 트랜잭션을 발행해 컨트랙트와 상호작용은 하나 이 빈도를 낮춤으로써 어느정도 수수료를 절감할 수 있을 것이다.

둘째로 컨트랙트에 등록되는 정보 자체를 줄이는 것이다. 이미지 정보, 사용자 리스트나 상품 리스트를 자체 데이터베이스에 저장하고 실제 지불 절차만 컨트랙트를 사용하는 방법을 사용할 수 있다.

3. 제 3자에게 제공받는 신뢰할 수 있는 데이터

일반적인 경우 smart contract에서 내부 데이터 혹은 다른 smart contract로 부터 받는 데이터 외에 외부 데이터를 사용하거나 HTTP call이 불가능하다. 이를 대행해 주는 Oraclize와 같은 서비스가 있지만, 결과를 완전히 신뢰하기 힘드며, 단일 장애점(SPOF, single point of failure) 이 발생하여 contract가 정상적으로 작동하지 않을 수 있다. 이 같은 상황에 신뢰할 수 있는 외부데이터를 제공 받을 수 있는 이상적인 구조에 대해 서술하라.

의견

Oraclize와 투표자를 둬서 상호 검증할 수 있게끔 활용하는 방법을 생각해 볼 수 있겠다. Oraclize는 현재 컨트랙트에서 외부 데이터를 제공받을 수 있는 사실상 표준 기술이며 이를 활용하는 것이 효율적이다.

여러가지 이유로 Oraclize가 동작하지 않을 때 컨트랙트가 제대로 작동하지 않을 때가 있을 수 있다. 이럴 경우를 대비해 Oraclize관련 처리 루틴을 fetch부분과 process부분으로 분리해 두고, Oraclize가 다운 등의 이유로 동작하지 않을 땐 process부분에 필요한 정보를 직접적으로 입력해준다. 즉각적으로 실행하는 대신 잠시 지연시켜두고 사용자의 검증을 받을 수 있게 한다. 컨트랙트 서비스 제공 업자측에서 이를 공시해 사용자에게 유효성 검증을 받는다. 사용자는 예치금과 함께 정보의 타당성을 검증하며 다수의 사용자가 승인할 때만 실질적인 처리가 이루어 지도록 할 수 있다. 추후 Oraclize가 복구되었을 때 이를 다시 한 번 검증해 부정 검증이었을 경우 예치금을 몰수하며 올바른 검증이었을 경우엔 업자측에서 보상을 주는 방법으로 보완하는 방법도 생각해 볼 수 있다.

4. 수정가능한 smart contrat

일반적인 경우 한번 blockchian에 deploy된 smart contract의 경우 코드 및 contract를 수정할 수 없다. 하지만 언제든 뒤늦게 contract에서 에러가 발견되거나 기타 이유로 로직 변경, 추가가 필요할 수 있다. 이 같은 상황에 시도할 수 있는 최선의 방안과 그 이유를 서술하라.

의견

갱신 가능한 스마트 컨트랙트(Upgradeable contract)를 고려하기 전에 먼저 컨트랙트 배포 전 디버깅과 테스트를 거치는 방법이 있다. 뒤늦은 로직 변경에는 대응할 수 없으나 에러를 줄여줄 수 있는 방법 중 하나로 고려할 수 있다.

로직을 변경할 수 있는 Upgradeable contract를 고려할 때 먼저 생각해야할 건 컨트랙트를 실행하는 주체다. 서비스 제공 업자가 존재하고 해당 서버에서 컨트랙트와 상호작용하고 있다면 좀 더 간단한 접근을 고려해 볼 수 있다. 서비스를 운영하면서 컨트랙트를 호출하고 상호작용 하는건 web3를 사용한 서버 로직이며 이는 충분히 수정 가능하다. 만약 에러가 발견되거나 새로운 함수를 추가하는 등의 로직 변경이 필요하면 그에 맞는 컨트랙트를 만들어서 배포한 뒤 web3를 사용하는 서버 로직에서 가리키고 있는 컨트랙트 주소를 바꿔주고 새로운 함수를 호출하는 코드를 추가하면 된다. 기존 컨트랙트에 저장되어 있는 데이터는 별도로 마이그레이션 하거나 별개의 컨트랙트로 관리할 수 있다.

그 보다 좀 더 컨트랙트 중심의 로직을 활용하고 싶으면 디스패처-로직-스토리지 컨트랙트로 분리해 활용할 수도 있다. 디스패처는 최신 스마트 컨트랙트 주소를 가리키며 업데이트 가능하다. 디스패처와 로직간에는 delegatecall로 데이터를 전달하고 함수를 실행해 반환값을 돌려받는다. 저장되어 있는 데이터 또한 보존되어야 하므로 별도의 컨트랙트로 분리한다.

다만 갱신 가능한 스마트 컨트랙트를 사용한다면 결국 업자가 스마트 컨트랙트의 내용을 마음대로 변경할 수 있음을 뜻한다. 이는 신뢰 문제에 있어서 결국 사용자는 업자가 블록체인과 스마트 컨트렉트를 쓰든 쓰지않든 똑같이 업자를 믿고 갈 수 밖에 없다. 이런 상황에서 굳이 갱신 가능한 스마트 컨트랙트에 여러 기능을 넣어 구축하기 보다는 robust한 코어 로직을 스마트 컨트랙트를 토대로 운영하고 그 외에는 중앙화된 신뢰할 수 있는 서비스 제공 업자가 처리하거나 일반 사용자들에게 투표를 받아 일정 비율 이상이면 갱신 가능하게 만드는게 더 좋을 것이라 생각한다.

5. 마치며

위와 같은 질문을 받았을 때 매우 재밌었고 생각할 거리가 많았다. 또 나중에 생각하면 다른 답변이 나올 수 있고, 틀린 부분이 포함되었을 수 있다. 현재 블록체인과 관련된 기술이 한창 발전하고 있는 상황에서 나중엔 더 직관적인 해결책이 나오길 기대한다.