지난번에 이어 이번에는 Smart Contract 의 응용 예시를 일단 마무리 할 단계이다.
휴가와 개인사정으로 정말 오랜만의 글이 되었다 ^^;;
▌Crowd Funding 서비스 구성요소
이번 간단한 Crowd Funding 서비스의 컴포넌트 구성은 아래 그림과 같다.
매우 간단하다. Smart Contract 만을 사용하므로 Server 도 필요 없으며, 사용자 편의를 위한 HTML + JavaScript 기반의 UI 코드 정도만 있으면 된다. 웹서버도 필요 없고 HTML 파일을 Browser 로 열기만 해도 된다.
▪ Browser
Browser 에서는 새로운 Campaign 을 만들기 위한 간단한 FORM Element 한두개와 투자자가 Contract 로 보낼 투자금을 입력하기 위한 Input 1개, 달성 여부를 점검하기 위한 Text 한줄 정도면 충분하다
▪ web3.js
Contract 를 Deploy 하고 Contract 내의 함수를 실행하는 부분(Message Transaction, Call)은 web3.js 를 통해서 할 예정이다.
(향후에는 Web App 형태 말고 Go Native App 형태로도 한번 설명해볼 예정이다)
web3.js 는 내부적으로 RPC 나 IPC 를 사용하여 JSON 형태의 데이터 포맷으로 API 를 호출한다. 이번에는 간단하게 JSON RPC (HTTP) 로 호출하는 기본 동작을 해본다.
▪ Geth (Miner)
개발환경이므로 Geth 를 띄우고 직접 Mining 을 하도록 구성한다. 다음 글부터 이야기 하겠지만 사실 Geth 대신 TestRPC 를 사용하는게 개발할 때에는 더 편하기도 하다. 여유가 된다면 물론 여러 Miner 나 Node 들을 연결해서 사용하면 된다.
▪ CrowdFund (Contract)
Smart Contract Code 이다. Creation Transaction 으로 Blockchain 에 Contract 가 Deploy 된 상태의 그림이다.
: 이렇게 Smart Contract 와 Static Resource 만을 갖고 서버 없는 응용 개발이 가능한게 Smart Contract 의 큰 매력이며, Swarm 이 완성되면 Static Resource 의 배포 방식 또한 완전한 P2P 로 변화 될 것이다. 또한 Whisper 가 완성되면 이러한 응용(Dapp) 간의 Interaction 과 Communication 이 매우 빠르게 다양한 방법으로 가능해지므로 DApp 의 확산이 매우 급속화 될것이라고 믿는다.
▌Crowd Funding 의 동작 흐름
일단 Smart Contract 를 배포하는 프로세스가 반드시 필요하다. Solidity 로 개발한 Code 를 컴파일하여 Byte Code 와 ABI 를 얻어내고 (이 과정들은 향후 상세히 소개하겠다), 이후 부터는 web3.js 를 응용하거나 RPC API, Go Native Binding 을 사용하여 Smart Contract 의 함수를 실행하게 된다.
▪ UI 소개
이쁜 UI 에는 소질도 없고, 이해에 집중하기 위한 목적도 추가하여, 최대한 한단한 UI 를 만들어서 소개한다.
위 HTML Code 를 치는 귀챦음이 두려운 분들을 위해
Gist 에 코드를 올려놓았다.
▪ Contract Creation(Deploy)
화면의 Create 버튼을 누르면 저장 된 Contract Code (Byte Code) 를 이더리움에 Deploy 한다. 이걸 Contract Creation Transaction 이라고 부르며, 이렇게 Contract 가 정상적으로 Deploy 되면, 그 즉시 ABI 를 통해 생성한 JavaScript 객체를 활용하거나 JSON RPC API 를 통해 Contract 를 사용할 수 있다.
Contract Code 를 Compile 한 Byte Code 는 HTML 의 JavaScript Code 에 대략 아래 처럼 정의해놓았다.
코드가 너무 긴 관계로 한줄의 일부만 캡춰 했다. 이렇게 정의해놓은 Byte Code 는 "Create" 버튼을 누르면 아래와 같은 web3.js 를 사용한 JavaScript 코드로 Deploy 된다. (코드와 개발에 대한 설명은 다음에 자세히 할 예정이니 너무 깊게 생각 안해도 된다)
$('#btnCreate').click(function(){
crowd = Contract.new({
data:code,
gas:2000000,
from:web3.eth.accounts[0]
}, function (error, contract) {
if (!error) {
if (!contract.address) {
_log("Creating Contract : " + contract.transactionHash);
} else {
address = contract.address;
_log("Contract Deployed : " + contract.address);
}
} else
console.error(error);
});
});
위에 보면 data:code 부분이 보이는데, 이 부분에 바로 Byte Code 를 넣어서 Transaction 을 발생시키는 것이다.
Create 를 하면 아래와 같이 화면 상단의 TextArea 에 Contract 를 Deploy 한 Transaction 의 TxHash 가 출력되게 했고, Deploy 가 완료 되었다면 Deploy 된 Contract 의 주소를 출력하도록 했다.
한번 Deploy 된 Contract 는 Transaction Receipt 를 통해 Contract Address 를 가져올 수 있으며, 이 Contract Address 가 있으면 이후 부터는 이 Address 를 통해 바로 Contract 를 "사용" 하기만 하면 된다. 위 UI 에서는 At 버튼을 만들어서 주소를 써주고 At 을 누르면 ABI 를 통해 Contract 를 사용하기 위한 JavaScript 객체를 만든다.
▪ 신규 캠패인 시작
신규 캠페인을 시작하기 위해서는 Beneficiery 의 Address 와 목표 금액을 넣기로 했다. 현재 내 Morden-Net 의 Account 는 아래와 같이 두개가 있고, Campaign 의 beneficiery 는 두 개의 계좌 중 두번째 계좌로 지정하여 신규 캠페인을 생성하도록 하겠다. 목표 금액은 100 Ether 로 설정 한다.
현재 두번째 계좌에는 712 Ether 가 들어있는것을 알 수 있다. 이제 신규 Campaign 을 UI 를 통해 해본다.
New Campaign 버튼을 누르면 브라우져의 console 로그에 TxHash 를 찍도록 했다.
그리고 Transaction 이 처리 되었는지 보려면, web3.eth.getTransactionReceipt() 를 사용하는 방법과 etherscan.io 와 같은 Explorer 사이트를 활용하는 방법이 있다.
만약 Transaction 이 처리되어 Block 으로 묶였다면, 아래와 같이 Transaction 의 Receipt 정보가 보인다.
혹은 etherscan.io 와 같은 Explorer 사이트에서 확인해보면 아래와 같이 Transaction 이 처리되었음을 알 수 있다.
▪ 목표 달성여부 점검
특정 캠페인이 목표 투자금액을 달성하였는지를 확인해보도록 한다. 신규 캠페인을 만들거나 투자를 하는 경우와는 달리 달성이 되었는지 확인만 하는 작업은 궂이 Transaction 을 발생시켜 Gas 를 소모시킬 이유가 없다. 이럴 때에는 Local Blockchain 에서 CALL 을 통해 Gas 를 소모하는 Transaction 발생 없이 값을 확인할 수 있다.
정확히는, Global State 의 변경이 필요한 함수를 실행하는 경우는 SendTransaction 을 통해 Message Transaction 을 발생시켜야 하며, 나의 Local State 만을 임시로 변경하는 테스트 작업을 하거나 State 를 읽어들이기만 하는 작업(Solidity 에서 constant 함수)을 하는 경우에는 CALL 을 사용하도록 하는 것이다.
위와 같이 campaign ID 를 입력하고 check(call) 을 click 하면, 달성 여부와 목표 금액, 현재 모금된 금액이 즉시 표시된다.
▪ 투자 하기
이번에는 투자를 해보겠다. 나의 1 번 계좌에서 위에서 생성한 0 번 Campaign 에 150 이더를 투자 해 보겠다.
0번 Campaign 에 120 Ether 를 투자하도록 하고, 돈을 지불하는 쪽은 나의 Morden-Net 계좌 중 1번 계좌가 하도록 했다.
Transaction 이 처리되었는지 위의 "신규 캠페인 시작" 때와 동일한 방법으로 체크를 하고, Transaction 이 처리되었다면 목표 달성 여부를 다시한 번 check 해본다.
120 이더가 0 번 Campaign 에 전달되었다. Contract 의 전체 잔고도 120 Ether 가 된다. 이를 etherscan.io 를 통해 확인해본다.
위 처럼, Contract 로 120 Ether 가 이체 된 것을 볼 수 있으며, 102,147 Gas 가 소모 되었고, Input Data 는 4바이트의 NewCampaign() 함수의 Signature 인 함수 정의의 SHA-3 해쉬값 중 4바이트가 들어간 것을 볼 수 있다.
아래는 현재 Contract 의 상태 정보 중 일부이다.
▪ 목표달성 시 펀드 전달
이제 목표금액이 달성되었다는 사실을 확인 했으니 실제로 달성 금액이 Beneficiery 에게 이체 되도록 해보자. 여기서 한가지 주의해야 할 것이 있다. Smart Contract 는 스스로 함수를 실행할 수 없다는 것이다. 즉, 시스템 시각이 지정한 시각이 되거나 외부 시스템과 주기적으로 인터페이스 하여 조건을 만족하면 함수를 실행하도록 하는 등의 행위는 불가하다.
Smart Contract 는 반드시
1) 외부의 EOA 에 의해 발생한 Transaction 에 의해 실행되거나
2) 다른 Contract 의 호출에 의해 실행되거나
의 방법으로 호출된다.
그래서 우리의 Crowd Funding Contract 의 펀드 전달 기능 또한 누군가가 혹은 어떤 시스템이 Smart Contract 를 실행하여 Global State 를 변이하도록 Transaction 을 발생시켜야 한다. 아마도 이 상황에서는 Beneficiery 가 해당 Transaction 을 발생시키는게 합리적이긴 하겠다. 그러나 현재 Contract 는 다른 어떠한 EOA 나 Contract 도 함수를 실행시킬 수 있다.
UI 의 Check(send) 버튼을 누르면 Check(call) 때와 똑같은 함수를 실행한다 하지만 CALL 이 아니고 Message Transaction 을 발생 시킨다. 그러면 실제 아래 Contract Code 의 c.beneficiary.send(c.amount) 함수가 모든 Node 에서 실행되어 Global State 를 변경하게 된다.
function checkGoalReached(uint campaignID) returns (bool reached_, uint goal_, uint amount_) {
Campaign c = campaigns[campaignID];
goal_ = c.fundingGoal;
amount_ = c.amount;
if (c.amount < c.fundingGoal) {
reached_ = false;
return;
}
c.beneficiary.send(c.amount);
reached_ = true;
c.amount = 0;
}
Transaction 이 실행되고 난 후 Etherscan.io 를 통해 보면, Contract 가 120 Ether 를 Beneficiery 계좌로 보낸것을 볼 수 있다.
그리고 다시 0번 캠페인의 상태를 Check 해보면 돈이 빠져나간 것을 확인할 수 있다. 또한 2번째 계좌의 이전 Balance 가 712 Ether 에서 832 Ether 로 변경된 것을 확인할 수 있다.
▌결론
이번 2회에 걸친 글은 기술적인 글 보다는 "Smart Contract 로 이런게 가능하고, 매우 간단한 예시로는 이런게 있다" 를 전달하기 위한 목적의 글이다.
"저장" 뿐만이 아닌 "실행" 가능한 블록체인 기술로, 앞으로 매우 다양한 서비스를 개발하는 사례가 생겨날 것이라고 생각한다. 이러한 사례를 만들어 내기 위한 기술적인 글들을 다음 부터 또 올려보도록 하겠다.