이번 포스팅에서는 블록체인 익스플로러(Blockchain Explorer)를 만들기 위해 프라이빗 블록체인 네트워크에서 RPC를 설정하는 방법에 대해 알아보고자 한다. 블록체인 익스플로러는 기본적으로 특정 블록체인 네트워크에 대한 검색 엔진이다. 대표적인 예로 Blockchain.com 사이트에서 제공하는 explorer가 있는데 사용자는 해당 사이트에서 특정 블록체인 및 지갑 주소의 거래와 관련된 다양한 정보들을 조회할 수 있다.
우선 geth를 사용해 프라이빗 네트워크를 구축한 다음 web3 라이브러리를 사용해 해당 네트워크 상의 블록 정보들을 조회할 수 있도록 만들어 볼 예정이다. 블록체인 익스플로러 사이트를 만들어보기 위한 기본적인 설정 작업이라고 생각하면 될 것 같다.
< 목차 >
- puppeth
- web3로 통신하기
1. puppeth
geth를 빌들해서 실행시키기 위해 git clone 받은 go-ethereum 디렉토리 안에서 make geth 명령어를 입력했었다. 하지만 go-ethereum 디렉토리 안에서 아래와 같이 make all 명령어를 입력해주면 geth 이외에도 go-ethereum 안에 있는 모든 파일들이 빌드된다.
$ # go-ethereum 모든 파일 빌드
$ make all
위와 같이 go-ethereum/build/bin 디렉토리 안에 모든 파일들이 빌드된 것을 확인할 수 있다. 이 중에서 우리가 실행시킬 것은 바로 puppeth 이다.
이전 포스팅에서는 genesis.json 파일을 만들어 제네시스 블록 속성들을 직접 작성해주었었다. 하지만 puppeth 을 사용하면 이러한 번거로움 없이 제네시스 블록, 부트노드, 채굴자 및 ethstats 서버에 이르기까지 새로운 이더리움 네트워크를 생성할 수 있다. 프라이빗 네트워크를 생성하고자 할 때 초기 설정값들을 잡아주는 도구라고 생각하면 될 것 같다. 터미널에 puppeth 를 입력할 시 아래와 같은 세팅 화면이 나오게 되고 목적에 맞게 알맞은 값들을 선택해주면 된다.
puppeth을 실행하기에 앞서 루트 디렉토리 안에서 datadir로 사용할 node 디렉토리를 생성해주도록 하자. 그리고 아래의 명령어를 입력하여 새로운 계정을 생성해주도록 하자. 계정을 먼저 생성해주는 이유는 puppeth 을 사용할 때 pre-funded account 를 지정해주기 위함이다.
$ geth --datadir node account new
이제 puppeth을 사용해 Configure new genesis 작업을 진행해주면 된다. pre-funded account 에는 앞서 만들어주었던 계정을 넣어주고 Export genesis configurations 를 통해 다음과 같이 bitkunst_ 디렉토리 안에 genesis configuration 파일들을 만들어 주었다.
이제 genesis configuration 파일들을 이용해 geth를 생성해주도록 하자.
$ geth --datadir node init ./bitkunst_/bitkunst.json
node 디렉토리 안에 genesis configuration 파일들을 토대로 geth 디렉토리가 생성된 것을 확인할 수 있다. datadir 로 되어있는 node를 이용해 geth를 실행시키게 되면 앞서 puppeth를 이용해 만들어주었던 genesis 블록 정보로 프라이빗 네트워크가 생성된다.
$ # geth 실행
geth --datadir node
Chain ID : 701 인 프라이빗 네트워크가 생성된 것을 알 수 있다.
2. web3로 통신하기
이전 포스팅에서는 IPC를 사용하여 로컬 프로세스에서 geth로 실행시킨 프라이빗 블록체인 네트워크와 통신을 주고 받았었다. IPC를 사용하기 위해 geth를 실행시킬 때 생성되는 파일인 geth.ipc 파일을 이용하였는데 해당 파일은 로컬에서만 접근이 가능하다. 따라서 메타마스크 혹은 다른 컴퓨터에서 우리의 프라이빗 블록체인 네트워크와 통신하기 위해서는 몇가지 설정들을 잡아줘야만 한다.
geth를 실행할 때 다음과 같은 옵션값을 줘서 실행시켜주도록 하자.
$ geth --datadir node --http --http.addr "0.0.0.0" --http.port 9000 --http.corsdomain "*" \
--http.api "admin,miner,txpool,web3,personal,eth,net" --syncmode full --networkid 701
각각의 옵션값이 의미하는 바는 다음과 같다.
- --http.addr "0.0.0.0" : 모든 ip에 대한 접근 허용.
- --http.port 9000 : 사용할 포트 번호. (9000번 사용)
- --http.corsdomain "*" : cors 설정하기. (여기서는 모든 도메인 허용)
- --http.api "admin,miner,txpool,web3,personal,eth,net" : 외부에서 어떤 모듈을 사용할 수 있게 할 것인지 설정.
- --syncmode full : 동기화 모드 full.
- --networkid 701 : 네트워크 아이디 701. (chain ID 와 동일한 값 입력)
주의할 점은 옵션값으로 들어가는 --networkid의 경우 puppeth에서 genesis configuration을 만들 때 입력했던 chain ID 와 반드시 그 값이 동일해야만 한다.
이제 geth attach [ip주소] 명령어를 통해 geth.ipc 파일이 없이도 우리의 프라이빗 네트워크와 통신하는 것이 가능하다.
현재 geth로 생성한 프라이빗 블록체인 네트워크 쪽에서 외부와 통신할 수 있는 통로를 만들어 놓은 상황이다. 이로써 Node.js 환경 뿐만 아니라 메타마스크와 같은 지갑 프로그램에서도 우리가 만든 프라이빗 블록체인 네트워크와 통신하는 것이 가능해졌다. 그리고 통신을 하기 위해 사용하게 될 것이 바로 web3 라이브러리이다. (web3 라이브러리를 사용해 노드와 통신)
이제 web3 라이브러리를 사용해 geth로 생성한 프라이빗 네트워크와 통신하는 것을 테스트 코드로 작성해보자. 우선 jest와 web3 라이브러리를 설치해주었다.
$ npm init -y
$ npm install web3 jest
jest.config.js 파일에서는 다음과 같이 간단하게 설정을 잡아주었다.
// jest.config.js 파일
const config = {
verbose: true, // 테스트 실행시 터미널 창에서 테스트 항목 확인
testMatch: ['<rootDir>/**/*.test.(js|ts)'], // 테스트 코드를 실행할 파일명
};
module.exports = config;
테스트 코드를 작성하기 전에 터미널에서 geth attach http://127.0.0.1:9000 를 입력해 geth와 통신할 수 있는 콘솔창을 열고 mine.start(8) 로 블록을 마이닝해주었다. 그런 다음 블록 number를 조회하기 위한 테스트 코드를 다음과 같이 작성하였다.
// block.test.js 파일
const Web3 = require("web3");
describe("Block test", () => {
let web3;
it("web3 연결 테스트", async () => {
web3 = new Web3(new Web3.providers.HttpProvider("http://127.0.0.1:9000"));
const block_number = await web3.eth.getBlockNumber();
console.log("블록 number : ", block_number);
});
});
web3 인스턴스를 생성할 때 new Web3( ) 의 인자값으로 new Web3.providers.HttpProvider("http://127.0.0.1:9000") 를 전달하여 geth 로 생성한 프라이빗 네트워크에 RPC를 사용해 요청을 보낼 수 있도록 하였다. 이제 web3 인스턴스의 메소드만을 사용해 http://127.0.0.1:9000 으로 요청을 보내는 것이 가능하다. 또한 Node.js 환경에서 geth로 생성한 프라이빗 블록체인 네트워크에 요청을 보내 원하는 결과값을 가져오는 것이므로 async-await을 사용해 비동기 처리를 해주었다.
다음으로 작성한 테스트 코드는 특정 블록 number에 해당하는 블록 정보를 조회하는 메소드에 대한 테스트 코드이다.
// block.test.js 파일
const Web3 = require("web3");
describe("Block test", () => {
let web3;
it("web3 연결 테스트", async () => {
web3 = new Web3(new Web3.providers.HttpProvider("http://127.0.0.1:9000"));
const block_number = await web3.eth.getBlockNumber();
console.log("블록 number : ", block_number);
const block = await web3.eth.getBlock(10, true);
console.log("블록 number 10 : ", block);
});
});
web3.eth.getBlock( ) 메소드의 인자값으로 가져오고 싶은 블록의 number 를 전달해주면 해당 블록의 정보를 조회하는 것이 가능하다. 여기서는 10번 블록의 정보를 조회하였다.
이제 트랜잭션에 관한 테스트를 진행해보도록 하자. geth attach http://127.0.0.1:9000으로 열어놓은 콘솔창에서 person.newAccount( ) 메소드를 통해 계정을 하나 생성해주었다.
그리고 코인베이스 계정에서 새로 생성한 계정으로 10 ETH를 전송하는 트랜잭션을 발생시켜주었다.
personal.sendTransaction({from: eth.coinbase, to: '54d550ef9da97e38d05fa0589414737e305a6b36', value: web3.toWei(10, 'ether')}, '1234')
txpool 을 조회해보면 다음과 같이 트랜잭션이 pending 상태로 되어있는 것을 확인할 수 있다.
miner.start(8) 메소드를 통해 블록을 마이닝 한 다음 계정의 잔액을 조회해보면 10 ETH 가 전송된 것을 확인할 수 있다.
마지막으로 web3.eth.getTransaction( ) 메소드와 web3.eth.getTransactionReceipt( ) 메소드의 차이점에 대해 간략히 알아보고 마무리짓도록 하겠다. 두 메소드 모두 인자값으로 트랜잭션 해시값을 받는데 getTransaction( ) 메소드가 반환하는 트랜잭션 데이터와 getTransactionReceipt( ) 메소드가 반환하는 트랜잭션 데이터는 서로 상이하다. 아래와 같이 트랙잭션 데이터가 담긴 블록에서 트랜잭션 해시값을 조회한 다음 메소드의 인자값으로 넣어주도록 하자.
// block.test.js
const Web3 = require("web3");
describe("Block test", () => {
let web3;
it("web3 연결 테스트", async () => {
web3 = new Web3(new Web3.providers.HttpProvider("http://127.0.0.1:9000"));
const block_number = await web3.eth.getBlockNumber();
console.log("블록 number : ", block_number);
const block = await web3.eth.getBlock(10, true);
console.log("블록 number 10 : ", block);
});
// getTransaction() 사용자가 서명까지 완료한 데이터의 결과물을 보여줌
// EVM을 거치기 전의 트랜잭션 내용
it("getTransaction", async () => {
const tx = await web3.eth.getTransaction(
"0x75c503bd98e2bbf2c8272f9ae3920411d4dce0bccbf842eaab5c0c2763a9f546"
);
console.log("tx : ", tx);
});
// geth 가 트랜잭션 내용을 실행한 결과물 (EVM을 거쳤다가 나온 트랜잭션)
it("getTransactionReceipt", async () => {
const tx = await web3.eth.getTransactionReceipt(
"0x75c503bd98e2bbf2c8272f9ae3920411d4dce0bccbf842eaab5c0c2763a9f546"
);
console.log("tx Receipt : ", tx);
});
});
web3.eth.getTransaction( ) 메소드가 반환하는 트랜잭션 객체는 서명 완료 시점까지의 트랜잭션 객체이다. 다시말해 EVM을 거치기 전까지의 트랜잭션 객체라고 생각하면 된다.
이와 달리, web3.eth.getTransactionReceipt( ) 메소드가 반환하는 트랜잭션 객체는 geth 에서 트랜잭션 내용을 실행한 결과물이다. 즉, EVM을 거쳤다가 나온 트랜잭션 객체라고 생각하도록 하자.
'Ethereum' 카테고리의 다른 글
Ethereum/이더리움 - JavaScript로 스마트 컨트랙트 배포 및 실행 (0) | 2022.07.12 |
---|---|
Ethereum/이더리움 - 스마트 컨트랙트 배포 및 실행 (3) | 2022.07.11 |
Ethereum/이더리움 - private 네트워크 (1) | 2022.06.30 |
Ethereum/이더리움 - 메타마스크 연결하기 (5) | 2022.06.29 |
Ethereum/이더리움 - Web3 (0) | 2022.06.29 |