[강좌] 이더소셜 PHP API 서버 만들기 #6. 트랜잭션 기록하기

in topmining •  6 years ago 

안녕하세요.
쌩광부입니다.

지난 강좌
https://steemit.com/@topmining
https://www.ddengle.com/@TopMining

전체 소스
https://github.com/topmining/ethersocial-php-api

이번 강좌는 블록을 읽어서 트랜잭션을 데이터베이스에 저장하는 과정입니다.
이더리움의 블록 구조는 지난 시간에 설명을 했으니 참고해주시고요.

먼저 MySQL의 아래 2개의 테이블을 생성합니다.

CREATE TABLE `block` (
  `blocknumber` bigint(20) NOT NULL,
  `timestamp` bigint(20) DEFAULT NULL,
  `blockhash` varchar(66) DEFAULT NULL,
  `transactions` int(11) DEFAULT NULL,
  PRIMARY KEY (`blocknumber`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

CREATE TABLE `txaccount` (
  `hash` varchar(66) NOT NULL,
  `fromaddr` varchar(42) DEFAULT NULL,
  `toaddr` varchar(42) DEFAULT NULL,
  `timestamp` bigint(20) DEFAULT NULL,
  `blocknumber` bigint(20) DEFAULT NULL,
  `blockhash` varchar(66) DEFAULT NULL,
  `txindex` int(11) DEFAULT NULL,
  `gas` decimal(65,0) DEFAULT NULL,
  `gasprice` decimal(65,0) DEFAULT NULL,
  `value` decimal(65,0) DEFAULT NULL,
  `input` text,
  `v` varchar(22) DEFAULT NULL,
  `r` varchar(82) DEFAULT NULL,
  `s` varchar(82) DEFAULT NULL,
  `nonce` varchar(22) DEFAULT NULL,
  PRIMARY KEY (`hash`),
  KEY `idx_from` (`fromaddr`,`blocknumber`),
  KEY `idx_to` (`toaddr`,`blocknumber`),
  KEY `idx_block` (`blocknumber`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

block 테이블에는 블록에 대한 기본 정보들을 저장합니다.
큰 의미는 없지만 마지막으로 몇 번째 블록을 읽었는지를 확인합니다.

txaccount 테이블에는 트랜잭션 내용을 저장합니다.

지난 강좌에서 만들었던 api-config.php 파일에 아래 내용을 추가합니다.

$mysql_host = "127.0.0.1";
$mysql_user = "esnapi"; // YOUR ID
$mysql_pass = "1234"; // YOUR PASSWORD
$mysql_database = "ethersocial";

MySQL 접속을 위한 정보인데요.
이 부분은 알아서 수정하시는 센스!

또 지난 강좌에서 만들었던 json-rpc.php 파일에 아래 내용을 추가합니다.

function getEtherRpcMulti($host, $port, $list) {
    $ch = curl_init();

    curl_setopt($ch, CURLOPT_URL, $host);
    curl_setopt($ch, CURLOPT_PORT, $port);
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, TRUE);
    curl_setopt($ch, CURLOPT_HTTPHEADER, array('Content-Type: application/json'));
    curl_setopt($ch, CURLOPT_POST, TRUE);

        $data = "[";
    $n = 0;
    foreach($list as $item) {
        if($n > 0) $data .= ",";
        $data .= '{"jsonrpc":"2.0","method":"'.$item['method'].'","params":['.$item['params'].'],"id":'.$item['id'].'}';
        $n++;
    }
    $data .= "]";
    curl_setopt($ch, CURLOPT_POSTFIELDS, $data);

    $ret = curl_exec($ch);
    curl_close($ch);

    return $ret;
}

기존에 getEtherRpc 함수를 만들어서 사용했었는데요.
이번에는 getEtherRpcMulti 함수를 추가해서 JSON RPC를 동시에 여러개를 호출할 수 있게합니다.
블록이 많아지는 경우 매우 많은 호출을 해야되기 때문에 1건씩 호출하면 매우 느립니다.
그렇기 때문에 이 함수를 만들어 좀 더 빠르게 호출할 수 있습니다.

이제 본격적인 코딩을 위해 read_block.php 파일을 만듭니다.
전체 소스는 아래 링크를 확인해주세요.

https://github.com/topmining/ethersocial-php-api/blob/master/read_block.php

소스를 한줄 한줄 설명하겠습니다.

use Brick\Math\BigInteger;
wei 단위의 매우 큰 이더리움의 숫자를 처리하기 위해 BigInteger를 이용합니다.

$json = getEtherRpc($api_host, $api_port, 'eth_blockNumber', '"latest"', $id++);
eth_blockNumber 명령을 호출해서 현재 블록체인의 마지막 블록이 몇 번인지를 확인합니다.

$tonumber = hexdec($data['result']);
결과값이 Hex값이기 때문에 10진수로 변경합니다.

$conn = new mysqli($mysql_host, $mysql_user, $mysql_pass, $mysql_database);
MySQL에 연결합니다.

if($query = $conn->query("select max(blocknumber) from block"))
block 테이블에서 저장된 최종 블록을 읽어옵니다.
저희는 read_block.php 파일을 지속적으로 실행시켜서 저장된 최종 블록 이후에 생성된 블록을 읽어와 저장하게 됩니다.

$blocknumber=$fromblock;
while($blocknumber<=$tonumber)

$fromblock은 기존에 block 테이블에 저장된 최종 블록입니다.
$tonumber는 블록체인에 저장된 최종 블록입니다.
즉, $fromblock에서 $tonumber까지 반복해서 블록을 확인하기 위함이죠.

$list = [];
for($i=0; $i<100; $i++) {
  if($blocknumber > $tonumber) break;
  $list[] = array(
    "method" => "eth_getBlockByNumber",
    "params" => '"0x'.dechex($blocknumber).'", true',
    "id" => $id++
  );
  $blocknumber++;
}

RPC 명령을 100개씩 배열에 넣습니다.

$json = getEtherRpcMulti($api_host, $api_port, $list);
위에 만들어 놓은 getEtherRpcMulti 함수를 이용해 블록 정보를 읽어옵니다.

foreach($datas as $data)
결과값을 순차적으로 읽습니다.
RPC를 Multi로 호출하면 배열 형태로 값을 가져올 수 있습니다.

foreach($transactions as $tr)
블록에는 여러개의 트랜잭션이 저장되기 때문에 반복해서 읽습니다.

$value = BigInteger::parse(str_replace("0x", "", $tr['value']), 16);
이더리움의 숫자 단위는 wei라는 10의 18승 이라는 엄청나게 큰 값이므로 기존 integer로는 처리할 수가 없습니다.
그렇기 때문에 BigInteger를 이용하는 거죠. ^^

if(strcasecmp($fromaddr, "0x2f16af67dbd141c53beb03a533de6ab3bd0e69df") == 0) continue;

이 부분은 생략해도 되는 부분입니다만.
이더소셜의 경우 지난번 트랜잭션 공격이 있었는데요.
위 주소에서 의미없는 엄청나게 많은 트랜잭션을 발생시켜 문제가 되었죠.
그렇기 때문에 해당 주소의 경우는 아예 테이블에 저장하지 않는게 좋습니다.
DB 용량을 엄청나게 줄일 수 있죠.

$sql = "insert into txaccount ... on duplicate key update ...

테이블에 저장하기 위한 쿼리를 만듭니다.
이때 on duplicate key update 쿼리를 만들어 중복되는 경우 새로 insert되지 않도록 합니다.

$sql = "insert into block ... on duplicate key update ...

트랜잭션 저장이 완료되면 블록을 저장합니다.
이때도 마찬가지로 on duplicate key update 쿼리를 만들어 중복 방지는 필수!

코딩을 마쳤으면 한번 실행해볼까요.
우분투 콘솔창에서 read_block.php 파일이 있는 곳으로 이동 후 아래와 같이 입력합니다.
물론 MySQL이 실행중이며 위에 설명한 테이블이 만들어져 있어야 하고요.
또한 gesn도 실행중인 상태여야 겠죠.

$ php read_block.php
Current block height: 2602588
Block: 100
Block: 200
Block: 300
Block: 400
...

위와 같이 나오면 성공!!
100 블록씩 읽어서 DB 테이블에 저장하는 과정입니다.
2602588 블록까지 저장되면 중지되는데요.
아주 많은 시간이 걸립니다.
일단은 최종 블록까지 저장될때까지 기다리셔야 되고요.
그 이후에는 crontab을 이용해 주기적으로 새로 생성된 블록을 기록해야합니다.

crontab에 대해서는 아래 링크를 확인하세요.
https://zetawiki.com/wiki/%EB%A6%AC%EB%88%85%EC%8A%A4_%EB%B0%98%EB%B3%B5_%EC%98%88%EC%95%BD%EC%9E%91%EC%97%85_cron,_crond,_crontab

오늘 강좌는 끝~~
다음 시간에는 계정별로 저장된 트랜잭션을 읽는 API를 만들겠습니다.

그럼 다음 강좌도 많이 기대해주세요~~

Authors get paid when people like you upvote their post.
If you enjoyed what you read here, create your account today and start earning FREE STEEM!
Sort Order:  

Hello @topmining! This is a friendly reminder that you have 3000 Partiko Points unclaimed in your Partiko account!

Partiko is a fast and beautiful mobile app for Steem, and it’s the most popular Steem mobile app out there! Download Partiko using the link below and login using SteemConnect to claim your 3000 Partiko points! You can easily convert them into Steem token!

https://partiko.app/referral/partiko