OrbitsNetwork

2019.06 월간 보고서 (OrbitsNetwork)

목차

  1. Network Layer에서의 Node 설계

  2. Bitcoin P2P

  3. Golang

Network Layer에서 Node 설계

2019 Q3

  • Planet Wallet Launching

  • Network Layer Development

GBT Protocol 로드맵 상 2019년 3분기를 목표로 Network Layer 개발을 진행하고 있습니다. Micro Node와 Super Node의 네트워크 연결시 발생될 여러 상황들에 대해 기존 프로젝트들을 통해 장단점을 분석해나가고 있습니다.

Orbits Network는 Public BlockChain과 Private BlockChain이 조합된 형태입니다. Public BlockChain 방식으로 동작할 Micro Node의 네트워크 연결은 Bitcoin P2P, Ethereum P2P 운영을 분석하고 이를 가장 효율적인 형태로 응용하여 활용합니다. Private BlockChain 방식으로 동작할 Suprer Node의 네트워크 연결은 Hyperledger Fabric P2P, Tendermint P2P, EOS P2P 운영을 분석하고 이를 가장 효율적인 형태로 응용하여 활용합니다.

블록체인 노드들은 서로 트랜잭션과 블록 데이터를 끊임없이 주고 받습니다. 데이터를 받은 노드는 또 다른 노드로 이를 릴레이합니다. 만약 어떤 노드가 1MB 정도의 블록을 단순히 자기가 알고 있는 노드들에 보내고, 이 노드들이 또 자기가 알고 있는 네트워크상에는 이 데이터가 계속 복제되어 떠다니게 됩니다. 이미 그 블록을 받은 노드가 또 받을 수도 있습니다.

단순히 이런 상태라면 네트워크의 효율은 크게 떨어지기 때문에 데이터가 중복되지 않고 필요한 노드에만 전달될 수 있는 일종의 질서가 필요합니다.

이러한 질서를 프로토콜(Protocol, 통신규약)이라 합니다.

1. Micro Node

GBT Protocol Dapp 사용

  • 마이크로노드는 Dapp 개발자가 만든 Dapp을 사용한다. Dapp 사용 간, GBT 이동, SC 실행에 대한 트랜잭션을 생성합니다.

트랜잭션 검증

  1. 마이크로 노드는 GBT 트랜잭션을 검증하여 해당 트랜잭션이 유효한지 판단합니다.

  2. 검증자들 간에 합의된 내용을 바탕으로 새롭게 갱신된 GBT 상태를 저장합니다.

2. Super Node

블록 생성

  1. 마이크로 노드와 매크로 노드로부터 전파된 블록을 확인하여 순차적으로 확정한다. 만약 잘못된 블록이 전파되면 reject 합니다.

  2. 슈퍼 노드는 마이크로 노드와 매크로 노드가 블록 생성에 대해 투표한 결과를 기록합니다.

  3. 슈퍼 노드는 네트워크에서 발생하는 모든 트랜잭션을 블록 형태로 보유합니다.

마이크로 노드 검증 풀 생성

  1. 슈퍼 노드는 마이크로 노드의 트랜잭션을 검증할 노드를 일정 주기마다 랜덤으로 선정합니다.

  2. 슈퍼 노드는 마이크로 노드로부터 발생한 트랜잭션을 정렬하여 선정된 검증 풀에 전파합니다.

  3. 선별 슈퍼 노드는 검증 풀 생성을 위해 개별 마이크로 노드의 온/오프라인 상태를 점검합니다.

Bitcoin P2P

1. 노드 관리 메시지

네트워크에 참여한 노드들은 주변 노드의 상황을 파악해야 합니다. 노드들의 IP주소도 알아야 하고 각 노드가 지원하는 기능도 알아야 합니다. 그리고 노드들이 계속 활동 중인지도 알아야 합니다. 이를 위해 version, verack, addr, ping, pong 메시지를 주고 받습니다.

Command "addr"
bool AddAddress(CAddrDB& addrdb, const CAddress& addr)
{
if(!addr.IsRoutable())
return false;
if(addr.ip == addrLocalHost.ip)
return false;
CRITICAL_BLOCK(cs_mapAddresses)
{
map<vector<unsigned char>, CAddress>::iterator it =
mapAddresses.find(addr.GetKey());
if(it == mapAddresses.end())
{
//New address
mapAddresses.insert(make_pair(addr.GetKey(), addr));
addrdb.WriteAddress(addr);
return true;
}
else
{
CAddress& addrFound = (*it).second;
if((addrFound.nServices | addr.nServies) != addrFound.nServices)
{
//Services have been added
addrFound.nServices |= addr.nServices;
addrdb.WriteAddress(addrFound);
return true;
}
}
}
return false;
}

2.블록 데이터 동기화

블록체인 데이터가 전혀 없거나, 일부만 가지고 있는 풀 노드가 네트워크에 접속하면 다른 풀 노드와 블록체인을 맞춰야 합니다. 다른 풀 노드에게 부족한 블록 데이터를 요청해 블록체인을 최신 상태로 유지합니다. 그래야 트랜잭션을 검증하고 전파할 수 있으며, 새로 생성되는 블록을 검증하고 체인에 연결할 수 있습니다. 이를 블록 데이터 동기화 과정이라고 합니다.

Command “getdata”
class CNode
{
//.....
void PushInventory(const CInv& inv)
{
CRITICAL_BLOCK(cs_inventory)
if(!setInventoryKnown.count(inv))
vInventoryToSend.push_back(inv);
}
//......
}
inline void RelayInventory(const CInv& inv)
{
//Put on lists to offer to the other nodes
CRITICAL_BLOCK(cs_vNodes)
foreach(CNode* pnode, vNodes)
pnode->PushInventory(inv);
}
template<typename T>
void RelayMessage(const CInv& inv, const T& a)
{
CDataStream ss(SER_NETWORK);
ss.reserve(10000);
ss << a;
RelayMessage(inv, ss);
}
template<>
inline void RelayMessage<>(const CInv& inv, const CDataStream& ss)
{
CRITICAL_BLOCK(cs_mapRelay)
{
//Expire old relay messages
while(!vRelayExpiration.empty() && vRelayExpiration.front().first < GetTime())
{
mapRelay.erase(vRelayExpiration.front().second);
vRelayExpiration.pop_fron();
}
//Save original serialized message so newer versions are preserved
mapRelay[inv] = ss;
vRelayExpiration.push_back(make_pair(GetTime() + 15 * 60, inv));
}
RelayInventory(inv)
}

3. 신규 블록 데이터 릴레이

4. 트랜잭션 릴레이

새로운 트랜잭션이 발생하면 각 노드들은 이를 검증하고 인근 노드로 전파합니다. 트랜잭션 데이터도 inv, getdata, tx 메시지를 통해 이뤄집니다. 이때 데이터 타입은 MSG_TX나 MSG_WITNESS 입니다.

Command "tx"
void AddOrphanTx(const CDataStream& vMsg)
{
CTransaction tx;
CDataStream(vMsg) >> tx;
uint256 hash = tx.GetHash();
if(mapOrphanTransactions.count(hash))
return;
CDataStream* pvMsg = mapOrphanTransactions[hash] = new CDataStream(vMsg);
foreach(const CTxIn& txin, tx.vin)
mapOrphanTransactionsByPrev.insert(make_pair(txin.prevout.hash, pvMsg));
}
void EraseOrhpanTx(uint256 hash)
{
if(!mapOrphanTransactions.count(hash))
return;
const CDataStream* pvMsg = mapOrphanTransactions[hash];
CTransaction tx;
CDataStream(*pvMsg) >> tx;
foreach(const CTxIn& txin, tx.vin)
{
for(multimap<uint256, CDataStream*>::iterator mi =
mapOrphanTransactionsByPrev.lower_bound(txin.prevout.hash);
mi != mapOrphanTransactionsByPrev.upper_bound(txin.prevout.hash);)
{
if((*mi).second == pvMsg)
mapOrphanTransactionsByPrev.erase(mi++);
else
mi++;
}
}
delete pvMsg;
mapOrphanTransactions.erase(hash);
}

Golang

Go언어는 웹 브라우저, 서버, 데이터베이스 등 규모가 크고 복잡한 애플리케이션을 개발하는 데 적합합니다. 이러한 분야는 이제 메모리 관리에 시간을 쏟기보다는 로직에 집중하는 것이 중요해졌습니다. 따라서 메모리를 일일이 신경 쓰지 않아도 되는 Go언어로 작성하면 생산성을 높일 수 있습니다.

Go언어는 메모리 관리가 다소 느슨해도 되고, 규모가 크고 복잡하며 유지보수가 빈번한 곳에서 편리하게 사용할 수 있습니다. 그리고 다양한 네트워크 라이브러리(패키지)를 제공하므로 인터넷 프로그래밍에 유용합니다.

블록체인에서의 Golang 사용

고루틴 활용 웹크롤러 제작 실습

//최종 실습 예제
package main
import (
"bufio"
"fmt"
"net/http"
"os"
"strings"
"sync"
"github.com/yhat/scrape"
"golang.org/x/net/html"
"golang.org/x/net/html/atom"
)
//스크랩핑 대상 URL
const (
urlRoot = "http://"
)
//첫 번째 방문(메인페이지) 대상으로 원하는 url을 파싱 후 반환하는 함수
func parseMainNodes(n *html.Node) bool {
if n.DataAtom == atom.A && n.Parent != nil {
return scrape.Attr(n.Parent, "class") == "row"
}
return false
}
//에러 체크 공통 함수
func errCheck(err error) {
if err != nil {
panic(err)
}
}
//동기화를 위한 작업 그룹 선언
var wg sync.WaitGroup
//url 대상이 되는 페이지(서브페이지) 대상으로 원하는 내용을 파싱 후 반환
func scrapContents(url string, fn string) {
//작업 종료 알림
defer wg.Done()
//Get 방식 요청
resp, err := http.Get(url)
errCheck(err)
//요청 Body 닫기
defer resp.Body.Close()
//응답 데이터(Html)
root, err := html.Parse(resp.Body)
errCheck(err)
//Response 데이터(html)의 원하는 부분 파싱
matchNode := func(n *html.Node) bool {
return n.DataAtom == atom.A && scrape.Attr(n, "class") == "deco"
}
//파일 스트림 생성(열기) -> 파일명, 옵션, 권한
file, err := os.OpenFile("c:\\scrape\\"+fn+".txt", os.O_CREATE|os.O_RDWR, os.FileMode(0777))
errCheck(err)
//메소드 종료시 파일 닫기
defer file.Close()
//쓰기 버퍼 선언
w := bufio.NewWriter(file)
//matchNode 메소드를 사용해서 원하는 노드 순회(Iterator)하면서 출력
for _, g := range scrape.FindAll(root, matchNode) {
//url 및 해당 데이터 출력
fmt.Println("result : ", scrape.Text(g))
//파싱 데이터 -> 버퍼에 기록
w.WriteString(scrape.Text(g) + "\r\n")
}
w.Flush()
}
func main() {
//메인 페이지 Get 방식 요청
resp, err := http.Get(urlRoot) //response(응답), request(요청)
errCheck(err)
//요청 Body 닫기
defer resp.Body.Close()
//응답 데이터(Html)
root, err := html.Parse(resp.Body)
errCheck(err)
//ParseMainNodes 메소드를 크롤링(스크랩핑) 대상 URL 추출
urlList := scrape.FindAll(root, parseMainNodes)
for _, link := range urlList {
//대상 url 1차 출력
//fmt.Println("Check Main Link : ", link, idx)
//fmt.Println("TargetUrl : ", scrape.Attr(link, "href"))
fileName := strings.Replace(scrape.Attr(link, "href"), "http://", "", 1)
//fmt.Println(strings.Replace("oink oink oink oink", "k", "ky", 2))
//fmt.Println("fileName : ", fileName)
//작업 대기열에 추가
wg.Add(1) //Done 개수와 일치
//고루틴 시작 -> 작업 대기열 개수와 같아야 함
go scrapContents(scrape.Attr(link, "href"), fileName)
}
//모든 작업이 종료 될 때까지 대기
wg.Wait()
}

Reference

  1. 파이썬으로 배우는 블록체인 구조와 이론 / 박미정,박은진 / 위키북스

  2. A Dissection of Bitcoin / Paul Huang

  3. 가장 빨리 만나는 Go언어 / 이재홍 / 길벗