Rinkeby 테스트 네트워크 Deploy 스크립트 만들기

지루한 부분의 마지막을 달리고 있습니다. 이 글이 아마도 솔리디티 코딩과 관련한 마지막 글이 될거 같습니다. 컨트랙트 부분의 기능이 추가되면 다시 솔리디티 코딩으로 돌아오겠죠~

지금까지는 로컬 테스트 네트워크에 컨트랙트를 배포하여 작업하고 테스트를 진행했습니다. 그러나 이번에는 정말 여러 개발자가 사용하는 테스트 네트워크에 컨트랙트를 배포하여 돌려볼 수 있는 Deploy 스크립트를 작성해 보겠습니다. 저도 떨립니다~ 정말 개발자 대열에 들어서는 걸까요?

테스트 네트워크라고 하지만 실제 메인 네트워크에 배포하는 것과 큰 차이가 없습니다. 그래서 작업할 부분이 좀 됩니다. 메인넷, 테스트넷, 로컬넷의 차이점은 아래 글을 참고하세요.

사실 deploy하는 것은 Unit Test하면서 web3를 이용해서 매번 사용했던 기능입니다. 다음 코드를 사용했었습니다.

// setup code before running a test
beforeEach( async () => {
  // get all the accounts
  accounts= await web3.eth.getAccounts();

  // create a contract instance with arguments and deploy it
  // parse the json interface, so the javascript object can be used for contract
  dream_story= await new web3.eth.Contract( JSON.parse( compiled_contract.interface ) )
    // tell web3 that we want to deploy a new conpy of the contract.
    // do not forget about the arguments that the constructor of the contract requires
    // calling deploy does not deploy the contract, it creates an object to be deployed
    .deploy({ data: compiled_contract.bytecode, arguments: [INIT_MIN_DONW_PRICE] })
    // send transaction that creates the contract
    .send( { from: accounts[0], gas: '1000000' } )
});


비교적 간단히 배포를 했는데, Rinkeby 테스트 네트워크에 배포하기 위해서는 몇 가지 툴들이 필요합니다.


Rinkeby에서 사용가능한 Ether 받기

그전에 테스트 네트워크에 배포하는 것이기 때문에 이더(Ether)가 필요합니다. 비록 테스트용이지만 이것도 채굴을 하던지, 누구한테 받던지 해야 합니다. 이더가 없으면 배포도 안되고, 컨트랙트의 상태변수를 변경하는 함수도 실행할 수 없습니다. Rinkeby에서 사용가능한 이더를 받는 방법은 다음을 참고하세요.

그런데 최근에 방법이 조금 변경됐습니다. Github의 gist대신 google+나 facebook에 이더 수신 계정을 포스팅하고 그 주소를 Rinkeby 창에 복사하는 식으로요. 이 때 가능하면 30 ether를 신청하세요. 이부분은 금방 하실 수 있을 것입니다. 저는 개발에 집중해서 글을 써보겠습니다. 혹시 모르시면 문의주세요.


Rinkeby 네트워크 배포 설명

먼저 앞서 로컬 테스트 네트워크를 구축할 때 사용한 그림을 다시 보겠습니다.

앞서는 로컬 테스트 네트워크 Ganache를 사용했었습니다. Ganache가 별도의 테스트 계정을 만들어 주고, 이더까지 채워줘서 편하게 사용했습니다. 이에 대해서는 Unit Test 환경구축 장을 참고하세요.

그러나, Rinkeby 네트워크에 접속하고 배포하는 것은 이보다 까다롭습니다. 계정도 직접 만들어야 하며 이더도 채워 넣어야 합니다. 전체적 구성도를 먼저 살펴보겠습니다.


이전 구성과 특별히 다른 점은 Infura API, Infura Node입니다. 이더리움 네트워크에 접속하기 위해서는 이더리움 클라이언트가 필요한데 여기서는 Infura라는 서비스를 이용합니다.


계정 Unlock

Rinkeby 네트워크에서 사용할 계정이 필요합니다. 다음 그림과 같이 Metamask를 설치하여 계정을 생성합니다. Metamask는 웹 브라우저에 설치하는 플러그 인입니다. 크롬에 설치하기 위해서는 아래 링크로 이동하여 설치합니다.  크롬 브라우저에 간단히 설치 가능합니다.


그리고 네트워크를 Rinkeby로 설정합니다. 이때 생성한 계정은 Rinkeby뿐 아니라 메인 네트워크에서도 사용가능합니다. 단, Rinkeby의 이더는 메인넷에서는 사용할 수 없습니다. 

계정을 복구하기 위해서는 Metamask에서 12개의 seed words를 제공합니다. 이것만 있으면 모든 계정을 복구할 수 있습니다. 매우 중요한 정보죠. 메인 네트워크라면 절대 노출해서는 안 되는 정보입니다. 이 seed words의 역할은 Provider가 계정 암호를 묻지 않고 unlock할 수 있게 하는 것입니다. 즉, 특정 계정을 사용하여 deploy할 때, seed words만 있으면 개인키나 공개키를 입력하지 않아도 됩니다. 또 seed words로 무수히 많은 개인키, 공개키를 생성할 수도 있습니다.

Metamask의 setting에서 다음과 같이 seed words를 확인할 수 있습니다.



이더리움 클라이언트 노드

로컬에 이더리움 풀노드를 구성할 수도 있지만, 번거로운 작업입니다. 여기서 풀노드라고 하는 것은 블록체인 상의 모든 블록데이터를 다운받은 노드를 의미합니다. 또 테스트 네트워크라고는 하지만 블록 데이터를 싱크해야 해서 저장공간 및 싱크 시간이 필요합니다. 따라서 여기서는 Infura API와 Infura 노드를 사용합니다. Infura 노드는 이더리움 네트워크의 풀노드로서 Infura API를 이용하면 누구나 접속 가능하며, 따라서 이더리움 네트워크에 컨트랙트 배포가 가능합니다. 여기서는 이더리움 메인넷이 아니라 Rinkey 네트워크를 이용할 것입니다. 참고로, Infura를 운영하는 회사는 이더리움 풀노드를 보유하고 있어 사용자가 손쉽게 네트워크를 변경해서 사용할 수 있습니다.
Infura 노드를 이용하기 위해 Infura API key를 얻어야 합니다. 이를 위해 가입 및 몇 가지 준비가 필요합니다.


Infura 가입 및 API 키 획득

https://infura.io 웹사이트에 접속합니다.

② 가입합니다. "Get Started for Free"를 클릭합니다.


이메일에 접속하여 인증합니다.


가입이 완료되었습니다.


③ 새 프로젝트 생성 및 API key 획득

Infura도 업데이트가 되는지 사용법이 바뀌고 있습니다. 사용법은 홈페이지(https://medium.com/coinmonks/rpc-access-to-ethereum-with-infura-318854b7732f)를 참고하세요.

로그인 후 infura 홈페이지에서 dashboard로 이동하여 "Create New Project"를 클릭합니다.


적절한 프로젝트 이름을 넣습니다. 여기서는 "DreamFactory"로 합니다.

④ Endpoint 변경

프로젝트가 생성되면 다음과 같은 화면이 나타납니다. 여기서 ENDPOINT를 Rinkeby로 변경 후 API key 저장 또는 복사합니다.


HD Wallet Provider 설치

위 Rinkeby 네트워크 구성도에서 Provider에 HD Wallet Provider라는 것이 보입니다. 여기서 HD wallet이라는 것은 Hierarchical Deterministic Wallet으로 결정적 계층 지갑이란 의미입니다. 한마디로 seed words로 부터 결정적으로 지갑을 무수히 만들어 내는데, 계층 구조를 갖는 형태입니다.
HD Wallet Provider의 주요 역할은 Seed words를 입력받아 계정을 unlock하고 Infura API key를 이용하여 Infura API로 Rinkeby 네트워크에 접속, 컨트랙트를 배포하게 하는 것입니다. 따라서 다음과 같이 node 패키지 설치가 필요합니다. 설치는 프로젝트 root 디렉터리에서 해야 합니다.

$ cd  <project_root_dir>
$ npm install --save truffle-hdwallet-provider


이로써 Rinkeby 테스트넷에 컨트랙트를 배포하기 위한 준비를 모두 마쳤습니다. 그럼 이제 배포하기 위한 스크립트를 작성해 보겠습니다. 사실 스크립트 없이 Remix를 이용하면 테스트넷에 컨트랙트 배포가 가능합니다만, 여기서는 그 과정을 스크립트로 좀 파헤쳐 보겠습니다. 스크립트로 배포한 후에 배포 주소를 이용해서 Remix로 접근하는 것도 하겠습니다.


준비사항

먼저 Node.js를 최신 버전으로 업데이트합니다. 잘 모르겠다면 스택오버플로우의 글(https://stackoverflow.com/questions/41195952/updating-nodejs-on-ubuntu-16-04)을 참고하세요.

$ sudo npm install -g n
$ sudo n latest


deploy.js 스크립트 만들기

다음과 같이 compile.js가 있는 디렉터리에 deploy.js라는 파일 이름으로 스크립트를 만듭니다. 각 라인마다 주석을 달았습니다. 세부적인 내용은 주석을 참고하세요.

// wallet provider module
const HDWalletProvider= require( 'truffle-hdwallet-provider' );
// Web3 constructor function
const Web3= require( 'web3' );
// get the compiled contract of DreamFactory, which will be deployed
const factory_contract= require( './build/DreamFactory.json' );
// create a provider
const provider= new HDWalletProvider(
  // put your seed words from metamask
  'social universe put your seed words slush salmon trade dynamic runway other',
  // put your infura api key for rinkeby
  'https://rinkeby.infura.io/v3/your_api_key'
);
// get the web3 instance using the provider
const web3= new Web3( provider );
// to use async functionality, it should be inside a function
// async function means the function runs asynchronously.
// like it runs separately from the main event loop. no sync with the main event loop.
// sync function means the function holds the event loop to sync
const deploy= async () => {
  // get all accounts generated from the seed words
  // await means it waits for the result since the handling smart contract takes time.
  const accounts= await web3.eth.getAccounts();

  console.log( 'balance of accounts[0]: ', await web3.eth.getBalance( accounts[0] ) );

  // console log for deployment. use use the first account to deploy the contract
  console.log( 'Attempting to deploy from account', accounts[0] );
  // create a new contract instance and deploy it
  // web3 does know not about json file but javascript object, so need to parse the json file
  const deployed_factory= await new web3.eth.Contract(
    JSON.parse( factory_contract.interface )
  )
    // deploy the contract using the bytecode
    .deploy( { data: '0x' + factory_contract.bytecode } )
    // use the accounts[0] to execute the deployment
    .send( { from: accounts[0] } );
  // consonle log for the deployed contract address
  console.log( 'DreamFactory contract deployed to', deployed_factory.options.address );
};

// now call the deploy function to deploy the contract
deploy();


여기서 여러분들의 내용을 채워 넣어야 하는 부분이 두 가지입니다.

  • Metamask에서 생성한 Seed words
  • Infura API key

// create a provider
const provider= new HDWalletProvider(
  // put your seed words from metamask
  'social universe put your seed words slush salmon trade dynamic runway other',
  // put your infura api key for rinkeby
  'https://rinkeby.infura.io/v3/your_api_key'
);


이 두 가지를 여러분의 내용으로 변경해야 provider가 제대로 생성됩니다. 제것은 비공개로 처리하였습니다. 또 한 가지 주의할 점은 provider 설정할 때 seed words와 api key 사이에 , 넣는 걸 잊지 마세요.

앞의 deploy.js 스크립트가 하는 것을 간단히 요약하면 다음과 같습니다.

  • 필요한 패키지를 import
  • 접속하고자 하는 네트워크에 맞는 provider 설정 (매우중요!)
  • 컴파일된 DreamFactory 컨트랙트 파일로부터 bytecode를 추출하여 배포
  • async 기능을 사용하기 위해 deploy라는 함수를 만들어서 호출

배포하는 것은 이전에 Unit Test 코드 작성할 때 많이 사용했던 코드입니다. 그런데 한 가지 주의가 필요합니다.


deploy.js 스크립트 주의점

지금까지 컨트랙트 배포를 위해 Unit Test에서는 다음과 같은 코드를 사용했었습니다.

  dream_story= await new web3.eth.Contract( JSON.parse( compiled_contract.interface ) )
    .deploy({ data: compiled_contract.bytecode, arguments: [INIT_MIN_DONW_PRICE] })
    .send( { from: accounts[0], gas: '1000000' } )


그런데 이렇게 deploy.js 파일을 작성하고 배포하면 다음과 같이 에러가 발생합니다!

UnhandledPromiseRejectionWarning: Error: The contract code couldn't be stored, please check your gas limit.이라는 메시지가 보입니다. 그래서 gas 값이 더 커야 하나 하고 2000000으로 해봐도 똑같은 에러가 납니다. 그래서 왕창 크게 했더니 이번에는 Error: exceeds block gas limit block gas limit을 넘었다는 에러가 발생합니다.

혹시나 하고 Remix에 가서 Inject Web3(Rinkeby)를 선택하고 Deploy하니 또 잘됩니다.


역시 로컬 네트워크에서 잘되는 것들이 비록 테스트 네트워크지만 공개된 네트워크에서 하니깐 안 됩니다. 여기 저기 찾아보니 원인은 truffle-hdwallet-provider 버전에 있었습니다!

먼저 여러분의 truffle-hdwallet-provider 버전을 확인해보세요. 프로젝트 root 디렉터리로 이동하여 다음 명령어를 실행합니다.

$ npm list truffle-hdwallet-provider



아마 제 강좌를 따라오신 분들은 위와 같이 버전이 0.0.5 일 것입니다. 이게 바로 문제입니다. 문제를 해결하는 방법은 두가지입니다.

① truffle-hdwallet-provider 버전을 0.0.3으로 다운그레이드
② deploy 함수 수정

왠지 이미 깔려 있는 패키지를 다운그레이드 하는 것은 좀 아쉽죠? 그래서 ②번 deploy 함수를 수정하는 방법을 택합니다. 다음과 같이 deploy 함수를 약간 수정합니다.

const deployed_factory= await new web3.eth.Contract(
    JSON.parse( factory_contract.interface )
  )
    // deploy the contract using the bytecode
    .deploy( { data: '0x' + factory_contract.bytecode } )
    // use the accounts[0] to execute the deployment
    .send( { from: accounts[0] } );


바뀐 부분을 알아차리셨나요? 바로 deploy 함수 인자에 '0x' +가 추가되었습니다. 그리고, send함수 인자에 gas 부분이 빠졌습니다. 2개 다 수정해야 합니다. 하나라도 수정 안 하면 또 다른 에러가 발생합니다!


deploy.js 실행

자 드디어, deploy.js를 이용하여 Rinkeby 테스트넷에 DreamFactory 컨트랙트를 배포할 차례입니다. 두둥! 그렇습니다. 우리가 배포할 것은 DreamFactory뿐입니다. DreamStory 컨트랙트는 사용자가 요청하면 DreamFactory 컨트랙트가 알아서 배포할 것입니다.


테스트넷이지만 컨트랙트가 배포되는데 꽤 오랜 시간이 걸립니다. 1-2분 정도요. 인내를 가지고 기다려 보세요. 저같은 경우는 프롬프트 반환이 안돼서 Ctrl-C로 종료시켰습니다. 별 문제는 없습니다. 컨트랙트 배포 주소를 복사합니다.


rinkeby.etherscan.io

테스트넷이지만 아주 훌륭한 웹페이지(http://rinkeby.etherscan.io)를 제공합니다.
이 사이트에 접속한 후 우측 상단에 복사한 컨트랙트 주소를 붙여 넣습니다.


그러면, 배포된 컨트랙트에 어떤 트랜잭션들이 일어나는지 리스트로 볼 수 있습니다. 와우!


또 각 트랜잭션의 상세 내역도 볼 수가 있구요.


이번에는 Remix에 아래와 같이 Injected Web3 (rinkeby)를 선택한 후 At Address에 복사한 컨트랙트 주소를 입력합니다. 그리고 At Address를 클릭하면 다음과 같이 배포된 DreamFactory 컨트랙트에 접근할 수 있습니다!


실제로 테스테넷에서 돌고 있는 컨트랙트로 이 주소로 접속하여 저와 같은 인터페이스를 보실 수 있습니다. 배포된 컨트랙트 주소는 별도의 파일에 저장해서 보관해 놓으면 좋습니다. 테스트넷에 매번 배포하는 것보다 배포된 컨트랙트를 재사용하면 좋겠죠?

파일 이름은 적당히 FACTORY_ADDRESS라고 하고 내용은 컨트랙트 주소(0x75A31f56efEba84D7A1D99ac1b29Bb062cCD57d9)를 넣습니다.


오늘의 실습: Rinkeby테스트넷에 대해서 알아보세요. 또 어떤 테스트 네트워크가 있는지도 알아보세요.



 [한빛미디어 블록체인 도서 보러가기]