안녕하세요.
저는 공주대학교 컴퓨터공학부 컴퓨터소프트웨어전공 17학번 문승현입니다.
현재 블록체인 기술에 대해 공부를 하여 공부한 내용을 정리하고자 게시글을 작성하였습니다.
미리 읽어주시는 분들께 감사하며 잘못된 지식이 있을 경우 마음 편하게 피드백주시면 감사하겠습니다.
먼저 제가 깃허브를 돌아다니다 괜찮은 자료를 발견하여 분석을 시작한 코인의 깃허브 주소입니다.
https://github.com/golbin/g-coin
먼저 다운받게 되면 위와 같이 파일들이 있습니다.
golbin
- app.py
- gcoin
- blockchain.py
- block.py
- book.py
- config.py
- miner.py
- node.py
- proof.py
- __pycache__
- requirements.txt
- transaction.py
패키지
패키지 설치
분석
app.py
from argparse import ArgumentParser from flask import Flask, jsonify, request from gcoin.node import Node from gcoin.miner import Miner from gcoin.blockchain import BlockChain from gcoin.transaction import Transaction app = Flask('g-coin') app.node = Node() app.miner = Miner(app.node.id) app.blockchain = BlockChain() @app.route('/transaction', methods=['POST']) def add_transaction(): # data.sender(str) # data.recipient(str) # data.amount(int) data = request.get_json() transaction = Transaction.init_from_json(data) try: next_index = app.blockchain.add_transaction(transaction) except Exception as e: return jsonify({'message': str(e)}), 403 response = {'message': 'Transaction will be added to {next_index}th block.'} return jsonify(response), 201 @app.route('/transaction', methods=['GET']) def get_pending_transactions(): transactions = [t.dump() for t in app.blockchain.transactions] return jsonify(transactions), 201 @app.route('/mine', methods=['POST']) def mine(): """Mining Have to make a standalone process But it's just a prototype """ block = app.miner(app.blockchain) response = { 'message': "New block is mined!", 'block': block.dump() } return jsonify(response), 200 @app.route('/chain', methods=['GET']) def get_full_chain(): response = { 'chain': app.blockchain.dump(), 'length': len(app.blockchain) } return jsonify(response), 200 @app.route('/node', methods=['GET']) def get_all_nodes(): response = { 'nodes': list(app.node.neighbor), 'total': len(app.node) } return jsonify(response), 201 @app.route('/node', methods=['POST']) def add_node(): # data.address(str) data = request.get_json() app.node.add(data['address']) response = { 'message': 'New app.node is added.', 'total': len(app.node) } return jsonify(response), 201 @app.route('/chain/valid', methods=['GET']) def valid_chain(): valid = app.blockchain.valid() response = {'result': valid} return jsonify(response), 200 @app.route('/consensus', methods=['POST']) def consensus(): new_blockchain = app.node.consensus_with_neighbor(app.blockchain) if new_blockchain: app.blockchain = new_blockchain response = {'message': 'Our chain was replaced.'} else: response = {'message': 'I\'m King of the World.'} return jsonify(response), 200 if __name__ == '__main__': parser = ArgumentParser() parser.add_argument('--port', default=5000, type=int) args = parser.parse_args() app.run(host='0.0.0.0',port=args.port)
보게 되면 --port 로 받은 인자를 int 형으로 플라스크의 포트로 설정하는 것을 볼 수 있습니다.
네, app.py는 그냥 플라스크를 실행하는 것을 알 수 있습니다.
아, 참 깃허브에서 받은 코드가 다른 부분이 있습니다.
app.run(host='0.0.0.0',port=args.port)
바로 이 부분인데요.
아마 host='0.0.0.0'이 없으실겁니다.
저는 외부 접속을 허용하기 위해 이 부분에 코드를 추가하였습니다.
config.py
# for the first chain GENESIS_HASH = 'g' # for mining rewards GENESIS_ACCOUNT_ID = '0' AMOUNT_OF_REWARD = 1 # for proof of work DIFFICULTY = 1 # number of digits is difficulty PROOF_DIGITS = '0' # Finding PROOF_DIGITS * DIFFICULTY ('00', '000', ..) is proof of work
config.py부터 보도록 하겠습니다.
GENESIS_HASH = 'g'
첫번째의 체인의 해쉬를 설정하는 부분입니다.
GENESIS_ACCOUNT_ID = '0' AMOUNT_OF_REWARD = 1
채굴을 할 때에 나누어줄 갯수와 초기의 계정의 아이디를 설정하는 부분입니다.
쉽게 말하면 '신', '관리자'의 계정을 설정하는 부분입니다.
DIFFICULTY = 1 # number of digits is difficulty PROOF_DIGITS = '0' # Finding PROOF_DIGITS * DIFFICULTY ('00', '000', ..) is proof of work
채굴에 대해 난이도를 설정하는 부분입니다.
현재의 코드는 난이도가 1로 설정이 되어있어 PROOF_DIGITS * 1로 '0' 되어있습니다.
이 난이도가 2로 설정이 되면 PROOF_DIGITS * 2가 되어 '00' 으로 동작한다는 것을 주석을 통해 알 수 있었습니다.
block.py
import json import hashlib from time import time import gcoin.config as cfg from gcoin.transaction import Transaction class BlockHeader: def __init__(self, previous_hash=None, timestamp=0, difficulty=0, proof=0): """Block Args: previous_hash (str): timestamp (float): difficulty (int): proof (int): """ self.previous_hash = previous_hash self.timestamp = timestamp if timestamp else time() self.difficulty = difficulty if difficulty else cfg.DIFFICULTY self.proof = proof def hash(self): """Make hash of a header of current block for finding proof Don't use 'proof' because there is no 'proof' in new block at the first time. """ header_dump = self.dump() header_dump.pop('proof', None) header_dump = json.dumps(header_dump, sort_keys=True).encode() header_hash = hashlib.sha256(header_dump).hexdigest() return header_hash def dump(self, proof=True): data = { 'previous_hash': self.previous_hash, 'timestamp': self.timestamp, 'difficulty': self.difficulty } if proof: data['proof'] = self.proof return data @classmethod def init_from_json(cls, data): return cls(data['previous_hash'], data['timestamp'], data['difficulty'], data['proof']) class Block: def __init__(self, transactions, previous_hash=None): """Block Args: transactions (list): list of Transaction object previous_hash (str): """ self.transactions = transactions self.header = BlockHeader(previous_hash) @property def previous_hash(self): return self.header.previous_hash @property def difficulty(self): return self.header.difficulty @property def proof(self): return self.header.proof @proof.setter def proof(self, value): self.header.proof = value 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 { 'header': self.header.dump(), 'transactions': [t.dump() for t in self.transactions] } @classmethod def init_from_json(cls, data): transactions = [Transaction.init_from_json(t) for t in data['transactions']] header = BlockHeader.init_from_json(data['header']) self = cls(transactions) self.header = header return self
블록의 구조를 볼 수 있는 코드입니다.
transactions : 거래 정보들
previous_hash : 이전 해쉬
proof : 보상?
timestamp : 블록이 생성된 시간
hash : 자기 블록의 해쉬
difficulty : 어려움?
import gcoin.config as cfg
일단 아까 저희가 처음에 본 config.py를 임포트하는 것을 볼 수 있습니다.
self.difficulty = difficulty if difficulty else cfg.DIFFICULTY
난이도를 설정한 것을 불러와 대입을 하는 것을 볼 수 있습니다.
class BlockHeader: def __init__(self, previous_hash=None, timestamp=0, difficulty=0, proof=0): """Block Args: previous_hash (str): timestamp (float): difficulty (int): proof (int): """ self.previous_hash = previous_hash self.timestamp = timestamp if timestamp else time() self.difficulty = difficulty if difficulty else cfg.DIFFICULTY self.proof = proof def hash(self): """Make hash of a header of current block for finding proof Don't use 'proof' because there is no 'proof' in new block at the first time. """ header_dump = self.dump() header_dump.pop('proof', None) header_dump = json.dumps(header_dump, sort_keys=True).encode() header_hash = hashlib.sha256(header_dump).hexdigest() return header_hash def dump(self, proof=True): data = { 'previous_hash': self.previous_hash, 'timestamp': self.timestamp, 'difficulty': self.difficulty } if proof: data['proof'] = self.proof return data @classmethod def init_from_json(cls, data): return cls(data['previous_hash'], data['timestamp'], data['difficulty'], data['proof'])
인자는 4개로 previous_hash, timestamp, difficulty, proof가 있습니다.
previous_hash : string 타입으로 이전 블록의 해쉬 값입니다.
timestamp : float 타입으로 블록의 생성 시간입니다.
difficulty : int 타입으로 난이도 결정의 값입니다.
proof : int 타입으로 아직까지 무슨 역할을 하는 아이인진 모르겠네요.
인자를 받으며 에러를 위해 초기 값들은 None, 0, 0, 0으로 설정하는 것을 볼 수 있습니다.
현재 자기 자신의 값에 받은 값을 대입하는 것을 볼 수 있습니다.
hash()
header_dump = self.dump() header_dump.pop('proof', None) header_dump = json.dumps(header_dump, sort_keys=True).encode() header_hash = hashlib.sha256(header_dump).hexdigest()
init_from_json()
def init_from_json(cls, data): return cls(data['previous_hash'], data['timestamp'], data['difficulty'], data['proof'])
JSON 데이터를 받아 블록정보를 초기화합니다.
dump()
def dump(self, proof=True): data = { 'previous_hash': self.previous_hash, 'timestamp': self.timestamp, 'difficulty': self.difficulty } if proof: data['proof'] = self.proof return data
블록의 정보를 반환해주는 함수입니다.
class Block: def __init__(self, transactions, previous_hash=None): """Block Args: transactions (list): list of Transaction object previous_hash (str): """ self.transactions = transactions self.header = BlockHeader(previous_hash) @property def previous_hash(self): return self.header.previous_hash @property def difficulty(self): return self.header.difficulty @property def proof(self): return self.header.proof @proof.setter def proof(self, value): self.header.proof = value 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 { 'header': self.header.dump(), 'transactions': [t.dump() for t in self.transactions] } @classmethod def init_from_json(cls, data): transactions = [Transaction.init_from_json(t) for t in data['transactions']] header = BlockHeader.init_from_json(data['header']) self = cls(transactions) self.header = header return self
transactions : list 타입으로 거래 정보들이 들어있는 곳입니다.
previous_hash : string 타입으로 이전 해쉬 값이 들어있는 곳입니다.
그리고 함수들의 역할은 위에서 설명한 함수들과 같은 역할을 하는 아이들이라 딱히 큰 설명은 없이 넘어가도 될 것 같습니다.
transaction.py
class Transaction: def __init__(self, sender, recipient, amount): """Transaction Args: sender (str): recipient (str): amount (int): positive number """ 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): return cls(data['sender'], data['recipient'], data['amount'])
파일 명을 보고 딱 알아차릴 수 있겠네요!
거래정보를 관련하는 아이일 것이라 예상할 수 있었습니다.
인자는 3개로 sender, recipient, amount 입니다.
sender : string 타입으로 보내는 이의 정보가 담기는 변수입니다.
recipient : string 타입으로 받는 이의 정보가 담기는 변수입니다.
amount : int 타입으로 보낼 갯수가 담기는 변수입니다.
if amount < 1: raise Exception('Amount have to be positive number.')
dump()
def dump(self): return { 'sender': self.sender, 'recipient': self.recipient, 'amount': self.amount }
init_from_json()
def init_from_json(cls, data): return cls(data['sender'], data['recipient'], data['amount'])
json 값을 초기화해주는 함수입니다.
book.py
"""account book""" import gcoin.config as cfg 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) 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 == cfg.GENESIS_ACCOUNT_ID: # 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)
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()
add()
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 == cfg.GENESIS_ACCOUNT_ID: # 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)
현재 계정을 초기화하네요.
dicinary 타입으로 계정마다 갯수를 정하게 하는 것 같습니다.
check_balance()
if transaction.sender == cfg.GENESIS_ACCOUNT_ID: # for mining rewards return True
만약 보내는 이가 '신', '관리자' 계정이면 무조건 True가 되네요.
이에 대한 코드는 보안에 대한 취약점이 되지 않을까 싶습니다.
if transaction.sender in self.account: account = self.account[transaction.sender] return account.sum() - transaction.amount >= 0 else: return False
거래 내용에 보내는 이가 계정 안에 있으면 계정에 대한 갯수를 보내거나 지불하는 것을 볼 수 있으며 계정 안에 없을 경우에는 False를 리턴하는 것을 볼 수 있습니다.
get_account()
apply()
남은 blockchain.py, miner.py, node.py, proof.py 는 다음 게시글에서 정리하도록 하겠습니다.
읽어주셔서 감사합니다.
'0x30 Study > 0x32 Blockchain' 카테고리의 다른 글
g-coin 분석(2) (33) | 2018.03.05 |
---|---|
g-coin 분석(1) (1) | 2018.03.03 |
BlockChain을 공부하자! (0) (62) | 2018.02.28 |