본문 바로가기
개발 일지/블록체인

내 프라이빗 블록체인에서 입출금 트랜잭션 데몬구현

by StelthPark 2022. 2. 19.

우선 이전에 작성한 글에선 docker에 컨테이너를 만들때 생성단계에서 포트를 부여하지 않았다. 그래서 기존 2개의 노드를 연결해주는 방식으로 노드를 운영했더니 서버단에서 블록체인 네트워크에 접속 할 때 localhost:8545로 접속하면 아래와 같이 오류가 나왔다. 내가 만든 프라이빗 블록체인 네트워크에 접근할 수 없는 것이다.

 

그리하여 이미 생성된 컨테이너에 직접포트를 부여하기엔 어렵다고 생각해 기존컨테이너를 이미지화하고 그 이미지로 새로운 컨테이너를 생성하며 포트를 부여하는 방법을 사용하였다.

 

1. 포트를 연 새 컨테이너를 만들자

 

생성한 con_ubuntu를 이미지로 먼저 만들어보자.

sudo docker ps

 

해당 명령어로 con_ubuntu 컨테이너가 작동중인지 확인하자. 

sudo docker stop 컨테이너이름 또는 컨테이너 ID

 

만약 작동중이라면 해당 명령어를 통해 컨테이너를 중지시킨다.

#docker commit <옵션> <컨테이너 이름> <이미지 이름>:<태그>

 

이미지를 생성할때는 commit이라는 명령어를 사용한다. 나는 con_ubuntu라는 이미지를 새로 만들것이니 

sudo docker commit con_ubuntu con_ubuntu:latest

이미지가 생성된것을 확인하기 위해 docker images를 입력해본다.

 

짠!!! 잘 생성되었다. 문제는 commit 명령어를 입력하면 커서가 깜빡이고 용량이 커서 몇번 깜빡이다가 멈춰있다.. 여기서 다른작업을 해도 되지만 기다리면 만들어졌다고 sha256이 출력된다. 인내심을 가지고 기다려보자!!!

 

이제 포트8545를 연 컨테이너를 만들고 가동시키자.

 

sudo docker run --cap-add=NET_ADMIN -it -p 8545:8545 --name con_ubuntu2 con_ubuntu

우리가 금방 만든 con_ubuntu를 사용해 con_ubuntu2 컨테이너를 8545포트를 열어 실행시키고 컨테이너를 만들게 된다. 이후 이전 포스팅한 글처럼 geth를 가동하면 localhost:8545로 서버가 접속할 수있다.

 

2. 입출금을 기록하자

노드 가동을 위해 도커 컨테이너를 시작하고 geth로 접속한다

docker start con_ubuntu2
cd ~
ls
cd go-ethereum
geth --networkid 1007 --datadir test_node1 --nodiscover --port 30303 --allow-insecure-unlock --http --http.port "8545" --http.addr "0.0.0.0" --http.corsdomain "*" --http.api "eth, net, web3, miner, debug, personal, rpc" console

주의할 점은 go-ethereum 디렉토리가 있는 곳에서 geth를 실행하자. 아니면 네트워크가 활성화 되지 않는다. geth에 들어오면 명령어 입력창이 >로 바뀐다. eth.blockNumber를 찍어 블록을 확인해보자

 

현재 2개의 계정이 있고 블록넘버는0이다. 로컬에서 node.js로 해당 네트워크에 web3로 연결하며 입출금되는 블록정보를 모아 데이터베이스에 저장하고자한다. vscode에서 web3로 지금 동작중인 프라이빗 블록체인에 접속해보자!

 

2.1 내 블록체인에 외부에서 접근하기

web3를 통해 프로바이더를 받아 접근할수 있다. 우리는 8545포트로 생성한 컨테이너이므로 변수명이 ganache이지만 해당변수를 통해 아래 web3로 접근 할 수있다. docker에서 컨터이너를 열고 geth로 노드를 접속시키면 web3.eth.blockNumber로 바로위에서 geth를 통한 blockNumber를 한것과 같은 실행을 할수 있게된다.

 

node로 index.js를 돌리게 되면 아래와 같이 index.js안에 있는 getAccount에 의해 위 생성된 지갑들이 서버단 터미널에 보이게 된다.

 

 

2.2 POST 요청을 날려 외부에서 계정 생성하기

해당 newAccount의 요청 주소는 http://localhost:3000/account/new 이다. 페이로드로 password를 보내야하며 이는 우리가 마이이더월렛 같은 웹지갑등에서도 지갑을 만들때 들어가는 비밀번호와 비슷한개념이다 요청을 날리게 되면 나의 블록체인에 web3로 접근하여 새 계정을 만들어준다. 그리고 내 서버의 DB에 비밀번호와 주소를 저장시키게 된다.

 

password를 넣어 post요청을 날리니 응답으로 생성된 지갑주소가 나오고 DB에 새 주소와 비밀번호가 저장된다. 몰론 Docker로 접속중인 geth에 personal.listAccount를 찍어보면 아래와 같이 나온다.

 

새롭게 주소가 하나 추가되었다.

 

2.3 입출금블록을 확인하는 코드를 써보자

입출금을 확인하는 코드를 작성하여 pm2로 특정 시간마다 계속 동작시키게 하면 새로운 블록이 생성되면 그 블록이 입출금 블록인지 확인하여 내 DB에 저장시키게 될것이다. 여기서 고려해야할 것은 한번 동작 시킬때마다 제네시스 블록부터 검사하면 이미 검사한 블록까지 새로 검사하게 되므로 불필요한 행동을 많이하게 될 것이다. 이를 blockNumber라는 파일을 하나만들어서 안에 든 숫자를 지금까지 검사한 블록수로 저장시키고 다음 동작을 할때 이 저장된 최근 검사 블록수 보다 한자리 높은 수부터 검사하면 효율적이게 될 것이다. 이제 작성된 로직을 살펴보자.

 

const fs = require("fs");
const path = require("path");

// 가장 마지막에 확인한 블록번호 조회
const checkedBlockNum = Number(
fs.readFileSync(path.join("/home/juneyoung/Desktop/node-daemon", "/utils/blockNumber"), {
encoding: "utf-8",
})
);

const Web3 = require("web3");
const web3 = new Web3(new Web3.providers.HttpProvider("http://localhost:8545")); 내 블록체인에 접근
const allTransactions = [];
let lastest = checkedBlockNum;

const getTx = async (tx) => await web3.eth.getTransaction(tx); 

const getLastestTransactions = async () => {
try {
// 최신 블록번호가 마지막에 확인한 블록번호보다 크다면,
// 그 차이만큼 블록을 조회하기 위해 범위 업데이트
await web3.eth.getBlockNumber((err, result) => {
if (err) throw err;
if (result > lastest) {
lastest = result;
}
});

if (checkedBlockNum === lastest) {
return [];
} else {
// 가장 마지막에 확인한 블록의 다음 블록부터 가장 최신 블록까지의 모든 트랜잭션 조회
for (let i = checkedBlockNum + 1; i <= lastest; i++) {
const block = await web3.eth.getBlock(i);
// 트랜잭션 해시로 모든 트랜잭션 조회
for (let tx of block.transactions) {
// 블록안에 든 트랜잭션의 tx들만 모아서 정보를 가져와 allTransactions에 담는다.
allTransactions.push(getTx(tx));
}
}

// 모든 트랜잭션 중에서, 조건에 부합하는 트랜잭션을 배열로 리턴(Promise)
return Promise.all(allTransactions)
.then((data) => {
const result = [];

for (let tx of data) {
//입출금만 제어
if (tx.from !== undefined && tx.to !== undefined && Number(tx.value) > 0) {
// 송금자,수신자가 있어야하며 보내는 msg.value가 있어야한다.
result.push(tx);
}
}
return result;
})
.then((data) => {
// 가장 마지막에 확인한 블록번호 저장
fs.writeFileSync(path.join("/home/juneyoung/Desktop/node-daemon", "/utils/blockNumber"), String(lastest));
return data;
});
}
} catch (err) {
console.log(err);
}
};

module.exports = { getLastestTransactions };

작성된 getLastestTransaction를 외부로 익스포트 해주고 해당 함수를 index.js에서 받는다.

 

daemon.js 를 pm2로 동작시키면 getBlock함수가 작동되고 내부에선 아까 익스포트로 받은 함수가 실행된다. 실행이 완료 되면 결과를 tx.hash와 block넘버를 받아 DB에 차곡차곡 쌓게된다! 이제 geth든 web3를 이용해서든 송금트랜잭션을 하나 만들어 블록에 올리고 데이터베이스에 담아보자

 

우선 보낼 계정의 lock을 해제해주어야한다. 계정 잠금을 풀어야 해당 계정에서 돈을 인출하거나 보낼 수 있겠지?

해제했다. 이후 라우터로 작성한 post를 날려 1eth를 보내는 전송 트랜잭션을 만들어보자. 참고로 채굴중이어야 내 트랜잭션을 생성하고 블록에 올릴 수있으니 miner.start(1)로 채굴을 시작시켜놓자.

 

트랜잭션을 만들었고 전송됐다. txhash를 반환받았으며 이후에 pm2를 돌려 입출금을 감시할때 우리 데이터 베이스에 해당 tx와 블록넘버가 담겨야 할것이다.

 

이제 Daemon.js를 pm2로 동작 시키자. 최근까지 조회한 블록넘버가 적힌 blockNumber 파일에 일단 0을 입력하고 pm2를 동작시키는데 pm2 명령어는 아래와 같다.

더보기
더보기

실행 : pm2 start <실행시킬 서버.js> 

리스트 확인 : pm2 list

중지 : pm2 stop <app_name>

재시작 : pm2 restart <app_name>

삭제 : pm2 delete <app_name>

 

나는 터미널에서 npx pm2 start daemon.js --cron "*/10 * * * * *" 을 작동시켰다. 이는 daemon.js 라는 파일을 10초마다 1번씩 동작 시킨다는것으로 cron뒤에 초 분 시간 이런식으로 단위를 나누게 된다. 동작이후 10분마다 어떤 처리를 하는지 console 등을 확인할 수있는 npx pm2 monit에 들어가보자.

 

pm2는 작동시키면 10초 상관없이 일단 한번 작동시킨다. 그후 10초마다 작동시키게 되며 위 처럼 데이터베이스에 정확히 담겼다. pm2가 동작중이면 계속해서 새로운 입출금이 생길때마다 데이터베이스에 담게된다!

 

 

 

댓글