Karen
15k
2018-01-31 16:18:36 작성 2018-01-31 17:05:58 수정됨
1
3662

골빈해커님의 g-coin 분석 by tykim - 1부


블록체인은 잘 모르지만 믿고 따라해보는 골빈해커님이 블록체인 g-coin 소스를 올려주셨기에 한 번 탐독해봤습니다.

무엇보다 가장 놀란 건 소스 코드가 너무 간결하네요. 소스 코드 한 줄 한 줄에 내공이 느껴집니다. 오랜만에 이런 힐링 되는 소스 코드를 보게 되는 바람에 급 탐독하게 되었습니다.

이론적인 배경이 없는 상태에서 소스 코드를 분석하면서 이해하는 방식이라 ‘파이썬을 조금 알고’, ‘저와 같이 이론 배경이 없으면서’, ‘퍼즐 맞추기를 좋아하시는 분’이 읽으시면 재미있을 것 같네요.





소스 코드


골빈해커님의 깃허브에서 소스 코드를 다운로드 받으실 수 있습니다.

gcoin 폴더에 들어가시면 아래 파일을 보실 수 있습니다.

  • block.py
  • blockchain.py
  • book.py
  • miner.py
  • node.py
  • proof.py
  • transaction.py


기초적인 개념을 코드와 같이 익혀볼 예정입니다. 자 따라가보시죠.




거래 정보 (transaction.py) 알아보기


먼저 거래에 대해서 알아보겠습니다.

‘transaction.py’을 열어봅니다.

거래 정보에는 ‘보내는 이’, ‘받는 이’, ‘거래량’이 포함되어 있군요.


class Transaction:

    def __init__(self, sender, recipient, amount):

        """Transaction



        Args:

            sender (str): 보내는 이

            recipient (str): 받는 이

            amount (int): 거래량 (양수)

        """

        self.sender = sender

        self.recipient = recipient

        self.amount = amount



        if amount < 1:

            raise Exception('Amount have to be positive number.')



    def dump(self): # 거래 정보를 반환합니다.

        return {

            'sender': self.sender,

            'recipient': self.recipient,

            'amount': self.amount

        }



    @classmethod

    def init_from_json(cls, data): # JSON으로부터 거래 정보를 로딩합니다.

        return cls(data['sender'],

                   data['recipient'],

                   data['amount'])


이제 함수를 살펴볼까요?

  • init(): 입력되는 보내는 이, 받는 이, 거래량으로 거래 정보를 초기화합니다.
  • dump(): 거래 정보를 반환하는 함수인데, 어딘가 쓰이겠죠?
  • init_from_json(): JSON으로부터 거래 정보를 초기화하는 함수입니다. 정보를 JSON으로 주고 받나 봅니다.


가볍게 코드 리딩하기에 부담 없는 시작이네요.




블록(block.py) 알아보기


다음은 블록에 대해서 알아보겠습니다. 블록은 아래 항목들의 정보가 포함되어 있네요.

  • transactions (list): 거래정보 목록
  • proof (int): ???
  • previous_hash (str): ???
  • timestamp (float): 블록이 생성된 시각


일단 블록이 아까 본 거래 정보가 리스트로 되어 있어 여러 개를 가지고 있을 수 있습니다.

timestamp에는 블록이 생성되는 시각이 담겨 있네요.

‘proof’와 ‘previous_hash’는 모르는 개념이지만 ‘proof’는 10으로, ‘previouse_hash’는 문자열로 타입입니다.



import json

import hashlib

from time import time



from gcoin.transaction import Transaction



class Block:

    def __init__(self, transactions, proof=0,

                 previous_hash=None, timestamp=0):

        """Block



        Args:

            transactions (list): list of Transaction object

            proof (int):

            previous_hash (str):

            timestamp (float):

        """

        self.transactions = transactions

        self.proof = proof if proof else 10

        self.timestamp = timestamp if timestamp else time()

        self.previous_hash = previous_hash if previous_hash else 'g'



    def hash(self):

        """Make hash of current block"""

        block_dump = json.dumps(self.dump(), sort_keys=True).encode()

        block_hash = hashlib.sha256(block_dump).hexdigest()



        return block_hash



    def dump(self):

        return {

            'transactions': [t.dump() for t in self.transactions],

            'previous_hash': self.previous_hash,

            'proof': self.proof,

            'timestamp': self.timestamp

        }



    @classmethod

    def init_from_json(cls, data):

        transactions = [Transaction.init_from_json(t)

                        for t in data['transactions']]



        return cls(transactions,

                   data['proof'],

                   data['previous_hash'],

                   data['timestamp'])


이제 함수를 보겠습니다.


  • init(): 블록 정보를 초기화합니다.
  • dump(): 블록 정보를 반환합니다. 여기서 아까본 거래 정보에서 Transaction::dump() 함수가 쓰이네요. 리스트로 되어 있기에 하나씩 덤프 뜨면서 리스트로 넣습니다. (덤프 뜨다: 내용을 복사해서 가지고 오다.)
  • init_from_json(): JSON 데이터를 받아서 블록 정보를 초기화합니다. dump()와 같이 거래 정보는 여러 개이기 때문에 이를 위한 처리가 포함되어 있네요.
  • hash(): 끼약~ 뭔가 어려워 보이는 함수들이 많네요. 해쉬 개념도 필요해 보입니다.



hash()


hash() 함수를 조금 더 살펴보겠습니다.

  1. 블록정보를 덤프뜬다. ‘self.dump()’
  2. 1번에서 뜬 블록정보를 json 형식으로 덤프뜬다. ‘json.dumps()’
  3. 2번에서 뜬 덤프를 block_dump에 저장한다.
  4. block_dump에 해당하는 해쉬값을 계산하여 반환한다. sha256와 hexdigest가 보이니 대충 256비트의 헥사(16진수)값으로 변환되나 봅니다.


해쉬에 대한 개념은 모르나 블록 정보를 256비트의 어떤 값으로 바꾸는 것 정도라고만 알아도 무방할 듯 하네요.

여기까지가 ‘거래 정보’와 그 거래 정보 여러 개를 담고 있는 ‘블록 정보’에 대해서 알아봤습니다.

‘채굴, 채굴’ 그러는데, 다음은 블록을 어떻게 채굴하는지 궁금해집니다.




채굴자(miner.py) 알아보기


‘채굴자’라는 의미 같은 ‘Miner’는 account_id 정보만 받네요. 음 채굴하려면 계정이 있어야 되나 봅니다.

  • ‘GENESIS_ACCOUNT_ID’은 뭔가 신 같은 존재의 ID가 있네요. ID 정보는 ‘0’입니다. 기억해두죠.
  • AMOUNT_OF_REWARD : 보상의 양이란 뜻인데, 채굴에 성공하면 받는 양 같습니다. 한 번 채굴할 때 하나씩 보상받습니다.


import gcoin.proof as proof

from gcoin.transaction import Transaction



GENESIS_ACCOUNT_ID = '0'

AMOUNT_OF_REWARD = 1



class Miner:

    def __init__(self, account_id):

        self.account_id = account_id



    def __call__(self, blockchain):

        last_block = blockchain.last_block()



        # Proof of Work

        new_proof = proof.find_proof(last_block.proof)



        # Adding mining rewards

        transaction = Transaction(GENESIS_ACCOUNT_ID,

                                  self.account_id, AMOUNT_OF_REWARD)

        blockchain.add_transaction(transaction)



        # Make new block with new proof,

        #   transactions and hash of last block

        block = blockchain.new_block(new_proof)



        return block


함수는 두 개뿐이네요.

  • init(): 주어진 계좌로 채굴자를 초기화합니다.
  • call(): 특수 함수인 것 같은데 이건 어떻게 호출 되죠? 문법은 조금 있다가 알아보겠습니다.



call()


일단 call() 함수 안을 살펴보겠습니다.

  1. 드디어 blockchain(블록체인)이란 녀석이 나왔네요. 블록체인에 대해서는 아직 살펴보지 않았지만, “blockchain.last_block()”을 통해서 가장 마지막에 있는 블록을 가져옵니다.
  2. proof(증명자)이란 녀석도 나왔습니다. 이 개념도 잘 모르지만 마지막 블록의 proof 값으로 증명을 찾아서 어떤 값을 반환하네요.
  3. 오~ 우리가 알고 있는 거래 정보(Transaction) 객체가 나왔습니다. 거래 정보와 입력값을 매칭해보죠. 신적인 존재로부터 채굴자의 계좌에 ‘1’만큼 보상을 하라는 거래 정보가 생겼네요. 벌써부터 부자가 된 느낌인데요?
    ○ sender <- GENESIS_ACCOUNT_ID
    ○ recipient <- self.account_id
    ○ amount <- AMOUNT_OF_REWARD
  4. blockchain에 add_transaction() 함수를 호출해서 거래를 추가하네요.
  5. 그리고는 새로운 증명과 함께 blockchain으로부터 블록을 하나 생성합니다.


아직 ‘proof(증명)’과 ‘blockchain(블록체인)’에 대해서는 모르지만 정리해보겠습니다.

  • 채굴자는 블록체인의 마지막 블록을 가지고 와서 새로운 증명을 찾은 다음,
  • 신으로부터 보상을 받는 거래 정보를 생성한 뒤 블록체인에게 추가해달라고 하고,
  • 찾은 새로운 증명으로 블록체인에 새로운 블록 생성 요청한 후
  • 새로 생긴 블록을 반환합니다.

채굴자는 증명 찾기의 대가로 블록체인으로부터 보상도 받고 새로운 블록도 받나봅니다.


다음은 blockchain과 proof을 봐야겠죠? proof가 더 간단해 보이니 이것부터 보겠습니다.




증명 찾기(proof.py) 알아보기


증명 찾기라는 용어 자체가 생소하지만 구글 검색 찬스는 나중에 하기로 하고, 일단 코드에 집중해보겠습니다.


import hashlib



DIFFICULTY = 1  # number of digits is difficulty

VALID_DIGITS = '0' * DIFFICULTY



def valid_proof(last_proof, proof):

    """ Validates proof



    last digits of hash(last_proof, proof)

        == VALID_DIGITS



    Args:

        last_proof (int): previous proof

        proof (int): proof to validate



    Returns:

        bool:

    """

    proof_seed = '{0}{1}'.format(last_proof, proof).encode()

    proof_hash = hashlib.sha256(proof_seed).hexdigest()



    return proof_hash[:DIFFICULTY] == VALID_DIGITS



def find_proof(last_proof):

    """proof of work



    Args:

        last_proof (int):



    Returns:

        int: proof

    """

    proof = 0



    while valid_proof(last_proof, proof) is False:

        proof += 1



    return proof


숫자의 갯수로 어려운 정도(DIFFICULTY)를 나타냅니다. 1이면 1만큼 어렵다는 뜻 같네요. 유효한 수(VALID_DIGITS)가 있는데, 이것이 어려운 정도랑 관련이 있네요.

  • 1만큼 어려우면 유효한 수는 ‘0’
  • 5만큼 어려우면 유효한 수는 ‘00000’
  • 10만큼 어려우면 유효한 수는 ‘0000000000’ 이렇게 되는 것 같습니다.


역시 함수는 두 개뿐이네요. (골빈해커님 이런 간결한 코드 감사합니다)

  • valid_proof(): ‘증명 검증’라는 정도로 이해해보죠.
  • find_proof(): 드디어 ‘증명 찾기’라는 함수입니다.



valid_proof()


먼저 ‘증명 검증(valid_proof)’입니다.

증명 씨앗(proof_seed)과 증명 해쉬(proof_hash) 두 가지 개념이 나오네요.

  1. 마지막 증명(last_proof)와 현재 증명(proof)을 이용해서 증명 씨앗(proof_seed)을 만들 뒤,
  2. 이 증명 씨앗(proof_seed)을 이용해서 증명 해쉬(proof_hash)를 계산합니다. 이것까지는 크게 어려운 것은 없네요. 블록 정보 안에 proof라는 숫자를 가지고 있으니 넘겨 받은 두 개의 proof을 이용해서 씨앗과 해당하는 해쉬라는 걸 만드는 것 같습니다.


proof_hash[:DIFFICULTY] == VALID_DIGITS


이게 주요 핵심 중 하나인 것 같네요.

증명 해쉬(proof_hash)는 해쉬값이라는 정체 불명의 숫자일 텐데, 어려운 정도 만큼 문자열을 가지고 와서 유효한 숫자랑 같은지 비교합니다.


예를 들어보겠습니다.


[가정1]

  • 증명해쉬(proof_hash) : ‘93A34B1’
  • 어려운 정도(DIFFICULTY) : 1
  • 유효한 숫자수(VALID_DIGITS) : ‘0’

따라서 proof_hash[:DIFFICULTY] == VALID_DIGITS proof_hash[:1] == ‘0’ ‘9’ == ‘0’ 결과는 FALSE 이네요.


이번엔 TRUE가 나오는 가정을 해볼까요?


[가정2]

  • 증명해쉬(proof_hash) : ‘03A34B1’
  • 어려운 정도(DIFFICULTY) : 1
  • 유효한 숫자수(VALID_DIGITS) : ‘0’

따라서 proof_hash[:DIFFICULTY] == VALID_DIGITS proof_hash[:1] == ‘0’ ‘0’ == ‘0’ 결과는 TRUE 이네요.


어려운 정도를 올려봅시다.


[가정3]

  • 증명해쉬(proof_hash) : ‘03A34B1’
  • 어려운 정도(DIFFICULTY) : 3
  • 유효한 숫자수(VALID_DIGITS) : ‘000’

따라서 proof_hash[:DIFFICULTY] == VALID_DIGITS proof_hash[:3] == ‘000’ ‘03A’ == ‘000’ 결과는 FALSE 이네요.


어려운 정도를 높이니깐 정체 불명의 해쉬값 시작이 어려운 만큼 ‘0’으로 채워져야 하네요.



find_proof()

다음 함수인 ‘증명 찾기(find_proof)’를 보겠습니다.

어디서 호출되는지 모르겠지만 (블록체인에서 호출이 되겠죠?) 함수 내용은 간단하네요.

  • 함수에서 인자로 받은 마지막 증명(last_proof)과 현재 증명(proof, 초기값은 0)으로 ‘증명 검증’을 해봅니다.
  • 증명 검증이 이루어지면 바로 반환합니다.
  • 증명 검증에 실패하면, proof를 하나 올리고, 다시 증명 검증을 하네요. 증명 검증이 될 때까지 반복하네요. 반복하면서 proof는 계속 올라갑니다. proof는 증명 씨앗을 만들 때 사용되므로 proof 값이 올라가면 증명 씨앗값이 바뀌고 그러면 증명 해쉬도 바뀌게 되겠네요.
  • 무한 반복하다 보면 유효한 수에 만족하여 증명을 마칩니다. 그 전까지는 무한 반복…
  • 증명 검증에 성공하면 proof 값은 엄청 높아져 있겠네요.
  • 난이도에 따라 만족해야 할 유효한 수가 늘어나기 때문에 반복 수도 늘겠네요. 사람들이 블록체인 얘기할 때 의문스러운 점이 있었는 데, 이제 이해가 되는군요.


난이도에 따라 채굴의 속도가 느려진다.


저는 무언가 의미있는 수학 문제를 풀어서 보상을 받는 형식인 줄 알았는데, 유효한 수를 만족하는 해쉬값을 찾는 것이 핵심인 듯 합니다.


무언가 많은 것이 이해되는 느낌입니다. 블록체인(blockchain.py)에서 모든 퍼즐 조각이 맞춰질 것 같네요.
잠시 쉬고 다시 시작합시다. 




통장(book.py) 알아보기


쉬지도 않고 보시는군요. 계속 달려가보겠습니다.

이제 남은 건 ‘book.py’, ‘node.py’, ‘blockchain.py’ 세 개 있습니다.

‘blockchain.py’을 잠깐 살펴보니 ‘통장(book)’를 사용하고 있어 먼저 알아보겠습니다.

book.py에는 계정(Account)와 통장(Book) 두 개의 클래스가 있네요.



"""account book"""



class Account:

    def __init__(self):

        self.target = []  # sender or recipient

        self.amount = []  # - / + amount



    def sum(self):

        return sum(self.amount)



    def add(self, target, amount):

        self.target.append(target)

        self.amount.append(amount)


계정은 ‘타겟’과 ‘양’ 정보의 리스크로 되어 있습니다. 타겟은 ‘보내는 이’ 또는 ‘받는 이’가 될 수 있고, ‘양’도 +, -로 지정할 수 있네요.


만약 보냈다고 하면

  • 타켓에는 받는 이
  • 양은 -

로 기록될 것 같고,

받았다면

  • 타켓에는 보내는 이
  • 양은 +

로 될 것 같네요. (‘이 코드로 가계부를 만들 수 있을 것 같습니다.’)


sum()이라는 함수는 주고 받은 양을 모두 합한 합계를 반환합니다. 어디에 사용 되는지는 모르겠으나 코드 자체는 어렵지 않으니 넘어가겠습니다.



class Book:

    def __init__(self):

        self.account = {}



    def check_balance(self, transaction):

        """Check sender's balance

        TODO: check balance in transactions in next blocks



        Args:

            transaction (obj): Transaction object



        Returns:

            bool:

        """

        if transaction.sender == '0':  # for mining rewards

            return True

        if transaction.sender in self.account:

            account = self.account[transaction.sender]

            return account.sum() - transaction.amount >= 0

        else:

            return False



    def get_account(self, account_id):

        if account_id not in self.account:

            self.account[account_id] = Account()



        return self.account[account_id]



    def apply(self, transactions):

        """Add new transactions to book in new block



        Args:

            transactions (obj): Transaction object

        """

        for t in transactions:

            sender = self.get_account(t.sender)

            recipient = self.get_account(t.recipient)



            sender.add(recipient, -t.amount)

            recipient.add(sender, t.amount)


함수는 네 개입니다.

  • init() : 계정를 리스트로 관리하나 봅니다.
  • check_balance() : 복잡을 것 같으니 좀 있다 보고요.
  • get_account() : 계정 ID로 계정 정보를 얻는데, 기존에 있는 계정 ID면 해당하는 계정 정보를 반환하고, 그렇지 않으면 신규로 발행해줍니다. 은행 가서 계정을 만들어주는 것 같네요.
  • apply(): 거래 정보들을 받아서 기록합니다.



apply()


어디서 호출 되는지 모르겠지만 (블록체인에서 호출 되겠죠?), apply() 먼저 살펴보겠습니다.

  • 인자로 거래 정보 목록을 받습니다.
  • 거래 정보에는 ‘받는 이’, ‘보내는 이’, ‘거래량’이 기록 되어 있는데요,
  • 계정 목록 중에 ‘받는 이’를 찾아서 ‘보내는 이’와 ‘거래량’을 마이너스로 기록해주고,
  • 계정 목록 중에 ‘보내는 이’를 찾아서 ‘받는 이’와 ‘거래량’을 플러스로 기록해줍니다.
  • 마치 우리가 송금을 하면, 받는 사람 통장과 보내는 사람 통장에 둘 다 찍히는 것과 동일하군요.



check_balance()


함수 이름부터 어려워 보이는 check_balance()가 하나 남았습니다. 이 또한 거래 정보를 인자로 받네요.

  • 거래 정보에서 보내는 이가 ‘0’님이라면 무조건 통과입니다. 이 ‘0’이 GENESIS_ACCOUNT_ID 이네요. 마치 미국에서 달러는 막 찍어 내는 것과 동일한 느낌입니다.
  • 거래 정보에서 보내는 이가 관리하고 있는 계정에 없다면 FALSE를 반환합니다. 신이 아닌 이상 정체모를 자금이 유입 돼버리면 경제가 무너지겠죠?
  • 만약 관리되고 있는 계정이라면 해당 계정의 잔고에 송금할 금액이 남아 있는지 보는군요.


정리하면 다음과 같습니다.

거래 정보가 올 때 신(GENESIS_ACCOUNT_ID) 계정이면 무한 남발 가능하나
존재하고 있지 않는 계정이거나 그 계정에 송금할 금액보다 잔고가 적을 경우 거래가 되지 않는다.




블록체인(blockchain.py) 알아보기


드디어 끝판왕이군요. 노드(node.py)가 남았지만 쉬지 않는 당신을 위해 노드는 다음 편에서 볼 예정입니다. 블록체인은 함수가 많아서 하나씩 살펴보겠습니다.


init()와 init_chain()


import gcoin.proof

from gcoin.book import Book

from gcoin.block import Block



class BlockChain:

    def __init__(self, chain=None):

        """init chain with existing chain

        or make this new blockchain



        Args:

            chain: list of dictionary of Block, see load_chain

        """

        self.chain = []

        self.book = Book()

        self.transactions = []



        if chain:

            self.load_chain(chain)

        else:

            self.init_chain()

            

    def init_chain(self):

        """Init genesis chain"""

        block = Block([])

        self.add_block(block)


블록체인은 체인(chain), 통장(book), 거래들(transactions)으로 구성 되어 있네요.

  • self.chain = [] : 제가 알고 있는 체인이라곤 자건거 체인밖에 없지만, 그냥 그거라도 생각해도 무방할 듯 합니다. 블록 정보를 죽 연결해 놓은 것 같습니다.
  • self.book = Book() : 통장은 블록체인에 하나만 존재하는가 봅니다.
  • self.transactions = [] : 거래는 계속 일어날 것이고, 그 거래를 리스트로 관리하려나 봅니다.


초기화할 때 체인(chain)이 인자로 넘어 오는 경우에는 이 체인의 정보를 읽어 들여 체인을 구성합니다. 만약 아무 인자도 없다면 빈 블록을 하나 만들어서 추가합니다.

‘block = Block([])’을 조금 더 살펴 보면, 블록의 생성 함수가 다음과 같이 정의되어 있으므로 빈 거래 정보를 담고 있는 빈 블록이 생기겠죠?

def __init__(self, transactions, proof=0,

             previous_hash=None, timestamp=0):


정리하면 다음과 같습니다.

기존에 있는 체인을 받을 경우 이 체인 정보로 블록체인을 초기화하고
그렇지 않은 경우 빈 블록하나로 새로운 체인을 하나 만들어낸다.



add_transaction()


    def add_transaction(self, transaction):

        """Add new transaction

        It will only add amount



        Args:

            transaction (obj): Transaction object



        Returns:

            int: index of next block of chain

                return -1 if it's not correct transaction

        """

        if self.book.check_balance(transaction):

            self.transactions.append(transaction)

            return len(self.chain) + 1

        else:

            raise Exception('Transaction is wrong.')


우리가 잘 아는 거래(transaction)가 나왔네요. 거래는 많이 살펴봤으니 퍼즐을 맞춰보죠.

  • 먼저 거래가 오면 통장(book)에서 유효한지 검사합니다. 여기서 질문 하나, 거래에서 보내는 이가 ‘신’이라면 검사할까요?
  • 유효한 거래면 거래 정보 리스트에 하나 추가하고 1이 증가된 체인 길이를 반환하네요.
  • 유효하지 않는 거래면 메시지를 띄웁니다.



new_block()과 add_block()


    def new_block(self, proof):

        last_block = self.chain[-1]



        block = Block(self.transactions, proof,

                      previous_hash=last_block.hash())



        self.add_block(block)



        self.transactions = []



        return block

    

    def add_block(self, block):

        self.chain.append(block)

        self.book.apply(block.transactions)


new_block() 함수가 어디서 호출 되었는지 기억나시나요? 스크롤을 올려봅시다. 퍼즐을 맞추는 단계라 스크롤을 많이 움직이어야 할 것 같습니다.

‘드르륵 드르륵’

맞습니다. ‘채굴자(miner.py)’의 call() 함수에서 호출됩니다. 채굴자만이 블록을 만들 수 있나 봅니다. 그럼 또 질문입니다. 채굴자는 proof 인자로 무엇을 넘길까요?

‘드르륵 드르륵’

맞습니다. 증명 검증을 완료한 후 찾은 신규 proof 값이 넘어오네요.


갑자기 의문이 듭니다. 이 proof 값도 계속 증가 되지는 못할 텐데 중복 되면 어떡하죠? 일단 넘어가겠습니다.


last_block = self.chain[-1]

체인의 마지막에 있는 블록(last_block)을 꺼냅니다.


block = Block(self.transactions, proof, previous_hash=last_block.hash())

블록을 하나 생성할 때, 가지고 있는 거래 정보들과 넘겨받은 신규 proof, 가지고 온 마지막 블록의 해쉬값을 이전 해쉬로 넘기네요.

엇? self.transactions을 넘긴다구요? 블록체인에 수많은 거래가 있을 텐데 이걸 블록 하나 만들 때마다 넘긴다구요??? 일단 이 놀람은 뒤로 하고 계속 보겠습니다.


self.add_block(block)

가지고 있는 체인 끝에 하나 추가한 후, 통장(book)에 블록이 가지고 있는 거래 정보를 기입합니다. 이 때 통장에선 어떤 일이 일어났죠?

‘드르륵 드르륵’

맞습니다. 통장에선 넘겨 받은 거래 정보를 이용해서 보내는 이, 받는 이의 계정을 찾아서 기록합니다.

어어? 그럼 블록으로 넘기지 않은 거래 정보들은 통장에 기록 되지 않는 것 같습니다.


self.transactions = []

오~~ 거래 정보 초기화? 뭔가 번쩍합니다. 여러분도 눈치 채셨나요? ‘유레카!!’ 여기서 중요한 개념이 나올 것 같습니다. 아직 저는 블록체인을 1도 모르는 상황이기에 코드로만 유추해보겠습니다.


여기까지 눈에 보이는 과정은 다음과 같습니다.

  1. 블록체인에서 거래 정보가 들어 오면 (add_transaction) 블록체인의 거래 정리 리스트(self.transactions)에 추가합니다.
  2. 거래 정보가 계속 들어오면 계속 추가하겠죠?
  3. 추가할 때 통장 잔고를 보면서 유효한 지 체크합니다.
  4. 이를 계속 반복합니다.


그러다 채굴자(minar.py)가 call() 함수에 의해 어렵게 어렵게 증명 찾기에 성공하는 순간 다음과 같은 일이 벌어집니다. call()이란 함수는 어딘가에서 계속 호출 되는가 봅니다.

  1. 증명 찾기에 성공하면 새로운 증명값을 받습니다.
  2. 이 증명값으로 새로운 블록을 하나 만들고 지금까지 거래 정보 리스트를 넘깁니다. 이 때 체인의 마지막 블록의 해쉬 정보도 같이 넘기네요.
  3. 블록체인에 새로 만든 블록을 추가합니다.
  4. 그 다음에는 통장에 거래 정보들을 기록하네요. 지금까지는 거래 정보 리스트를 블록체인에서 임시적으로만 가지고 있었으며 아직 통장엔 기록 되지 않았으니 아직 사용할 수는 없었고, 이 시점부터 통장에 기록 되니 사용이 가능합니다. (골빈해커님의 채굴해야 거래가 기록 돼요~라는 말이 이제야 이해됩니다.)
  5. 그리고는 체인에서 가지고 있는 거래 정보는 초기화합니다.


자 몇 가지 정리해볼까요?

  • 블록체인에서 이루어지는 거래 정보들은 잠시 블록체인에서 가지고 있는다.
  • 이 거래 정보는 임시적인 것으로 아직 통장에 기록 되지 않았기 때문에 계정이 반영 되지는 않는다.
  • 새로운 블록이 채굴 되면 그 블록에 거래 정보를 저장한다.
  • 새 블록 생성 시에 체인의 마지막 블록의 해쉬값을 넘겨서 연결 고리를 만들어준다.
  • 새 블록 생성 조건은 채굴자는 새로운 증명 찾기를 계속 시도하다가 어렵게 증명 찾기에 성공할 때다.
  • 그 때 채굴자는 보상을 받는다.


아마도 이 보상을 코인이라고 부르는 것 같습니다.

지금까지 블록이 코인인 줄 알았는데, 전혀 상관이 없네요. 코인은 순수하게 채굴에 대한 보상일 뿐이고, 블록은 거래 정보들을 담고 있지만 블록끼리 연결 고리 정보도 담고 있는 정도로 생각하시면 될 것 같습니다.

또 의문이 하나 생기네요. 채굴만 계속 하고 거래가 이뤄지지 않으면 블록들만 생길 텐데, 이 블록들에는 거래 정보가 없겠네요?


블록은 채굴자만이 만들 수 있고, 코인은 채굴에 성공한 채굴자에게만 보상으로 지급된다.
단 '신'은 코인을 남발할 수 있다.



valid()


    def valid(self):

        """Valid chain"""

        index = 1



        while index < len(self):

            prev_block = self.chain[index-1]

            curr_block = self.chain[index]



            # Check hash with previous hash

            if curr_block.previous_hash != prev_block.hash():

                return False



            # Check proof with previous proof

            if not gcoin.proof.valid_proof(prev_block.proof,

                                           curr_block.proof):

                return False



            index += 1



        return True


이 함수는 어디서 호출 되는지는 모르겠지만, 검증을 해 보는 것 같습니다. 두 가지를 검증하는데요,

  1. 가지고 있는 블록을 죽~ 돌면서 현재 블록에서 가지고 있는 이전 블록의 해쉬 정보와 블록체인에서 가지고 있는 이전 블록의 해쉬가 일치하는지 확인합니다.
  2. 이전 블록의 검증값과 현재 블록의 검증값으로 증명 검증을 해보네요. 증명 검증은 증명 찾기 함수에서 호출되는 그 함수입니다. (‘드르륵 드르륵’)


어랏? 여기서 의문입니다.

지금까지 알고 있는 증명 검증은 난이도에 따라 조건에 맞는 해쉬값을 찾는 과정이 있는데요. 난이도가 변경 되지 않는다면 별 문제가 되지 않겠지만, 만약 중간에 난이도가 바뀐다면 이 조건에 의해서 모두 검증이 실패날 것 같네요.

어렴풋이 블록체인은 난이도가 점점 어려워진다는 얘기를 들은 기억이 있어 걱정이 앞서네요. 의문은 골빈해커님한테 물어 보기로 하고 일단 넘어가죠.



load_chain()


    def load_chain(self, chain):

        """load chain from list of dictionary

        from existing blockchain



        Args:

            chain (list):

                [{

                    transactions: [{

                        sender: 'dsf9s9f0ad'

                        recipient: 'dfsad90fasf'

                        amount: 12

                    }]

                    proof: 318832940000

                    previous_hash: 'fj9afje9ajf9sef0s0f'

                    timestamp: 1506057125.900785

                }]

        """

        for block in chain:

            block = Block.init_from_json(block)

            self.add_block(block)


앞서 호출 되었던 체인 로딩하는 함수입니다. 주어진 체인 정보로 현재 블록체인에서 블록을 생성하여 체인을 재구성합니다.



기타


    def last_block(self):

        return self.chain[-1]



    def dump(self):

        return [block.dump() for block in self.chain]



    def __len__(self):

        return len(self.chain)


마지막 블록을 가지고 온다거나 블록체인을 덤프 뜨거나 체인의 길이를 반환하는 함수들입니다. 코어 쪽은 노드(node.py) 하나가 남았고 실제 어플리케이션(app.py) 소스 코드 분석이 남았네요.




요약


몇 가지 의문들이 남아있지만 구글 검색과 소스 코드 원제작자인 골빈해커님이 해결해 주실 것이라 믿고 이만 정리해보겠습니다.

블록체인의 개념을 전혀 모른 상태에서 따라해보는 것도 나쁘지 않네요. 주워 들은 얘기들과 소스 코드와 얇팍한 추론으로 퍼즐을 맞추는 즐거움이랄까요? 그래도 많은 수확이 있었던 것 같습니다.

  • 블록체인은 블록이 연결된 리스트를 얘기한다.
  • 채굴자만이 새 블록을 만들 수 있다. 난이도가 높을 수록 만들기 힘들다.
  • 새 블록을 만드면 보상(코인)을 받는다.
  • 새 블록에 그간 블록체인에서 이루어진 거래 정보들을 저장한다.
  • 모든 코인 거래는 유효한지 검사 된다.
  • 블록체인 자체도 유효한지 검사 된다. 두 가지로 말이죠. (‘드르륵 드르륵’)
  • ‘신’은 코인을 막 만들 수 있다.


정리하면서 또 의문이 하나 드네요. 이 코인을 돈으로 어떻게 사죠? 채굴자는 보상으로 얻는다고 치고, 채굴자한테 돈을 주면 채굴자에게 코인을 받는 식이 되겠죠?


비행기에서 한숨도 못 잤지만 너무 즐거운 시간을 보낸 것 같습니다.

골빈해커님 감사합니다~






김태영님 블로그 : https://tykimos.github.io/2018/01/21/g_coin_analysis_part1/


0
  • 댓글 1

  • charlatan
    4k
    2018-01-31 16:58:41

    파이썬을 잘 모르지만 코드와 글을 보면 그 동안 여기저기 기웃거리며 보던 내용들이 조금 더 구체적으로 이해가 되기 시작하는 것 같네요. 돈을 주고 코인을 사려면 지갑에 해당하는 book.py에 의해 코인이 더해지지 않을까 싶네요? 비트코인은 머클트리라는 이진트리로 거래내역을 저장하는데 그런 부분이 명확하지 않고 해쉬도 두번을 돌리는데 여기서는 한번만 돌린 건가 싶네요. 원리를 이해하는데 많은 도움이 되겠습니다.

  • 로그인을 하시면 댓글을 등록할 수 있습니다.