나만의 블록체인 구축해보기 (Ethereum private network) Tech Info

많은 기업들이 자신들만의 블록체인 솔루션들을 가지고 있다고 말하고 있는데 그게 가능한 이유는 대부분의 블록체인 시스템이 오픈소스 프로젝트이기 때문이겠지만 이더리움 덕분인지도 모르겠다. 이더리움은 굳이 소스를 포크해서 수정하지 않더라도 손쉽게 독자적인 블록체인을 구축할수 있는 기능을 제공하고 있다. 블록체인 시스템을 구축하기 위해선 핵심이 되는 블록체인 기반의 P2P 시스템 외에도 블록의 내용을 검색하기 위한 block explorer, 가상화폐를 보관하고 주고받기 위한 wallet, 그리고 smart contract를 개발하고 테스트하기 위한 개발환경이 필요한데 이런 요소들이 이더리움 생태계에선 모두 잘 구축되어 있다.

며칠만 투자하면 Ethereum private network 기반의 블록체인 솔루션을 뚝딱하고 만들 수 있다. 구글링 해보면 이미 관련자료를 많이 찾아볼 수 있지만 앞에서 언급한 요소들을 모두 한번에 모아서 정리해볼까 한다. 이더리움으로 private blockchain을 구성하고, block explorer를 붙여서 구동시키고, 메타메스크 wallet 간에 코인을 전송하고, ERC20 토큰도 생성해서 이것도 메타메스크 wallet으로 전송해보자.

1. Ethereum client 설치
비트코인 프로그램은 Bitcoin Core라고 부르는 반면에 이더리움은 client라는 용어를 사용한다. client라는 어감과는 달리 이게 full node를 구현하는 프로그램이고 private network에서는 서버의 역할을 할 것이다. client 프로그램은 다양한 버전이 존재하는데 하나의 client 프로그램에 종속성이 생기지 않도록 의도한 것이란다. 여기선 Go 언어로 개발된 Geth를 이용할 것이다. (OS는 Mac이다)

$ brew tap ethereum/ethereum
$ brew install ethereum
cs

2. Private network 구성하기
genesis 파일을 만들어서 init 한 후에 geth를 실행하면 private network 구성은 끝이다.

genesis 파일의 각 속성들이 뭔지 궁금하겠지만 대부분 별로 중요하지 않다. 블록 생성시 필수로 포함되는 정보들을 기술하고 있어서 무의미한 정보들이 많다. genesis 블록이 다르면 별개의 chain을 구성하게 되기때문에 이렇게 genesis 블록을 만들어 이더리움을 구동하면 일종의 fork가 되는 셈이다. alloc 항목에 초기 ETH 발행량을 지정해 둘수 있는데 단위에 주의해야 한다. 단위는 wei라서 1 ETH면 0이 18개 붙어야 한다. difficulty는 초기 블록 생성시간을 결정하게 되는데 중간중간 바꿔도 된다. 내 경우는 0x400000로 지정하니 약 1분 정도의 주기로 블록생성이 됐다.

genesis 파일을 만들었으면 먼저 init을 실행한 후,
$ geth --datadir ethData init ethData/genesis.json
cs

geth를 실행한다.
$ geth --datadir ethData --mine --minerthreads=1 --networkid 123 console 2> ethData/eth.log
cs

geth는 CPU mining을 하는데 thread 하나로는 CPU 로드가 거의 없다. mining reward는 미리 생성해둔 account 중 첫번째 account로 할당된다.
account를 여러 개 만들어두고 이더를 전송해볼수 있다. 나중엔 메타메스크 지갑으로도 보내볼 것이다. geth console에서 아래와 같이 실행하면 되는데, 보통 이더 전송시에는 from에 해당하는 account를 먼저 unlock 시켜야한다.
> personal.unlockAccount(eth.accounts[0])
> eth.sendTransaction({from:eth.accounts[0], to:"0x40E0f81535849278379Db61A8f4961138c0922bA", value:web3.toWei(100"ether")})
cs

3. Block explorer 붙이기
이더리움에서 가장 널리 사용되는 block explorer는 etherscan 이지만 오픈소스가 아니다.
아래 explorer는 간편하게 설치하고 테스트해볼 수 있으니 이걸 이용해보자.

block explorer는 이더리움 client와 rpc로 통신하므로 geth를 rpc 옵션을 주고 다시 실행해야 한다.
$ geth --datadir ethData --mine --minerthreads=1 --networkid 123 --rpc --rpcaddr 10.64.33.149 --rpccorsdomain "http://10.64.33.149:8000" console 2> ethData/eth.log
cs

이제 ethnamed explorer의 config.json 파일에 이 rpc 주소를 적어두고 실행하면 된다.
$ cat app/config.json 
{
    "rpcUrl" : "http://10.64.33.149:8545"
}
cs

4. 이더리움 지갑 연동하기
사용자들이 이 private ethereum에 붙어서 이더를 주고 받거나 토큰을 주고 받으려면 지갑과 연동이 필요하다.
가장 유명한 이더리움 지갑은 메타메스크라는 크롬 확장앱이다. 메타메스크에선 접속할 네트워크를 선택할 수 있는데 main net과 test network 외에 사용자 정의 RPC를 이용할 수도 있다. 이 사용자 정의 RPC를 선택하고 rpc url에 private network의 rpc url을 입력하면 된다.

이제 이 메타메스크 지갑의 주소로 앞에서 했던대로 이더를 전송해 볼 수 있다. 아래 그림과 같이 네트워크 이름은 "프라이빗 네트워크"로 표시된다.

5. ERC20 토큰 발행해보기
이 private network에서도 ERC20 token 같은 smart contract를 개발하고 실행할 수 있다. ERC20 token 의 샘플 프로그램은 이더리움의 공식 가이드에 잘 설명이 되어있지만, private network에서는 여기서 사용하는 ethereum wallet을 이용할 수 없다. 대신에 geth console과 웹 기반의 remix IDE를 이용해서 deploy하는게 가능하다.
아래 가이드에 비교적 깔끔하게 설명이 되어있으니 참고하자.

요점만 추려보면 이렇다:
- 먼저 solidity code를 remix IDE에 붙여넣는다.
- compile 후 details 버튼을 눌러보면 WEB3DEPLOY 라는 javascript 코드를 찾을수 있다. 이걸 파일로 저장한다.
- geth console에서 loadScript() 라는 함수로 이 javascript 파일을 로드해 실행한다.

위 절차에 따라 이더리움 가이드의 코드를 실행하면 geth console에서 myadvancedtoken 객체가 생성된걸 확인할 수 있다.
> myadvancedtoken
{
  abi: [{
      constant: false,
      inputs: [{...}, {...}],
      name"setPrices",
      outputs: [],
      payable: false,
      stateMutability: "nonpayable",
      type: "function"
  }, {
      constant: true,
      inputs: [],
      name"name",
      outputs: [{...}],
      payable: false,
      stateMutability: "view",
      type: "function"
  }, {
      constant: false,
      inputs: [{...}, {...}],
      name"approve",
      outputs: [{...}],
      payable: false,
      stateMutability: "nonpayable",
      type: "function"
  }, {
      constant: true,
      inputs: [],
      name"totalSupply",
      outputs: [{...}],
      payable: false,
      stateMutability: "view",
      type: "function"
  }, {
      constant: false,
      inputs: [{...}, {...}, {...}],
      name"transferFrom",
      outputs: [{...}],
      payable: false,
      stateMutability: "nonpayable",
      type: "function"
  }, {
      constant: true,
      inputs: [],
      name"decimals",
      outputs: [{...}],
      payable: false,
      stateMutability: "view",
      type: "function"
  }, {
      constant: false,
      inputs: [{...}],
      name"burn",
      outputs: [{...}],
      payable: false,
      stateMutability: "nonpayable",
      type: "function"
  }, {
      constant: true,
      inputs: [],
      name"sellPrice",
      outputs: [{...}],
      payable: false,
      stateMutability: "view",
      type: "function"
  }, {
      constant: true,
      inputs: [{...}],
      name"balanceOf",
      outputs: [{...}],
      payable: false,
      stateMutability: "view",
      type: "function"
  }, {
      constant: false,
      inputs: [{...}, {...}],
      name"mintToken",
      outputs: [],
      payable: false,
      stateMutability: "nonpayable",
      type: "function"
  }, {
      constant: false,
      inputs: [{...}, {...}],
      name"burnFrom",
      outputs: [{...}],
      payable: false,
      stateMutability: "nonpayable",
      type: "function"
  }, {
      constant: true,
      inputs: [],
      name"buyPrice",
      outputs: [{...}],
      payable: false,
      stateMutability: "view",
      type: "function"
  }, {
      constant: true,
      inputs: [],
      name"owner",
      outputs: [{...}],
      payable: false,
      stateMutability: "view",
      type: "function"
  }, {
      constant: true,
      inputs: [],
      name"symbol",
      outputs: [{...}],
      payable: false,
      stateMutability: "view",
      type: "function"
  }, {
      constant: false,
      inputs: [],
      name"buy",
      outputs: [],
      payable: true,
      stateMutability: "payable",
      type: "function"
  }, {
      constant: false,
      inputs: [{...}, {...}],
      name"transfer",
      outputs: [{...}],
      payable: false,
      stateMutability: "nonpayable",
      type: "function"
  }, {
      constant: true,
      inputs: [{...}],
      name"frozenAccount",
      outputs: [{...}],
      payable: false,
      stateMutability: "view",
      type: "function"
  }, {
      constant: false,
      inputs: [{...}, {...}, {...}],
      name"approveAndCall",
      outputs: [{...}],
      payable: false,
      stateMutability: "nonpayable",
      type: "function"
  }, {
      constant: true,
      inputs: [{...}, {...}],
      name"allowance",
      outputs: [{...}],
      payable: false,
      stateMutability: "view",
      type: "function"
  }, {
      constant: false,
      inputs: [{...}],
      name"sell",
      outputs: [],
      payable: false,
      stateMutability: "nonpayable",
      type: "function"
  }, {
      constant: false,
      inputs: [{...}, {...}],
      name"freezeAccount",
      outputs: [],
      payable: false,
      stateMutability: "nonpayable",
      type: "function"
  }, {
      constant: false,
      inputs: [{...}],
      name"transferOwnership",
      outputs: [],
      payable: false,
      stateMutability: "nonpayable",
      type: "function"
  }, {
      inputs: [{...}, {...}, {...}],
      payable: false,
      stateMutability: "nonpayable",
      type: "constructor"
  }, {
      anonymous: false,
      inputs: [{...}, {...}],
      name"FrozenFunds",
      type: "event"
  }, {
      anonymous: false,
      inputs: [{...}, {...}, {...}],
      name"Transfer",
      type: "event"
  }, {
      anonymous: false,
      inputs: [{...}, {...}, {...}],
      name"Approval",
      type: "event"
  }, {
      anonymous: false,
      inputs: [{...}, {...}],
      name"Burn",
      type: "event"
  }],
  address: "0x82cefac7e04da13d07f476198e35f631db421991",
  transactionHash: "0x7137c7e0dec714da99761efc5ad05c877f4cfbaf2710d3188f20a2adb4faca6f",
  Approval: function(),
  Burn: function(),
  FrozenFunds: function(),
  Transfer: function(),
  allEvents: function(),
  allowance: function(),
  approve: function(),
  approveAndCall: function(),
  balanceOf: function(),
  burn: function(),
  burnFrom: function(),
  buy: function(),
  buyPrice: function(),
  decimals: function(),
  freezeAccount: function(),
  frozenAccount: function(),
  mintToken: function(),
  namefunction(),
  owner: function(),
  sell: function(),
  sellPrice: function(),
  setPrices: function(),
  symbol: function(),
  totalSupply: function(),
  transfer: function(),
  transferFrom: function(),
  transferOwnership: function()
}
cs

위 항목에서 address 항목에 표시된 주소가 이 토큰의 주소인데, 메타메스크 지갑에 등록할때 이 주소를 사용하면 된다. 우선 메타메스크 지갑의 주소로 이 토큰을 전송해둔다. 이때도 단위에 주의해야 하는데, 이더리움 가이드의 TokenERC20 class는 decimals 값이 18이다. 이걸 0으로 바꾸면 token value에 항상 0을 18개씩 붙이는 수고로움을 피할수 있다. 단, 이경우 token은 소수점 단위로는 사용할 수 없는데 token 성격에 따라 이게 더 바람직할 수도 있을 것이다.
myadvancedtoken.transfer.sendTransaction("0x40E0f81535849278379Db61A8f4961138c0922bA"1000, {from:eth.accounts[0]})
cs

이렇게 전송한 토큰은 메타메스크 지갑의 토큰추가 메뉴로 토큰 종류를 추가해두면 확인할 수 있다.

1 2 3 4 5 6 7 8 9 10 다음



통계정보