일요일, 12월 16

[KUBL] 예제로 알아보는 이더리움 웹 DApp 개발 과정

0

예제로 알아보는 이더리움 웹 DApp 개발 과정

 

마스터링 이더리움의 DApps 챕터에 나오는 경매 DApp을 예제로 이더리움 웹 DApp 개발 과정을 알아보자.

전체 소스코드도 책의 Github repository에 있다.

(마스터링 이더리움 문서 주소: https://github.com/ethereumbook/ethereumbook/blob/develop/12dapps.asciidoc)

(소스코드 깃허브 주소: https://github.com/ethereumbook/ethereumbook/tree/develop/code/auction_dapp)

 

어플리케이션 혹은 앱은 응용 소프트웨어로서, 모바일 앱도 앱이고 웹 앱도 앱이다. 이번 글에서 다룰 것은 모바일 앱이 아니라 웹 앱을 만드는 과정이다. DApp은 Decentralized App인데, 탈중앙화된 앱이다.

 

예제로 볼 경매 디앱의 구성요소는 크게 네 가지다(그림1 참고). 프론트엔드는 앱에서 사용자로부터 입력을 받거나 출력하는 부분이고, 백엔드는 그 입출력하는 데이터를 처리하는 부분이라고 간단히 생각할 수 있다. 데이터 스토리지는 데이터 저장과 관련된 것이고, 메시지 커뮤니케이션은 오늘 만들 이 앱에만 해당하는 건데, 채팅하는 기능 때문에 필요하다. 따라서 보통은 그냥 프론트, 백, 데이터 저장 이 세 개만 생각해도 된다.

그림1. 경매 디앱의 구성요소 (1)

이제 각 구성요소에 대해 좀 더 자세히 알아보자(그림2 참고).

프론트엔드는 보통처럼 html, css, javascript로 웹 페이지를 만들면 된다. 이렇게 만든 프론트엔드는 메타마스크랑 web3.js로 이더리움과 연동하면 된다.

백엔드에서는 물건 소유권을 ERC721 토큰으로 구현하고, 경매 과정을 스마트 컨트랙트로 처리한다. 하지만 모든 데이터를 이더리움에 올리면 비용이 너무 많이 들기 때문에 정말로 신뢰성과 탈중앙화가 필요한 핵심부분만 이더리움에 올리고 이미지, 비디오, 디앱의 프론트엔드 웹 인터페이스(HTML, CSS, JavaScript 등) 같은 건 오프체인으로 저장하면 된다.

데이터 스토리지는 기존 centralized한 데이터 저장소를 사용하기도 하는데, “진짜” 디앱은 데이터스토리지도 탈중앙화시킨 것이므로 오프체인이더라도 Swarm 같은 p2p 데이터 저장소에다 저장하면 좀 더 디앱다워진다.

메시지 커뮤티케이션은 경매 앱에서 유저들끼리 메시지를 주고받을 때 사용된다. 보통 centralized한 서버와 연결된 채팅방으로 구현하는데, “진짜” 디앱으로 만들려면 이것 또한 p2p 메세징 프로토콜인 Whisper를 사용해서 만들면 된다.

그림2. 경매 디앱의 구성요소(2)

이제 경매가 이루어지는 과정을 설계해야 한다. 그림3의 다이어그램이 경매 과정이 어떻게 되는지를 보여주는 그림이다. (1)경매에 물건을 내놓을 사람이 먼저 물건의 소유권을 토큰으로 만들고 (2)경매를 열어서 (3)다른 사람들이 컨트랙트에 이더를 보냄으로써 입찰한다. 이후, 낙찰된 사람에게는 토큰을 주고, 이더는 경매를 연 사람에게 돌아간다. 나머지 입찰자에게는 이더를 다시 돌려주도록 한다. 이 과정을 잘 기억하며 스마트 컨트랙트로 구현해보자.

그림3. 경매 진행 과정 (출처: 마스터링 이더리움)

백엔드 – 스마트 컨트랙트 작성

전체 코드 중에 백엔드 파트만 보면 그림4와 같다. 먼저 ERC721 부터 살펴보자. 앞에서 말했듯이 물건의 소유권을 NFT토큰(대체불가능한 토큰)으로 표현할것인데, 이를 구현하기 위해서 만들어놓은 토큰의 표준 규약인 ERC721을 따라야한다.

그림4. 경매 앱의 백엔드 소스코드 파일목록

ERC721은 OpenZeppelin library에 소스코드가 공개되어있어서 이를 따라 스마트컨트랙트를 작성하면 된다. 표준 규약대로 코드를 작성해야하기 때문에 작성되어있는 인터페이스대로 구현해야한다.

(OpenZeppelin library 깃허브 주소:

https://github.com/OpenZeppelin/openzeppelin-solidity/tree/master/contracts/token/ERC721)

ERC721코드 이외에 유용하게 쓸 함수들이 들어있는 소스코드도 있다(그림5 참고). 스마트컨트랙트에서 연산 시 오버플로우 문제가 발생하는 것을 막기 위해 사용하는 SafeMath 라이브러리도 있다.

그림5. 그 밖의 유용한 라이브러리 각각에 정의된 함수 목록

가장 중요한 파트가 이 DeedRepository랑 바로 다음에 소개할 AuctionRepository인데, 먼저 DeedRepository에는 ERC721Token를 구체화시킨 코드로서 각 경매에 대한 토큰을 발급하고 추적하는 내용의 코드가 있다(그림6 참고).

그림6. DeedRepository에 정의된 함수 목록

AuctionRepository에는 제일 핵심인 경매가 어떻게 돌아가는지에 대한 코드가 있고, 경매 관련 자료구조는 그림7에도 나와있듯이 전체 경매를 저장할 배열, 호가 배열과 경매의 매핑, 소유자와 경매의 매핑, 호가 구조체, 경매 구조체로 구성되어있다.

그림7. AuctionRepository에 정의된 자료구조

이렇게 저장된 데이터를 다루는 함수도 여러가지 있다(그림8 참고). 이 함수가 실행되면서 경매가 진행되는것이다. 완성된 소스코드는 앞서 말했듯이 마스터링이더리움 깃허브 리포지토리에 있다.

그림8. AuctionRepository에 정의된 경매 관련 함수목록

백엔드 – 스마트 컨트랙트 배포

자, 이렇게 만든 스마트 컨트랙트를 배포해보자. 이더리움 스마트 컨트랙트를 배포하는데 여러 옵션이 있는데, 이 글에서는 그림9에서 색칠된 부분을 따라서 할것이다. 트러플로 Ropsten 테스트넷에 배포를 할것이고, Infura, HDWallet을 통해서 클라이언트로 연결되는 구조이다. 인퓨라가 메타마스크가 작동되도록 해주는 인프라를 제공하고, HDWalletProvider는 트랜잭션에 서명이 가능하게한다. 이제부터 이 과정을 순서대로 하나씩 진행해보자.

그림9. 이더리움 스마트 컨트랙트 배포 옵션 (이미지 출처: coinmonks 미디엄 아티클)

1. DApp을 만들 폴더의 최상단에 backend폴더를 생성한다.
2. backend폴더 내로 디렉토리를 이동한다.
3. 아래 명령어로 Truffle 초기화를 진행한다.
truffle init

성공적으로 초기화를 마치면 그림10과 같이 보이고, 해당 디렉토리에 새로운 Truffle관련 폴더 및 파일이 생성되어 있는것을 확인할 수 있다.

그림10. Truffle 초기화 성공 시 cmd창

4. solidity파일을 모두 contracts폴더 내로 이동한다.

앞서 작성했던 ERC721, utils, DeedRepository.sol, AuctionRepository.sol 을 모두 contracts 폴더 내로 이동한다.

5. migrations폴더 내에 2_deploy_contracts.js 파일을 새로 생성하고 아래 소스코드로 내용을 채운다.
var AuctionRepository = artifacts.require("./AuctionRepository.sol");
var DeedRepository = artifacts.require("./DeedRepository.sol");
// DeedRepository => 0xbb55adc67f64d1e6f08ba7523ecd2eca2ee434a3
module.exports = function(deployer) {
    deployer.deploy(AuctionRepository);
    deployer.deploy(DeedRepository, "Ultra Auction NFT", "UANFT");
};

6. Infura 가입 후 API키를 발급받는다.

Infura사이트(https://infura.io/)에 가입하여 새 프로젝트를 생성한 후 API키를 발급받는다.

그림11. Infura 프로젝트 관리 페이지

7. truffle.js에서 configuration을 설정한다.

truffle.js파일 안에 아래 소스코드로 채운다. Infura_apikey에는 6.에서 발급받은 API키를, mnemonic에는 이더리움 지갑 mnemonic 코드를 넣는다.

var HDWalletProvider = require("truffle-hdwallet-provider");
var infura_apikey = "";
var mnemonic = "";

module.exports = {
    networks: {
        development: {
            host: "localhost",
            port: 8545,
            network_id: "*" // Match any network id
        },
        ropsten: {
            provider: new HDWalletProvider(mnemonic, "https://ropsten.infura.io/"+infura_apikey),
            network_id: 3
        }
    }
};

8. backend폴더 내에서 아래 명령어로 truffle 컴파일을 한다.
truffle compile

윈도우환경에서 truffle.cmd와 truffle.exe가 이름이 같아 혼동되어 에러가 발생하는 경우가 잦은데, 아래와 같이 truffle.cmd로 명시하여 명령어를 입력해야한다.

truffle.cmd compile

그림12. truffle 컴파일 중의 cmd창

9. truffle-hdwallet-provider를 설치한다.

8.을 진행하다보면 윈도우환경에서는 hdwallet provider가 없다는 에러가 발생하며 컴파일이 안될 것이다. 이 때 아래 명령어로 truffle-hdwallet-provider를 설치해주면 된다.

npm install truffle-hdwallet-provider

그림13. truffle-hdwallet-provider 설치 성공 시 cmd창

10. Truffle로 Ropsten 테스트넷에 컨트랙트 배포하기

아래 명령어로 컨트랙트를 배포한다.

truffle.cmd deploy --network ropsten

AuctionRepository와 DeedRepository를 배포한 후 각각의 주소가 보일것이다. 나중에 쓰이므로 복사해서 저장해두자.

그림14. Truffle로 컨트랙트 배포 성공 시 cmd창

11. 이더스캔에서 컨트랙트가 잘 배포되었는지 확인한다.

Ropsten 테스트넷에 생성되는 블록과 컨트랙트 현황을 확인할수 있는 ropsten 이더스캔사이트(https://ropsten.etherscan.io/)에 10.에서 확인한 주소를 입력하여 검색해보자.

그림15. 이더스캔에서 컨트랙트가 배포되었음을 확인하는 창

 

프론트엔드 – 웹 페이지 만들기

frontend폴더를 backend폴더가 있는 폴더 안에 생성하고, 그 안에서 웹 페이지를 제작한다. 이 예제에서는 Vue.js로 웹 페이지를 만들었는데, 이 과정은 생략하고 바로 프론트엔드와 백엔드 연동 단계로 넘어가자. 마스터링 이더리움 깃허브 리포지토리에서 프론트엔드 소스코드만 가져와서 다음 과정을 진행하며 실습해도 좋다. 해당 리포지토리에 있는 프론트엔드 소스코드의 파일구조는 다음과 같다.

frontend/
|-- build
| |-- build.js
| |-- check-versions.js
| |-- logo.png
| |-- utils.js
| |-- vue-loader.conf.js
| |-- webpack.base.conf.js
| |-- webpack.dev.conf.js
| `-- webpack.prod.conf.js
|-- config
| |-- dev.env.js
| |-- index.js
| `-- prod.env.js
|-- index.html
|-- package.json
|-- package-lock.json
|-- README.md
|-- src
| |-- App.vue
| |-- components
| | |-- Auction.vue
| | `-- Home.vue
| |-- config.js
| |-- contracts
| | |-- AuctionRepository.json
| | `-- DeedRepository.json
| |-- main.js
| |-- models
| | |-- AuctionRepository.js
| | |-- ChatRoom.js
| | `-- DeedRepository.js
| `-- router
| `-- index.js

프론트엔드와 백엔드 연동

1. frontend/src/config.js에 DeedRepository와 AuctionRepository 컨트랙트 주소를 수정한다.

config.js파일에 아래와 같은 소스코드가 있을텐데, 앞에서 스마트컨트랙트 배포 시 얻은 DeedRepository와 AuctionRepository의 주소를 각각 입력한다.

var DeedRepository = require('./contracts/DeedRepository');
var AuctionRepository = require('./contracts/AuctionRepository');

module.exports = {
    JSONRPC_ENDPOINT: 'http://52.59.238.144:8545',
    JSONRPC_WS_ENDPOINT: 'ws://52.59.238.144:8546', //'ws://52.59.238.144:8546',
    BZZ_ENDPOINT: 'http://52.59.238.144:8500',
    SHH_ENDPOINT: 'ws://52.59.238.144:8546',
    DEEDREPOSITORY_ADDRESS: '0xec6d6842b615e0112000d37f5ec9ceafe4f465f3',
    AUCTIONREPOSITORY_ADDRESS: '0x780cdfbcddf2aa0093adc6add0e7b25ec6bebb58',
    DEEDREPOSITORY_ABI: DeedRepository.abi,
    AUCTIONREPOSITORY_ABI: AuctionRepository.abi,
    GAS_AMOUNT: 500000,

    //whisper settings
    WHISPER_SHARED_KEY: '0x8bda3abeb454847b515fa9b404cede50b1cc63cfdeddd4999d074284b4c21e15'
}

2. 메타마스크에서 Ropsten 테스트넷에 로그인한다.

그림16. 메타마스크 로그인 이후 화면

3. frontend폴더에서 노드 패키지 매니저(npm)를 설치한다.

아래 명령어를 입력한다.

npm install

그림17. npm 설치가 성공적으로 된 이후 cmd 창

4. npm을 실행한다.

아래 명령어로 npm을 실행한다.

npm run dev

그림18. npm을 실행했을 때 cmd 창5. DApp 작동 여부 확인

5. DApp 실행 여부 확인

http://localhost:8080/로 접속하여 DApp이 정상적으로 작동하는지 확인한다.

그림19. DApp 메인 화면

성공적으로 이더리움 웹 DApp을 만들었다. 여기까지만 해도 DApp이라고 부를 수 있지만, 맨 처음에 말했던 것처럼 p2p 메세징 프로토콜을 사용하면 좀 더 탈중앙화된 DApp을 만들 수 있다. 또한, Swarm을 이용해서 이미지, 비디오, 디앱의 프론트엔드 웹 인터페이스(HTML, CSS, JavaScript 등)등을 업로드하면 더욱 탈중앙화할 수 있다.

 

  • 본 분석칼럼은 Tconomy 의 분석 분야 기여파트너인 고려대학교 블록체인학회(KUBL)의 지적재산입니다. 이 분석은 분석파트너의 판단이며 Tconomy 의 편집 방향이나 의견과 일치하지 않습니다.
Share.

Comments are closed.