-
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 passBlock 들에는 다음과 같은 정보가 들어가게 됩니다.
- 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" # nonceBlockchain을 위한 서버 구현
그리고 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)
Terminal로 server 실행 그리고 결과를 보기 위해 http://localhost:5000/chain 에 접속하였습니다. 존재하는 block 정보들을 볼 수 있습니다.

block chain 정보 확인 http://localhost:5000/mine 에 접속하여 채굴을 해서 새 block을 얻어보았습니다. url을 여러 번 쳐서 여러번 채굴하였습니다.

서버에 채굴 요청 여러 번 채굴 후에 http://localhost:5000/chain 를 다시 치면 다음과 같이 block들이 늘어난 것을 확인할 수 있습니다.

block chain 정보 다시 확인 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 결과를 간접적으로 확인할 수 있습니다.

이전 transaction이 새로 생성된 block 에서 보임 소스 코드
게시글 맨 윗 부분에 언급한 것과 같이 유튜브 강의 링크 및 소스코드 링크는 다음과 같습니다.
- 유튜브 강의: 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 댓글