이더리움 - web3

707·2022년 6월 30일
0

블록체인

목록 보기
7/10
post-thumbnail

Web3

(1) 타입스크립트 환경설정

대부분의 설정은 기존의 타입스크립트 환경과 유사함

🌱 npm

npm init -y
// 타입스크립트 관련
npm i -D typescript ts-node @types/node 
// lint 관련
npm i -D eslint prettier eslint-plugin-prettier eslint-config-prettier
// jest 관련
npm i -D ts-jest @types/jest babel-core @babel/preset-typescript @babel/preset-env
// web3와 ether.js
npm i web3 ethereumjs-tx @types/web3

❗️설정하면서 중간에 이것저것 받으면서 web3-typescript-typings 이라는 라이브러리를 받았었는데 얘를 받으니까 설정된 타입이 web3라이브러리랑 달라서 함수의 인자나 리턴값 등이 맞지 않는 이슈가 있었다. 버전문제인지는 모르겠지만 일단 이 라이브러리는 사용하지 않는걸로..

🌱 tsconfig.json

{
  "compilerOptions": {
    "outDir": "./dist/",
    "esModuleInterop": true,
    "moduleResolution": "node",
    "resolveJsonModule": true,
    "strict": true,
    "baseUrl": ".",
    "typeRoots": ["./node_modules/@types", "./@types"],
    "paths": {
      "@core/*": ["src/core/*"],
      "*": ["@types/*"]
    }
  }
}

🌱 babel.config.ts

module.exports = {
  presets: [
    [
      '@babel/preset-env',
      {
        targets: { node: 'current' },
      },
    ],
    '@babel/preset-typescript',
  ],
};

🌱 jest.config.ts

import type { Config } from '@jest/types';
const config: Config.InitialOptions = {
  moduleFileExtensions: ['js', 'ts', 'json', 'jsx', 'tsx', 'node'],
  testMatch: ['<rootDir>/**/*.test.(js|ts)'],
  testEnvironment: 'node',
  verbose: true,
  preset: 'ts-jest',
};

export default config;

moduleFileExtensions에 js와 ts만 넣은 경우에 package

🌱 .eslintrc & .prettierrc

.eslintrc

{
  "extends": ["plugin: prettier/recommended"]
}

.prettierrc

{
  "printWidth": 120,
  "tabWidth": 2,
  "singleQuote": true,
  "traillingComma": "all",
  "semi": true
}



(2) web3 라이브러리

web3.js와 ethers.js
Web3.js와 ethers.js는 모두 개발자가 Ethereum 블록 체인과 상호 작용할 수 있도록하는 기능을하는 JavaScript 라이브러리이다. 경우에 따라 둘 중 더 적합한 라이브러리가 있어 본 게시글에서는 두 라이브러리를 병행해서 사용하고 있다.

RPC통신이란?

Web3에 앞서 먼저 rpc에 대해 알아보자.
web3에 대한 설명을 찾아보면

json-rpc 프로토콜로 이더리움 노드와 통신하는 기능을 하는 라이브러리

라는 설명을 볼 수 가 있는데 rpc를 알아야 좀 더 쉽게 이해가 되기 때문이다.

rpc는 REST와 같은 API 아키텍쳐의 일종이다. http통신을 할 때 지금까지는 REST방식으로 GET/POST/PUT/DELETE 등의 메소드를 사용해왔다.
RPC방식은 원격 프로시저 호출이라는 의미로 다른 컨텍스트에서 함수의 원격 실행을 허용하는 사양이다.
클라이언트에서 직접적으로 함수단위로 호출해서 사용이 가능한 이 방식을 이더리움 네트워크가 채택하고 있기 때문에 우리는 rpc방식으로 통신을 하여야한다.

이를 curl 형태로 나타내면 다음과 같다.

curl -X POST \
		-H "Content-type: application/json" \
		--data '{"jsonrpc" : "2.0", "method": "eth_accounts", "params": []}' \
		http://localhost:8545

가나쉬를 실행한 뒤 위 코드를 통해 가나쉬에 요청을 보내면 응답을 주는데,
data에 jsonrpc의 버전정보method에 함수명을 입력하면 서버측에서 해당 함수가 실행되면서 함수의 리턴값을 응답으로 보내주는 형태의 통신이 일어난다.
아래는 위 요청에 대한 가나쉬 서버의 응답이다.

{
	"jsonrpc":"2.0",
	"result":[
		"0x0c4990165af30f3259b217082ce4ab90dfc04754",
		"0x52147030561859690cca20d92cdcd430412e5966",
		"0x6c6b900f8e7817cfb0178bea913477d9bcaf0720",
		"0x2da56cc87da6124e0392894ff1446fec69356f20",
		"0xd0e28f8a7692ff765c21c1899ca510b95f7821e0",
		"0x1b12fb9b61ac1690d66a176a2eab74f250161b84",
		"0xf63a9b0dbd8173d857a4f79fa6b9e4e5d8045abe",
		"0x7b51470ae94f08cdf844df4a998393e61efa909f",
		"0x726ad88ef8321e13c604f8615b6af3b26f7bb6bd",
		"0x6dceb89b7351c9a6686a99946882851e75dacd0b"
	]
}

jsonrpc를 명시해주고 실제 data는 result에 담겨있다.


web3의 장점

위의 약간은 귀찮은 data 전송을 메소드 하나로 쉽게 할 수 있도록 해준다. 내부적으로 데이터형식의 변환이 필요한 곳도 알아서 처리를 해주기 때문에 사용자는 어떤 함수를 실행할지, 함수의 인자값으로 보낼 params에는 뭘 넣을지 정도만 작성해주면 된다.

web3 라이브러리를 이용하면 브라우저든 노드든 상관없이 어디서든 블록체인네트워크와의 쉬운 통신이 가능하다!

코드는 아래와 같다.

const web3 = new Web3(new Web3.providers.HttpProvider('http://127.0.0.1:8545'));
const accounts = await web3.eth.getAccounts();
console.log(accounts)
/* 콘솔
[
  "0x0c4990165af30f3259b217082ce4ab90dfc04754",
  "0x52147030561859690cca20d92cdcd430412e5966",
  "0x6c6b900f8e7817cfb0178bea913477d9bcaf0720",
  "0x2da56cc87da6124e0392894ff1446fec69356f20",
  "0xd0e28f8a7692ff765c21c1899ca510b95f7821e0",
  "0x1b12fb9b61ac1690d66a176a2eab74f250161b84",
  "0xf63a9b0dbd8173d857a4f79fa6b9e4e5d8045abe",
  "0x7b51470ae94f08cdf844df4a998393e61efa909f",
  "0x726ad88ef8321e13c604f8615b6af3b26f7bb6bd",
  "0x6dceb89b7351c9a6686a99946882851e75dacd0b"
]
*/


web3 라이브러리로 실행 가능한 함수들은 여기서 확인할 수 있다.

이더리움 관련 메소드로는

  • web3.eth.getBlockNumber()
  • web3.eth.getAccounts()
  • web3.eth.getBalance(account)
  • web3.eth.getTransactionCount(sender)
  • web3.eth.sendSignedTransaction(serializedTx)

등이 있고 그 외에도

  • web3.utils.toHex()
  • web3.utils.toWei()

와 같은 단위변환 util 함수도 있다.


(3) ether.js 라이브러리

기본적으로 web3와 비슷한 기능을 한다고 보면 될 것 같다. 이더리움 블록체인 생태계와의 상호작용을 하기위한 라이브러리로 정보를 받아오고 트랜잭션을 발생시킬 수도 있다.

web3에 비해서는 자료가 많이 없다...

오늘 공부한 파트에서는 이 라이브러리를 트랜잭션 객체를 생성하는 용도로 사용했다.

import * as ether from 'ethereumjs-tx';
const ethTx = ether.Transaction;

// 👇 트랜잭션 객체의 내용은 하단에서 더 자세히 설명할 예정
const txObject: ether.TxData = {
      nonce: web3.utils.toHex(txCount), 
      to: receiver,
      value: web3.utils.toHex(web3.utils.toWei('1', 'ether')),
      gasLimit: web3.utils.toHex(6721975),
      gasPrice: web3.utils.toHex(web3.utils.toWei('1', 'gwei')),
      data: web3.utils.toHex(''),
    };

const tx = new ethTx(txObject)


(4) 트랜잭션

지난 몇 주간 직접 블록체인 서버를 구축하고 비트코인 방식의 트랜잭션을 코드로 구현해보았다.

이더리움의 트랜잭션은 비트코인의 트랜잭션과 다음과 같은 차이가 있다

  • 트랜잭션마다 in과 Out을 따로 생성하지 않고
  • utxo의 개념이 없으며
  • 트랜잭션마다 nonce가 있어서 한 account에서 발생된 트랜잭션간의 순서로 balance를 파악한다.

새로 계정을 생성하게 되면 nonce는 0
첫번째 트랜잭션을 전송하게 되면 nonce가 1
두번째 트랜잭션을 전송하게 되면 nonce가 2
이렇게 전송할 때마다 증가하는 식이다.

만약 이 상태에서 nonce가 4인 트랜잭션을 처리하려고 한다면 nonce가 3인 트랜잭션의 전송내역이 있어야 한다. 처리되지 않은 nonce 3인 트랜잭션은 풀에 남아있다가 3,4가 연달아 처리되게 된다.

❗️ nonce가 필요한 이유?

nonce는 중복되지 않고 순차적이기 때문에, 같은 nonce 에 여러 트랜잭션 전송이 발생하였다면 해당 nonce 중 제일 높은 가스비를 지불한 트랜잭션이 처리된다. 이더리움에서는 이러한 방법으로 이중 지불 문제를 방지할 수 있다.
너무 낮은 가스비를 지불한 경우에는 (아직 트랜잭션이 pending상태인 경우) 같은 nonce로 가스비를 높여 다시 트랜잭션을 보내게 된다면 취소된 효과를 볼 수도 있다.

트랜잭션Object

const txObject: ether.TxData = {
      nonce: web3.utils.toHex(txCount), // 보내는 사람의 트랜잭션 수 (현재 nonce를 보내면 알아서 +1해줌)
      to: receiver, // 받는 사람의 account
      value: web3.utils.toHex(web3.utils.toWei('1', 'ether')), // 전송할 금액. 단위로 wei를 써야함 + hex 변환
      gasLimit: web3.utils.toHex(6721975), // 트랜잭션의 최대 가스량
      gasPrice: web3.utils.toHex(web3.utils.toWei('1', 'gwei')), // 가스 단위 가격, wei
      data: web3.utils.toHex(''), // 지금은 작성 X. 스마트컨트랙트가 트랜잭션에 포함될 때 해당 내용을 넣음
    };

위에서 언급했듯 비트코인의 트랜잭션과는 내용이 조금 다른데,
utxo 관련 속성이 빠지고 noncegas 관련된 내용이 추가되었다.

이더리움은 evm을 통한 트랜잭션 및 컨트랙트 처리가 이루어지므로 해당 자원을 이용하는 수수료를 지불하게 되는데 이 비용이 바로 gas이다.

기본적으로 트랜잭션 전송 시에는 21000의 gas를 소비하게 되고
추가적인 연산의 양에 따라 추가적인 gas를 소모하게 된다.
다른 코드가 없는 트랜잭션에는 balance의 추가/감소 연산만 있어 4gas를 소모하며 결과적으로 21004라는 gas가 책정되게 된다.

서명 생성 및 트랜잭션 전송

etherjs 라이브러리를 사용하여 트랜잭션 인스턴스를 생성한 뒤 서명을 한다.
sign메소드와 serialize메소드를 이용해 서명 등을 생성한다. 이 때 서명에 사용된 private key로 from account 정보가 자동으로 생성되어 최종 트랜잭션 객체에 포함된다.
web3.eth.sendSignedTransaction() 메소드를 사용하여 만들어진 트랜잭션 인스턴스를 블록체인 서버 (지금은 가나쉬)로 보내고 해당 트랜잭션이 블록에 올라가게된다.

const tx = new ethTx(txObject); // tx 객체 생성
tx.sign(privateKey); // tx 객체에 서명 추가 (return값이 void)
const serializedTx = tx.serialize();
const txHash = await web3.eth.sendSignedTransaction('0x' + serializedTx.toString('hex'));



최종 트랜잭션 내용

{
      transactionHash: '0x65b95bc07d1e8847b8eeb32c6113cc0dbf49b9e8d21c0b4704032758ca06ddf3',
      transactionIndex: 0,
      blockHash: '0x71eb9d6914284d9cce0316dcbccb8be5239484edeea1def5d2edec0c75f2f4e9',
      blockNumber: 5,
      from: '0x0c4990165af30f3259b217082ce4ab90dfc04754',
      to: '0x52147030561859690cca20d92cdcd430412e5966',
      gasUsed: 21004,
      cumulativeGasUsed: 21004,
      contractAddress: null,
      logs: [],
      status: true,
      logsBloom: '0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000'
    }


💬 테스트 파일의 전체 내용은 github 참조

0개의 댓글