-
Python으로 비트코인 구현하기SW개발 2021. 5. 17. 00:01
Python으로 비트코인 구현하기
본 게시물은 유튜브 강의 ❝[블록집착남] 블록체인, 제가 직접 만들어 보겠습니다 (상)❞ 를 기반으로 학습한 내용입니다.
강의에 사용된 원본 소스코드는 여기를 확인하시면 되고, 본 게시물에 작성된 제 소스코드는 여기를 확인하시면 됩니다.
강의에서는 python 2.x 를 사용하였으나, 제 코드는 python 3.7을 기반으로 작성하였습니다.
Python으로 진행하기 때문에 초심자에게 적합한 학습 코스입니다.
Blockchain 필수 함수
Block 체인에 필요한 필수 함수는 다음과 같습니다.
- init 함수
- new block 생성 함수
- new transaction 생성 함수
- hash 함수
- last_block (이전 블록)
따라서 blockchain.py 파일을 만들고, 각 함수들의 간단한 원형을 만들어주고 시작하였습니다.
class Blockchain(object): def __init__(self): self.chain = [] self.current_transactions = [] def new_block(self): # Creates a new Block and adds it to the chain pass def new_transaction(self): # Adds a new transaction to the list of transaction pass @staticmethod def hash(block): # Hashes a Block pass @property def last_block(self): # Returns the last Block in the chain pass
Block 들에는 다음과 같은 정보가 들어가게 됩니다.
- index: 몇 번째 블록인지
- Timestamp: 언제 블록이 생성되었는지
- Transaction: 거래 목록
- Proof: 마이닝의 결과
- Prev_hash: 블록의 무결성을 검증하기 위한 것
이제 각 함수들을 구현해봅니다. 위에서 소개한 필수 함수 외에 검증에 사용되는 pow 함수와 valid proof 함수를 추가로 구현하였습니다. (skip 하고 바로 아래 blockchain.py 전체 소스코드를 훑어보셔도 됩니다)
- init 함수: 클래스의 생성자. Block이 만들어지면 가장 먼저 실행되는 함수입니다. 이 함수에서 하는 동작은 다음과 같습니다.
- chain 연결
- 임시 transaction 저장
- genesis block 생성
def __init__(self): self.chain = [] # chain에 여러 block들 들어옴 self.current_transaction = [] # 임시 transaction 넣어줌 # genesis block 생성 self.new_block(previous_hash=1, proof=100)
- new block 생성 함수이며, 이 함수에서 하는 동작은 다음과 같습니다.
- block 구조 생성
- chain에 생성된 block 추가
def new_block(self, proof, previous_hash=None): # Creates a new Block and adds it to the chain block = { 'index' : len(self.chain)+1, 'timestamp' : time(), # timestamp from 1970 'transactions' : self.current_transaction, 'proof' : proof, 'previous_hash' : previous_hash or self.hash(self.chain[-1]), } self.current_transaction = [] self.chain.append(block) return block
- new transaction 함수이며, 이 함수에서 하는 동작은 다음과 같습니다.
- 새 transaction 생성하고 연결
def new_transaction(self, sender, recipient, amount): # Adds a new transaction to the list of transaction self.current_transaction.append( { 'sender' : sender, # 송신자 'recipient' : recipient, # 수신자 'amount' : amount # 금액 } ) return self.last_block['index'] + 1
- hash 함수이며, 이 함수에서는 hash 변환을 하게 됩니다.
- 이미 존재하는 sha256 라이브러리를 사용하면 된다
@staticmethod def hash(block): # Hashes a Block block_string = json.dumps(block, sort_keys=True).encode() # hash 라이브러리로 sha256 사용 return hashlib.sha256(block_string).hexdigest()
- pow 함수이며, 이 함수에서 하는 동작은 다음과 같습니다.
- valid_proof 함수를 통해 맞을 때까지 반복적으로 검증하면서
- proof 증가하다가 찾으면 proof 번호 반환
- 이전 proof p와 현재 구해야할 proof p' 가 있을 때 Hash(p + p') 의 앞 4자리 == "0000" 이 되는 이러한 p'를 구한다.
def pow(self, last_proof): proof = 0 # valid proof 함수를 통해 맞을 때까지 반복적으로 검증 while self.valid_proof(last_proof, proof) is False: proof += 1 return proof
- valid proof 함수이며, 이 함수에서 하는 동작은 다음과 같습니다.
- 앞의 4자리가 000 으로 시작하는 해시값을 찾는 함수
@staticmethod def valid_proof(last_proof, proof): # 전 proof와 구할 proof 문자열 연결 guess = str(last_proof + proof).encode() # 이 hash 값 저장 guess_hash = hashlib.sha256(guess).hexdigest() # 앞 4자리가 0000 이면 True return guess_hash[:4] == "0000" # nonce
필요한 라이브러리를 포함하여, 전체 코드는 다음과 같습니다. (blockchain.py)
import hashlib # hash 함수용 sha256 사용할 라이브러리 import json from time import time # from urlparse import urlparse from urllib.parse import urlparse class Blockchain(object): def __init__(self): self.chain = [] # chain에 여러 block들 들어옴 self.current_transaction = [] # 임시 transaction 넣어줌 # genesis block 생성 self.new_block(previous_hash=1, proof=100) def new_block(self, proof, previous_hash=None): # Creates a new Block and adds it to the chain block = { 'index' : len(self.chain)+1, 'timestamp' : time(), # timestamp from 1970 'transactions' : self.current_transaction, 'proof' : proof, 'previous_hash' : previous_hash or self.hash(self.chain[-1]), } self.current_transaction = [] self.chain.append(block) return block def new_transaction(self, sender, recipient, amount): # Adds a new transaction to the list of transaction self.current_transaction.append( { 'sender' : sender, # 송신자 'recipient' : recipient, # 수신자 'amount' : amount # 금액 } ) return self.last_block['index'] + 1 @staticmethod def hash(block): # Hashes a Block block_string = json.dumps(block, sort_keys=True).encode() # hash 라이브러리로 sha256 사용 return hashlib.sha256(block_string).hexdigest() @property def last_block(self): # Returns the last Block in the chain return self.chain[-1] def pow(self, last_proof): proof = 0 # valid proof 함수를 통해 맞을 때까지 반복적으로 검증 while self.valid_proof(last_proof, proof) is False: proof += 1 return proof @staticmethod def valid_proof(last_proof, proof): # 전 proof와 구할 proof 문자열 연결 guess = str(last_proof + proof).encode() # 이 hash 값 저장 guess_hash = hashlib.sha256(guess).hexdigest() # 앞 4자리가 0000 이면 True return guess_hash[:4] == "0000" # nonce
Blockchain을 위한 서버 구현
그리고 blockchain.py가 동작하는 것을 쉽게 볼 수 있고, 새 transaction 도 요청하기 쉽도록 웹 서버 형식으로 구현해봅니다. python의 flask 프레임워크를 사용하였습니다. flask가 설치되지 않은 경우 windows에서는 명령 프롬프트를 열고 다음과 같이 설치합니다.
pip install Flask
웹 서버에는 다음과 같이 3 개의 URL을 구현할 예정입니다.
- /chain: 현재 블록체인 보여줍니다
- /transaction/new: 새 트랜잭션을 생성합니다
- /mine: server에게 새 블록 채굴을 요청합니다
서버를 구현할 것인데, 포트 번호는 5000 번으로 하였습니다.
if __name__ == '__main__': app.run(host='0.0.0.0', port=5000)
그리고 flask 구현을 위해 윗부분에 다음과 같이 코드를 입력합니다.
app = Flask(__name__) # Universial Unique Identifier # 노드 식별을 하기 위해 uuid 함수를 사용! node_identifier = str(uuid4()).replace('-','') # 아까 짜놓은 블록체인 객체를 선언 blockchain = Blockchain()
/chain은 다음과 같이 구현합니다. 전체 block을 출력합니다.
@app.route('/chain', methods=['GET']) def full_chain(): response = { 'chain' : blockchain.chain, # 블록체인을 출력 'length' : len(blockchain.chain), # 블록체인 길이 출력 } return jsonify(response), 200
/transactions/new는 다음과 같이 구현합니다. transaction 요청이 오면 transaction을 생성합니다.
# post는 url에 데이터를 붙여서 보내는 get과 달리 숨겨서 보내는 방식 @app.route('/transactions/new', methods=['POST']) def new_transaction(): values = request.get_json() # json 형태를 받아서 저장 required = ['sender', 'recipient', 'amount'] # 해당 데이터가 존재해야함 # 데이터가 없으면 에러를 띄움 if not all(k in values for k in required): return 'missing values', 400 # Create a new Transaction # 새 트랜잭션 만들고 삽입 index = blockchain.new_transaction(values['sender'],values['recipient'],values['amount']) response = {'message' : 'Transaction will be added to Block {%s}' % index} return jsonify(response), 201
/mine은 다음과 같이 구현합니다. coinbase transaction으로 구현하여, 채굴시 1 코인씩 얻을 수 있도록 하였습니다.
# 채굴이 되게 할 것 # coinbase transaction: 채굴할 때마다 1 코인씩 준다 @app.route('/mine', methods=['GET']) def mine(): last_block = blockchain.last_block last_proof = last_block['proof'] proof = blockchain.pow(last_proof) blockchain.new_transaction( sender='0', # 채굴시 생성되는 transaction (0 = 운영자) recipient=node_identifier, # 지갑 주소처럼 사용 amount=1 # coinbase transaction ) # Forge the new Block by adding it to the chain # 전 블록에 대한 hash를 떠놓고 previous_hash = blockchain.hash(last_block) # 검증하는 걸 넣어서 블록을 새로 생성 block = blockchain.new_block(proof, previous_hash) # block 이 제대로 mine 되었다는 정보를 json 형태로 띄워줌 response = { 'message' : 'new block found', 'index' : block['index'], 'transactions' : block['transactions'], 'proof' : block['proof'], 'previous_hash' : block['previous_hash'] } return jsonify(response), 200
헤더를 포함한 전체 소스코드는 다음과 같습니다. (server.py)
from flask import Flask, request, jsonify import json from uuid import uuid4 # Our blockchain.py API from blockchain import Blockchain app = Flask(__name__) # Universial Unique Identifier # 노드 식별을 하기 위해 uuid 함수를 사용! node_identifier = str(uuid4()).replace('-','') # 아까 짜놓은 블록체인 객체를 선언 blockchain = Blockchain() @app.route('/chain', methods=['GET']) def full_chain(): response = { 'chain' : blockchain.chain, # 블록체인을 출력 'length' : len(blockchain.chain), # 블록체인 길이 출력 } # json 형태로 리턴 (200 은 웹 사이트 에러가 없을 때 뜨는 숫자) return jsonify(response), 200 # post는 url에 데이터를 붙여서 보내는 get과 달리 숨겨서 보내는 방식 @app.route('/transactions/new', methods=['POST']) def new_transaction(): values = request.get_json() # json 형태를 받아서 저장 required = ['sender', 'recipient', 'amount'] # 해당 데이터가 존재해야함 # 데이터가 없으면 에러를 띄움 if not all(k in values for k in required): return 'missing values', 400 # Create a new Transaction # 새 트랜잭션 만들고 삽입 index = blockchain.new_transaction(values['sender'],values['recipient'],values['amount']) response = {'message' : 'Transaction will be added to Block {%s}' % index} return jsonify(response), 201 # 채굴이 되게 할 것 # coinbase transaction: 채굴할 때마다 1 코인씩 준다 @app.route('/mine', methods=['GET']) def mine(): last_block = blockchain.last_block last_proof = last_block['proof'] proof = blockchain.pow(last_proof) blockchain.new_transaction( sender='0', # 채굴시 생성되는 transaction (0 = 운영자) recipient=node_identifier, # 지갑 주소처럼 사용 amount=1 # coinbase transaction ) # Forge the new Block by adding it to the chain # 전 블록에 대한 hash를 떠놓고 previous_hash = blockchain.hash(last_block) # 검증하는 걸 넣어서 블록을 새로 생성 block = blockchain.new_block(proof, previous_hash) # block 이 제대로 mine 되었다는 정보를 json 형태로 띄워줌 response = { 'message' : 'new block found', 'index' : block['index'], 'transactions' : block['transactions'], 'proof' : block['proof'], 'previous_hash' : block['previous_hash'] } return jsonify(response), 200 if __name__ == '__main__': app.run(host='0.0.0.0', port=5000)
결과
visual studio code 에서 작업시에는 Terminal > New Terminal을 선택하고 cmd로 다음을 실행하였습니다.
C:\주소...\Blockchain> python server.py * Serving Flask app 'server' (lazy loading) * Environment: production WARNING: This is a development server. Do not use it in a production deployment. Use a production WSGI server instead. * Debug mode: off * Running on all addresses. WARNING: This is a development server. Do not use it in a production deployment. * Running on http://192.168.212.150:5000/ (Press CTRL+C to quit)
그리고 결과를 보기 위해 http://localhost:5000/chain 에 접속하였습니다. 존재하는 block 정보들을 볼 수 있습니다.
http://localhost:5000/mine 에 접속하여 채굴을 해서 새 block을 얻어보았습니다. url을 여러 번 쳐서 여러번 채굴하였습니다.
여러 번 채굴 후에 http://localhost:5000/chain 를 다시 치면 다음과 같이 block들이 늘어난 것을 확인할 수 있습니다.
new transaction은 URL 접속으로 바로 할 수 없어서 postman 사이트를 이용하거나 혹은 다음과 같이 테스트 스크립트를 작성하여 진행할 수도 있습니다. requests 라이브러리가 없는 경우 다음과 같이 명령 프롬프트에 입력하여 설치해줍니다.
pip install requests
그리고 다음 소스코드를 입력합니다. (test_transaction.py)
import requests import json headers = {'Content-Type' : 'application/json; charset=utf-8'} data = { "sender": 1004, "recipient": 0, "amount": 1004, } print(requests.post("http://localhost:5000/transactions/new", headers=headers, data=json.dumps(data)).content)
해당 소스코드를 실행합니다. 웹 서버가 중지되지 않고 계속 실행된 상태에서 해당 코드를 실행을 해야하기 때문에 visual studio code에서 새 terminal을 다시 열어서 코드를 실행하였습니다.
C:\주소...\Blockchain> python test_transaction.py
transaction 결과 확인을 url 접속으로 바로 할 수는 없지만 채굴을 했을 때 이전 transaction 결과를 간접적으로 확인할 수 있습니다.
소스 코드
게시글 맨 윗 부분에 언급한 것과 같이 유튜브 강의 링크 및 소스코드 링크는 다음과 같습니다.
- 유튜브 강의: https://www.youtube.com/watch?v=Gno15LgVbcc
- 원본 소스코드: https://github.com/tr0y-kim/ez_blockchain
- 본 게시물의 소스코드: https://github.com/BY1994/Blockchain
'SW개발' 카테고리의 다른 글
Github profile 만들기 (0) 2021.10.08 github ssh 연결 방법 (0) 2021.10.08 MkDocs 를 이용하여 문서 사이트 만들기 (0) 2021.03.21 Time Timer extension 만들기 - 2 (Timer 형태로 변형하기) (0) 2019.07.17 Time Timer extension 만들기 - 1 (시계 형태 만들기) (0) 2019.07.17 댓글