블링블링 범블링

나도 dApp 개발해보자 강좌 - 4 본문

Technology/블록 체인

나도 dApp 개발해보자 강좌 - 4

뻠스키 2018. 4. 5. 12:16

※ 이 글은 chaintalk의 atomrigs 님이 쓰신 글을 인용하였습니다.


(4) 컨트랙트 엑세스


이번 편에서는 지난 편에서 테스트넷에 올린 컨트랙트를 어떻게 엑세스 하는지 알아보려고 합니다.
지난 편에서 숙제를 하지 않지 않으신 분은 반드시 3편으로 돌아가서 숙제부터 하고 오시는게 이해에 훨씬 도움이 될 겁니다.

dApp 은 컨트랙트 + 사용자 인터페이스 입니다.
사용자 인터페이스(브라우저)를 이용해 컨트랙트(로직과 데이타)를 이용하게 되는 것인데, 이 중간에 둘을 연결해 주는 다리가 있습니다.
이 다리는 두개의 주요 구성요소가 있습니다.
하나는 web3.js 라는 라이브러리이고, 다른 하나는 ABI (application binary interface)입니다.
web3.js 는 이더리움 블록체인 관련 모든 표준적인 기능을 한군데 전부 모아놓은 자바스크립트 라이브러리입니다. 
소스를 한번 보고 싶으신 분은 다음 링크를 참조하세요.
지금 소스를 이해하지 못한다고 실망할 필요는 없습니다. 어짜피 이것은 블랙박스 같은 역할을 해주는 기능이니, 그 안의 작동 메카니즘을 전부 이해하지 못한다고 해서 사용하는데 문제가 있는 것은 아닙니다.
ABI 는 사용하고자 하는 컨트랙트에서 제공하는 함수가 어떤 것인지, 어떤 입력값을 받는지를 정의해 놓은 리스트입니다. 실제 내용을 보면 역시 별 것 아니구나 하는 것을 알아채실 수 있을 겁니다.

우리가 구현하고자 하는 최종적인 사용자 인터페이스는 물론 브라우저 웹페이지 형태이겠지만, 그 전에 이 중간 다리인 web3 와 abi 를 살펴보는 것부터 시작하겠습니다.

이번 편에서는 다루는 내용은 전부 자바스크립트입니다. 혹시 자바스크립트를 평생에 전혀 접해 본 적이 없는 사람은 우선 이것부터 좀 알아봐야 되겠지요.
제대로 공부를 한번 해보고 싶은 분은 다음의 글을 추천합니다.

그렇다고 해서 겁부터 먹을 필요는 없습니다. 
한번 봅시다. 얼마나 복잡 또는 간단 한지요.

자 일단, 지난편 온라인 컴파일러인 remix 로 돌아갑시다.

혹시 지난 편 셋팅이 다 없어졌다면, 소스 내용은 원래데로 카피해 놓고, 아래 그림처럼 "At Address" 버튼을 눌러서 원래 컨트랙트 주소를 불러옵니다.




그런 다음 페이지 빈 공간 아무데나 마우스 커서를 놓고 오른 쪽 버튼을 누르면, 메뉴가 뜨는데, 거기서 "inspect" 를 누릅니다.




새 창이 나올텐데, Console 이라는 탭을 눌러주면 입력할 수 있는 프롬프트가 뜹니다. 첫 줄에는 web3 가 인젝트, 즉 삽입되는 메시지가 있습니다. 앞에서 인스톨한 메타마스크가 web3 를 미리 띄워 놓은 겁니다. web3 가 바로 사용할 수 있도록 준비된 것이지요.

이렇게 오픈한 화면이 무엇인가 궁금할텐데요, 웹페이지 뒤에 가서 자바스크립트로 직접 로직과 페이지를 콘트롤할 수 있는 곳입니다.
어떤 기능을 체크하거나 어떤 문제가 발생했을 시 매우 자주 이용하게 되는 기능입니다.
인터액티브하게 프로그래밍 언어를 실행해서, 컴파일 없이 그 결과를 바로 알 수 있는 이런 식의 방식을 스크립트 방식이라고 합니다.
앞에서 우리가 코딩한 컨트랙트는 반드시 사전에 컴파일을 해야 했지만, 자바스크립트와 같은 스크립트 언어는 별도의 컴파일 없이 바로 명령어를 주고 그 답을 확인해 볼 수 있는 편리함이 있습니다.




그럼, 이제 이 툴로 우리가 블록체인에 올렸던 컨트랙트에 엑세스 해봅시다.



일단 web3 의 기본 기능들을 쉽게 살펴 볼 수 있습니다.

web3.providers.HttpProvider
만일 메타마스크를 사용하지 않았다면, web3 가 어디 블록체인 노드를 연결할 것인지 설정하는 함수인데, 지금은 메타마스크가 이미 자기 노드에 연결하고 있는 상태이니 따로 설정을 안해도 됩니다.

web3.eth.accounts
accounts 에는 현재 연결된 블록체인에 사용될 어카운트의 주소정보가 들어 있습니다. 지갑에 주속가 여러개 있을 수 있기 때문에 리스트로 결과가 출력됩니다.

web3.eth.accounts[0]
위의 리스트중에 제일 첫번째 것을 출력하기 위해서 [0] 의 위치를 지정했습니다. 프로그래밍 언어에서는 첫번째의 것이 1이 아니라  0 부터 시작합니다.
web3 다음에 '.' 을 찍으면 web3에서 제공하는 여러가지 값들과 함수들의 리스트가 나옵니다. 한번 대충 훑어 봐 두세요. 구체적으로 뭔지 몰라도 됩니다.

자 이제 컨트랙트 엑세스에 필요한 구체적인 작업을 해봅시다.



우리가 올려 놓은 컨트랙트에 접근하려면 그게 어디에 있는지 알아야 겠지요. 컨트랙트 주소를 contractAddress 라는 변수에 할당합니다.
앞에 나오는 "var" 라는 것은 변수(variable)라는 것을 선언하는 것입니다. 자바스크립트는 타입선언은 필요로 하지 않는 동적 변수 방식을 씁니다. 변수가 처음 나왔을 때만 var 를 씁니다. 아래 선언문에서, 주소값은 여러분의 컨트랙트 주소값으로 바꾸어야 합니다.

var contractAddress = '0xc5244053ecA508a11951400fc7Af28738Fd0ce77';

다음은 ABI 를 선언하는 것입니다. ABI 값은 왼쪽 리믹스 스크린에서 카피해옵니다. 내용을 유심히 들여다 보면 컨트랙트에서 선언한 함수의 이름, 입력값, 출력값 변수 타입, 이름등이 망라되어 있습니다. 이 ABI 가 있어야만 컨트랙트에 있는 함수를 어떻게 써야 될지를 알 수 있게 됩니다.

 

var abi = [{"constant":false,"inputs":[{"name":"x","type":"uint256"}],"name":"set","outputs":[],"payable":false,"type":"function"},{"constant":true,"inputs":[],"name":"get","outputs":[{"name":"","type":"uint256"}],"payable":false,"type":"function"}];

컨트랙트의 원형(또는 클래스)은 주소 정보 없이 abi만 가지고 만듭니다.

var simpleStorageContract = web3.eth.contract(abi);

같은 컨트랙트가 여러개의 주소에 동시에 존재할 수도 있겠지요. 지금 우리의 예제만 해도 똑같은 컨트랙트가 각자 다른 주소에 존재할테니까요.

이 개념을 클래스 - 인스턴트 라고 보통 이야기합니다. 클래스는 어떤 구체적인 대상이 아니라 그것의 개념적 형태, 원형이 됩니다. 반면 그 클래스를 실제로 구현한 개개의 존재는 인스턴스라고 합니다. 예를 들자면, 현대 소나타 GS 라는 모델이 클래스라고 한다면, 실제 존재하는 각각의 소나타 GS 자동차는 그 클라스의 구현, 인스턴스가 됩니다. 

그렇다면 위에서 만든 simpleStorageContract 는 ABI 정의를 통해 어떤 함수가 들어 있는지 정의된 클래스입니다. 이 클래스는 여러 주소에 각각 다른 인스턴스로 존재할 수 있습니다. 그래서 우리가 엑세스하려고 하는 인스턴스가 어느 것인지 구체화하려면 컨트랙트 주소를 주어야 합니다.

 

var simpleStorage = simpleStorageContract.at(contractAddress); 

simpleStorageContract에 하나의 구체적인 컨트랙트 주소를 'at' 이라는 함수를 이용해 부여함으로써 simpleStorage 라는 인스턴스를 만들었습니다.

simpleStorage 는 여러분 각자의 주소에 있는 개별 컨트랙트가 됩니다.

이렇게 해서 컨트랙트를 엑세스할 수 있는 모든 준비가 끝났습니다.

 

이제 이 컨트랙트를 이용해 컨트랙트안에 있는 storedData 라는 변수에 특정한 값을 저장해 봅시다.

 

 

 

 위의 스크린에서 첫번째 명령어를 살펴봅시다.

 

simpleStorage.set(12345); 

 컨트랙트에서 우리가 만들어 두었던 set() 함수를 이용해 보았습니다. 입력값으로 12345 를 주었습니다. 부호없는 정수값을 받도록 설계했던 것 기억하실 겁니다.

그런데 에러가 납니다.

메타마스크는 synchronous 트랜잭션을 지원하지 않는다는 겁니다. callback 함수를 지정해라 이런 메시지가 나옵니다.

갑자기 난해해지기 시작합니다. 하지만 개념을 잘 파악하면 그리 어려운 내용이 아닙니다.

 

웹페이지에서 우리가 어떤 함수를 호출해서 서버나 다른 벡엔드(지금의 경우에는 블록체인)에 요청 또는 리퀘스트를 보냈을 때 이를 어떻게 처리햐느냐에 따라 두가지 방식이 있습니다. 동기식 synchronous 과 비동기식 asynchronous (줄여서 async) 이 있습니다.

동기식은 브라우저가 하나의 함수를 불러서 서버에 요청하면, 서버가 답을 해줄 때까지 다른 작업을 중단하고 계속 그 답을 기다리고 있는 방식입니다. 보통은 별 문제가 없지만, 하지만 만일 서버가 답을 빨리 주지 않을 경우, 브라우저에서 다른 것을 전혀 할 수 없고 먹통이 되는 수가 있습니다. 반면 비동기식은 브라우저가 요청을 한 후 답을 안 기다리고 바로 다른 작업을 수행해 버립니다. 대신 서버한테 답이 준비되면 실행해야 할 함수를 미리 줍니다. 이렇게 비동기식으로 처리하면, 브라우저는 서버를 기다리지 않아도 되기 때문에 막힘이 없게 되고, 나중에 답이 오면 그 때 서버한테 부탁했던 함수가 실행되니 따로 더 대기할 필요가 없게 됩니다. 메타마스크가 거의 대부분의 함수를 전부 비동기식으로 처리하기로 한 것은, 블록체인의 특성상, 새 블록이 생성될 때까지 기다려야 할 때도 있고, 메타마스크의 경우 라이트 클라이언트이기 때문에 데이타를 외부 다른 노드에서 받아와야 되는 시간도 필요하기 때문입니다.

앞으로 우리가 볼 예제들에서는 모두 메타마스크를 지원하기 위해 비동기식 호출을 하도록 하겠습니다.

비동기식 호출을 한다는 것은 백엔드 쪽에 미리 실행될 함수를 같이 던져 줘야 한다는 것을 의미하는 것이고, 이 함수를 콜백(callback) 함수라고 부릅니다.

 

위의 명령어를 비동기식으로 호출하면 다음과 같이 됩니다.

 

simpleStorage.set(12345, function(e,r){console.log(r);});

동기식으로 호출한 것과의 차이는 12345 값 뒤에 콜백 함수를 던져 준겁니다.

 

function(e,r){console.log(r);} 

함수인데 특별히 이름도 없습니다. 이렇게 이름을 생략해도 상관없습니다. 이 함수는 메타마스크(백엔드)가 클라이언트가 요청한 작업을 완료했을 때 호출하는 함수입니다. 입력값이 두개인데, e 는 에러가 있다면 에러 메시지, r 은 요청한 내용이 있다면 이를 리런해주는 변수입니다. r  이라고 해도 좋고 response 라고 넣어도 좋지만 그냥 간략하게 r 이라고 붙였습니다. console 은 지금 이 명령어들을 넣고 있는 콘솔프롬프트이고 log 라는 명령어는 입력값을 화면에 프린트 해주라는 겁니다. 

전체적으로 다시 해석하면 메타마스크가 블록체인의 컨트랙트에 있는 storedData 에 12345 라는 값을 저장하고, 그 결과값 r 을 화면에 뿌려주라는 것이지요.

 

그럳데 앞 편에서 코딩했던 set 함수를 다시 찾아보면 그 함수에는 리턴값이 없습니다. 그렇다면 r 에는 도대체 어떤 것이 들어오게 될까요?

우리가 선언한 함수 자체에는 리턴값이 없었지만, 메타마스크가 트랜잭션을 보내면, 그 트랜잭션의 주소값을 자동으로 리턴하게 되어 있습니다. 위의 그림에서 호출된 함수 다음에 출력된 0x45... 값은 이 트랜잭션이 기록된 블록체인상의 주소 값입니다.

 

여기서 한가지 더 짚고 갈 것이 있습니다.

web3 가 처리하는 함수에는 두가지가 있습니다.

하나는 블록체인의 상태를 변화시키는 함수이고, 다른 하나는 상태변화를 일으키지 않는 읽기 전용의 함수입니다.

상태변화를 일으키기 위해서는 개스를 소모하는 트랜잭션을 블록체인에 보내야 하지만, 상태변화가 없는 함수는 트랜잭션을 보낼 필요 없이, 그냥 읽기만 하면 됩니다. 읽는 데에는 개스소모가 없습니다.

상태변화를 일으키는 트랜잭션을 보내는데 사용하는 함수가 sendTransaction() 이고 읽기만 하는 함수를 실행하는 것이 call() 입니다.

어떤 함수를 실행할 때 명시적으로 sendTransaction() 또는 call() 을 선언하지 않으면, web3 는 컨트랙트 상에 정의된 함수의 형태 키워드로 판단해서 둘 중에 적합한 것을 자동으로 골라서 실행합니다. 그 키워드가 constant 입니다. 함수의 리턴 앞에 constant 를 넣으면 이 함수는 읽기 전용이라는 것이 되고 sendTransaction 을 실행하는게 아니라 call() 를 실행합니다. 앞 편에서 정의한 get 함수를 보면 contant가 선언되었음을 확인할 수 있을 겁니다.

 

function get() constant returns (uint) {

        return storedData;

    }

반면 set() 함수는 이 키워드를 사용하지 않았습니다.

function set(uint x) {

        storedData = x;

    }

따라서 set 함수는 상태변화를 포함하는 함수라는 것을 의미하게 되고, ABI 에도 이것이 포함되어 있습니다. 그래서 우리가 simpleStorage.set(12345, function(e,r){console.log(r);});  실행하면 web3 가 뒤에서 자동으로 sendTransaction 을 실행하고, 이 트랜잭션의 주소값을 리턴해 주는 것입니다.

 

만일 이렇게 자동으로 처리하는 방식을 따르지 않고 직접 명시적으로 sendTransaction 을 호출하면 다음과 같은 명령어가 됩니다.

 

simpleStorage.set.sendTransaction(12345, function(e,r){console.log(r);});  

이 경우에 sendTransaction 을 넣거나 빼거나 처리 과정과 결과는 동일합니다. 나중에 sendTransaction 에 수동으로 개스값이나 다른 파라미터 셋팅이 필요할 경우에는 이렇게 명시적으로 써야 하는 경우도 있습니다.

 

이렇게 set() 명령을 사용해 12345 라는 임의의 값을 블록체인위에 저장을 해보았습니다.

 

다음은 이를 불러오는 함수에 대한 설명해 보겠습니다.

 

 

우리가 코딩했던 get 은 입력값을 받지 않고 그냥 storedData 에서 바로 값을 꺼내서 리턴하는 것이었습니다. 그런데 이번에도 비동기식 호출을 위해서 get에 콜백함수를 던져 주었습니다.

 

simpleStorage.get(function(e,r){console.log(r.toNumber());}); 

 r.toNumber() 라는게 뭔가하면, 돌려 받은 r 의 형식이 자바스크립트의 어브젝트 구조로 되어 있어서 정수형태로 프린트하기 위해 toNumber() 라는 내장함수를 이용해 바꾼 것입니다. 결과값은 우리가 앞에서 set 한 값인 12345 가 출력이 됩니다.

이번에는 get 함수가 constant 라고 미리 컨트랙트 디자인 때 선언해 두었고, 또 ABI 에 이것이 반영되어 있으므로, sendTransaction 은 호출되지 않고 call() 함수가 뒤에서 실행됩니다. call() 함수는 필요한 데이타를 블럭체인에서 읽기만 합니다. 

이번의 경우에도 call() 함수를 명시적으로 실행할 수도 있는데, 그렇게 되면 명령문은 다음과 같이 됩니다.

 

simpleStorage.get.call(function(e,r){console.log(r.toNumber());});  

이로써 우리는 브라우저 콘솔 상에서 컨트랙트에 엑세스해서 블록체인상에 있는 변수값을 바꾸고 또 이를 불러 읽어오는 방법을 배웠습니다.

다음 편에서는 이 방법을 실제 웹페이지 상에서 포함시켜서 브라우저에서 직접 함수들을 실행할 수 있도록 사용자 인터페이스에 붙여 보는 작업을 해보겠습니다.

 




--------------------숙제----------------------

 

지난편에 각자 작성했던 컨트랙트를 이용해 원하는 숫자를 저장하고, 이를 다시 불러오는 명령어들을 실행해 본후,

블록체인 익스플로러상에 어떠한 변화가 생겼는지 아래처럼 링크를 달아주세요.

https://testnet.etherscan.io/address/0xc5244053eca508a11951400fc7af28738fd0ce77 

최소 2개 이상의 트랜잭션이 보이도록 해주세요.

Comments