블링블링 범블링

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

Technology/블록 체인

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

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

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


(5) 첫번째 dApp 의 완성





dApp 은 스마트 컨트랙트 + 사용자 인터페이스 라고 했습니다. 우리는 전편까지 스마트 컨트랙트를 만들고 이것을 블록체인에 올리고, web3 와 ABI 를 이용해 엑세스하는 것을 해보았습니다. 여기에 사용자 인터페이스를 더하면 최종적인 dApp이 완성됩니다. 위의 스크린 샷은 완성된 첫번째 dApp 입니다.

 

이 dApp 은 깃허브에 올려 놓았습니다. 

https://atomrigs.github.io/simplestorage.html 

 

구체적인 HTML/Javascript 코드를 보기 전에 이 dApp 이 사용자에게 제공할 인터페이스는 무엇인가를 정리해 봅시다. 

  • (1) 지정된 주소에 올려져 있는 simple storage 컨트랙트의 storedData 에 저장된 값을 불러와서 사용자에게 보여준다.
  • (2) 사용자가 지정한 정수값을 입력 받아서 위의 storedData 에 저장한다.
위의 두 가지가 simple storage dApp 의 코어 기능입니다. 그런데 위 코어 기능을 수행함에 있어 사용자에게 추가적인 편의성을 제공해 준다면, 더욱 좋은 dApp 이 될 수 있을 겁니다. 우리가 추가하고 싶은 기능은 다음과 같습니다.

  • (3) 지정한 컨트랙트의 주소를 화면에 보여주고, 이 주소를 클릭하면 블록체인에 기록된 트랜잭션들을 보여준다.
  • (4) 현재 사용중인 어카운트 주소를 보여주고, 이주소를 클릭하면 블록체인에 기록된 트랜잭션들을 보여준다.
  • (5) 현재 storedData 에 기록된 값을 보여줄 때, 현재의 블록넘버도 같이 보여준다.
  • (6) 사용자가 새로운 값을 입력해서 저장하면, 이 트랜잭션 아이디를 보여주고, 이 트랜잭션이 새 블록체인에 기록되면 자동으로 현재 저장된 값과 트랜잭션의 상태를 변경해서 보여준다.
이 중에서 제일 마지막 기능은 초급수준을 조금 넘어서는 테크닉이 필요한데, 이 기능이 없으면 이 dApp 을 사용함에 있어 매우 불편함이 따를 것이므로 추가하기로 합니다.

이번 강좌를 완전히 이해하려면 최소한 초보적인 수준의 HTML / CSS / JavaScript 지식을 필요로 하지만, 아직 배우지 못한 분이더라도 일단 한번 부딪혀 보십시요. 이해가 되지 않는 부분은 댓글로 달아 주세요.
이번 dApp 을 코딩함에 있어서 다른 자바스크립트 라이브러리나 툴은 사용하지 않았습니다. jquery 는 이제 거의 표준적으로 사용되고 있지만 이번 강좌에서는 사용하지 않았습니다. jquery 에 익숙한 분은 본인이 편한 데로 응용하시면 됩니다.

자 그럼 전체 페이지 소스를 훑어 봅시다.


<!DOCTYPE html>
<html>
<head>
  <meta charset="UTF-8">
  <meta http-equiv="CACHE-CONTROL" content="NO-CACHE">
  <link rel="stylesheet" type="text/css" href="https://cdnjs.cloudflare.com/ajax/libs/milligram/1.2.3/milligram.min.css">
  <title>Simple Storage Dapp 예제 </title>
  <style>
    body {margin-left:50px;}
    #storedData {font-size:300%; margin-right:10px;}
    #newValue {width: 200px; margin-right:10px; text-align:right;}
  </style>
</head>
<body>
<h3>Simple Storage dApp 예제</h3>
<ul>
  <li>컨트랙트 주소: <span id="contractAddr"></span></li>
  <li>내 어카운트 주소: <span id="accountAddr"></span></li>
  <li>컨트랙트에 저장된 값: <span id="storedData"></span>
    <button onclick="getValue()">새로고침</button> (현재블록: <span id="lastBlock"></span>)</li>
  <li><input id="newValue" type="text"><button onclick="setValue()">새 값으로 저장하기</button>
      <div id="result"></div></li>
  <li>새 값을 저장한 후 팬딩 트랜잭션이 블록에 포함되면 자동으로 페이지가 업데이트될 것입니다.</li>
</ul>

컨트랙트 소스

HTML 소스<br>
<br><br>
<p>
<a href="http://www.chaintalk.io/archive/lecture?sca=%EB%82%98%EB%8F%84+dApp+%EA%B0%9C%EB%B0%9C"><i>나도 dApp 개발해 보자 시리즈 by Atomrigs © 2017</i></a>
</p>

</body>
<script>
var contractAddress = '0xc5244053ecA508a11951400fc7Af28738Fd0ce77';
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"}];
var simpleStorageContract;
var simpleStorage;

window.addEventListener('load', function() {

  // Checking if Web3 has been injected by the browser (Mist/MetaMask)
  if (typeof web3 !== 'undefined') {
    // Use Mist/MetaMask's provider
    window.web3 = new Web3(web3.currentProvider);
  } else {
    console.log('No web3? You should consider trying MetaMask!')
    // fallback - use your fallback strategy (local node / hosted node + in-dapp id mgmt / fail)
    window.web3 = new Web3(new Web3.providers.HttpProvider("http://localhost:8545"));
  }
  // Now you can start your app & access web3 freely:
  startApp();
});

function startApp() {
  simpleStorageContract = web3.eth.contract(abi);
  simpleStorage = simpleStorageContract.at(contractAddress);
  document.getElementById('contractAddr').innerHTML = getLink(contractAddress);
  web3.eth.getAccounts(function(e,r){
    document.getElementById('accountAddr').innerHTML = getLink(r[0]);
  });

  getValue();
}

function getLink(addr) {
  return '<a target="_blank" href=https://testnet.etherscan.io/address/' + addr + '>' + addr +'</a>';
}

function getValue() {
  simpleStorage.get(function(e,r){
    document.getElementById('storedData').innerHTML=r.toNumber();

  });
  web3.eth.getBlockNumber(function(e,r){
    document.getElementById('lastBlock').innerHTML = r;
  });
}

function setValue() {

  var newValue = document.getElementById('newValue').value;
  var txid
  simpleStorage.set(newValue, function(e,r){
    document.getElementById('result').innerHTML = 'Transaction id: ' + r + '<span id="pending" style="color:red;">(Pending)</span>';
    txid = r;
  });
  var filter = web3.eth.filter('latest');
  filter.watch(function(e, r) {
    getValue();
    web3.eth.getTransaction(txid, function(e,r){
      if (r != null && r.blockNumber > 0) {
        document.getElementById('pending').innerHTML = '(기록된 블록: ' + r.blockNumber + ')';
        document.getElementById('pending').style.cssText ='color:green;';
        document.getElementById('storedData').style.cssText ='color:green; font-size:300%;';
        filter.stopWatching();
      }
   });
 });
}

</script>
</html>
이 소스에 나와 있는 모든 태그들을 전부 설명할 수는 없습니다. 구현하고자 하는 기능 부분을 중심으로 설명하겠습니다.
일단 html 문서는 <html> ... </html> 구조로 되어 있고 각각의 태그마다 특성들을 가지고 있어서 필요한 태그를 가져다 씁니다.
<head>...</head> 부분은 페이지에는 직접 보이지 않는 여러가지 속성들을 지정합니다. 서치 엔진을 위한 내용들도 있구요. 디자인을 위한 CSS 설정등도 있습니다.
아래 link 태그는 css 프레임워크의 하나인 milligram 파일을 가져온 겁니다. 화면에 버튼 등이 이쁘게 나오는 것은 다 이 파일 덕분입니다.

<link rel="stylesheet" type="text/css" href="https://cdnjs.cloudflare.com/ajax/libs/milligram/1.2.3/milligram.min.css">

 화면에 실제로 뿌려지는 내용은 <body>...</body> 안에 정의되어 있습니다. 그 중에서도 다음이 주 내용입니다.

 

<ul>
  <li>컨트랙트 주소: <span id="contractAddr"></span></li>
  <li>내 어카운트 주소: <span id="accountAddr"></span></li>
  <li>컨트랙트에 저장된 값: <span id="storedData"></span>
    <button onclick="getValue()">새로고침</button> (현재블록: <span id="lastBlock"></span>)</li>
  <li><input id="newValue" type="text"><button onclick="setValue()">새 값으로 저장하기</button>
      <div id="result"></div></li>
  <li>새 값을 저장한 후 팬딩 트랜잭션이 블록에 포함되면 자동으로 페이지가 업데이트될 것입니다.</li>
</ul>
컨트랙트 주소, 어카운트 주소, 현재 저장된 값, 새 저장 값을 입력 받는 박스와 버턴 등을 보여 줍니다.
이들 태그에 <span id="contractAddr"> 와 같이 id 를 지정한 곳이 많은데, 이것은 나중에 블록체인에서 받아 온 값을 넣어 줄 때 찾기 쉽도록 하기 위한 것입니다. <span>...</span> 이라는 태크는 자체적으로는 아무런 시각적 변화를 주지 않는 자리표시 정도의 의미만 있습니다.

두 개의 버튼이 보이는데, 첫번째는 onclick 이라는 이벤트에 getValue() 라는 함수를 실행하라는 설정이 되어 있습니다.

<button onclick="getValue()">새로고침</button>
요즘 많이 쓰이는 자바스크립트 프레임워크에서는 이런 방식보다 좀 더 체계적이고 다이나믹하게 이벤트 관리를 하지만 일단 가장 초보적인 방식을 썼습니다. 해당 버튼을 클릭하면 getValue() 를 실행합니다. 이 getValue 가 블록체인에 가서 필요한 정보를 요청해서, 위에서 지정한 여러 <span>...</span>자리에 데이타를 써주는 역할을 수행합니다. 이 함수가 구체적으로 무엇을 하는지에 대해서는 아래 자바 스크립트 부분에서 다시 다루겠습니다.

<input id="newValue" type="text"><button onclick="setValue()">새 값으로 저장하기</button>
사용자의 입력값을 받기 위해서 <input> 필드를 사용했습니다. 그리고 옆에 있는 버튼을 클릭하면(onclick), setValue() 함수가 실행되는데, 이 setValue() 함수는 왼쪽의 <input> 필드에 입력된 값을 가져다 블록체인에 트랜잭션을 보내고, 이 트랜잭션이 새 블록에 포함되어서 우리의 컨트랙트에 있는 값이 업데이트 되면 화면에 그 결과를 자동으로 업데이트 해주는 역할을 합니다.

그 밑에 있는 컨트랙트 소스 보기와, HTML 소스 보기 링크들은 이 dApp 의 기능과 직접 상관없는 부분입니다. 단지 최종 결과 페이지를 보았을 때, 그 소스들을 쉽게 볼 수 있도록 도와주기 위해서 붙여 놓은 것일 뿐입니다.

자. 이제 </body> 태그에 의해서 페이지에 직접 쓸 내용은 끝나고, 그 다음부터는 다이나믹하게 페이지 내용을 업데이트 시키게 될 자바스크립트 내용이 나옵니다. 자바스크립트 내용을 html 페이지 앞에 쓸 수 도 있으나, 본문 내용이 먼저 다 뿌려진 다음 자바스크립트가 실행되는 것이 더 정확한 경우가 많아서 이런 식으로 뒤에다 붙여 놓는 경우가 많습니다.

첫번째 스크립트 문은 web3.js 자바스크립트 라이브러리를 불러 오는 겁니다. 이 파일의 위치는 어디 서버에서 불러오는지에 따라 다를 수 있습니다. 보통 cdn 서비스에 올려놓고 불러오는 것이 속도 면에서 유리합니다. 그런데 우리가 전편에서 크롬의 console 로 보았을 때, 이미 메타마스크가 web3를 브라우저 메모리상에 삽입시켜 놓고 있었습니다. 그래서 사실은 이 라이브러리를 여기에 넣어 놓지 않더라도, 메타마스크를 사용하면 기능 구현에 아무런 지장이 없습니다. 하지만 메타마스크를 쓰지 않고 다른 노드 소프트웨어를 사용했을 경우, 이렇게 명시적으로 라이브러리를 불러와야 되는 경우도 있어서 포함시켜 놓았습니다.  그 다음줄에 <!--  --> 라고 jquery 가 마크 되어 있습니다. 보통은 다 jquery를 거의 집어넣습니다. 너무 편한 기능들이 많아서 입니다. 하지만 여기서는 최대한 다른 라이브러리 의존도를 없애기 위해서 jquery 를 주석처리 했습니다. <!-- --> 이렇게 둘러싸인 내용은 브라우저가 그냥 무시합니다.

var contractAddress = '0xc5244053ecA508a11951400fc7Af28738Fd0ce77';
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"}];
var simpleStorageContract;
var simpleStorage;
우리가 쓸 주요 변수들을 전역(글로벌) 변수로 사용하기 위해 자바스크립트 제일 위쪽(또는 루트)에 선언했습니다.
'0xc5244053ecA508a11951400fc7Af28738Fd0ce77' 컨트랙트는 저의 컨트랙트 주소이기 때문에 여러분은 자신의 컨트랙트 주소로 바꾸어 넣어야 합니다.
contractAddress 와 abi 값은 우리가 이미 알고 있으므로 미리 정의해 두었습니다. 하지만 simpleStorageContract 와 simpleStorage 변수는 선언만 하고 아직 값을 부여하지 않았습니다. 왜냐하면 이것은 컨트랙트에 접속해야만 받을 수 있는데, 이를 위해서는 web3.js가 준비되었다는 확인하는 것이 필요하기 때문입니다. web3.js 가 제대로 준비가 안되었는데, 블록체인에 먼저 접속하려고 시도하면 에러가 날 가능성이 생깁니다.

그래서 web3.js 가 제대로 다 로드 되도록 한 다음 우리의 컨트랙트를 불러오도록 합시다.

window.addEventListener('load', function() {

  // Checking if Web3 has been injected by the browser (Mist/MetaMask)
  if (typeof web3 !== 'undefined') {
    // Use Mist/MetaMask's provider
    window.web3 = new Web3(web3.currentProvider);
  } else {
    console.log('No web3? You should consider trying MetaMask!')
    // fallback - use your fallback strategy (local node / hosted node + in-dapp id mgmt / fail)
    window.web3 = new Web3(new Web3.providers.HttpProvider("http://localhost:8545"));
  }
  // Now you can start your app & access web3 freely:
  startApp();
});
이 부분은 메타스크립트에서 제공한 샘플코드입니다. 
window.addEventListener('load', function() {...}) 은 브라우저에서 로딩이 다 되면 function() {} 안에 있는 명령들을 실행하라는 것입니다.
web3가 메타마스크 등에 의해 이미 브라우저에 올라 와 있다면, 그것을 이용해, 즉 web3.currentProvider 를 이용해 새 web3 인스턴스를 만듭니다. 기존 셋팅을 그대로 다 받아서 쓰겠다는 겁니다. 그러나 만일 web3 가 아직 올라오지 않았다면, 즉 typeof web3 == 'undefined' 라면, 로컬에서 제공되는 다른 노드 제공자에 연결하겠다는 것입니다.
이 작업을 다 끝낸 후 비로소 startApp() 이라는 메인 함수를 실행합니다.

function startApp() {
  simpleStorageContract = web3.eth.contract(abi);
  simpleStorage = simpleStorageContract.at(contractAddress);
  document.getElementById('contractAddr').innerHTML = getLink(contractAddress);
  web3.eth.getAccounts(function(e,r){
    document.getElementById('accountAddr').innerHTML = getLink(r[0]);
  });

  getValue();
}
startApp()은 메인 컨트랙트 인스턴스를 만들고 필요한 값을 불러서 화면을 업데이트하는 getValue()를 실행합니다. simpleStorage 인스턴스를 생성하는 것은 이미 지난 편에서 다 해보았으므로 잘 이해가 되지 않는다면, 지난 편을 다시 보시기 바랍니다.

document.getElementById('id').innerHTML 이 자주 등장합니다. 페이지 상에서 특정한 위치에 정보를 업데이트하기 위해서 사용하고 있습니다. 
getLink(address) 함수는 뒤에서 따로 정의해 놓았는데, 주소를 받아서 테스트넷으로 링크를 만들어 주는 함수입니다. 같은 것이 반복되는 루틴이 있으면 이렇게 별도의 함수로 뽑아서 처리하는게 코드의 가독성을 높이고 관리하기 쉽도록 도와줍니다.

web3.eth.getAccounts(function(e, r) {...})은 이 예제에서 처음 나온 비동기식(asynchronous) 호출입니다. 블록체인 관련 웹 어플리케이션에서 이러한 비동기식 호출의 개념을 잘 이해하고 정확히 사용하는 것이 매우 중요합니다. 호출의 결과를 받는데 시간이 걸리니 이걸 받으려고 마냥 기다리지 않고 그냥 다음 프로세스로 넘어가되, web3 가 호출에서 지시한 사항을 다 끝내고 났을 때 어떤 행동을 해야 될 지를 가르쳐 줍니다. 여기서 e 는 에러가 났을 때 그 메시지를 받아 주는 것이고, r 은 리턴값이 있을 때 이를 받아 줍니다. 리턴 값이 있다면 이것을 가지고 무엇을 해야할지를 함수 내{..} 에 지시를 해주는 겁니다.

너무나 중요한 개념이기 때문에, 동기식 호출과 비동기식 호출을 다시 한번 비교해 봅시다.

 동기식(synchronous) 호출

 비동기식(asynchronous) 호출

 결과값이 리턴 될 때까지 프로세스를 중지하고 기다림 (블록킹)

 결과값 리턴을 기다리지 않고 다음 프로세스로 진행

var newValue = getNewValue(); 

document.getElementById('target_id').innerHTML = newValue; 

doAnotherThing();

getNewValue(function(e,r) {

  var newValue = r;

  document.getElementById('target_id').innerHTML = newValue;

}

doAnotherThing();

 getNewValue()가 리턴값을 newValue에 할당할 까지 대기함. 따라서 이 작업이 끝나지 않으면 뒤의 작업들은 실행되지 않음

getNewValue()가 리턴값 r 을 주지 않더라도, 그냥 다음번 명령인 doAnotherThing() 을 실행함. 나중에 r 값이 오면 그 때 document.getElementById('target_id').innerHTML = newValue 를 실행

리턴값을 받는데 시간이 걸릴 경우, 그 시간 동안 브라우저가 먹통됨. 하지만 특정 시점에서 여러가지 변수 값을 확인할 때는 정확함.

시간이 지체 되는 호출을 병렬로 즉시 보내고 다음 프로세스로 넘어갈 수 있으나, 일정한 시점에 어떤 리턴 값이 돌아 왔는지 비교 확인하는데 주의가 필요.


메타마스크는 거의 모든 블록체인 관련호출을 비동식만 지원합니다. 그리고 앞으로 많은 이더리움 사용자가 메타마스크를 사용할 것이므로, 이를 지원하기 위해서는 우리도 비동기식 호출을 기본으로 해야 합니다. 하지만, 이미 값이 로컬 메모리에 할당 되어 있거나 계산에 의해 값이 구해지는 경우 비동기식 호출을 하는 것은 비효율적이 되거나, 아예 비동기식 호출이 지원되지 않습니다.

web3.eth.getAccounts() 는 현재 사용중인 지갑 어카운트의 주소를 리턴합니다. 
document.getElementById('accountAddr').innerHTML = getLink(r[0]); 는 리턴된 리스트 중 첫번째 주소를 accountAddr 라는 id 를 가진 span 에 업데이트 시켜줍니다. 우리가 이 함수를 비동기식으로 호출했기 때문에, 리턴값이 돌아 왔는지 상관없이, 바로 다음 프로세스인 getValue() 를 호출합니다.

function getValue() {
  simpleStorage.get(function(e,r){
    document.getElementById('storedData').innerHTML=r.toNumber();

  });
  web3.eth.getBlockNumber(function(e,r){
    document.getElementById('lastBlock').innerHTML = r;
  });
}
getValue() 함수는 내부에서 다시 두 개의 함수를 호출합니다. simpleStorage.get() 함수와 web3.eth.getBlockNumber() 를 실행하게 됩니다. 둘 다 비동기식 호출이기 때문에 호출만 하고 기다리지 않고 다음 프로세스들로 바로 넘어갑니다.
simpleStorage.get() 함수는 컨트랙트 상에 있는 storedData 에 저장된 정수를 가져다 'storedData' 라는 id 를 가진 span 위치에 넣어주게 됩니다.
web3.eth.getBlockNumber() 는 현재의 마지막 블록을 구해서 역시 필요한 위치에 넣어줍니다.

function setValue() {

  var newValue = document.getElementById('newValue').value;
  var txid
  simpleStorage.set(newValue, function(e,r){
    document.getElementById('result').innerHTML = 'Transaction id: ' + r + '<span id="pending" style="color:red;">(Pending)</span>';
    txid = r;
  });
  var filter = web3.eth.filter('latest');
  filter.watch(function(e, r) {
    getValue();
    web3.eth.getTransaction(txid, function(e,r){
      if (r != null && r.blockNumber > 0) {
        document.getElementById('pending').innerHTML = '(기록된 블록: ' + r.blockNumber + ')';
        document.getElementById('pending').style.cssText ='color:green;';
        document.getElementById('storedData').style.cssText ='color:green; font-size:300%;';
        filter.stopWatching();
      }
   });
 });
}
이번 강좌의 하일라이트는 바로 이 setValue() 함수입니다. 사용자의 새 입력값을 받아서 블록체인에 보내고 그 트랜잭션 값을 받아 놓은 뒤, 그 트랙재션이 새 블록에 포함되면 그 결과를 다시 화면에 업데이트하는 역할입니다. 앞으로 더 복잡한 dApp 이 나오더라도 이런 식의 루틴은 매우 자주 나오게 됩니다. 조금 난이도가 있지만 확실히 이해할 필요가 있습니다.

  var newValue = document.getElementById('newValue').value;
  var txid
  simpleStorage.set(newValue, function(e,r){
    document.getElementById('result').innerHTML = 'Transaction id: ' + r + '<span id="pending" style="color:red;">(Pending)</span>';
    txid = r;
  });

여기는 그렇게 생소하지 않습니다. 인풋 박스에 있는 값을 가져다가 simpleStorage.get() 에 넣어줌으로써 블록체인에 저장합니다. 'result' id 를 가진 span 에 리턴되어온 트랜잭션 id 와 함께 붉은색으로 (Pending) 이라고 넣어줍니다.

 

  var filter = web3.eth.filter('latest');
  filter.watch(function(e, r) {... }

여기서 web3.eth.filter() 라는게 나오는데요, 블록체인 상의 변화를 체크하는 역할을 합니다. 'lastest' 라는 조건은 새 블록을 의미합니다. 그래서 filter.watch() 한다는 것은 새 블록이 발견되면, watch() 안에 포함된 명령을 실행하라는 것입니다.

 

    getValue();
    web3.eth.getTransaction(txid, function(e,r){
      if (r != null && r.blockNumber > 0) {
        document.getElementById('pending').innerHTML = '(기록된 블록: ' + r.blockNumber + ')';
        document.getElementById('pending').style.cssText ='color:green;';
        document.getElementById('storedData').style.cssText ='color:green; font-size:300%;';
        filter.stopWatching();
      }
   });

watch() 안에 포함된 첫번째 명령은 getValue() 함수를 실행하라는 것입니다.

앞에서 처음 페이지가 로딩될 때 실행한 함수였지요. 그걸 재 실행하라는 겁니다. 왜 재 실행합니까? 마지막 블럭넘버가 바뀌었기 때문이죠. 그에 따라 컨트랙트에 있는 storedData 값도 변화되어 있을 수 있기 때문에 최신 값으로 바꾸라는 겁니다. 지금 컨트랙트에는 다른 사용자가 있을 수도 있습니다. 한 사용자가 업데이트 하지 않았다 하더라도 다른 사용자가 값을 바꾸었을 수도 있습니다. 그래서 새 블록이 나왔으면, storedData 에 있는 값을 갱신해 놓자는 거지요.

 

그 다음 라인에 있는  web3.eth.getTransaction(txid, function(e,r){}) 는 우리가 앞에서 받았던 업데이트 트랜잭션 id (txid) 로 블록체인에서 그 기록을 한번 가져와 보는 겁니다. 만일 이 txid 가 새 블록에 포함되었다면 그 결과값에 블럭넘버가 포함되어 있겠지요. 가끔은 내가 보낸 트랜잭션이 포함되지 않은 채 새 블록이 생성되었을 수도 있습니다. 그럴 경우에는 아예 r 이 없거나, r.blockNumber 가 배정되지 않았겠지요. 그것은 우리가 보낸 값이 아직 저장되지 않았다는 것을 의미합니다. 그렇게 되면 if (r != null && r.blockNumber > 0) 에서 거짓이 되겠지요. "&&" 라는 것은 "AND" 의 의미입니다. 두 가지가 다 충족되어야 합니다. 리턴값이 null 이 아니고 거기에 블록넘버가 나온다(>0)는 것은 정상적으로 블록체인에 포함이 되었다는 뜻입니다. 그렇다면 아까 '(Panding)' 이라고 해두었던 내용 대신 '기록된 블럭' 이라는 안내 문구와 이 트랜잭션이 포함된 블록넘버를 보여주게 됩니다. 그리고 storedData 값도 새 트랜잭션이 반영된 것이기 때문에 녹색으로 문자색을 바꾸어줍니다. 

watch() 안에서 실행되는 두 함수가 반드시 동시에 실행되는 것은 아니기 때문에, 현재 storedData 값과 업데이트 트랜잭션 관련 화면갱신에 시간차가 날 수 있습니다. 

 

 filter.stopWatching(); 

리고 나서 정상적으로 트랜잭션이 반영되었다면, 더 이상 새 블록을 모니터링 하지 않고 필터링을 끝내게 됩니다. 만일 여기서 filter를 끝내지 않는다면, 이 페이지를 오픈해 놓으면 블록이 업데이트 될 때 마다 getValue()가 계속 실행 됩니다. 이런 식의 지속적인 watch가 필요한 dApp 들도 있겠지만 본 예제에서는 업데이트 값이 반영되고 나면 더 이상 watch 하지 않도록 했습니다.


이로써 우리는 Simple Storage 컨트랙트를 사용한 하나의 dApp을 완성했습니다. 매우 단순한 기능의 구현이지만, dApp 의 핵심 개념을 보여주기에는 충분하다고 봅니다. 더 어렵고 복잡한 dApp 을 만들기 전에 정확한 기본 개념을 파악하는 것도 도움이 많이 됩니다.

 

자 그렇다면 이 소스 코드를 가지고 직접 테스트하고 위의 샘플 페이지처럼 직접 외부에 오픈 하려면 어떻게 해야 할까요?

가능하면 별다른 소프트웨어를 깔지 않고 진행하려 했지만, 여기서부터는 기본적인 개발환경 셋팅부터 시작 해야겠습니다.

전문적인 개발환경을 제대로 갖추려면 상당히 세심한 셋팅과 노력이 필요하지만, 일단 꼭 필요한 것부터 해봅시다.

 

GitHub 계좌 오픈

 

https://github.com/  

 

에 가서 등록하시면 됩니다. 전문적인 개발을 직업으로 하지 않는다 해도 어카운트 하나 쯤 오픈해 둘만 합니다.

일반문서도 여기에 보관하면 버전관리도 쉽게 하고 백업하는 용도로도 쓸 수 있습니다.

 

어카운트 오픈한 다음 심플한 HTML 페이지를 호스팅 할 수 공간을 하나 만들어야 되는데, 제 프로젝트를 방문해 보세요.

 

https://github.com/atomrigs/atomrigs.github.io 

 

이왕 가셨으면 저 한테 별하나 날려 주시구요.

 

 

 

 

 

 위의 Repository 는 다른 것과 좀 틀린데 여기에 올린 페이지는 깃서버로 웹 호스팅이 자동으로 된다는 점입니다. 이 서비스를 깃 페이지라고 부릅니다. 이 깃 페이지를 셋업하려면 오른쪽 위 코너에 있는 '+' 를 눌러서 New Repository 를 선택하고 다음과 같이 셋팅합니다.

 

 

반드시 본인의 id 와 .github.io 앞에 있는 이름이 같아야 합니다. 틀리면 나중에 atomrigs.github.io 도메인으로 파일 접근이 안됩니다.

그 다음 부터 자세한 깃허브 사용법은 조금 검색해보면 많이 나올 겁니다. 너무 전문적인 학습은 아직 필요 없고 기본 컨셉만 잡고 파일 만들 수 있으면 됩니다.

 

그 다음 본인의 깃허브 페이지와 로컬 컴퓨터에서 작업할 파일을 서로 싱크시킬 필요가 있습니다. 직접 깃허브에서 에디팅할 수도 있지만, 효율성이 매우 떨어집니다. 그래서 깃허브 데스크탑을 깔고 온라인 버전을 클론해서 서로 싱크세팅을 해야 합니다.

 

https://desktop.github.com/ 

 

에 가셔서 자신의 시스템에 맞는 버전을 깔고 데스크탑 버전을 오픈하세요.

 

 

 

그런후에 clone 탭을 누르고 본인의 계정 밑에 있는 본인아이디.github.io 를 선택하고 클론하세요.

온라인 쪽과 로컬 파일을 생성하고 수정해보고 싱크해보고 왔다 갔다 해보세요. 조금 해보면 왜 이런 툴을 쓰는지 감이 올 겁니다.

 

이로써 온라인에 위에서 작업한 본인의 파일을 올리고 직접 dApp 을 돌릴 수 있게 되었습니다.

 

그러나 로컬에서 작업한 파일을 직접 브라우저에서 file:/// 로 오픈해 보면 메타마스크가 잘 작동하지 않을 껍니다. 이것은 브라우저에서 보안상 일부 기능을 막아 놓기 때문입니다. 결국 로컬에서도 웹 서버가 하나 필요합니다. 웹 서버라고 하니 거창한데 이것 역시 쉽게 구할 수 있습니다.

 

앞으로 우리가 제일 많이 보게 될 웹서버는 node.js 기반입니다. 만일 지금 프로그래밍 공부를 시작한다면, 그냥 javascript, jquery, node.js 부터 시작하는게 좋습니다. 그래서 node.js 를 깝니다.

 

https://nodejs.org/en/ 

 

명색이 개발자인데 가장 최신 버전을 까는게 좋겠지요. 

인스톨이 끝나면 노드를 실행해서 다음 명령어를 날리세요. 웹 개발에 아주 좋은 서버 모듈를 인스톨합니다.

 

npm install live-server -g

live-server 는 http-server 와 비슷한 패키지인데 다른 특징은 호스팅하고 있는 파일이 변경되면 페이지가 자동으로 리로드가 됩니다. 잠깐이라도 수고를 덜어 줍니다.

 

그런다음 node.js command prompt 를 클릭해서 도스창(cmd 창)으로 나갑니다.

 

 

 

 

여기서 아까 로컬에 싱크했던 atomrigs.github.io 디렉토리로 이동한 다음

live-server 명령어 하나만 쳐주면 됩니다.

 

 

 

자 이제 작업하던 디렉토리를 베이스로 해서 웹서버가 가동되기 시작했습니다. 이제 로컬서버에서 페이지를 불러 올 수 있습니다.

http://127.0.0.1:8080/simplestorage.html 

 

그리고 소스 페이지를 수정하면 자동으로 페이지가 리로딩됩니다.

로컬과 깃허브에서 위의 예제를 다 테스트해볼 수 있는 환경이 완성된 것입니다.

 

텍스트 파일 에디터는 평소 자주 쓰는 것을 사용하면 되겠지만, 앞으로 solidity 문법지원과 깃허브 지원 등의 기능을 고려한다면 atom 에디터를 추천합니다.

 

https://atom.io/ 

 

요즘 아톰이 너무 유행이네요 ㅋㅋ

 

-------------

 

내용이 너무 길었나요?

여기까지 오시느라 너무 수고가 많았습니다. 설명히 부족한 부분들은 댓글로 많이 질문해 주세요.

 

이번에는 숙제를 면제해드리고 싶지만, 앞으로 갈 길을 생각하니 더 내도록 하겠습니다. ㅋㅋ

 

(1) 공통 숙제

위의 예제들을 본인의 로컬 컴퓨터와 깃트허브에 작성하시고 

본인의 페이지를 아래의 url 처럼 방문할 수 있도록 해주시고 그 주소를 댓글로 올려주세요.

 

https://atomrigs.github.io/simplestorage.html  

 

물론 여기서 atomrigs 는 본인의 아이디로 대체되어야 합니다.

 

(2) 도전자 숙제

좀 더 도전 의식이 있는 분들을 위해 중급문제 하나 추가합니다. 이 숙제는 꼭 안 하셔도 되지만 성공하면 특별 상장을 수여합니다.

위의 예제는 정해진 컨트랙트 주소만을 사용해 기능을 구현했습니다. 

이 과제는 사용자에게서 다른 컨트랙트 (물론 동일한 내용이어야겠지만)의 주소를 입력 받아 이를 이용해 동일한 기능을 수행하게 하는 것입니다.

결과물은 

https://atomrigs.github.io/simplestorage2.html  

 

로 작성해서 올려 주세요. 

 

(3) 노가다 숙제

시간이 너무 많아서 뭔가를 더하고 싶은 분을 위한 숙제입니다.

컨트랙트 히스토리 라는 버튼을 만들도 클릭했을 때 주어진 컨트랙트의 최근 10개의 트랜잭션을 화면에 리스트로 보여주어야 합니다.

보여줄 정보 내용은 알아서 결정하세요. 최소한 txid 는 포함되어야겠지요.

 

결과물은

https://atomrigs.github.io/simplestorage3.html  

 

로 올려주세요.

Comments