ABOUT ME

-

Today
-
Yesterday
-
Total
-
choco@desktop:~/tistory
$ 정보처리기사 요점정리
1과목 2과목 3과목 4과목 5과목 실기

$ Linux Kernel
Power Management DVFS
  • 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)

    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 에서 보임

     

    소스 코드

     

    게시글 맨 윗 부분에 언급한 것과 같이 유튜브 강의 링크 및 소스코드 링크는 다음과 같습니다.

     

    댓글

Designed by Tistory.